Packages
membrane_core
0.12.4
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
alias Membrane.Core.Element.DemandHandler
alias Membrane.{Clock, Core, ResourceGuard, Sync}
alias Membrane.Core.{SubprocessSupervisor, TimerController}
alias Membrane.Core.Element.{
BufferController,
DemandController,
EffectiveFlowController,
EventController,
LifecycleController,
PadController,
State,
StreamFormatController
}
require Membrane.Core.Message, as: Message
require Membrane.Core.Telemetry, as: Telemetry
require Membrane.Logger
@type options :: %{
module: module,
name: Membrane.Element.name(),
node: node | nil,
user_options: Membrane.Element.options(),
sync: Sync.t(),
parent: pid,
parent_clock: Clock.t(),
parent_path: Membrane.ComponentPath.path(),
log_metadata: Logger.metadata(),
subprocess_supervisor: pid(),
parent_supervisor: pid()
}
@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) :: GenServer.on_start()
def start_link(options),
do: do_start(:start_link, options)
@doc """
Works similarly to `start_link/3`, but does not link to the current process.
"""
@spec start(options) :: GenServer.on_start()
def start(options),
do: do_start(:start, options)
defp do_start(method, options) do
%{module: module, name: name, node: node, user_options: user_options} = options
Membrane.Logger.debug("""
Element #{method}: #{inspect(name)}
node: #{node},
module: #{inspect(module)},
element options: #{inspect(user_options)},
method: #{method}
""")
# rpc if necessary
if node do
result = :rpc.call(node, GenServer, :start, [__MODULE__, options])
# TODO: use an atomic way of linking once https://github.com/erlang/otp/issues/6375 is solved
with {:start_link, {:ok, pid}} <- {method, result}, do: Process.link(pid)
result
else
apply(GenServer, method, [__MODULE__, options])
end
end
@impl GenServer
def init(options) do
Process.link(options.parent_supervisor)
observability_config = %{
name: options.name,
component_type: :bin,
pid: self(),
parent_path: options.parent_path,
log_metadata: options.log_metadata
}
Membrane.Core.Observability.setup(observability_config)
SubprocessSupervisor.set_parent_component(options.subprocess_supervisor, observability_config)
{:ok, resource_guard} =
SubprocessSupervisor.start_utility(options.subprocess_supervisor, {ResourceGuard, self()})
Telemetry.report_init(:element)
ResourceGuard.register(resource_guard, fn -> Telemetry.report_terminate(:element) end)
state =
Map.take(options, [:module, :name, :parent_clock, :sync, :parent, :subprocess_supervisor])
|> Map.put(:resource_guard, resource_guard)
|> State.new()
state = LifecycleController.handle_init(options.user_options, state)
{:ok, state, {:continue, :setup}}
end
@impl GenServer
def handle_continue(:setup, state) do
state = LifecycleController.handle_setup(state)
{:noreply, state}
end
@impl GenServer
def handle_call(Message.new(:get_clock), _from, state) do
{:reply, state.synchronization.clock, state}
end
@impl GenServer
def handle_call(
Message.new(:handle_link, [direction, this, other, params]),
_from,
state
) do
{reply, state} = PadController.handle_link(direction, this, other, params, state)
{:reply, reply, state}
end
@impl GenServer
def handle_call(Message.new(:set_stream_sync, sync), _from, state) do
state = put_in(state.synchronization.stream_sync, sync)
{:reply, :ok, state}
end
@impl GenServer
def handle_call(Message.new(:get_child_pid, _child_name), _from, state) do
{:reply, {:error, :element_cannot_have_children}, state}
end
@impl GenServer
def handle_call(message, {pid, _tag}, _state) do
raise Membrane.ElementError,
"Received invalid message #{inspect(message)} from #{inspect(pid)}"
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(:atomic_demand_increased, pad_ref), state) do
state = DemandController.snapshot_atomic_demand(pad_ref, state)
{:noreply, state}
end
defp do_handle_info(Message.new(:resume_handle_demand_loop), state) do
state = DemandHandler.handle_delayed_demands(state)
{:noreply, state}
end
defp do_handle_info(Message.new(:buffer, buffers, _opts) = msg, state) do
pad_ref = Message.for_pad(msg)
state = BufferController.handle_buffer(pad_ref, buffers, state)
{:noreply, state}
end
defp do_handle_info(Message.new(:stream_format, stream_format, _opts) = msg, state) do
pad_ref = Message.for_pad(msg)
state = StreamFormatController.handle_stream_format(pad_ref, stream_format, state)
{:noreply, state}
end
defp do_handle_info(Message.new(:event, event, _opts) = msg, state) do
pad_ref = Message.for_pad(msg)
state = EventController.handle_event(pad_ref, event, state)
{:noreply, state}
end
defp do_handle_info(Message.new(:play), state) do
state = LifecycleController.handle_playing(state)
{:noreply, state}
end
defp do_handle_info(Message.new(:handle_unlink, pad_ref), state) do
state = PadController.handle_unlink(pad_ref, state)
{:noreply, state}
end
defp do_handle_info(Message.new(:timer_tick, timer_id), state) do
state = TimerController.handle_tick(timer_id, state)
{:noreply, state}
end
defp do_handle_info(Message.new(:parent_notification, notification), state) do
state = Core.Child.LifecycleController.handle_parent_notification(notification, state)
{:noreply, state}
end
defp do_handle_info(
Message.new(:sender_effective_flow_control_resolved, [
input_pad_ref,
effective_flow_control
]),
state
) do
state =
EffectiveFlowController.handle_sender_effective_flow_control(
input_pad_ref,
effective_flow_control,
state
)
{:noreply, state}
end
defp do_handle_info(Message.new(:terminate), state) do
state = LifecycleController.handle_terminate_request(state)
{:noreply, state}
end
defp do_handle_info(Message.new(_type, _args, _opts) = message, _state) do
raise Membrane.ElementError, "Received invalid message #{inspect(message)}"
end
defp do_handle_info({:membrane_clock_ratio, clock, ratio}, state) do
state = TimerController.handle_clock_update(clock, ratio, state)
{:noreply, state}
end
defp do_handle_info(message, state) do
state = LifecycleController.handle_info(message, state)
{:noreply, state}
end
end