View Source Timer usage examples

Examples below illustrate how to use :start_timer, :timer_interval and :stop_timer actions on the example of Membrane.Source, but the API looks the same for all kinds of the Membrane Components

Simple example

The example below emits an empty buffer every 100 milliseconds.

defmodule MySource do
  use Membrane.Source

  def_output_pad :output, accepted_format: SomeFormat

  @impl true
  def handle_init(_ctx, _opts), do: {[], %{}}

  @impl true
  def handle_playing(_ctx, state) do
    # let's start a timer named :my_timer that will tick every 100 milliseconds ...

    start_timer_action = [
      start_timer: {:my_timer, Membrane.Time.milliseconds(100)}
    ]
    
    # ... and send a stream format
    actions = start_timer_action ++ [stream_format: {:output, %SomeFormat{}}]
    {actions, state}
  end

  # this callback is executed every 100 milliseconds
  @impl true
  def handle_tick(:my_timer, ctx, state) do
    actions = [buffer: {:output, %Membrane.Buffer{payload: ""}}]
    {actions, state}
  end
end

Advanced example

The example below emits an empty buffer every 100 milliseconds if it hasn't been stopped or paused by the parent.

The source accepts the following notifications from the parent:

  • :pause - after receiving it the source will pause sending buffers. The paused source can be resumed again.
  • :resume - resumes sending buffers from the paused source.
  • :stop - the stopped source won't send any buffer again.
defmodule MyComplexSource
  use Membrane.Source

  def_output_pad :output, accepted_format: SomeFormat

  @impl true
  def handle_init(_ctx, _opts) do 
    # after starting a timer, the status will always be either :resumed, :paused 
    # or :pause_on_next_handle_tick
    {[], %{status: nil}}
  end

  @impl true 
  def handle_playing(_ctx, state) do
    # let's start a timer named :my_timer ...
    start_timer_action = [
      start_timer: {:my_timer, Membrane.Time.milliseconds(100)}
    ]
    
    # ... and send a stream format
    actions = start_timer_action ++ [stream_format: {:output, %SomeFormat{}}]
    {actions, %{state | status: :resumed}}
  end

  @impl true
  def handle_parent_notification(notification, _ctx, state) when notification in [:pause, :resume, :stop] do
    case notification do
      :pause when state.status == :resumed -> 
        # let's postpone pausing :my_timer to the upcoming handle_tick
        {[], %{state | status: :pause_on_next_handle_tick}}

      :resume when state.status == :paused -> 
        # resume :my_timer by returning :timer_interval action
        actions = [timer_interval: {:my_timer, Membrane.Time.milliseconds(100)}]
        {actions, %{state | status: :resumed}}

      :resume when state.status == :pause_on_next_handle_tick -> 
        # case when we receive :pause and :resume notifications without a tick 
        # between them
        {[], %{state | status: :resumed}}

      :stop -> 
        # stop :my_timer using :stop_timer action
        {[stop_timer: :my_timer], %{state | status: :stopped}}
    end
  end

  @impl true
  def handle_tick(:my_timer, _ctx, state) do
    case state.status do
      :resumed -> 
        buffer = %Membrane.Buffer{payload: ""}
        {[buffer: {:output, buffer}], state}

      :pause_on_next_handle_tick -> 
        # pause :my_timer using :timer_interval action with interval set to :no_interval
        actions = [timer_interval: {:my_timer, :no_interval}]
        {actions, %{state | status: :paused}}
    end
  end
end