Current section

Files

Jump to
membrane_core lib membrane core timer_controller.ex
Raw

lib/membrane/core/timer_controller.ex

defmodule Membrane.Core.TimerController do
@moduledoc false
use Bunch
alias Membrane.Clock
alias Membrane.Core.{CallbackHandler, Component, Timer}
require Membrane.Core.Component
require Membrane.Element.CallbackContext.Tick
@spec start_timer(Timer.id_t(), Timer.interval_t(), Clock.t(), Component.state_t()) ::
{:ok, Component.state_t()}
| {{:error, {:timer_already_exists, id: Timer.id_t()}}, Component.state_t()}
def start_timer(id, interval, clock, state) do
if state.synchronization.timers |> Map.has_key?(id) do
{{:error, {:timer_already_exists, id: id}}, state}
else
clock |> Clock.subscribe()
timer = Timer.start(id, interval, clock)
state |> Bunch.Access.put_in([:synchronization, :timers, id], timer) ~> {:ok, &1}
end
end
@spec timer_interval(Timer.id_t(), Timer.interval_t(), Component.state_t()) ::
{:ok, Component.state_t()}
| {{:error, {:unknown_timer, id: Timer.id_t()}}, Component.state_t()}
def timer_interval(id, interval, state) do
with {:ok, timer} <- state.synchronization.timers |> Map.fetch(id) do
Bunch.Access.put_in(
state,
[:synchronization, :timers, id],
Timer.set_interval(timer, interval)
)
~> {:ok, &1}
else
:error -> {{:error, {:unknown_timer, id}}, state}
end
end
@spec stop_timer(Timer.id_t(), Component.state_t()) ::
{:ok, Component.state_t()}
| {{:error, {:unknown_timer, id: Timer.id_t()}}, Component.state_t()}
def stop_timer(id, state) do
{timer, state} = state |> Bunch.Access.pop_in([:synchronization, :timers, id])
if timer |> is_nil do
{{:error, {:unknown_timer, id}}, state}
else
:ok = timer |> Timer.stop()
timer.clock |> Clock.unsubscribe()
{:ok, state}
end
end
@spec handle_tick(Timer.id_t(), Component.state_t()) ::
{:ok, Component.state_t()} | {{:error, any}, Component.state_t()}
def handle_tick(timer_id, state) do
context = Component.callback_context_generator(:any, Tick, state)
# the first clause checks if the timer wasn't removed before receiving this tick
withl present?: true <- Map.has_key?(state.synchronization.timers, timer_id),
callback:
{:ok, state} <-
CallbackHandler.exec_and_handle_callback(
:handle_tick,
Component.action_handler(state),
%{context: context},
[timer_id],
state
),
# in case the timer was removed in handle_tick
present?: true <- Map.has_key?(state.synchronization.timers, timer_id) do
state
|> Bunch.Access.update_in([:synchronization, :timers, timer_id], &Timer.tick/1)
~> {:ok, &1}
else
present?: false -> {:ok, state}
callback: {{:error, _reason}, _state} = err -> err
end
end
@spec handle_clock_update(Timer.id_t(), Clock.ratio_t(), Component.state_t()) ::
{:ok, Component.state_t()}
def handle_clock_update(clock, ratio, state) do
state
|> update_in(
[:synchronization, :timers],
&Bunch.Map.map_values(&1, fn
%Timer{clock: ^clock} = timer -> timer |> Timer.update_ratio(ratio)
timer -> timer
end)
)
~> {:ok, &1}
end
end