Skip to content
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

Add WHIP support #49

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ For more examples, see [examples.livemd](examples.livemd).
|---|---|---|
| MP4 | `"*.mp4"` | `"*.mp4"` |
| WebRTC | `{:webrtc, signaling}` | `{:webrtc, signaling}` |
| WHIP | `{:whip, "http://*", token: "token"}` | `{:whip, "http://*", token: "token"}` |
| RTMP | `"rtmp://*"` | _not supported_ |
| RTSP | `"rtsp://*"` | _not supported_ |
| HLS | _not supported_ | `"*.m3u8"` |
Expand Down Expand Up @@ -102,13 +103,13 @@ Make sure you have [Elixir](https://elixir-lang.org/) installed. The first call
The CLI API is similar to the Elixir API, for example:

```elixir
Boombox.run(input: "file.mp4", output: {:webrtc, "ws://localhost:8830"})
Boombox.run(input: "file.mp4", output: {:whip, "http://localhost:3721", token: "token"})
```

is equivalent to:

```sh
./boombox -i file.mp4 -o --webrtc ws://localhost:8830
./boombox -i file.mp4 -o --whip http://localhost:3721 --token token
```

It's also possible to pass an `.exs` script:
Expand Down
4 changes: 2 additions & 2 deletions examples.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ System.put_env("PATH", "/opt/homebrew/bin:#{System.get_env("PATH")}")
# In case of problems installing Nx/EXLA/Bumblebee,
# you can remove them and the Nx backend config below.
# Examples that don't mention them should still work.
Mix.install([:boombox, :kino, :nx, :exla, :bumblebee, :websockex, :membrane_simple_rtsp_server])
Mix.install([{:boombox, path: "/Users/matheksm/m/boombox"}, :kino, :nx, :exla, :bumblebee, :websockex])

Nx.global_default_backend(EXLA.Backend)
```
Expand Down Expand Up @@ -79,7 +79,7 @@ To send the stream, visit http://localhost:1234/webrtc_from_browser.html.
Note: don't stop this cell to finish recording - click 'disconnect' or close the browser tab instead, so the recording is finalized properly.

```elixir
Boombox.run(input: {:webrtc, "ws://localhost:8829"}, output: "#{out_dir}/webrtc_to_mp4.mp4")
Boombox.run(input: {:whip, port: 8829, token: "whip_it!"}, output: "#{out_dir}/webrtc_to_mp4.mp4")
```

```elixir
Expand Down
10 changes: 10 additions & 0 deletions lib/boombox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ defmodule Boombox do
(path_or_uri :: String.t())
| {:mp4, location :: String.t(), transport: :file | :http}
| {:webrtc, webrtc_signaling()}
| {:whip, uri :: String.t(), token: String.t()}
| {:rtmp, (uri :: String.t()) | (client_handler :: pid)}
| {:rtsp, url :: String.t()}
| {:stream, in_stream_opts()}
Expand All @@ -29,6 +30,7 @@ defmodule Boombox do
(path_or_uri :: String.t())
| {:mp4, location :: String.t()}
| {:webrtc, webrtc_signaling()}
| {:whip, uri :: String.t(), [{:token, String.t()} | {bandit_option :: atom(), term()}]}
| {:hls, location :: String.t()}
| {:stream, out_stream_opts()}

Expand Down Expand Up @@ -140,6 +142,14 @@ defmodule Boombox do
{:webrtc, uri} when is_binary(uri) ->
value

{:whip, uri} when is_binary(uri) ->
parse_opt!(direction, {:whip, uri, []})

{:whip, uri, opts} when is_binary(uri) and is_list(opts) ->
if Keyword.keyword?(opts) do
{:webrtc, {:whip, uri, opts}}
end

{:rtmp, arg} when direction == :input and (is_binary(arg) or is_pid(arg)) ->
value

Expand Down
6 changes: 3 additions & 3 deletions lib/boombox/utils/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ defmodule Boombox.Utils.CLI do
i_type = [get_switch_type(argv, :input, aliases), :keep]
o_type = [get_switch_type(argv, :output, aliases), :keep]

switches =
[input: i_type, output: o_type] ++
Keyword.from_keys([:mp4, :webrtc, :rtmp, :hls, :transport], [:string, :keep])
endpoints = Keyword.from_keys([:mp4, :webrtc, :port, :whip, :hls], [:string, :keep])
options = Keyword.from_keys([:transport, :uri, :token, :rtmp], [:string, :keep])
switches = [input: i_type, output: o_type] ++ endpoints ++ options

{input, output} =
OptionParser.parse(argv, strict: switches, aliases: aliases)
Expand Down
18 changes: 14 additions & 4 deletions lib/boombox/webrtc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule Boombox.WebRTC do

@spec create_input(Boombox.webrtc_signaling(), Boombox.output(), State.t()) :: Wait.t()
def create_input(signaling, output, state) do
signaling = resolve_signaling(signaling)
signaling = resolve_signaling(signaling, :input)

keyframe_interval =
case output do
Expand Down Expand Up @@ -71,7 +71,7 @@ defmodule Boombox.WebRTC do

@spec create_output(Boombox.webrtc_signaling(), State.t()) :: {Ready.t() | Wait.t(), State.t()}
def create_output(signaling, state) do
signaling = resolve_signaling(signaling)
signaling = resolve_signaling(signaling, :output)
startup_tracks = if webrtc_input?(state), do: [:audio, :video], else: []

spec =
Expand Down Expand Up @@ -175,11 +175,21 @@ defmodule Boombox.WebRTC do
%Ready{actions: [spec: spec], eos_info: Map.values(tracks)}
end

defp resolve_signaling(%Membrane.WebRTC.SignalingChannel{} = signaling) do
defp resolve_signaling(%Membrane.WebRTC.SignalingChannel{} = signaling, _direction) do
signaling
end

defp resolve_signaling(uri) when is_binary(uri) do
defp resolve_signaling({:whip, uri, opts}, :input) do
uri = URI.new!(uri)
{:ok, ip} = :inet.getaddr(~c"#{uri.host}", :inet)
{:whip, [ip: :any, port: uri.port] ++ opts}
end

defp resolve_signaling({:whip, uri, opts}, :output) do
{:whip, [uri: uri] ++ opts}
end

defp resolve_signaling(uri, _direction) when is_binary(uri) do
uri = URI.new!(uri)
{:ok, ip} = :inet.getaddr(~c"#{uri.host}", :inet)
{:websocket, ip: ip, port: uri.port}
Expand Down
14 changes: 14 additions & 0 deletions test/boombox_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ defmodule BoomboxTest do
Compare.compare(output, "test/fixtures/ref_bun10s_opus_aac.mp4")
end

@tag :file_whip
async_test "mp4 file -> webrtc/whip -> mp4 file", %{tmp_dir: tmp} do
output = Path.join(tmp, "output.mp4")

t =
Task.async(fn ->
Boombox.run(input: @bbb_mp4, output: {:whip, "http://127.0.0.1:3721"})
end)

Boombox.run(input: {:whip, "http://127.0.0.1:3721"}, output: output)
Task.await(t)
Compare.compare(output, "test/fixtures/ref_bun10s_opus_aac.mp4")
end

@tag :http_webrtc
async_test "http mp4 -> webrtc -> mp4 file", %{tmp_dir: tmp} do
output = Path.join(tmp, "output.mp4")
Expand Down