Skip to content

Implement bin #68

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 51 commits into from
Apr 29, 2025
Merged

Implement bin #68

merged 51 commits into from
Apr 29, 2025

Conversation

FelonEkonom
Copy link
Member

@FelonEkonom FelonEkonom commented Mar 7, 2025

closes #8

@FelonEkonom FelonEkonom self-assigned this Mar 7, 2025
@FelonEkonom FelonEkonom moved this to In Progress in Smackore Mar 7, 2025
@darthez darthez removed this from Smackore Mar 25, 2025
@FelonEkonom FelonEkonom marked this pull request as ready for review March 26, 2025 14:04
@FelonEkonom FelonEkonom requested a review from mat-hek March 26, 2025 14:04
Copy link
Member

@mat-hek mat-hek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, let's add the docs ;)

Comment on lines 12 to 33
@type input() ::
(path_or_uri :: String.t())
| {:mp4, location :: String.t(), transport: :file | :http}
| {:webrtc, Boombox.webrtc_signaling()}
| {:whip, uri :: String.t(), token: String.t()}
| {:rtmp, (uri :: String.t()) | (client_handler :: pid)}
| {:rtsp, url :: String.t()}
| {:rtp, Boombox.in_rtp_opts()}
| {:stream, pid(), Boombox.in_stream_opts()}

@type output ::
(path_or_uri :: String.t())
| {path_or_uri :: String.t(), [Boombox.force_transcoding()]}
| {:mp4, location :: String.t()}
| {:mp4, location :: String.t(), [Boombox.force_transcoding()]}
| {:webrtc, Boombox.webrtc_signaling()}
| {:webrtc, Boombox.webrtc_signaling(), [Boombox.force_transcoding()]}
| {:whip, uri :: String.t(), [{:token, String.t()} | {bandit_option :: atom(), term()}]}
| {:hls, location :: String.t()}
| {:hls, location :: String.t(), [Boombox.force_transcoding()]}
| {:rtp, Boombox.out_rtp_opts()}
| {:stream, pid(), Boombox.out_stream_opts()}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does it differ from Boombox.input()/output()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boombox has {:stream, options}
Boombox.Bin has {:stream, pid(), options}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we spoke, let's create a private bin to handle this

def handle_playing(_ctx, state), do: {[], state}

@impl true
def handle_pad_added(pad_ref, ctx, state) when state.input == :membrane_pad do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's throw a readable error when someone specifies a different input and links some pads

@mat-hek mat-hek requested a review from varsill March 28, 2025 09:42
@FelonEkonom
Copy link
Member Author

FelonEkonom commented Mar 28, 2025

Overall looks good, let's add the docs ;)

I would add the docs after adding output pads, maybe the API will change till then and I hope I will have a better idea how to fix issue with passing {:stream, ...} option to Boombox.Bin

@FelonEkonom FelonEkonom requested a review from mat-hek March 28, 2025 13:19
lib/boombox.ex Outdated
opts
|> Enum.split_with(fn {key, _value} -> key == :force_transcoding end)
end
defp resolve_stream_endpoint(endpont, _parent), do: endpont
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
defp resolve_stream_endpoint(endpont, _parent), do: endpont
defp resolve_stream_endpoint(endpoint, _parent), do: endpoint

if video_format != nil do
Compare.compare(out_file, "test/fixtures/ref_bun10s_aac.mp4",
kinds: [:video],
subject_tracks_number: tracks_number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's quite long, but how about:

Suggested change
subject_tracks_number: tracks_number
expected_subject_tracks_number: tracks_number

?

raise """
Boombox.Bin pad #{inspect(pad_ref)} was added while the Boombox.Bin playback \
is already :playing. All Boombox.Bin pads have to be linked before it enters \
:playing playback. To achieve so, link all pads of Boombox.Bin in the same spec \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:playing playback. To achieve so, link all pads of Boombox.Bin in the same spec \
:playing playback. To achieve it, link all the pads of Boombox.Bin in the same spec \

@spec create_input(CallbackContext.t()) :: Ready.t() | Wait.t() | no_return()
def create_input(ctx) when ctx.playback == :playing and ctx.pads == %{} do
raise """
Cannot create input of type #{inspect(__MODULE__)}, while there are no pads. \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Cannot create input of type #{inspect(__MODULE__)}, while there are no pads. \
Cannot create input of type #{inspect(__MODULE__)}, as there are no pads available. \

:ok = maybe_log_transcoding_related_warning(state.input, state.output)

with :membrane_pad <- state.input, {:stream, _pid, _opts} <- state.output do
raise "Boombox bin cannot have pad input and stream output at the same time"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, please remember to change these raise messages once the code is separated into public bin and private bin

@FelonEkonom FelonEkonom requested a review from mat-hek April 14, 2025 15:15
@mat-hek mat-hek requested a review from varsill April 22, 2025 14:55
Copy link
Member

@mat-hek mat-hek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As always, add some examples to examples.livemd

pads
|> Enum.find(fn {Pad.ref(name, _id), %{options: options}} ->
name == :output and
(Bunch.listify(options.codec) -- @codecs[options.kind]) -- [nil] != []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this hard to read, maybe refactor to Enum.all?

Comment on lines 147 to 161
# <<<<<<< HEAD
# assert abs(length(ref_audio_bufs) - length(sub_audio_bufs)) <= 1

# Enum.zip(sub_audio_bufs, ref_audio_bufs)
# |> Enum.each(fn {sub, ref} ->
# # The results differ between operating systems
# # and subsequent runs due to transcoding.
# # The threshold here is obtained empirically and may need
# # to be adjusted, or a better metric should be used.
# boundary = options[:audio_error_bounadry] || 30_000
# assert samples_min_squared_error(sub.payload, ref.payload, 16) < boundary
# end)
# =======
# assert samples_min_squared_error(sub_audio, ref_audio, 16) < 30_000
# >>>>>>> origin/master
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

Comment on lines 6 to 8
If you use it as a Membrane Source, you have to specify `:input` option.
If you use it as a Membrane Sink, you have to specify `:output` option.
Boombox.Bin cannot be used as both Source and Sink at the same time.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe mention what pads they need to link too, and provide some example, like child(%Boombox.Bin{input: "file.mp4"}) |> via_out(:output, options: [kind: :video]) |> ...

Comment on lines 12 to 35
@type input() ::
(path_or_uri :: String.t())
| {:mp4, location :: String.t(), transport: :file | :http}
| {:webrtc, Boombox.webrtc_signaling()}
| {:whip, uri :: String.t(), token: String.t()}
| {:rtmp, (uri :: String.t()) | (client_handler :: pid)}
| {:rtsp, url :: String.t()}
| {:rtp, Boombox.in_rtp_opts()}
| {:stream, pid(), Boombox.in_stream_opts()}
| :membrane_pad

@type output ::
(path_or_uri :: String.t())
| {path_or_uri :: String.t(), [Boombox.transcoding_policy()]}
| {:mp4, location :: String.t()}
| {:mp4, location :: String.t(), [Boombox.transcoding_policy()]}
| {:webrtc, Boombox.webrtc_signaling()}
| {:webrtc, Boombox.webrtc_signaling(), [Boombox.transcoding_policy()]}
| {:whip, uri :: String.t(), [{:token, String.t()} | {bandit_option :: atom(), term()}]}
| {:hls, location :: String.t()}
| {:hls, location :: String.t(), [Boombox.transcoding_policy()]}
| {:rtp, Boombox.out_rtp_opts()}
| {:stream, pid(), Boombox.out_stream_opts()}
| :membrane_pad
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still duplicated

@FelonEkonom FelonEkonom requested a review from mat-hek April 25, 2025 09:38
Comment on lines 148 to 150
If set to `:always`, the media stream will be decoded and/or encoded, even if the
format of the stream arriving at the #{inspect(__MODULE__)} endpoint matches the \
output pad codec.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding some example when :always is helpful, i.e. when we want to "fix" the stream or support the keyframe requests? (We have a similar examples for the :never option)

Comment on lines 224 to 225
#{inspect(__MODULE__)} cannot accept input and output options at the same time, but both were provided.\
Input: #{inspect(opts.input)}, output: #{inspect(opts.output)}.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That message only describes a situation when nil_opts_number == 0, how about nil_opts_number == 2?

@@ -4,18 +4,18 @@ defmodule Boombox.HLS do
import Membrane.ChildrenSpec

require Membrane.Pad, as: Pad
alias Boombox.Pipeline.Ready
alias Boombox.InternalBin.Ready
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure Ready and Wait should be in InternalBin, since they are use outside of the InternalBin namespace as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they are used only by modules that are used only inside internal bin
I can move these modules inside internal_bin directory to make it more clear

@FelonEkonom FelonEkonom requested a review from varsill April 29, 2025 09:42
@FelonEkonom FelonEkonom merged commit 0794dfb into master Apr 29, 2025
2 of 3 checks passed
@FelonEkonom FelonEkonom deleted the implement-bin branch April 29, 2025 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow for pluging boombox into a membrane pipeline
3 participants