View Source Upgrading to v0.11
Improvements in v0.11 required some breaking changes, so here comes the guide that will help you adjust your code to the new API. See the changelog for details.
Deps upgrade
Update membrane_core
to v0.11
defp deps do
[
{:membrane_core, "~> 0.11.0"},
...
]
end
Use the new way of interaction with Membrane.Pipeline
Use the new return type of Membrane.Pipeline.start/3
and Membrane.Pipeline.start_link/3
- {:ok, pid} = Membrane.Pipeline.start_link(...)
- send(pid, :message)
+ {:ok, _pipeline_supervisor, pipeline} = Membrane.Pipeline.start_link(...)
+ send(pipeline, :message)
Now, when you get EXIT
signal from pipeline
, it means only that the pipeline process is dead.
When you get EXIT
signal from pipeline_supervisor
, it means that the pipeline,
all its children, descendants and utilities are dead.
In tests, it's best to use Membrane.Testing.Pipeline.start_link_supervised!/1
. See the Testing section for details.
Update format of tuples returned from callbacks
Update type of tuples returned from callbacks to {actions, state}
If callback returns:
{:ok, state}
, change it to{[], state}
- {:ok, state}
+ {[], state}
{{:ok, actions}, state}
, change it to{actions, state}
- {{:ok, event: {pad, event}}, state}
+ {[event: {pad, event}], state}
{:error, reason}
or{{:error, reason}, state}
, raise an error, instead of returning value from callback
- {:error, :something_went_wrong}
+ raise "Something went wrong!"
Update callbacks
Add context
argument to handle_init
- def handle_init(options) do
+ def handle_init(_context, options) do
Instead of handle_stopped_to_prepared/2
, implement handle_setup/2
- def handle_stopped_to_prepared(ctx, state) do
+ def handle_setup(ctx, state) do
In general, handle_init
should cover simple tasks like parsing options, while handle_setup
is there for complex and long-lasting operations. See the docs for details.
Rename handle_prepared_to_playing/2
to handle_playing/2
- def handle_prepared_to_playing(ctx, state) do
+ def handle_playing(ctx, state) do
Remove handle_playing_to_prepared/2
, handle_prepared_to_stopped/2
and handle_shutdown/2
. Instead, use Membrane.ResourceGuard
, Membrane.UtilitySupervisor
or handle_terminate_request/2
:
- def handle_stopped_to_prepared(_ctx, state) do
- resource = create_resource()
- {:ok, %{state | resource: resource}}
- end
- ...
- def handle_prepared_to_stopped(_ctx, state) do
- cleanup(state.resource)
- end
+ def handle_setup(ctx, state) do
+ resource = create_resource()
+ # The resource will be automatically cleaned up when the component terminates
+ # unless you do that earlier by calling `Membrane.Resource.cleanup/2`
+ Membrane.ResourceGuard.register(
+ ctx.resource_guard,
+ fn -> cleanup(resource)
+ end)
+ {[], %{state | resource: resource}}
+ end
- def handle_stopped_to_prepared(_ctx, state) do
- {:ok, pid} = GenServer.start_link(SomeServer, options)
- {:ok, %{state | utility: pid}}
- end
- ...
- def handle_prepared_to_stopped(_ctx, state) do
- GenServer.stop(state.utility)
- end
+ def handle_setup(ctx, state) do
+ Membrane.UtilitySupervisor.start_link_child(ctx.utility_supervisor, SomeServer)
+ {[], %{state | utility: pid}}
+ end
- def handle_prepared_to_stopped(_ctx, state) do
- do_some_work(state)
- {:ok, state}
- end
+ def handle_terminate_request(ctx, state) do
+ # This will only be called upon graceful termination,
+ # i.e. `Membrane.Pipeline.terminate/1`, termination of parent
+ # or being removed by parent (`remove_child` action).
+ # Won't be called if component crashes.
+ do_some_work(state)
+
+ # You can defer component termination by not returning `:terminate`
+ # from this callback and returning it later instead.
+ {[terminate: :normal], state}
+ end
Rename handle_other/3
into handle_info/3
(unless your handle_other/3
was used to receive message from parent - if so, see subsection about handle_parent_notification/3
below)
@impl true
- def handle_other(message, _ctx, state) do
+ def handle_info(message, _ctx, state) do
Rename playback_state
to playback
in contexts. Rely on :stopped
instead of :prepared
- def handle_other(message, %{playback_state: :playing}, state) do
+ def handle_info(message, %{playback: :playing}, state) do
- def handle_other(message, %{playback_state: :prepared}, state) do
+ def handle_info(message, %{playback: :stopped}, state) do
In elements, rename handle_caps/4
to handle_stream_format/4
- def handle_caps(pad_ref, caps, ctx, state) do
+ def handle_stream_format(pad_ref, stream_format, ctx, state) do
In pipelines and bins, change arguments in handle_element_start_of_stream
and handle_element_end_of_stream
, since till now on they accept children name and pad reference as two separate arguments instead of storing them as tuple in the first argument.
use Membrane.Pipeline
...
@impl true
- def handle_element_end_of_stream({child_name, pad_ref}, context, state)
+ def handle_element_end_of_stream(child_name, pad_ref, context, state)
In pipelines and bins, rename handle_notification/4
into handle_child_notification/4
@impl true
- def handle_notification(notification, child, context, state) do
+ def handle_child_notification(notification, child, context, state) do
In elements and bins, add an implementation of handle_parent_notification/3
in place of the handle_other/3
that were responsible for receiving messages from the parents.
@impl true
- def handle_other(parent_notification, context, state) do
+ def handle_parent_notification(parent_notification, context, state) do
Rename actions returned from callbacks
In elements:
:caps
->:stream_format
:notify
->:notify_parent
In bins:
:notify
->:notify_parent
:forward
->:notify_child
In pipelines:
:forward
->:notify_child
- {{:ok, caps: %My.Format{freq: 1}}, state}
+ {[stream_format: %My.Format{freq: 1}], state}
use Membrane.Bin
...
- {{:ok, [forward: {child, :response}]}, state}
+ {[notify_child: {child, :response}], state}
Update pads definitions
Instead of using :caps
, use :accepted_format
option.
Option :accepted_format
can receive:
- Module name
- caps: My.Format
+ accepted_format: My.Format
- Elixir pattern
- caps: {My.Format, field: one_of([:some, :enumeration])}
+ accepted_format: %My.Format{field: value} when value in [:some, :enumeration]
- caps: :any
+ accepted_format: _any
- Call to
any_of
function. You can pass there as many arguments, as you want. Each argument should be an Elixir pattern or a module name
- caps: [My.Format, My.Another.Format]
+ accepted_format: any_of(My.Format, My.Another.Format)
- caps: [My.Format, {My.Another.Format, field: :strict_value}, My.Yet.Another.Format]
+ accepted_format: any_of(My.Format, %My.Another.Format{field: :strict_value}, My.Yet.Another.Format)
Update options definitions
This section applies to options defined via def_options
macro and pad options defined in def_input_pad
or def_output_pad
.
Remove :type
key and related value from the options definitions. Add :spec
instead, if it hasn't been added before, and proper :inspector
, if the option has a default value, that shouldn't be inspected by inspect/1
during generating docs.
- def_options tick_interval: [
- type: :time,
- default: Membrane.Time.seconds(1)
- ],
- process: [
- type: :pid
- ]
+ def_options tick_interval: [
+ spec: Membrane.Time.t(),
+ default: Membrane.Time.seconds(1),
+ inspector: &Membrane.Time.inspect/1
+ ],
+ process: [
+ spec: pid()
+ ]
Membrane.Time functions
Rename Membrane.Time.to_<unit name>/1
into Membrane.Time.round_to_<unit name>/1
:
- rounded_time = Membrane.Time.to_seconds(time)
+ rounded_time = Membrane.Time.round_to_seconds(time)
Update the children definitions
Children definitions syntax (previously known as ParentSpec
, after the name of a structure used to define children), that was used in Membrane.Pipeline.Action.spec_t
and Membrane.Bin.Action.spec_t
actions, has changed.
Since there are quite a few changes concerning children definition, we have decided to present them in the subsections below.
Update children names
We have restricted the available names for the children. Previously, the children name could be of any()
type, now it is specified as follows: tuple() | atom()
.
In case you have used a child name that does not match the new children names specification, you can wrap that name into a tuple, i.e.:
- child_name = [:why_would, :i_use, :a_list, :as_a, :child_name?]
+ child_name = {:child_name, [:why_would, :i_use, :a_list, :as_a, :child_name?]}
Update the children and links definition
Rename the following function calls:
Membrane.ParentSpec.link/1
->Membrane.ChildrenSpec.get_child/1
Membrane.ParentSpec.link/2
->Membrane.ChildrenSpec.child/2
Membrane.ParentSpec.link_bin_input/1
->Membrane.ChildrenSpec.bin_input/1
Membrane.ParentSpec.to/2
->Membrane.ChildrenSpec.get_child/2
Membrane.ParentSpec.to/3
->Membrane.ChildrenSpec.child/4
Membrane.ParentSpec.to_bin_output/2
->Membrane.ChildrenSpec.bin_output/2
- import Membrane.ParentSpec
- children = %{source: SomeSource, filter: SomeFilter}
- links = [link(:source) |> to(:another_filter, SomeFilter) |> to(:filter) |> to(:sink, SomeSink)]
+ import Membrane.ChildrenSpec
+ structure = [
+ child(:source, SomeSource),
+ child(:filter, SomeFilter),
+ get_child(:source) |> child(:another_filter, SomeFilter) |> get_child(:filter) |> child(:sink, SomeSink)
+ ]
Substitute the Membrane.ParentSpec
structure with a tuple
%Membrane.ParentSpec{children: children, links: links, options...}
should be changed into {structure, options...}
with the children
and links
defined as
described in the step above. Note, that there is no distinguishement between children
and links
-
the children and links can be concatenated, so in case you have a separate children
and links
lists, simply merge them.
- spec = %Membrane.ParentSpec{children: children, links: links, node: another_node, log_metadata: metadata}
+ spec = {structure, node: another_node, log_metadata: metadata}
Below there is an example that aggregates the changes that need to be done to concerning the :spec
action preparation:
- children = %{source: SomeSource, filter: SomeFilter}
- links = [link(:source) |> to(:another_filter, SomeFilter) |> to(:filter) |> to(:sink, SomeSink)]
- spec = Membrane.ParentSpec{children: children, links: links, crash_group: {:first_group, :temporary}, node: another_node}
+ structure = [
+ child(:source, SomeSource),
+ child(:filter, SomeFilter),
+ get_child(:source) |> child(:another_filter, SomeFilter) |> get_child(:filter) |> child(:sink, SomeSink)
]
+ spec = {structure, crash_group: {:first_group, :temporary}, node: another_node}
- {{:ok, spec: spec}, %{}}
+ {[spec: spec], %{}}
Testing
Instead of using Membrane.Testing.Pipeline.start_link/1
, use Membrane.Testing.Pipeline.start_link_supervised!/1
- {:ok, pipeline} = Membrane.Testing.Pipeline.start_link(options)
+ pipeline = Membrane.Testing.Pipeline.start_link_supervised!(options)
+ # The pipeline will be shut down at the end of the test.
The Membrane.Testing.Pipeline.options()
has also changed - now there is no more separate :links
and :children
fields. Instead, you need to
use a :structure
field:
- {:ok, pipeline} = Membrane.Testing.Pipeline.start_link(children: children, links: links)
+ {:ok, pipeline} = Membrane.Testing.Pipeline.start_link(structure: structure)
In order to wait for the Membrane.Testing.Pipeline
to get into the :playing
playback, use assert_pipeline_play/2
instead of assert_pipeline_playback_changed/4
:
- assert_pipeline_playback_changed(pipeline, :prepared, :playing)
+ assert_pipeline_play(pipeline)