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
defguardp is_timer_present(timer_id, state)
when is_map_key(state.synchronization.timers, timer_id)
@spec start_timer(Timer.id(), Timer.interval(), Clock.t(), Component.state()) ::
Component.state()
def start_timer(id, _interval, _clock, state) when is_timer_present(id, state) do
raise Membrane.TimerError, "Timer #{inspect(id)} already exists"
end
def start_timer(id, interval, clock, state) do
clock |> Clock.subscribe()
timer = Timer.start(id, interval, clock)
put_in(state, [:synchronization, :timers, id], timer)
end
@spec timer_interval(Timer.id(), Timer.interval(), Component.state()) ::
Component.state()
def timer_interval(id, interval, state) do
with {:ok, timer} <- state.synchronization.timers |> Map.fetch(id) do
put_in(
state,
[:synchronization, :timers, id],
Timer.set_interval(timer, interval)
)
else
:error -> raise Membrane.TimerError, "Timer #{inspect(id)} doesn't exist"
end
end
@spec stop_all_timers(Component.state()) :: Component.state()
def stop_all_timers(state) do
for {_id, timer} <- state.synchronization.timers do
stop_and_unsubscribe(timer)
end
Bunch.Access.put_in(state, [:synchronization, :timers], %{})
end
@spec stop_timer(Timer.id(), Component.state()) :: Component.state()
def stop_timer(id, state) do
{timer, state} = state |> Bunch.Access.pop_in([:synchronization, :timers, id])
if timer |> is_nil do
raise Membrane.TimerError, "Timer #{inspect(id)} doesn't exist"
else
stop_and_unsubscribe(timer)
state
end
end
@spec handle_tick(Timer.id(), Component.state()) :: Component.state()
def handle_tick(timer_id, state) when is_timer_present(timer_id, state) do
state =
CallbackHandler.exec_and_handle_callback(
:handle_tick,
Component.action_handler(state),
%{context: &Component.context_from_state/1},
[timer_id],
state
)
# in case the timer was removed in handle_tick
if is_timer_present(timer_id, state) do
update_in(state, [:synchronization, :timers, timer_id], &Timer.tick/1)
else
state
end
end
def handle_tick(_timer_id, state) do
state
end
@spec handle_clock_update(Timer.id(), Clock.ratio(), Component.state()) ::
Component.state()
def handle_clock_update(clock, ratio, state) do
update_in(
state,
[:synchronization, :timers],
&Bunch.Map.map_values(&1, fn
%Timer{clock: ^clock} = timer -> timer |> Timer.update_ratio(ratio)
timer -> timer
end)
)
end
@spec stop_and_unsubscribe(Timer.t()) :: :ok
defp stop_and_unsubscribe(timer) do
Timer.stop(timer)
Clock.unsubscribe(timer.clock)
end
end