Packages
membrane_core
0.8.0
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/bin.ex
defmodule Membrane.Core.Bin do
@moduledoc false
use Bunch
use GenServer
import Membrane.Helper.GenServer
alias __MODULE__.{LinkingBuffer, State}
alias Membrane.{CallbackError, Core, ComponentPath, Pad, Sync}
alias Membrane.Core.{CallbackHandler, Message, Telemetry}
alias Membrane.Core.Child.{PadController, PadSpecHandler}
require Membrane.Core.Message
require Membrane.Core.Telemetry
require Membrane.Logger
@type options_t :: %{
name: atom,
module: module,
parent: pid,
user_options: Membrane.Bin.options_t(),
parent_clock: Membrane.Clock.t(),
log_metadata: Keyword.t()
}
@doc """
Starts the Bin based on given module and links it to the current
process.
Bin options are passed to module's `c:handle_init/1` callback.
Process options are internally passed to `GenServer.start_link/3`.
Returns the same values as `GenServer.start_link/3`.
"""
@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),
do: do_start(node, :start_link, options, process_options)
@doc """
Works similarly to `start_link/2`, 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
if options.module |> Membrane.Bin.bin?() do
Membrane.Logger.debug("""
Bin #{method}: #{inspect(options.name)}
node: #{node},
module: #{inspect(options.module)},
bin options: #{inspect(options.user_options)},
process options: #{inspect(process_options)}
""")
# rpc if necessary
if node == node() do
apply(GenServer, method, [Membrane.Core.Bin, options, process_options])
else
:rpc.call(node, GenServer, method, [Membrane.Core.Bin, options, process_options])
end
else
raise """
Cannot start bin, passed module #{inspect(options.module)} is not a Membrane Bin.
Make sure that given module is the right one and it uses Membrane.Bin
"""
end
end
@doc """
Changes bin's playback state to `:stopped` and terminates its process
"""
@spec stop_and_terminate(bin :: pid) :: :ok
def stop_and_terminate(bin) do
Message.send(bin, :stop_and_terminate)
:ok
end
@impl GenServer
def init(options) do
Process.monitor(options.parent)
%{name: name, module: module, log_metadata: log_metadata} = options
name_str = if String.valid?(name), do: name, else: inspect(name)
:ok = Membrane.Logger.set_prefix(name_str <> " bin")
Logger.metadata(log_metadata)
:ok = ComponentPath.set_and_append(log_metadata[:parent_path] || [], name_str <> " bin")
Telemetry.report_init(:bin)
clock_proxy = Membrane.Clock.start_link(proxy: true) ~> ({:ok, pid} -> pid)
clock = if Bunch.Module.check_behaviour(module, :membrane_clock?), do: clock_proxy, else: nil
state =
%State{
module: module,
name: name,
parent_pid: options.parent,
synchronization: %{
parent_clock: options.parent_clock,
timers: %{},
clock: clock,
clock_provider: %{clock: nil, provider: nil, choice: :auto},
clock_proxy: clock_proxy,
# This is a sync for siblings. This is not yet allowed.
stream_sync: Sync.no_sync(),
latency: 0
},
children_log_metadata: log_metadata
}
|> PadSpecHandler.init_pads()
with {:ok, state} <-
CallbackHandler.exec_and_handle_callback(
:handle_init,
Membrane.Core.Bin.ActionHandler,
%{state: false},
[options.user_options],
state
) do
{:ok, state}
else
{{:error, reason}, _state} ->
raise CallbackError, kind: :error, callback: {module, :handle_init}, reason: reason
{other, _state} ->
raise CallbackError, kind: :bad_return, callback: {module, :handle_init}, value: other
end
end
@impl GenServer
# Bin-specific message.
# This forwards all :demand, :caps, :buffer, :event
# messages to an appropriate element.
def handle_info(Message.new(type, _args, for_pad: pad) = msg, state)
when type in [:demand, :caps, :buffer, :event, :push_mode_announcement] do
outgoing_pad =
pad
|> Pad.get_corresponding_bin_pad()
LinkingBuffer.store_or_send(msg, outgoing_pad, state)
~> {:ok, &1}
|> noreply()
end
@impl GenServer
def handle_info(Message.new(:handle_unlink, pad_ref), state) do
PadController.handle_pad_removed(pad_ref, state)
|> noreply()
end
@impl GenServer
def handle_info(message, state) do
Core.Parent.MessageDispatcher.handle_message(message, 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()
end
@impl GenServer
def handle_call(Message.new(:linking_finished), _from, state) do
PadController.handle_linking_finished(state)
|> reply()
end
@impl GenServer
def handle_call(Message.new(:get_clock), _from, state) do
reply({{:ok, state.synchronization.clock}, state})
end
@impl GenServer
def terminate(reason, state) do
Telemetry.report_terminate(:bin)
:ok = state.module.handle_shutdown(reason, state.internal_state)
end
end