-
Notifications
You must be signed in to change notification settings - Fork 15
Update return values in callbacks to reflect 1.0 spec #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
0d8ea79
Update return values in callbacks to reflect 1.0 spec
kidq330 ea2b9fa
Update basic_pipeline/07_Redemands.md
kidq330 df86952
Update rtmp_to_hls pipeline spec
kidq330 9297575
Changes after 2nd round of review
kidq330 d04878e
Add missing opts to handle_init
kidq330 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,126 +8,115 @@ which is invoked once the pipeline is initialized. | |
| **_`lib/rtmp_to_hls/pipeline.ex`_** | ||
|
|
||
| ```elixir | ||
| @impl true | ||
| def handle_init(_opts) do | ||
| ... | ||
| children: %{ | ||
| src: %Membrane.RTMP.SourceBin{port: 9009}, | ||
| sink: %Membrane.HTTPAdaptiveStream.SinkBin{ | ||
| manifest_module: Membrane.HTTPAdaptiveStream.HLS, | ||
| target_window_duration: 20 |> Membrane.Time.seconds(), | ||
| muxer_segment_duration: 8 |> Membrane.Time.seconds(), | ||
| storage: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{directory: "output"} | ||
| } | ||
| }, | ||
| ... | ||
| end | ||
| @impl true | ||
| def handle_init(_opts) do | ||
| ... | ||
| spec = [ | ||
| child(:src, %Membrane.RTMP.SourceBin{socket: 9009}) | ||
| |> via_out(:audio) | ||
| |> via_in(Pad.ref(:input, :audio), | ||
| options: [encoding: :AAC, segment_duration: Membrane.Time.seconds(4)] | ||
| ) | ||
| |> child(:sink, %Membrane.HTTPAdaptiveStream.SinkBin{ | ||
| manifest_module: Membrane.HTTPAdaptiveStream.HLS, | ||
| target_window_duration: :infinity, | ||
| persist?: false, | ||
| storage: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{directory: "output"} | ||
| }), | ||
| get_child(:src) | ||
| |> via_out(:video) | ||
| |> via_in(Pad.ref(:input, :video), | ||
| options: [encoding: :H264, segment_duration: Membrane.Time.seconds(4)] | ||
| ) | ||
| |> get_child(:sink) | ||
| ] | ||
| ... | ||
| end | ||
| ``` | ||
|
|
||
| First, we define the list of children. The following children are defined: | ||
|
|
||
| - `:src` - a `Membrane.RTMP.SourceBin`, an RTMP server, which, according to its `:port` configuration, will be listening on port `9009`. This bin will be acting as a source for our pipeline. For more information on RTMP Source Bin please visit [the documentation](https://hexdocs.pm/membrane_rtmp_plugin/Membrane.RTMP.SourceBin.html). | ||
| - `:src` - a `Membrane.RTMP.SourceBin` RTMP server, which, according to its `:port` configuration, will be listening on port `9009`. This bin will be acting as a source for our pipeline. For more information on RTMP Source Bin please visit [the documentation](https://hexdocs.pm/membrane_rtmp_plugin/Membrane.RTMP.SourceBin.html). | ||
| - `:sink` - a `Membrane.HTTPAdaptiveStream.SinkBin`, acting as a sink of the pipeline. The full documentation of that bin is available [here](https://hexdocs.pm/membrane_http_adaptive_stream_plugin/Membrane.HTTPAdaptiveStream.SinkBin.html). We need to specify some of its options: | ||
| - `:manifest_module` - a module which implements [`Membrane.HTTPAdaptiveStream.Manifest`](https://hexdocs.pm/membrane_http_adaptive_stream_plugin/Membrane.HTTPAdaptiveStream.Manifest.html#c:serialize/1) behavior. A manifest allows aggregate tracks (of a different type, i.e. an audio track and a video track as well as many tracks of the same type, i.e. a few video tracks with different resolutions). For each track, the manifest holds a reference to a list of segments, which form that track. Furthermore, the manifest module is equipped with the `serialize/1` method, which allows transforming that manifest to a string (which later on can be written to a file). In that case, we use a built-in implementation of a manifest module - the `Membrane.HTTPAdaptiveStream.HLS`, designed to serialize a manifest into a form required by HLS. | ||
| - `:target_window_duriation` - that value determines the minimal manifest's duration. The oldest segments of the tracks will be removed whenever possible if persisting them would result in exceeding the manifest duration. | ||
| - `:muxer_segment_duration` - the maximal duration of a segment. Each segment of each track shouldn't exceed that value. In our case, we have decided to limit the length of each segment to 8 seconds. | ||
| - `:target_window_duration` - determines the minimal manifest's duration. The oldest segments of the tracks will be removed whenever possible if persisting them would result in exceeding the manifest duration. | ||
| - `:storage` - the sink element, the module responsible for writing down the HLS playlist and manifest files. In our case, we use a pre-implemented `Membrane.HTTPAdaptiveStream.FileStorage` module, designed to write the files to the local filesystem. We configure it so that the directory where the files will be put in the `output/` directory (make sure that that directory exists as the storage module won't create it itself). | ||
|
|
||
| The fact that the configuration of a pipeline, which performs relatively complex processing, consists of just two elements, proves the power of [bins](/basic_pipeline/12_Bin.md). Feel free to stop for a moment and read about them if you haven't done it yet. | ||
|
|
||
| After providing the children's specifications, we are ready to connect the pads between these children. Take a look at that part of the code: | ||
| **_`lib/rtmp_to_hls/pipeline.ex`_** | ||
| At the same time, we configure the pads linking the two bins. | ||
| `:src` has two output pads: the `:audio` pad and the `:video` pad, transferring the appropriate media tracks. | ||
| The source's `:audio` pad is linked to the input `:audio` pad of the sink - along with an `:encoding` option and a `:segment_duration`. | ||
| - `:encoding` is an atom, describing the codec which is used to encode the media data - when it comes to audio data, we will be using the AAC codec. | ||
| - `:segment_duration` specifies the maximal duration of a segment. Each segment of each track shouldn't exceed that value. In our case, we have decided to limit the length of each segment to 4 seconds. | ||
|
|
||
| ```elixir | ||
| @impl true | ||
| def handle_init(_opts) do | ||
| ... | ||
| links: [ | ||
| link(:src) | ||
| |> via_out(:audio) | ||
| |> via_in(Pad.ref(:input, :audio), options: [encoding: :AAC]) | ||
| |> to(:sink), | ||
| link(:src) | ||
| |> via_out(:video) | ||
| |> via_in(Pad.ref(:input, :video), options: [encoding: :H264]) | ||
| |> to(:sink) | ||
| ] | ||
| ... | ||
| end | ||
| ``` | ||
| At the time of writing, the available codecs accepted by `:encoding` are `:H264` and `:H265` for video, and `:AAC` for audio. | ||
|
|
||
| The structure of links reflects the desired architecture of the application. | ||
| `:src` has two output pads: the `:audio` pad and the `:video` pad, transferring the appropriate media tracks. | ||
| The source's `:audio` pad is linked to the input `:audio` pad of the sink - along with the `:encoding` option. That option is an atom, describing the codec which is used to encode the media data - when it comes to audio data, | ||
| we will be using AAC coded. | ||
| At the time of the writing, only `:H264` and `:AAC` codecs are available to be passed as an `:encoding` option - the first one is used with video data, and the second one is used with audio data. | ||
| By analogy, the source's `:video` pad is linked with the sink's `:video` pad - and the `:encoding` to be used is H264. | ||
| We refer to previously defined elements using `get_child` to also configure the `:video` pads, using `:H264` as the preferred encoding and a segment duration of 4 seconds. | ||
|
|
||
| The final thing that is done in the `handle_init/1` callback's implementation is returning the desired actions: | ||
| The final thing that is done in the `handle_init/1` callback's implementation is returning the pipeline structure through the `:spec` action: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
| **_`lib/rtmp_to_hls/pipeline.ex`_** | ||
|
|
||
| ```elixir | ||
| @impl true | ||
| def handle_init(_opts) do | ||
| ... | ||
| { {:ok, spec: spec, playback: :playing}, %{} } | ||
| end | ||
| @impl true | ||
| def handle_init(_opts) do | ||
| ... | ||
| {[spec: spec], %{}} | ||
| end | ||
| ``` | ||
|
|
||
| The first action is the `:spec` action, which spawns the children. The second action changes the playback state of the pipeline into the `:playing` - meaning, that data can start flowing through the pipeline. | ||
|
|
||
| ## Starting the pipeline | ||
|
|
||
| The pipeline is started with `Supervisor.start_link`, as a child of the application, inside the `lib/rtmp_to_hls/application.ex` file: | ||
|
|
||
| **_`lib/rtmp_to_hls/application.ex`_** | ||
|
|
||
| ```elixir | ||
| @impl true | ||
| def start(_type, _args) do | ||
| children = [ | ||
| # Start the Pipeline | ||
| Membrane.Demo.RtmpToHls, | ||
| ... | ||
| ] | ||
| opts = [strategy: :one_for_one, name: RtmpToHls.Supervisor] | ||
| Supervisor.start_link(children, opts) | ||
| end | ||
| @impl true | ||
| def start(_type, _args) do | ||
| children = [ | ||
| # Start the Pipeline | ||
| Membrane.Demo.RtmpToHls, | ||
| ... | ||
| ] | ||
| opts = [strategy: :one_for_one, name: RtmpToHls.Supervisor] | ||
| Supervisor.start_link(children, opts) | ||
| end | ||
| ``` | ||
|
|
||
| ## HLS controller | ||
|
|
||
| The files produced with the pipeline are written down to the `output/` directory. We need to make them accessible via HTTP. | ||
| The Phoenix Framework provides tools to achieve that - take a look at the `RtmpToHlsWeb.Router`: | ||
| The Phoenix Framework provides tools to achieve that - take a look at `RtmpToHlsWeb.Router`: | ||
| **_`lib/rtmp_to_hls_web/router.ex`_** | ||
|
|
||
| ```elixir | ||
| scope "/", RtmpToHlsWeb do | ||
| pipe_through :browser | ||
| pipe_through :browser | ||
|
|
||
| get "/", PageController, :index | ||
| get "/video/:filename", HlsController, :index | ||
| end | ||
| ``` | ||
| get "/", PageController, :index | ||
| get "/video/:filename", HlsController, :index | ||
| end | ||
| ``` | ||
|
|
||
| We are directing HTTP requests on `/video/:filename` to the HlsController, whose implementation is shown below: | ||
| **_`lib/rtmp_to_hls_web/controllers/hls_controller.ex`_** | ||
|
|
||
| ```elixir | ||
| defmodule RtmpToHlsWeb.HlsController do | ||
| use RtmpToHlsWeb, :controller | ||
| use RtmpToHlsWeb, :controller | ||
|
|
||
| alias Plug | ||
| alias Plug | ||
|
|
||
| def index(conn, %{"filename" => filename}) do | ||
| path = "output/#{filename}" | ||
| def index(conn, %{"filename" => filename}) do | ||
| path = "output/#{filename}" | ||
|
|
||
| if File.exists?(path) do | ||
| conn |> Plug.Conn.send_file(200, path) | ||
| else | ||
| conn |> Plug.Conn.send_resp(404, "File not found") | ||
| end | ||
| if File.exists?(path) do | ||
| conn |> Plug.Conn.send_file(200, path) | ||
| else | ||
| conn |> Plug.Conn.send_resp(404, "File not found") | ||
| end | ||
| end | ||
| end | ||
| ``` | ||
|
|
||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we want to change it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mb, copied over from https://github.com/membraneframework/membrane_demo/blob/d21a5422eca90a1ad5751f8f602f0aaccd652afb/rtmp_to_hls/lib/rtmp_to_hls/pipeline.ex