diff --git a/README.md b/README.md index 57d5bc0..a6b20a9 100644 --- a/README.md +++ b/README.md @@ -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"` | @@ -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: diff --git a/examples.livemd b/examples.livemd index 8d4b59e..adc1e82 100644 --- a/examples.livemd +++ b/examples.livemd @@ -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) ``` @@ -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 diff --git a/lib/boombox.ex b/lib/boombox.ex index 80f0213..becc0da 100644 --- a/lib/boombox.ex +++ b/lib/boombox.ex @@ -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()} @@ -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()} @@ -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 diff --git a/lib/boombox/utils/cli.ex b/lib/boombox/utils/cli.ex index 741f388..8eb2aa6 100644 --- a/lib/boombox/utils/cli.ex +++ b/lib/boombox/utils/cli.ex @@ -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) diff --git a/lib/boombox/webrtc.ex b/lib/boombox/webrtc.ex index d9a9ba0..fffb387 100644 --- a/lib/boombox/webrtc.ex +++ b/lib/boombox/webrtc.ex @@ -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 @@ -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 = @@ -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} diff --git a/test/boombox_test.exs b/test/boombox_test.exs index 3a5317f..1828b8b 100644 --- a/test/boombox_test.exs +++ b/test/boombox_test.exs @@ -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")