Packages
membrane_core
0.5.2
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.{Message, State}
alias Membrane.PlaybackState
require PlaybackState
require Message
use Bunch
use Membrane.Log, tags: :core
@type handler_return_t ::
{:ok | {:error, any()}, State.t()} | {:stop, any(), State.t()}
@doc """
Callback invoked when playback state is going to be changed.
"""
@callback handle_playback_state(PlaybackState.t(), PlaybackState.t(), State.t()) ::
handler_return_t
@doc """
Callback invoked when playback state has changed.
"""
@callback handle_playback_state_changed(
PlaybackState.t(),
PlaybackState.t(),
State.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 [:terminating, :stopped, :prepared, :playing]
@states_positions @states |> Enum.with_index() |> Map.new()
defmacro __using__(_) do
quote location: :keep do
alias unquote(__MODULE__)
@behaviour unquote(__MODULE__)
@impl unquote(__MODULE__)
def handle_playback_state(_old, _new, state), do: {:ok, state}
@impl unquote(__MODULE__)
def handle_playback_state_changed(_old, _new, state), do: {:ok, state}
@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.
nil means no callback should be called.
"""
@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
| nil
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 state_change_callback(:stopped, :terminating), do: nil
@spec request_playback_state_change(pid, Membrane.PlaybackState.t()) :: :ok
def request_playback_state_change(pid, new_state)
when Membrane.PlaybackState.is_playback_state(new_state) do
Message.send(pid, :change_playback_state, new_state)
:ok
end
@spec change_playback_state(PlaybackState.t(), module(), State.t()) :: handler_return_t()
def change_playback_state(new_playback_state, handler, state) do
%{playback: playback} = state
playback =
case playback do
%{target_state: :terminating} -> playback
_ -> %{playback | target_state: new_playback_state}
end
if playback.pending_state == nil and playback.state != playback.target_state do
do_change_playback_state(handler, %{state | playback: playback})
else
{:ok, %{state | playback: playback}}
end
end
defp do_change_playback_state(handler, state) do
playback = state.playback
with {:ok, next_playback_state} <-
next_state(playback.state, playback.target_state),
{:ok, state} <-
handler.handle_playback_state(playback.state, next_playback_state, state) do
playback = %{state.playback | pending_state: next_playback_state}
state = %{state | playback: playback}
if state.playback.async_state_change do
{:ok, state}
else
continue_playback_change(handler, state)
end
end
end
@spec suspend_playback_change(pb) :: {:ok, pb} when pb: State.t()
def suspend_playback_change(state) do
playback = %{state.playback | async_state_change: true}
{:ok, %{state | playback: playback}}
end
def suspended?(state), do: state.playback.async_state_change
@spec continue_playback_change(module, State.t()) :: handler_return_t()
def continue_playback_change(handler, state) do
old_playback = state.playback
new_playback = %{
old_playback
| async_state_change: false,
state: old_playback.pending_state,
pending_state: nil
}
state = %{state | playback: new_playback}
handler_res =
handler.handle_playback_state_changed(
old_playback.state,
old_playback.pending_state,
state
)
case handler_res do
{:stop, _reason, state} = stop_tuple ->
maybe_notify_controller(handler, state)
stop_tuple
{:ok, state} ->
maybe_notify_controller(handler, state)
change_playback_state(state.playback.target_state, handler, state)
end
end
defp maybe_notify_controller(handler, %{controlling_pid: controlling_pid} = state)
when not is_nil(controlling_pid),
do:
handler.notify_controller(:playback_changed, state.playback.state, state.controlling_pid)
defp maybe_notify_controller(_handler, _state), do: :ok
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