Packages
membrane_core
0.2.1
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}
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(Playback.state_t(), Playback.state_t(), Playbackable.t()) ::
handler_return_t
@doc """
Callback invoked when playback state has changed.
"""
@callback handle_playback_state_changed(
Playback.state_t(),
Playback.state_t(),
Playbackable.t()
) :: handler_return_t
@doc """
Callback that is to notify controller that playback state has changed.
"""
@callback notify_controller(:playback_changed, Playback.state_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 :: Playback.state_t(), next_state :: Playback.state_t()) ::
atom()
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
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 != new_playback_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
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
def lock_target_state(playbackable) do
if not Playbackable.get_playback(playbackable).target_locked? do
{:ok, switch_target_lock(playbackable)}
else
{{:error, :playback_already_locked}, playbackable}
end
end
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
def suspend_playback_change(playbackable) do
{:ok, playbackable |> Playbackable.update_playback(&%{&1 | async_state_change: true})}
end
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
with {:ok, curr_pos} <-
@states_positions[current]
|> Bunch.error_if_nil(:invalid_current_playback_state),
{:ok, target_pos} <-
@states_positions[target]
|> Bunch.error_if_nil(:invalid_target_playback_state) do
next_state =
cond do
curr_pos < target_pos -> @states |> Enum.at(curr_pos + 1)
curr_pos > target_pos -> @states |> Enum.at(curr_pos - 1)
end
{:ok, next_state}
end
end
end