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?]}

Rename the following function calls:

- 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)