Packages
membrane_core
0.8.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/element.ex
defmodule Membrane.Core.Element do
@moduledoc false
# Module containing functions spawning, shutting down, inspecting and controlling
# playback of elements. These functions are usually called by `Membrane.Pipeline`
# or `Membrane.Bin`.
#
# Modules in this namespace are responsible for managing elements: handling incoming
# data, executing callbacks and evaluating actions. These modules can be divided
# in terms of functionality in the following way:
# - Controllers handle messages received from other elements or calls from other
# controllers and handlers
# - Handlers handle actions invoked by element itself
# - Models contain some utility functions for accessing data in state
# - `Membrane.Core.Element.State` defines the state struct that these modules
# operate on.
use Bunch
use GenServer
import Membrane.Helper.GenServer
alias Membrane.{Clock, Element, Sync}
alias Membrane.Core.Element.{LifecycleController, PlaybackBuffer, State}
alias Membrane.Core.{Message, PlaybackHandler, Telemetry, TimerController}
alias Membrane.ComponentPath
alias Membrane.Core.Child.PadController
require Membrane.Core.Message
require Membrane.Core.Telemetry
require Membrane.Logger
@type options_t :: %{
module: module,
name: Element.name_t(),
user_options: Element.options_t(),
sync: Sync.t(),
parent: pid,
parent_clock: Clock.t(),
log_metadata: Keyword.t()
}
@doc """
Starts process for element of given module, initialized with given options and
links it to the current process in the supervision tree.
Calls `GenServer.start_link/3` underneath.
"""
@spec start_link(options_t, GenServer.options()) :: GenServer.on_start()
def start_link(options, process_options \\ []),
do: start_link(node(), options, process_options)
@spec start_link(node(), options_t, GenServer.options()) :: GenServer.on_start()
def start_link(node, options, process_options) when is_atom(node),
do: do_start(node, :start_link, options, process_options)
@doc """
Works similarly to `start_link/5`, but does not link to the current process.
"""
@spec start(options_t, GenServer.options()) :: GenServer.on_start()
def start(options, process_options \\ []),
do: start(node(), options, process_options)
@spec start(node(), options_t, GenServer.options()) :: GenServer.on_start()
def start(node, options, process_options),
do: do_start(node, :start, options, process_options)
defp do_start(node, method, options, process_options) do
%{module: module, name: name, user_options: user_options} = options
if Element.element?(options.module) do
Membrane.Logger.debug("""
Element #{method}: #{inspect(name)}
node: #{node},
module: #{inspect(module)},
element options: #{inspect(user_options)},
process options: #{inspect(process_options)}
""")
# rpc if necessary
if node == node() do
apply(GenServer, method, [__MODULE__, options, process_options])
else
:rpc.call(node, GenServer, method, [__MODULE__, options, process_options])
end
else
raise """
Cannot start element, passed module #{inspect(module)} is not a Membrane Element.
Make sure that given module is the right one and it uses Membrane.{Source | Filter | Sink}
"""
end
end
@doc """
Stops given element process.
It will wait for reply for amount of time passed as second argument
(in milliseconds).
Will trigger calling `c:Membrane.Element.Base.handle_shutdown/1`
callback.
"""
@spec shutdown(pid, timeout) :: :ok
def shutdown(server, timeout \\ 5000) do
GenServer.stop(server, :normal, timeout)
:ok
end
@impl GenServer
def init(options) do
Process.monitor(options.parent)
name_str = if String.valid?(options.name), do: options.name, else: inspect(options.name)
:ok = Membrane.Logger.set_prefix(name_str)
:ok = Logger.metadata(options.log_metadata)
:ok = ComponentPath.set_and_append(options.log_metadata[:parent_path] || [], name_str)
Telemetry.report_init(:element)
state = Map.take(options, [:module, :name, :parent_clock, :sync, :parent]) |> State.new()
with {:ok, state} <- LifecycleController.handle_init(options.user_options, state) do
{:ok, state}
else
{{:error, reason}, _state} -> {:stop, {:element_init, reason}}
end
end
@impl GenServer
def terminate(reason, state) do
Telemetry.report_terminate(:element)
{:ok, _state} = LifecycleController.handle_shutdown(reason, state)
:ok
end
@impl GenServer
def handle_call(Message.new(:get_clock), _from, state) do
reply({{:ok, state.synchronization.clock}, state})
end
@impl GenServer
def handle_call(Message.new(:linking_finished), _from, state) do
PadController.handle_linking_finished(state) |> reply(state)
end
@impl GenServer
def handle_call(
Message.new(:handle_link, [direction, this, other, other_info]),
_from,
state
) do
PadController.handle_link(direction, this, other, other_info, state) |> reply(state)
end
@impl GenServer
def handle_call(Message.new(:set_stream_sync, sync), _from, state) do
new_state = put_in(state.synchronization.stream_sync, sync)
reply({:ok, new_state})
end
@impl GenServer
def handle_call(message, _from, state) do
{{:error, {:invalid_message, message, mode: :call}}, state}
|> reply(state)
end
@impl GenServer
def handle_info({:DOWN, _ref, :process, parent_pid, reason}, %{parent_pid: parent_pid} = state) do
{:ok, state} = LifecycleController.handle_pipeline_down(reason, state)
{:stop, {:shutdown, :parent_crash}, state}
end
@impl GenServer
def handle_info(message, state) do
Telemetry.report_metric(
:queue_len,
:erlang.process_info(self(), :message_queue_len) |> elem(1)
)
do_handle_info(message, state)
end
@compile {:inline, do_handle_info: 2}
defp do_handle_info(Message.new(:change_playback_state, new_playback_state), state) do
PlaybackHandler.change_playback_state(new_playback_state, LifecycleController, state)
|> noreply(state)
end
defp do_handle_info(Message.new(type, _args, _opts) = msg, state)
when type in [:demand, :buffer, :caps, :event] do
PlaybackBuffer.store(msg, state) |> noreply(state)
end
defp do_handle_info(Message.new(:push_mode_announcement, [], for_pad: ref), state) do
PadController.enable_toilet_if_pull(ref, state) |> noreply(state)
end
defp do_handle_info(Message.new(:handle_unlink, pad_ref), state) do
PadController.handle_unlink(pad_ref, state) |> noreply(state)
end
defp do_handle_info(Message.new(:timer_tick, timer_id), state) do
TimerController.handle_tick(timer_id, state) |> noreply(state)
end
defp do_handle_info({:membrane_clock_ratio, clock, ratio}, state) do
TimerController.handle_clock_update(clock, ratio, state) |> noreply()
end
defp do_handle_info(Message.new(:log_metadata, metadata), state) do
:ok = Logger.metadata(metadata)
noreply({:ok, state})
end
defp do_handle_info(Message.new(_, _, _) = message, state) do
{{:error, {:invalid_message, message, mode: :info}}, state}
|> noreply(state)
end
defp do_handle_info(message, state) do
LifecycleController.handle_other(message, state) |> noreply(state)
end
end