Packages
membrane_core
0.5.3
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/bin.ex
defmodule Membrane.Bin do
@moduledoc """
Bins, similarly to pipelines, are containers for elements.
However, at the same time, they can be placed and linked within pipelines.
Although bin is a separate Membrane entity, it can be perceived as a pipeline within an element.
Bins can also be nested within one another.
There are two main reasons why bins are useful:
* they enable creating reusable element groups
* they allow managing their children, for instance by dynamically spawning or replacing them as the stream changes.
In order to create bin `use Membrane.Bin` in your callback module.
"""
use Bunch
use Membrane.Log, tags: :core
use GenServer
import Membrane.Helper.GenServer
alias Membrane.{Element, Pad, Sync}
alias Membrane.Bin.CallbackContext
alias Membrane.Core.{
Bin,
CallbackHandler,
Child,
Parent,
Message
}
alias Membrane.Core.Child.{PadController, PadModel, PadSpecHandler, PadsSpecs}
alias Membrane.Core.Bin.{State, LinkingBuffer}
alias Membrane.{CallbackError, Element}
require Element
require Message
require Pad
require PadsSpecs
require PadModel
require Membrane.PlaybackState
@type state_t :: Bin.State.t()
@type callback_return_t ::
{:ok | {:ok, [Membrane.Parent.Action.t()]} | {:error, any}, state_t()} | {:error, any}
@typedoc """
Defines options that can be passed to `start_link/3` and received
in `c:handle_init/1` callback.
"""
@type options_t :: struct | nil
@typedoc """
Type that defines a bin name by which it is identified.
"""
@type name_t :: any()
@doc """
Enables to check whether module is membrane bin.
"""
@callback membrane_bin? :: true
@doc """
Callback invoked on initialization of bin process. It should parse options
and initialize bin's internal state. Internally it is invoked inside
`c:GenServer.init/1` callback.
"""
@callback handle_init(options :: options_t) ::
{{:ok, [Membrane.Parent.Action.spec_action_t()]}, Bin.State.internal_state_t()}
| {:error, any}
@doc """
Callback that is called when new pad has beed added to bin. Executed
ONLY for dynamic pads.
"""
@callback handle_pad_added(
pad :: Pad.ref_t(),
context :: CallbackContext.PadAdded.t(),
state :: Bin.State.internal_state_t()
) :: callback_return_t
@doc """
Callback that is called when some pad of the bin has beed removed. Executed
ONLY for dynamic pads.
"""
@callback handle_pad_removed(
pad :: Pad.ref_t(),
context :: CallbackContext.PadRemoved.t(),
state :: Bin.State.internal_state_t()
) :: callback_return_t
@doc """
Automatically implemented callback used to determine whether bin exports clock.
"""
@callback membrane_clock? :: boolean()
@doc PadsSpecs.def_pad_docs(:input, :bin)
defmacro def_input_pad(name, spec) do
PadsSpecs.def_bin_pad(name, :input, spec)
end
@doc PadsSpecs.def_pad_docs(:output, :bin)
defmacro def_output_pad(name, spec) do
PadsSpecs.def_bin_pad(name, :output, spec)
end
@doc """
Defines that bin exposes a clock which is a proxy to one of its children.
If this macro is not called, no ticks will be forwarded to parent, regardless
of clock definitions in its children.
"""
defmacro def_clock(doc \\ "") do
quote do
@membrane_bin_exposes_clock true
Module.put_attribute(__MODULE__, :membrane_clock_moduledoc, """
## Clock
This bin exposes a clock of one of its children.
#{unquote(doc)}
""")
@impl true
def membrane_clock?, do: true
end
end
@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(
atom,
module,
bin_options :: options_t,
process_options :: GenServer.options()
) :: GenServer.on_start()
def start_link(my_name, module, bin_options \\ nil, process_options \\ []) do
if module |> bin? do
debug("""
Bin start link: module: #{inspect(module)},
bin options: #{inspect(bin_options)},
process options: #{inspect(process_options)}
""")
GenServer.start_link(__MODULE__, {my_name, module, bin_options}, process_options)
else
warn_error(
"""
Cannot start bin, passed module #{inspect(module)} is not a Membrane Bin.
Make sure that given module is the right one and it uses Membrane.Bin
""",
{:not_bin, module}
)
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({my_name, module, bin_options}) do
clock =
if module |> Bunch.Module.check_behaviour(:membrane_clock?) do
{:ok, pid} = Membrane.Clock.start_link(proxy: true)
pid
end
state =
%State{
bin_options: bin_options,
module: module,
name: my_name,
clock_proxy: clock,
synchronization: %{
parent_clock: clock,
timers: %{},
clock: clock,
# This is a sync for siblings. This is not yet allowed.
stream_sync: Sync.no_sync(),
latency: 0
}
}
|> PadSpecHandler.init_pads()
with {:ok, state} <-
CallbackHandler.exec_and_handle_callback(
:handle_init,
Membrane.Core.Bin.ActionHandler,
%{state: false},
[bin_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
@doc """
Checks whether module is a bin.
"""
@spec bin?(module) :: boolean
def bin?(module) do
module |> Bunch.Module.check_behaviour(:membrane_bin?)
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_announcment] do
outgoing_pad =
pad
|> Pad.get_corresponding_bin_pad()
LinkingBuffer.store_or_send(msg, outgoing_pad, state)
~> {:ok, &1}
|> noreply()
end
@impl GenServer
# Element-specific message.
def handle_info(Message.new(:demand_unit, [demand_unit, pad_ref]), state) do
Child.LifecycleController.handle_demand_unit(demand_unit, pad_ref, state)
|> 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
Parent.MessageDispatcher.handle_message(message, state)
end
@impl GenServer
def handle_call(Message.new(:set_controlling_pid, pid), _, state) do
Child.LifecycleController.handle_controlling_pid(pid, state)
|> reply()
end
@impl GenServer
def handle_call(
Message.new(:handle_link, [pad_ref, pad_direction, pid, other_ref, other_info, props]),
_from,
state
) do
{{:ok, info}, state} =
PadController.handle_link(pad_ref, pad_direction, pid, other_ref, other_info, props, state)
{{:ok, info}, state}
|> reply()
end
@impl GenServer
def handle_call(Message.new(:linking_finished), _, state) do
PadController.handle_linking_finished(state)
|> reply()
end
@impl GenServer
def handle_call(Message.new(:handle_watcher, watcher), _, state) do
Child.LifecycleController.handle_watcher(watcher, state)
|> reply()
end
@doc """
Brings all the stuff necessary to implement a pipeline.
Options:
- `:bring_spec?` - if true (default) imports and aliases `Membrane.ParentSpec`
- `:bring_pad?` - if true (default) requires and aliases `Membrane.Pad`
"""
defmacro __using__(options) do
quote location: :keep do
use Membrane.Parent, unquote(options)
alias unquote(__MODULE__)
@behaviour unquote(__MODULE__)
import Membrane.Element.Base, only: [def_options: 1]
import unquote(__MODULE__),
only: [def_input_pad: 2, def_output_pad: 2, def_clock: 0, def_clock: 1]
require Membrane.Core.Child.PadsSpecs
Membrane.Core.Child.PadsSpecs.ensure_default_membrane_pads()
@impl true
def membrane_bin?, do: true
@impl true
def membrane_clock?, do: false
@impl true
def handle_init(_options), do: {{:ok, spec: %Membrane.ParentSpec{}}, %{}}
@impl true
def handle_notification(notification, _from, state),
do: {{:ok, notify: notification}, state}
@impl true
def handle_pad_added(_pad, _ctx, state), do: {:ok, state}
@impl true
def handle_pad_removed(_pad, _ctx, state), do: {:ok, state}
defoverridable handle_init: 1,
handle_notification: 3,
handle_pad_added: 3,
handle_pad_removed: 3,
membrane_clock?: 0
end
end
end