Skip to content

Commit

Permalink
Write upgrading guide for v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
FelonEkonom committed Oct 16, 2023
1 parent 9943f16 commit f70b2e0
Showing 1 changed file with 214 additions and 0 deletions.
214 changes: 214 additions & 0 deletions guides/upgrading/v1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# Upgrading to v1.0.0

Comparing to v0.12, v1.0.0 introduces many changes in the public API of the framework. This guide was created to help you migrate to v1.0.0 of `membrane_core`.

### Deps

Update `membrane_core` to `v1.0.0`
```elixir
defp deps do
[
{:membrane_core, "~> 1.0.0"},
...
]
end
```

### Remove the pipeline's `:playback` action

There is no more `:playback` action available, as the pipeline will start playing automatically once its setup is completed.
If you need to start the pipeline as soon as possible, simply do not return the `:playback` action in `c:Membrane.Pipeline.handle_init/2` or `c:Membrane.Pipeline.handle_setup/2` callbacks:
```diff
@impl true
def handle_setup(_ctx, state) do
...
- {[playback: :playing], state}
+ {[], state}
end
```
In case you need to defer the moment the pipeline starts playing, and for this purpose you have returned `:playback` action in some other callback than `handle_init` or `handle_setup`, now you need to do two things - first, mark the setup as incomplete with `t:Membrane.Pipeline.Action.setup/0` action returned from `handle_setup`, to prevent it from automatically changing the playback to `:playing`:

```diff
+ @impl true
+ def handle_setup(_ctx, state) do
+ {[setup: :incomplete], state}
+ end
```

Then, you need to return `setup: :complete` instead of `playback: :playing` action in a callback after which completion you want your pipeline to start playing:
```diff
@impl true
def handle_info(:now_start_playing, _ctx, state) do
...
- {[playback: playing], state}
+ {[setup: :complete], state}
end
```

Note that the `:setup` action is also available in elements and bins and its main purpose is to handle long, asynchronous initialization of a component. See `t:Membrane.Bin.Action.setup/0` and `t:Membrane.Element.Action.setup/0`.

### Rename `handle_process/4` and `handle_write/4` into `handle_buffer/4`

Names of the callbacks that are used to process buffers have been unified. This applies to:
* _Membrane.Filter.handle_process/4_
* _Membrane.Endpoint.handle_write/4_
* _Membrane.Sink.handle_write/4_

and they became `c:Membrane.Element.WithInputPads.handle_buffer/4`

```diff
@impl true
- def handle_process(pad, buffer, ctx, state) do
+ def handle_buffer(pad, buffer, ctx, state) do
...
end
```

### Remove `handle_process_list/4` and `handle_write_list/4` callback

Since `v1.0.0`, you have to handle every single buffer separately in `handle_buffer/4` callback, instead of handling whole list of buffers in this in `handle_process_list/4` or `handle_write_list/4`.

```diff
@impl true
- def handle_process_list(pad_ref, buffers_list, ctx, state) do
+ def handle_buffer(pad_ref, buffer, ctx, state) do
...
end
```

```diff
@impl true
- def handle_write_list(pad_ref, buffers_list, ctx, state) do
+ def handle_buffer(pad_ref, buffer, ctx, state) do
...
end
```

### Change `mode` and `demand_mode` options to `flow_control` in pads' definitions in elements

For input pads, change:

```diff
use Membrane.Filter
# or Sink or Endpoint

- def_input_pad :input, mode: :push, ...
+ def_input_pad :input, flow_control: :push, ...

- def_input_pad :input, mode: :pull, demand_mode: :auto, demand_unit: :buffers, ...
+ def_input_pad :input, ...
# (because `flow_control: :auto` is the default whenever available - currently in Filters)
# Note that having `flow_control: :auto` doesn't allow to pass `demand_unit`,
# as it's determined automatically

- def_input_pad :input, mode: :pull, demand_unit: :buffers, ...
+ def_input_pad :input, flow_control: :manual, demand_unit: :buffers, ...
```

Same goes for output pads:

```diff
use Membrane.Filter
# or Source or Endpoint

- def_output_pad :output, mode: :push, ...
+ def_output_pad :output, flow_control: :push, ...

- def_output_pad :output, mode: :pull, demand_mode: :auto, ...
+ def_output_pad :output, ...

- def_output_pad :output, mode: :pull, ...
+ def_output_pad :output, flow_control: :manual, ...
```

Check `t:Membrane.Pad.element_spec/0` for details.

### Remove `mode` and `demand_unit` from pads definitions in bins

```diff
use Membrane.Bin

- def_input_pad :input, mode: :pull, demand_unit: :buffers, ...
+ def_input_pad :input, ...

- def_output_pad :output, mode: :push, ...
+ def_output_pad :output, ...
```

Check `t:Membrane.Pad.bin_spec/0` for details.

### Don't refer to callback contexts as to structs

Callback contexts are now plain maps instead of structs. So, for example, if you happen to have a match like below, convert it to a match on a map:

```diff
@impl true
- def handle_info(message, %Membrane.Element.CallbackContext.Info{pads: pads}, state) do
+ def handle_info(message, %{pads: pads}, state) do
...
end
```

Additionally, there's no `direction` field anymore in the callback context for `handle_pad_added` and `handle_pad_removed` - the direction can be determined by the pad name. All other fields remain the same.

```diff
@impl true
- def handle_pad_added(_pad, %{direction: :input}, state) do
+ def handle_pad_added(Pad.ref(:input, _id), _ctx, state) do
...
end
```

Check `t:Membrane.Element.CallbackContext.t/0`, `t:Membrane.Bin.CallbackContext.t/0` and `t:Membrane.Pipeline.CallbackContext.t/0` for outline of what can be found in the contexts. Additionally, each callback that provides extra fields in the context, has them mentioned in its docs.

### Use `Membrane.RCPipeline` instead of `Membrane.RemoteControlled.Pipeline`

```diff
- pipeline = Membrane.RemoteControlled.Pipeline.start_link!()
+ pipeline = Membrane.RCPipeline.start_link!()
```

Same goes for `Membrane.RemoteControlled.Message` -> `Membrane.RCMessage`

```diff
receive do
- %Membrane.RemoteControlled.Message.Notification{data: data} -> data
+ %Membrane.RCMessage.Notification{data: data} -> data
end
```

### Use `Membrane.Time.as_<unit>(time, :round)` instead of `Membrane.Time.round_to_<unit>(time)`

```diff
- Membrane.Time.round_to_milliseconds(time)
+ Membrane.Time.as_milliseconds(time, :round)
```

There is one exception for `round_to_timebase`, which changed to `divide_by_timebase`:
```diff
- Membrane.Time.round_to_timebase(time, timebase)
+ Membrane.Time.divide_by_timebase(time, timebase)
```

Check `Membrane.Time` for details.

### Implement you own `start/*`, `start_link/*` or `terminate/1` function (if you want to)

Until `v1.0.0-rc0` and `v0.12`, Membrane has generated `start/2`, `start_link/2`, and `terminate/1` functions in modules using `Membrane.Pipeline`, if code developer hadn't done it explicitly. Since `v1.0.0`, if you want to have these functions implemented in your pipeline module, you have to implement them on your own. Alternatively, you can always call `Membrane.Pipeline.start(YourPipelineModule, init_arg, opts)`, `Membrane.Pipeline.start_link(YourPipelineModule, init_arg, opts)`, and `Membrane.Pipeline.terminate(pipeline_pid)` from beyond `YourPipelineModule` without wrapping it into public functions.

### Rename `:structure` option passed to the `Membrane.Testing.Pipeline` into `:spec`

```diff
import Membrane.ChildrenSpec

spec =
child(:source, %Membrane.Testing.Source{some_field: some_arg})
|> child(:sink), %Membrane.Testing.Sink{another_filed: another_arg}

- pipeline = Membrane.Testing.Pipeline.start_link_supervised(structure: spec)
+ pipeline = Membrane.Testing.Pipeline.start_link_supervised(spec: spec)
```

### Adjust to possibility of receiving `end of stream` on pads, that haven't received `start of stream` yet.

Since `v1.0.0`, Membrane will execute `c:Membrane.Element.WithInputPads.handle_end_of_stream/3` for all pads, including these having `start_of_stream?: false`.

0 comments on commit f70b2e0

Please sign in to comment.