diff --git a/guides/upgrading/v1.0.0.md b/guides/upgrading/v1.0.0.md new file mode 100644 index 000000000..4b64cdf32 --- /dev/null +++ b/guides/upgrading/v1.0.0.md @@ -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_(time, :round)` instead of `Membrane.Time.round_to_(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`.