Boombox streaming examples
Logger.configure(level: :info)
# For ffmpeg and ffplay commands to work on Mac Livebook Desktop
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_CONFIG_BEGIN
boombox = {:boombox, github: "membraneframework/boombox"}
# This livebook uses boombox from the master branch. If any examples happen to not work, the latest stable version of this livebook
# can be found on https://hexdocs.pm/boombox/streaming.html or in the latest github release.
# MIX_INSTALL_CONFIG_END
Mix.install([
boombox,
:kino,
:nx,
:exla,
:bumblebee,
:websockex,
:membrane_simple_rtsp_server,
{:coerce, ">= 1.0.2"}
])
Nx.global_default_backend(EXLA.Backend)
# HTTP server for assets
data_dir = "/tmp/boombox_examples_data"
input_dir = "#{data_dir}/input"
File.mkdir_p!(input_dir)
out_dir = "#{data_dir}/output"
File.mkdir_p!(out_dir)
# match in case a dependency already started :inets
case :inets.start() do
:ok -> :ok
{:error, {:already_started, :inets}} -> :ok
err -> raise "Unexpected value returned by :inets.start/0: #{inspect(err)}"
end
case :inets.start(:httpd,
bind_address: ~c"localhost",
port: 1234,
document_root: ~c"#{data_dir}",
server_name: ~c"assets_server",
server_root: ~c"/tmp",
erl_script_nocache: true
) do
{:ok, _server} -> :ok
# port already in use — server likely started from another livebook
{:error, _} -> :ok
end
Setup
👋 Here are some streaming examples of using Boombox, covering broadcasting, relaying, and multi-protocol forwarding. Some of them use ffmpeg to generate a stream.
The cell below downloads assets to be used in the examples. The setup cell started an HTTP server on port 1234 that will serve static HTML files for sending/receiving the stream in the browser.
samples_url = "https://raw.githubusercontent.com/membraneframework/static/gh-pages/samples"
for {filename, remote} <- [
{"bun.mp4", "big-buck-bunny/bun33s.mp4"},
{"bun.mkv", "big-buck-bunny/bun33s.mkv"}
],
path = "#{input_dir}/#{filename}",
not File.exists?(path) do
%{status: 200, body: data} = Req.get!("#{samples_url}/#{remote}")
File.write!(path, data)
end
assets_url =
"https://raw.githubusercontent.com/membraneframework/boombox/master/examples/data"
for asset <- ["hls", "webrtc_from_browser", "webrtc_to_browser"],
path = "#{data_dir}/#{asset}.html",
not File.exists?(path) do
%{status: 200, body: data} = Req.get!("#{assets_url}/#{asset}.html")
File.write!(path, data)
end
Broadcast MP4 via HLS
To receive the stream, visit http://localhost:1234/hls.html after running the cell below
Boombox.run(input: "#{input_dir}/bun.mp4", output: {:hls, "#{out_dir}/index.m3u8"})
Stream MP4 via WebRTC
To receive the stream, visit http://localhost:1234/webrtc_to_browser.html after running the cell below.
Note: due to a bug in Chrome, it may not work there unless launched with --enable-features=WebRtcEncodedTransformDirectCallback. See https://issues.chromium.org/issues/351275970.
Boombox.run(input: "#{input_dir}/bun.mp4", output: {:webrtc, "ws://localhost:8830"})
Broadcast WebRTC via HLS
Visit http://localhost:1234/webrtc_from_browser.html to send the stream and http://localhost:1234/hls.html to receive it
Boombox.run(input: {:webrtc, "ws://localhost:8829"}, output: {:hls, "#{out_dir}/index.m3u8"})
Stream MP4 via HTTP, forward it via WebRTC
To receive the stream, visit http://localhost:1234/webrtc_to_browser.html after running the cell below.
Note: due to a bug in Chrome, it may not work there unless launched with --enable-features=WebRtcEncodedTransformDirectCallback. See https://issues.chromium.org/issues/351275970.
Boombox.run(
input: "#{samples_url}/big-buck-bunny/bun33s.mp4",
output: {:webrtc, "ws://localhost:8830"}
)
Receive RTSP, broadcast via HLS
To receive the stream, visit http://localhost:1234/hls.html after running the cell below
rtsp_port = 8554
Membrane.SimpleRTSPServer.start_link("#{input_dir}/bun.mp4", port: rtsp_port)
Boombox.run(input: "rtsp://localhost:#{rtsp_port}/", output: "#{out_dir}/index.m3u8")
Broadcast RTMP via HLS
To receive the stream, visit http://localhost:1234/hls.html after running the cell below
uri = "rtmp://localhost:5432"
t =
Task.async(fn ->
Boombox.run(input: uri, output: "#{out_dir}/index.m3u8")
end)
{_output, 0} = System.shell("ffmpeg -re -i #{input_dir}/bun.mp4 -c copy -f flv #{uri}")
Forward RTMP via WebRTC
To receive the stream, visit http://localhost:1234/webrtc_to_browser.html
Note: due to a bug in Chrome, it may not work there unless launched with --enable-features=WebRtcEncodedTransformDirectCallback. See https://issues.chromium.org/issues/351275970.
uri = "rtmp://localhost:5434"
t =
Task.async(fn ->
Boombox.run(input: uri, output: {:webrtc, "ws://localhost:8830"})
end)
{_output, 0} = System.shell("ffmpeg -re -i #{input_dir}/bun.mp4 -c copy -f flv #{uri}")
Task.await(t)
Receive RTP, broadcast via HLS
To receive the stream, visit http://localhost:1234/hls.html after running the cell below
rtp_port = 50001
t =
Task.async(fn ->
Boombox.run(
input: {:rtp, port: rtp_port, audio_encoding: :OPUS, video_encoding: :H264},
output: "#{out_dir}/index.m3u8"
)
end)
{_output, 0} =
System.shell("""
ffmpeg -re -i #{input_dir}/bun.mkv \
-map 0:v:0 -c:v copy -payload_type 96 -f rtp rtp://127.0.0.1:#{rtp_port} \
-map 0:a:0 -c:a copy -payload_type 120 -f rtp rtp://127.0.0.1:#{rtp_port}
""")
Process.sleep(200)
Task.shutdown(t)
Stream content of MP4 via SRT with basic authentication mechanism and broadcast it with HLS
Run the cell below and visit http://localhost:1234/hls.html.
Then, start streaming from OBS to the following URI:
-
srt://127.0.0.1:9710?streamid=some_stream_id&passphrase=some_password
For more information on how to configure OBS to stream via SRT, take a look here.
hls_output = "#{out_dir}/index.m3u8"
uri = "srt://127.0.0.1:9710"
stream_id = "some_stream_id"
password = "some_password"
Boombox.run(input: {:srt, uri, stream_id: stream_id, password: password}, output: hls_output)
Micro Twitch clone
Kino.start_child!({
Membrane.RTMPServer,
handler: %Membrane.RTMP.Source.ClientHandlerImpl{controlling_process: self()},
port: 5001,
use_ssl?: false,
handle_new_client: fn client_ref, app, stream_key ->
hls_dir = "#{out_dir}/#{stream_key}"
File.mkdir_p!(hls_dir)
Task.start(fn ->
Boombox.run(input: {:rtmp, client_ref}, output: "#{hls_dir}/index.m3u8")
end)
Kino.Markdown.new("""
New streamer connects with app #{app} and stream_key #{stream_key},
stream will be available at http://localhost:1234/hls.html?src=output/#{stream_key}/index.m3u8.
It may take a few seconds before the stream is playable.
""")
|> Kino.render()
end,
client_timeout: 1_000
})
button = Kino.Control.button("Connect streamer")
Kino.render(button)
button
|> Stream.filter(fn event -> event.type == :click end)
|> Kino.async_listen(fn _event ->
key = Base.encode16(:crypto.strong_rand_bytes(4))
uri = "rtmp://localhost:5001/streamer/#{key}"
{_output, 0} = System.shell("ffmpeg -re -i #{input_dir}/bun.mp4 -c copy -f flv #{uri}")
end)
:ok