Packages
membrane_core
0.4.3
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.7
1.2.6
1.2.5
retired
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc1
1.1.2
1.1.1
1.1.0
1.1.0-rc1
1.1.0-rc0
1.0.1
1.0.0
1.0.0-rc1
1.0.0-rc0
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
retired
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.2
0.10.1
0.10.0
0.9.0
0.8.2
0.8.1
0.8.0
0.7.0
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
Membrane Multimedia Framework (Core)
Current section
Files
Jump to
Current section
Files
lib/membrane/core/playback_handler.ex
defmodule Membrane.Core.PlaybackHandler do
@moduledoc false
# Behaviour for modules that have playback state, i.e. elements and pipelines
#
# There are three playback states: :stopped, :prepared and :playing.
# Playback state always changes only one step at once in this order, and can
# be handled by `handle_stopped_to_prepared`, `handle_playing_to_prepared`,
# `handle_prepared_to_playing` and `handle_prepared_to_stopped` callbacks.
alias Membrane.Core.{Playback, Playbackable}
alias Membrane.PlaybackState
use Bunch
@type handler_return_t :: {:ok | {:error, any()}, Playbackable.t()}
@doc """
Callback invoked when playback state is going to be changed.
"""
@callback handle_playback_state(PlaybackState.t(), PlaybackState.t(), Playbackable.t()) ::
handler_return_t
@doc """
Callback invoked when playback state has changed.
"""
@callback handle_playback_state_changed(
PlaybackState.t(),
PlaybackState.t(),
Playbackable.t()
) :: handler_return_t
@doc """
Callback that is to notify controller that playback state has changed.
"""
@callback notify_controller(:playback_changed, PlaybackState.t(), pid) :: :ok
@states [:stopped, :prepared, :playing]
@states_positions @states |> Enum.with_index() |> Map.new()
defmacro __using__(_) do
quote location: :keep do
alias Membrane.Core.Playbackable
alias unquote(__MODULE__)
@behaviour unquote(__MODULE__)
@impl unquote(__MODULE__)
def handle_playback_state(_old, _new, playbackable), do: {:ok, playbackable}
@impl unquote(__MODULE__)
def handle_playback_state_changed(_old, _new, playbackable), do: {:ok, playbackable}
@impl unquote(__MODULE__)
def notify_controller(:playback_changed, playback_state, controlling_pid) do
alias Membrane.Core.Message
require Message
Message.send(controlling_pid, :playback_state_changed, [self(), playback_state])
:ok
end
defoverridable unquote(__MODULE__)
end
end
@doc """
Returns callback name for transition between playback states
"""
@spec state_change_callback(prev_state :: PlaybackState.t(), next_state :: PlaybackState.t()) ::
:handle_playing_to_prepared
| :handle_prepared_to_playing
| :handle_prepared_to_stopped
| :handle_stopped_to_prepared
def state_change_callback(:stopped, :prepared), do: :handle_stopped_to_prepared
def state_change_callback(:playing, :prepared), do: :handle_playing_to_prepared
def state_change_callback(:prepared, :playing), do: :handle_prepared_to_playing
def state_change_callback(:prepared, :stopped), do: :handle_prepared_to_stopped
@spec change_playback_state(PlaybackState.t(), module(), Playbackable.t()) :: handler_return_t()
def change_playback_state(new_playback_state, handler, playbackable) do
{playback, playbackable} =
playbackable
|> Playbackable.get_and_update_playback(fn
%Playback{target_locked?: true} = p -> {p, p}
%Playback{} = p -> %Playback{p | target_state: new_playback_state} ~> {&1, &1}
end)
if playback.pending_state == nil and playback.state != playback.target_state do
do_change_playback_state(playback, handler, playbackable)
else
{:ok, playbackable}
end
end
defp do_change_playback_state(playback, handler, playbackable) do
with {{:ok, next_playback_state}, playbackable} <-
{next_state(playback.state, playback.target_state), playbackable},
{:ok, playbackable} <-
handler.handle_playback_state(playback.state, next_playback_state, playbackable) do
{async_state_change, playbackable} =
playbackable
|> Playbackable.get_and_update_playback(
&{&1.async_state_change, %Playback{&1 | pending_state: next_playback_state}}
)
if async_state_change do
{:ok, playbackable}
else
continue_playback_change(handler, playbackable)
end
end
end
@spec change_and_lock_playback_state(PlaybackState.t(), module(), Playbackable.t()) ::
handler_return_t()
def change_and_lock_playback_state(new_playback_state, handler, playbackable) do
with {:ok, playbackable} <- lock_target_state(playbackable) do
playbackable =
playbackable
|> Playbackable.update_playback(&%Playback{&1 | target_state: new_playback_state})
change_playback_state(new_playback_state, handler, playbackable)
end
end
@spec lock_target_state(pb) :: {:ok | {:error, :playback_already_locked}, pb}
when pb: Playbackable.t()
def lock_target_state(playbackable) do
if Playbackable.get_playback(playbackable).target_locked? do
{{:error, :playback_already_locked}, playbackable}
else
{:ok, switch_target_lock(playbackable)}
end
end
@spec unlock_target_state(pb) :: {:ok | {:error, :playback_already_unlocked}, pb}
when pb: Playbackable.t()
def unlock_target_state(playbackable) do
if Playbackable.get_playback(playbackable).target_locked? do
{:ok, switch_target_lock(playbackable)}
else
{{:error, :playback_already_unlocked}, playbackable}
end
end
@spec suspend_playback_change(pb) :: {:ok, pb} when pb: Playbackable.t()
def suspend_playback_change(playbackable) do
{:ok, playbackable |> Playbackable.update_playback(&%{&1 | async_state_change: true})}
end
@spec continue_playback_change(module, Playbackable.t()) :: handler_return_t()
def continue_playback_change(handler, playbackable) do
{old_playback, playbackable} =
playbackable
|> Playbackable.get_and_update_playback(fn p ->
{p,
%Playback{
p
| async_state_change: false,
state: p.pending_state,
pending_state: nil
}}
end)
with {:ok, playbackable} <-
handler.handle_playback_state_changed(
old_playback.state,
old_playback.pending_state,
playbackable
) do
playback = playbackable |> Playbackable.get_playback()
if controlling_pid = playbackable |> Playbackable.get_controlling_pid() do
handler.notify_controller(:playback_changed, playback.state, controlling_pid)
end
change_playback_state(playback.target_state, handler, playbackable)
end
end
defp switch_target_lock(playbackable) do
playbackable
|> Playbackable.update_playback(&%Playback{&1 | target_locked?: not &1.target_locked?})
end
defp next_state(current, target) do
withl curr: curr_pos when curr_pos != nil <- @states_positions[current],
target: target_pos when target_pos != nil <- @states_positions[target],
diff: true <- curr_pos != target_pos do
next_state =
if curr_pos < target_pos do
@states |> Enum.at(curr_pos + 1)
else
@states |> Enum.at(curr_pos - 1)
end
{:ok, next_state}
else
curr: _ -> {:error, :invalid_current_playback_state}
target: _ -> {:error, :invalid_target_playback_state}
diff: _ -> {:error, :target_and_current_states_equal}
end
end
end