├── .gitignore
├── rtp
├── test
│ └── test_helper.exs
├── config
│ └── config.exs
├── samples
│ ├── audio.opus
│ └── video.h264
├── .formatter.exs
├── receive.exs
├── send.exs
├── mix.exs
├── lib
│ └── membrane_demo_rtp
│ │ ├── receive_pipeline.ex
│ │ └── send_pipeline.ex
├── README.md
└── .gitignore
├── rtmp_to_hls
├── test
│ └── test_helper.exs
├── lib
│ ├── rtmp_to_hls_web
│ │ ├── views
│ │ │ ├── page_view.ex
│ │ │ ├── layout_view.ex
│ │ │ ├── error_view.ex
│ │ │ └── error_helpers.ex
│ │ ├── controllers
│ │ │ ├── page_controller.ex
│ │ │ └── hls_controller.ex
│ │ ├── templates
│ │ │ ├── layout
│ │ │ │ ├── app.html.heex
│ │ │ │ ├── live.html.heex
│ │ │ │ └── root.html.heex
│ │ │ └── page
│ │ │ │ └── index.html.heex
│ │ ├── router.ex
│ │ ├── gettext.ex
│ │ ├── endpoint.ex
│ │ └── telemetry.ex
│ ├── rtmp_to_hls.ex
│ ├── rtmp_to_hls
│ │ ├── application.ex
│ │ └── pipeline.ex
│ └── rtmp_to_hls_web.ex
├── .formatter.exs
├── priv
│ ├── static
│ │ ├── favicon.ico
│ │ ├── images
│ │ │ ├── membrane.png
│ │ │ └── phoenix.png
│ │ └── robots.txt
│ └── gettext
│ │ ├── en
│ │ └── LC_MESSAGES
│ │ │ └── errors.po
│ │ └── errors.pot
├── doc_assets
│ ├── OBS_settings.webp
│ └── RTMP_to_HLS_pipeline.png
├── config
│ ├── test.exs
│ ├── config.exs
│ ├── runtime.exs
│ ├── prod.exs
│ └── dev.exs
├── .gitignore
├── assets
│ ├── js
│ │ └── app.js
│ └── css
│ │ ├── app.css
│ │ └── phoenix.css
├── mix.exs
└── README.md
├── simple_element
├── test
│ └── test_helper.exs
├── config
│ └── config.exs
├── sample.mp3
├── run.exs
├── .formatter.exs
├── mix.exs
├── lib
│ └── membrane_demo
│ │ └── simple_element
│ │ ├── pipeline.ex
│ │ └── counter.ex
├── README.md
└── .gitignore
├── simple_pipeline
├── test
│ └── test_helper.exs
├── config
│ └── config.exs
├── sample.mp3
├── run.exs
├── .formatter.exs
├── mix.exs
├── lib
│ └── membrane_demo
│ │ └── simple_pipeline.ex
├── README.md
└── .gitignore
├── webrtc_live_view
├── test
│ ├── test_helper.exs
│ ├── webrtc_live_view_web
│ │ └── controllers
│ │ │ ├── page_controller_test.exs
│ │ │ ├── error_json_test.exs
│ │ │ └── error_html_test.exs
│ └── support
│ │ └── conn_case.ex
├── priv
│ └── static
│ │ ├── favicon.ico
│ │ ├── robots.txt
│ │ └── images
│ │ └── logo.svg
├── .formatter.exs
├── lib
│ ├── webrtc_live_view.ex
│ ├── webrtc_live_view_web
│ │ ├── controllers
│ │ │ ├── page_controller.ex
│ │ │ ├── page_html.ex
│ │ │ ├── error_json.ex
│ │ │ └── error_html.ex
│ │ ├── components
│ │ │ ├── layouts.ex
│ │ │ └── layouts
│ │ │ │ ├── root.html.heex
│ │ │ │ └── app.html.heex
│ │ ├── router.ex
│ │ ├── live
│ │ │ └── home.ex
│ │ ├── endpoint.ex
│ │ └── telemetry.ex
│ ├── webrtc_live_view
│ │ ├── pipeline.ex
│ │ ├── application.ex
│ │ └── contours_drawer.ex
│ └── webrtc_live_view_web.ex
├── config
│ ├── prod.exs
│ ├── test.exs
│ ├── config.exs
│ └── dev.exs
├── .gitignore
├── README.md
├── assets
│ └── js
│ │ └── app.js
└── mix.exs
├── rtmp_to_adaptive_hls
├── test
│ └── test_helper.exs
├── .formatter.exs
├── priv
│ ├── static
│ │ └── favicon.ico
│ └── gettext
│ │ ├── en
│ │ └── LC_MESSAGES
│ │ │ └── errors.po
│ │ └── errors.pot
├── lib
│ ├── rtmp_to_adaptive_hls_web
│ │ ├── views
│ │ │ ├── page_view.ex
│ │ │ ├── layout_view.ex
│ │ │ ├── error_view.ex
│ │ │ └── error_helpers.ex
│ │ ├── controllers
│ │ │ ├── page_controller.ex
│ │ │ └── hls_controller.ex
│ │ ├── templates
│ │ │ ├── layout
│ │ │ │ ├── app.html.heex
│ │ │ │ ├── live.html.heex
│ │ │ │ └── root.html.heex
│ │ │ └── page
│ │ │ │ └── index.html.heex
│ │ ├── router.ex
│ │ ├── gettext.ex
│ │ ├── endpoint.ex
│ │ └── telemetry.ex
│ ├── rtmp_to_adaptive_hls.ex
│ ├── rtmp_to_adaptive_hls
│ │ ├── client_handler.ex
│ │ └── application.ex
│ └── rtmp_to_adaptive_hls_web.ex
├── doc_assets
│ ├── OBS_settings.webp
│ └── RTMP_to_HLS_pipeline.png
├── config
│ ├── test.exs
│ ├── config.exs
│ ├── runtime.exs
│ ├── prod.exs
│ └── dev.exs
├── .gitignore
├── assets
│ ├── js
│ │ └── app.js
│ └── css
│ │ ├── app.css
│ │ └── phoenix.css
└── mix.exs
├── mix_audio
├── .formatter.exs
├── sound_1000f.wav
├── sound_500f.wav
├── .gitignore
├── README.md
└── mix_audio.exs
├── rtp_to_hls
├── .formatter.exs
├── stream.html
├── .gitignore
├── README.md
├── send.exs
└── rtp_to_hls.exs
├── rtsp_to_hls
├── config
│ ├── prod.exs
│ ├── test.exs
│ ├── dev.exs
│ └── config.exs
├── assets
│ └── video.mp4
├── server.exs
├── .formatter.exs
├── stream.html
├── .gitignore
├── rtsp_to_hls.exs
├── mix.exs
├── lib
│ └── pipeline.ex
└── README.md
├── camera_to_hls
├── .formatter.exs
├── stream.html
├── .gitignore
├── README.md
└── camera_to_hls.exs
├── camera_to_hls_nerves
├── rootfs_overlay
│ ├── etc
│ │ └── iex.exs
│ └── stream.html
├── .formatter.exs
├── lib
│ └── camera_to_hls_nerves
│ │ ├── application.ex
│ │ └── pipeline.ex
├── .gitignore
├── config
│ ├── config.exs
│ └── host.exs
├── rel
│ └── vm.args.eex
└── mix.exs
├── livebooks
├── audio_mixer
│ ├── assets
│ │ ├── beep.aac
│ │ └── sample.mp3
│ ├── README.md
│ └── audio_mixer.livemd
├── playing_mp3_file
│ ├── assets
│ │ └── sample.mp3
│ ├── README.md
│ └── playing_mp3_file.livemd
├── README.md
├── messages_source_and_sink
│ └── README.md
├── openai_realtime_with_membrane_webrtc
│ ├── assets
│ │ └── index.html
│ └── README.md
├── soundwave
│ └── README.md
├── rtmp
│ └── README.md
└── speech_to_text
│ └── README.md
├── .github
├── ISSUE_TEMPLATE
│ └── please--open-new-issues-in-membranefranework-membrane_core.md
└── workflows
│ ├── on_issue_opened.yaml
│ ├── on_pr_opened.yaml
│ ├── webrtc_videoroom_master_build.yml
│ └── webrtc_videoroom_tag_build.yml
└── .circleci
└── config.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/rtp/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/rtmp_to_hls/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/simple_element/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/simple_pipeline/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/webrtc_live_view/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/rtp/config/config.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | config :logger, level: :info
4 |
--------------------------------------------------------------------------------
/mix_audio/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["*.exs"]
4 | ]
5 |
--------------------------------------------------------------------------------
/rtp_to_hls/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["*.exs"]
4 | ]
5 |
--------------------------------------------------------------------------------
/rtsp_to_hls/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | config :logger, level: :debug
4 |
--------------------------------------------------------------------------------
/rtsp_to_hls/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | config :logger, level: :debug
4 |
--------------------------------------------------------------------------------
/simple_element/config/config.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | config :logger, level: :info
4 |
--------------------------------------------------------------------------------
/camera_to_hls/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["*.exs"]
4 | ]
5 |
--------------------------------------------------------------------------------
/simple_pipeline/config/config.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | config :logger, level: :info
4 |
--------------------------------------------------------------------------------
/rtp/samples/audio.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtp/samples/audio.opus
--------------------------------------------------------------------------------
/rtp/samples/video.h264:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtp/samples/video.h264
--------------------------------------------------------------------------------
/rtsp_to_hls/config/dev.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | config :logger, :console, format: "[$level] $message\n"
4 |
--------------------------------------------------------------------------------
/mix_audio/sound_1000f.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/mix_audio/sound_1000f.wav
--------------------------------------------------------------------------------
/mix_audio/sound_500f.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/mix_audio/sound_500f.wav
--------------------------------------------------------------------------------
/simple_element/sample.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/simple_element/sample.mp3
--------------------------------------------------------------------------------
/rtsp_to_hls/assets/video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtsp_to_hls/assets/video.mp4
--------------------------------------------------------------------------------
/simple_pipeline/sample.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/simple_pipeline/sample.mp3
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/views/page_view.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb.PageView do
2 | use RtmpToHlsWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/rtmp_to_hls/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | import_deps: [:phoenix],
3 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/rtsp_to_hls/server.exs:
--------------------------------------------------------------------------------
1 | Membrane.SimpleRTSPServer.start_link("assets/video.mp4", port: 30001)
2 |
3 | Process.sleep(:infinity)
4 |
--------------------------------------------------------------------------------
/simple_pipeline/run.exs:
--------------------------------------------------------------------------------
1 | {:ok, _supervisor, _pid} =
2 | Membrane.Pipeline.start_link(Membrane.Demo.SimplePipeline, "sample.mp3")
3 |
--------------------------------------------------------------------------------
/camera_to_hls_nerves/rootfs_overlay/etc/iex.exs:
--------------------------------------------------------------------------------
1 | NervesMOTD.print()
2 |
3 | # Add Toolshed helpers to the IEx session
4 | use Toolshed
5 |
--------------------------------------------------------------------------------
/rtmp_to_hls/priv/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtmp_to_hls/priv/static/favicon.ico
--------------------------------------------------------------------------------
/livebooks/audio_mixer/assets/beep.aac:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/livebooks/audio_mixer/assets/beep.aac
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | import_deps: [:phoenix],
3 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/livebooks/audio_mixer/assets/sample.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/livebooks/audio_mixer/assets/sample.mp3
--------------------------------------------------------------------------------
/rtmp_to_hls/doc_assets/OBS_settings.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtmp_to_hls/doc_assets/OBS_settings.webp
--------------------------------------------------------------------------------
/rtsp_to_hls/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: [
4 | "{config,lib,test}/**/*.{ex,exs}",
5 | "*.exs"
6 | ]
7 | ]
8 |
--------------------------------------------------------------------------------
/webrtc_live_view/priv/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/webrtc_live_view/priv/static/favicon.ico
--------------------------------------------------------------------------------
/rtmp_to_hls/priv/static/images/membrane.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtmp_to_hls/priv/static/images/membrane.png
--------------------------------------------------------------------------------
/rtmp_to_hls/priv/static/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtmp_to_hls/priv/static/images/phoenix.png
--------------------------------------------------------------------------------
/livebooks/playing_mp3_file/assets/sample.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/livebooks/playing_mp3_file/assets/sample.mp3
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/priv/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtmp_to_adaptive_hls/priv/static/favicon.ico
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/views/page_view.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb.PageView do
2 | use RtmpToAdaptiveHlsWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/rtmp_to_hls/doc_assets/RTMP_to_HLS_pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtmp_to_hls/doc_assets/RTMP_to_HLS_pipeline.png
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/doc_assets/OBS_settings.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtmp_to_adaptive_hls/doc_assets/OBS_settings.webp
--------------------------------------------------------------------------------
/rtp/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
4 | import_deps: [:membrane_core]
5 | ]
6 |
--------------------------------------------------------------------------------
/simple_element/run.exs:
--------------------------------------------------------------------------------
1 | alias Membrane.Demo.SimpleElement.Pipeline
2 |
3 | {:ok, _supervisor, _pid} =
4 | Membrane.Pipeline.start_link(Pipeline, "sample.mp3")
5 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/doc_assets/RTMP_to_HLS_pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/membraneframework/membrane_demo/HEAD/rtmp_to_adaptive_hls/doc_assets/RTMP_to_HLS_pipeline.png
--------------------------------------------------------------------------------
/simple_element/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | inputs: [
3 | "{lib,test,config}/**/*.{ex,exs}",
4 | "*.exs",
5 | ".formatter.exs"
6 | ],
7 | import_deps: [:membrane_core]
8 | ]
9 |
--------------------------------------------------------------------------------
/simple_pipeline/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | inputs: [
3 | "{lib,test,config}/**/*.{ex,exs}",
4 | "*.exs",
5 | ".formatter.exs"
6 | ],
7 | import_deps: [:membrane_core]
8 | ]
9 |
--------------------------------------------------------------------------------
/rtsp_to_hls/config/config.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | config :logger, :console,
4 | format: "$date $time $metadata[$level] $message\n",
5 | metadata: [:request_id]
6 |
7 | import_config "#{Mix.env()}.exs"
8 |
--------------------------------------------------------------------------------
/webrtc_live_view/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | import_deps: [:phoenix, :membrane_core],
3 | plugins: [Phoenix.LiveView.HTMLFormatter],
4 | inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"]
5 | ]
6 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb.PageController do
2 | use RtmpToHlsWeb, :controller
3 |
4 | def index(conn, _params) do
5 | render(conn, "index.html")
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/camera_to_hls_nerves/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: [
4 | "{mix,.formatter}.exs",
5 | "{config,lib,test}/**/*.{ex,exs}",
6 | "rootfs_overlay/etc/iex.exs"
7 | ],
8 | import_deps: [:membrane_core]
9 | ]
10 |
--------------------------------------------------------------------------------
/rtmp_to_hls/priv/static/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/webrtc_live_view/priv/static/robots.txt:
--------------------------------------------------------------------------------
1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb.PageController do
2 | use RtmpToAdaptiveHlsWeb, :controller
3 |
4 | def index(conn, _params) do
5 | render(conn, "index.html")
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/templates/layout/app.html.heex:
--------------------------------------------------------------------------------
1 |
2 | <%= get_flash(@conn, :info) %>
3 | <%= get_flash(@conn, :error) %>
4 | <%= @inner_content %>
5 |
6 |
--------------------------------------------------------------------------------
/livebooks/README.md:
--------------------------------------------------------------------------------
1 | # Livebook examples
2 |
3 | This folder contains interactive Livebook examples. To launch them you need to install [Livebook](https://livebook.dev) first. For Linux, we recommend [installing it via EScript](https://github.com/livebook-dev/livebook?tab=readme-ov-file#escript).
4 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/templates/layout/app.html.heex:
--------------------------------------------------------------------------------
1 |
2 | <%= get_flash(@conn, :info) %>
3 | <%= get_flash(@conn, :error) %>
4 | <%= @inner_content %>
5 |
6 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHls do
2 | @moduledoc """
3 | RtmpToHls keeps the contexts that define your domain
4 | and business logic.
5 |
6 | Contexts are also responsible for managing your data, regardless
7 | if it comes from the database, an external API or others.
8 | """
9 | end
10 |
--------------------------------------------------------------------------------
/rtp/receive.exs:
--------------------------------------------------------------------------------
1 | alias Membrane.Demo.RTP.ReceivePipeline
2 |
3 | {:ok, _supervisor, _pid} =
4 | Membrane.Pipeline.start_link(ReceivePipeline, %{
5 | video_port: 5000,
6 | audio_port: 5002,
7 | secure?: "--secure" in System.argv(),
8 | srtp_key: String.duplicate("a", 30)
9 | })
10 |
11 | Process.sleep(:infinity)
12 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveView do
2 | @moduledoc """
3 | WebrtcLiveView keeps the contexts that define your domain
4 | and business logic.
5 |
6 | Contexts are also responsible for managing your data, regardless
7 | if it comes from the database, an external API or others.
8 | """
9 | end
10 |
--------------------------------------------------------------------------------
/webrtc_live_view/test/webrtc_live_view_web/controllers/page_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.PageControllerTest do
2 | use WebrtcLiveViewWeb.ConnCase
3 |
4 | test "GET /", %{conn: conn} do
5 | conn = get(conn, ~p"/")
6 | assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb.LayoutView do
2 | use RtmpToHlsWeb, :view
3 |
4 | # Phoenix LiveDashboard is available only in development by default,
5 | # so we instruct Elixir to not warn if the dashboard route is missing.
6 | @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}}
7 | end
8 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.PageController do
2 | use WebrtcLiveViewWeb, :controller
3 |
4 | def home(conn, _params) do
5 | # The home page is often custom made,
6 | # so skip the default app layout.
7 | render(conn, :home, layout: false)
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHls do
2 | @moduledoc """
3 | RtmpToAdaptiveHls keeps the contexts that define your domain
4 | and business logic.
5 |
6 | Contexts are also responsible for managing your data, regardless
7 | if it comes from the database, an external API or others.
8 | """
9 | end
10 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/controllers/page_html.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.PageHTML do
2 | @moduledoc """
3 | This module contains pages rendered by PageController.
4 |
5 | See the `page_html` directory for all templates available.
6 | """
7 | use WebrtcLiveViewWeb, :html
8 |
9 | embed_templates "page_html/*"
10 | end
11 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb.LayoutView do
2 | use RtmpToAdaptiveHlsWeb, :view
3 |
4 | # Phoenix LiveDashboard is available only in development by default,
5 | # so we instruct Elixir to not warn if the dashboard route is missing.
6 | @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}}
7 | end
8 |
--------------------------------------------------------------------------------
/rtp/send.exs:
--------------------------------------------------------------------------------
1 | alias Membrane.Demo.RTP.SendPipeline
2 |
3 | {:ok, _supervisor, _pid} =
4 | Membrane.Pipeline.start_link(SendPipeline, %{
5 | video_port: 5000,
6 | video_ssrc: 1234,
7 | audio_port: 5002,
8 | audio_ssrc: 1236,
9 | secure?: "--secure" in System.argv(),
10 | srtp_key: String.duplicate("a", 30)
11 | })
12 |
13 | Process.sleep(:infinity)
14 |
--------------------------------------------------------------------------------
/rtmp_to_hls/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
1 | ## `msgid`s in this file come from POT (.pot) files.
2 | ##
3 | ## Do not add, change, or remove `msgid`s manually here as
4 | ## they're tied to the ones in the corresponding POT file
5 | ## (with the same domain).
6 | ##
7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge`
8 | ## to merge POT files into PO files.
9 | msgid ""
10 | msgstr ""
11 | "Language: en\n"
12 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
1 | ## `msgid`s in this file come from POT (.pot) files.
2 | ##
3 | ## Do not add, change, or remove `msgid`s manually here as
4 | ## they're tied to the ones in the corresponding POT file
5 | ## (with the same domain).
6 | ##
7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge`
8 | ## to merge POT files into PO files.
9 | msgid ""
10 | msgstr ""
11 | "Language: en\n"
12 |
--------------------------------------------------------------------------------
/rtmp_to_hls/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This is a PO Template file.
2 | ##
3 | ## `msgid`s here are often extracted from source code.
4 | ## Add new translations manually only if they're dynamic
5 | ## translations that can't be statically extracted.
6 | ##
7 | ## Run `mix gettext.extract` to bring this file up to
8 | ## date. Leave `msgstr`s empty as changing them here has no
9 | ## effect: edit them in PO (`.po`) files instead.
10 |
11 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/templates/layout/live.html.heex:
--------------------------------------------------------------------------------
1 |
2 | <%= live_flash(@flash, :info) %>
5 |
6 | <%= live_flash(@flash, :error) %>
9 |
10 | <%= @inner_content %>
11 |
12 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This is a PO Template file.
2 | ##
3 | ## `msgid`s here are often extracted from source code.
4 | ## Add new translations manually only if they're dynamic
5 | ## translations that can't be statically extracted.
6 | ##
7 | ## Run `mix gettext.extract` to bring this file up to
8 | ## date. Leave `msgstr`s empty as changing them here has no
9 | ## effect: edit them in PO (`.po`) files instead.
10 |
11 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/controllers/hls_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb.HlsController do
2 | use RtmpToHlsWeb, :controller
3 |
4 | alias Plug
5 |
6 | def index(conn, %{"filename" => filename}) do
7 | path = "output/#{filename}"
8 |
9 | if File.exists?(path) do
10 | conn |> Plug.Conn.send_file(200, path)
11 | else
12 | conn |> Plug.Conn.send_resp(404, "File not found")
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/templates/layout/live.html.heex:
--------------------------------------------------------------------------------
1 |
2 | <%= live_flash(@flash, :info) %>
5 |
6 | <%= live_flash(@flash, :error) %>
9 |
10 | <%= @inner_content %>
11 |
12 |
--------------------------------------------------------------------------------
/camera_to_hls_nerves/lib/camera_to_hls_nerves/application.ex:
--------------------------------------------------------------------------------
1 | defmodule CameraToHlsNerves.Application do
2 | use Application
3 |
4 | def start(_type, _args) do
5 | File.rm_rf!("/data/output")
6 | File.mkdir!("/data/output")
7 | File.cd!("/")
8 |
9 | children = [
10 | CameraToHlsNerves.Pipeline
11 | ]
12 |
13 | Supervisor.start_link(children, strategy: :one_for_one, name: CameraToHlsNervesSupervisor)
14 | end
15 |
16 | end
17 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/controllers/hls_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb.HlsController do
2 | use RtmpToAdaptiveHlsWeb, :controller
3 |
4 | alias Plug
5 |
6 | def index(conn, %{"filename" => filename}) do
7 | path = "output/#{filename}"
8 |
9 | if File.exists?(path) do
10 | conn |> Plug.Conn.send_file(200, path)
11 | else
12 | conn |> Plug.Conn.send_resp(404, "File not found")
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/please--open-new-issues-in-membranefranework-membrane_core.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Please, open new issues in membranefranework/membrane_core
3 | about: New issues related to this repo should be opened there
4 | title: "[DO NOT OPEN]"
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | Please, do not open this issue here. Open it in the [membrane_core](https://github.com/membraneframework/membrane_core) repository instead.
11 |
12 | Thanks for helping us grow :)
13 |
--------------------------------------------------------------------------------
/webrtc_live_view/test/webrtc_live_view_web/controllers/error_json_test.exs:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.ErrorJSONTest do
2 | use WebrtcLiveViewWeb.ConnCase, async: true
3 |
4 | test "renders 404" do
5 | assert WebrtcLiveViewWeb.ErrorJSON.render("404.json", %{}) == %{
6 | errors: %{detail: "Not Found"}
7 | }
8 | end
9 |
10 | test "renders 500" do
11 | assert WebrtcLiveViewWeb.ErrorJSON.render("500.json", %{}) ==
12 | %{errors: %{detail: "Internal Server Error"}}
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/camera_to_hls_nerves/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where third-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
--------------------------------------------------------------------------------
/rtmp_to_hls/config/test.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :rtmp_to_hls, RtmpToHlsWeb.Endpoint,
6 | http: [ip: {127, 0, 0, 1}, port: 4002],
7 | secret_key_base: "7QKtyBTigBZGpLDW0nc1U44mpE1U9kQzDd60hynU4bcnAUfLXPJLQxRlde8fPDhb",
8 | server: false
9 |
10 | # Print only warnings and errors during test
11 | config :logger, level: :warn
12 |
13 | # Initialize plugs at runtime for faster test compilation
14 | config :phoenix, :plug_init_mode, :runtime
15 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/components/layouts.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.Layouts do
2 | @moduledoc """
3 | This module holds different layouts used by your application.
4 |
5 | See the `layouts` directory for all templates available.
6 | The "root" layout is a skeleton rendered as part of the
7 | application router. The "app" layout is set as the default
8 | layout on both `use WebrtcLiveViewWeb, :controller` and
9 | `use WebrtcLiveViewWeb, :live_view`.
10 | """
11 | use WebrtcLiveViewWeb, :html
12 |
13 | embed_templates "layouts/*"
14 | end
15 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/config/test.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :rtmp_to_adaptive_hls, RtmpToAdaptiveHlsWeb.Endpoint,
6 | http: [ip: {127, 0, 0, 1}, port: 4002],
7 | secret_key_base: "7QKtyBTigBZGpLDW0nc1U44mpE1U9kQzDd60hynU4bcnAUfLXPJLQxRlde8fPDhb",
8 | server: false
9 |
10 | # Print only warnings and errors during test
11 | config :logger, level: :warn
12 |
13 | # Initialize plugs at runtime for faster test compilation
14 | config :phoenix, :plug_init_mode, :runtime
15 |
--------------------------------------------------------------------------------
/webrtc_live_view/test/webrtc_live_view_web/controllers/error_html_test.exs:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.ErrorHTMLTest do
2 | use WebrtcLiveViewWeb.ConnCase, async: true
3 |
4 | # Bring render_to_string/4 for testing custom views
5 | import Phoenix.Template
6 |
7 | test "renders 404.html" do
8 | assert render_to_string(WebrtcLiveViewWeb.ErrorHTML, "404", "html", []) == "Not Found"
9 | end
10 |
11 | test "renders 500.html" do
12 | assert render_to_string(WebrtcLiveViewWeb.ErrorHTML, "500", "html", []) ==
13 | "Internal Server Error"
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb.ErrorView do
2 | use RtmpToHlsWeb, :view
3 |
4 | # If you want to customize a particular status code
5 | # for a certain format, you may uncomment below.
6 | # def render("500.html", _assigns) do
7 | # "Internal Server Error"
8 | # end
9 |
10 | # By default, Phoenix returns the status message from
11 | # the template name. For example, "404.html" becomes
12 | # "Not Found".
13 | def template_not_found(template, _assigns) do
14 | Phoenix.Controller.status_message_from_template(template)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/templates/page/index.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb.ErrorView do
2 | use RtmpToAdaptiveHlsWeb, :view
3 |
4 | # If you want to customize a particular status code
5 | # for a certain format, you may uncomment below.
6 | # def render("500.html", _assigns) do
7 | # "Internal Server Error"
8 | # end
9 |
10 | # By default, Phoenix returns the status message from
11 | # the template name. For example, "404.html" becomes
12 | # "Not Found".
13 | def template_not_found(template, _assigns) do
14 | Phoenix.Controller.status_message_from_template(template)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/templates/page/index.html.heex:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/webrtc_live_view/config/prod.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # Note we also include the path to a cache manifest
4 | # containing the digested version of static files. This
5 | # manifest is generated by the `mix assets.deploy` task,
6 | # which you should run after static files are built and
7 | # before starting your production server.
8 | config :webrtc_live_view, WebrtcLiveViewWeb.Endpoint,
9 | cache_static_manifest: "priv/static/cache_manifest.json"
10 |
11 | # Do not print debug messages in production
12 | config :logger, level: :info
13 |
14 | # Runtime production configuration, including reading
15 | # of environment variables, is done on config/runtime.exs.
16 |
--------------------------------------------------------------------------------
/.github/workflows/on_issue_opened.yaml:
--------------------------------------------------------------------------------
1 | name: 'Close issue when opened'
2 | on:
3 | issues:
4 | types:
5 | - opened
6 | jobs:
7 | close:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout membrane_core
11 | uses: actions/checkout@v3
12 | with:
13 | repository: membraneframework/membrane_core
14 | - name: Close issue
15 | uses: ./.github/actions/close_issue
16 | with:
17 | GITHUB_TOKEN: ${{ secrets.MEMBRANEFRAMEWORKADMIN_TOKEN }}
18 | ISSUE_URL: ${{ github.event.issue.html_url }}
19 | ISSUE_NUMBER: ${{ github.event.issue.number }}
20 | REPOSITORY: ${{ github.repository }}
21 |
--------------------------------------------------------------------------------
/livebooks/audio_mixer/README.md:
--------------------------------------------------------------------------------
1 | # Membrane audio mixer
2 |
3 | This livebook shows how to mix a beep sound into background music over a period of time.
4 |
5 | To run the demo, [install Livebook](https://github.com/livebook-dev/livebook#escript) and open the `audio_mixer.livemd` file there.
6 |
7 | ## Copyright and License
8 |
9 | Copyright 2024, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
10 |
11 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
12 |
13 | Licensed under the [Apache License, Version 2.0](LICENSE)
14 |
--------------------------------------------------------------------------------
/camera_to_hls/stream.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The video should show up below. If it doesn't, make sure that the index.m3u8 file is generated in the output
8 | directory and refresh the page.
9 |
10 |
11 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/livebooks/messages_source_and_sink/README.md:
--------------------------------------------------------------------------------
1 | # Membrane messages source and sink
2 |
3 | This livebook shows how to setup a simple pipeline and send messages through it.
4 |
5 | To run the demo, [install Livebook](https://github.com/livebook-dev/livebook#escript) and open the `messages_source_and_sink.livemd` file there.
6 |
7 | ## Copyright and License
8 |
9 | Copyright 2024, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
10 |
11 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
12 |
13 | Licensed under the [Apache License, Version 2.0](LICENSE)
14 |
--------------------------------------------------------------------------------
/livebooks/playing_mp3_file/README.md:
--------------------------------------------------------------------------------
1 | # Membrane playing mp3 file demo
2 |
3 | This livebook shows how to load `MP3` audio from the file, transcode it to the `AAC` codec, and play it.
4 |
5 | To run the demo, [install Livebook](https://github.com/livebook-dev/livebook#escript) and open the `playing_mp3_file.livemd` file there.
6 |
7 | ## Copyright and License
8 |
9 | Copyright 2024, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
10 |
11 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
12 |
13 | Licensed under the [Apache License, Version 2.0](LICENSE)
14 |
--------------------------------------------------------------------------------
/rtp_to_hls/stream.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The video should show up below. If it doesn't, make sure that the index.m3u8 file is generated in the output
8 | directory and refresh the page.
9 |
10 |
11 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/webrtc_live_view/config/test.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :webrtc_live_view, WebrtcLiveViewWeb.Endpoint,
6 | http: [ip: {127, 0, 0, 1}, port: 4002],
7 | secret_key_base: "IMaoMRytJjFhXafOgZQyn2pDxQ6CR3WFaCf7srkc0hfwu8GkAPUEsLHyPmZQzks8",
8 | server: false
9 |
10 | # Print only warnings and errors during test
11 | config :logger, level: :warning
12 |
13 | # Initialize plugs at runtime for faster test compilation
14 | config :phoenix, :plug_init_mode, :runtime
15 |
16 | # Enable helpful, but potentially expensive runtime checks
17 | config :phoenix_live_view,
18 | enable_expensive_runtime_checks: true
19 |
--------------------------------------------------------------------------------
/rtsp_to_hls/stream.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The video should show up below. If it doesn't, make sure that the index.m3u8 file is generated in the output
8 | directory and refresh the page.
9 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/camera_to_hls_nerves/rootfs_overlay/stream.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The video should show up below. If it doesn't, make sure that the index.m3u8 file is generated in the /data/output
8 | directory and refresh the page.
9 |
10 |
11 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/components/layouts/root.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <.live_title default="WebrtcLiveView" suffix=" · Phoenix Framework">
8 | {assigns[:page_title]}
9 |
10 |
11 |
13 |
14 |
15 | {@inner_content}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb.Router do
2 | use RtmpToHlsWeb, :router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_live_flash
8 | plug :put_root_layout, {RtmpToHlsWeb.LayoutView, :root}
9 | plug :protect_from_forgery
10 | plug :put_secure_browser_headers
11 | end
12 |
13 | pipeline :api do
14 | plug :accepts, ["json"]
15 | end
16 |
17 | scope "/", RtmpToHlsWeb do
18 | pipe_through :browser
19 |
20 | get "/", PageController, :index
21 | get "/video/:filename", HlsController, :index
22 | end
23 |
24 | # Other scopes may use custom stacks.
25 | # scope "/api", RtmpToHlsWeb do
26 | # pipe_through :api
27 | # end
28 | end
29 |
--------------------------------------------------------------------------------
/livebooks/openai_realtime_with_membrane_webrtc/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | OpenAI Realtime with Membrane WebRTC demo
9 |
10 |
11 |
13 |
14 | OpenAI Realtime with Membrane WebRTC demo
15 | Connecting
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/controllers/error_json.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.ErrorJSON do
2 | @moduledoc """
3 | This module is invoked by your endpoint in case of errors on JSON requests.
4 |
5 | See config/config.exs.
6 | """
7 |
8 | # If you want to customize a particular status code,
9 | # you may add your own clauses, such as:
10 | #
11 | # def render("500.json", _assigns) do
12 | # %{errors: %{detail: "Internal Server Error"}}
13 | # end
14 |
15 | # By default, Phoenix returns the status message from
16 | # the template name. For example, "404.json" becomes
17 | # "Not Found".
18 | def render(template, _assigns) do
19 | %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/templates/layout/root.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= csrf_meta_tag() %>
8 | <%= live_title_tag assigns[:page_title] || "RtmpToHls", suffix: " · Membrane Framework" %>
9 |
10 |
11 |
12 |
13 | RTMP to HLS demo
14 | <%= @inner_content %>
15 |
16 |
17 |
--------------------------------------------------------------------------------
/livebooks/soundwave/README.md:
--------------------------------------------------------------------------------
1 | # Membrane Soundwave demo
2 |
3 | This livebook example shows how to perform real-time soundwave plotting with the use of the [Membrane Framework](https://github.com/membraneframework) and [Vega-Lite](https://vega.github.io/vega-lite/).
4 |
5 | To run the demo, [install Livebook](https://github.com/livebook-dev/livebook#escript) and open the `soundwave.livemd` file there.
6 |
7 | ## Copyright and License
8 |
9 | Copyright 2024, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
10 |
11 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
12 |
13 | Licensed under the [Apache License, Version 2.0](LICENSE)
14 |
--------------------------------------------------------------------------------
/mix_audio/.gitignore:
--------------------------------------------------------------------------------
1 | # HLS output
2 | /output.aac
3 |
4 | .elixir_ls
5 |
6 | # The directory Mix will write compiled artifacts to.
7 | /_build/
8 |
9 | # If you run "mix test --cover", coverage assets end up here.
10 | /cover/
11 |
12 | # The directory Mix downloads your dependencies sources to.
13 | /deps/
14 |
15 | # Where third-party dependencies like ExDoc output generated docs.
16 | /doc/
17 |
18 | # Ignore .fetch files in case you like to edit your project deps locally.
19 | /.fetch
20 |
21 | # If the VM crashes, it generates a dump, let's ignore it too.
22 | erl_crash.dump
23 |
24 | # Also ignore archive artifacts (built via "mix archive.build").
25 | *.ez
26 |
27 | # Ignore package tarball (built via "mix hex.build").
28 | membrane_demo_rtp_to_hls-*.tar
29 |
30 | # macOS
31 | .DS_Store
32 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/templates/layout/root.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= csrf_meta_tag() %>
8 | <%= live_title_tag assigns[:page_title] || "RtmpToHls", suffix: " · Membrane Framework" %>
9 |
10 |
11 |
12 |
13 | RTMP to HLS demo
14 | <%= @inner_content %>
15 |
16 |
17 |
--------------------------------------------------------------------------------
/rtp_to_hls/.gitignore:
--------------------------------------------------------------------------------
1 | # HLS output
2 | /output/*
3 |
4 | .elixir_ls
5 |
6 | # The directory Mix will write compiled artifacts to.
7 | /_build/
8 |
9 | # If you run "mix test --cover", coverage assets end up here.
10 | /cover/
11 |
12 | # The directory Mix downloads your dependencies sources to.
13 | /deps/
14 |
15 | # Where third-party dependencies like ExDoc output generated docs.
16 | /doc/
17 |
18 | # Ignore .fetch files in case you like to edit your project deps locally.
19 | /.fetch
20 |
21 | # If the VM crashes, it generates a dump, let's ignore it too.
22 | erl_crash.dump
23 |
24 | # Also ignore archive artifacts (built via "mix archive.build").
25 | *.ez
26 |
27 | # Ignore package tarball (built via "mix hex.build").
28 | membrane_demo_rtp_to_hls-*.tar
29 |
30 | # macOS
31 | .DS_Store
32 |
--------------------------------------------------------------------------------
/camera_to_hls/.gitignore:
--------------------------------------------------------------------------------
1 | # HLS output
2 | /output/*
3 |
4 | .elixir_ls
5 |
6 | # The directory Mix will write compiled artifacts to.
7 | /_build/
8 |
9 | # If you run "mix test --cover", coverage assets end up here.
10 | /cover/
11 |
12 | # The directory Mix downloads your dependencies sources to.
13 | /deps/
14 |
15 | # Where third-party dependencies like ExDoc output generated docs.
16 | /doc/
17 |
18 | # Ignore .fetch files in case you like to edit your project deps locally.
19 | /.fetch
20 |
21 | # If the VM crashes, it generates a dump, let's ignore it too.
22 | erl_crash.dump
23 |
24 | # Also ignore archive artifacts (built via "mix archive.build").
25 | *.ez
26 |
27 | # Ignore package tarball (built via "mix hex.build").
28 | membrane_demo_rtp_to_hls-*.tar
29 |
30 | # macOS
31 | .DS_Store
32 |
--------------------------------------------------------------------------------
/.github/workflows/on_pr_opened.yaml:
--------------------------------------------------------------------------------
1 | name: Add PR to Smackore project board, if the author is from outside Membrane Team
2 | on:
3 | pull_request_target:
4 | types:
5 | - opened
6 | jobs:
7 | maybe_add_to_project_board:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout membrane_core
11 | uses: actions/checkout@v3
12 | with:
13 | repository: membraneframework/membrane_core
14 | - name: Puts PR in "New PRs by community" column in the Smackore project, if the author is from outside Membrane Team
15 | uses: ./.github/actions/add_pr_to_smackore_board
16 | with:
17 | GITHUB_TOKEN: ${{ secrets.MEMBRANEFRAMEWORKADMIN_TOKEN }}
18 | AUTHOR_LOGIN: ${{ github.event.pull_request.user.login }}
19 | PR_URL: ${{ github.event.pull_request.html_url }}
20 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb.Router do
2 | use RtmpToAdaptiveHlsWeb, :router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_live_flash
8 | plug :put_root_layout, {RtmpToAdaptiveHlsWeb.LayoutView, :root}
9 | plug :protect_from_forgery
10 | plug :put_secure_browser_headers
11 | end
12 |
13 | pipeline :api do
14 | plug :accepts, ["json"]
15 | end
16 |
17 | scope "/", RtmpToAdaptiveHlsWeb do
18 | pipe_through :browser
19 |
20 | get "/", PageController, :index
21 | get "/video/:filename", HlsController, :index
22 | end
23 |
24 | # Other scopes may use custom stacks.
25 | # scope "/api", RtmpToAdaptiveHlsWeb do
26 | # pipe_through :api
27 | # end
28 | end
29 |
--------------------------------------------------------------------------------
/livebooks/openai_realtime_with_membrane_webrtc/README.md:
--------------------------------------------------------------------------------
1 | # OpenAI Realtime API Integration with Membrane WebRTC
2 |
3 | This is a [Livebook](https://livebook.dev) example, that allows to conversate with AI via browser using [OpenAI Realtime API](https://openai.com/index/introducing-the-realtime-api/).
4 |
5 | To run the demo, [install Livebook](https://github.com/livebook-dev/livebook#escript) and open the `openai_realtime_with_membrane_webrtc.livemd` file there.
6 |
7 | ## Copyright and License
8 |
9 | Copyright 2024, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
10 |
11 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
12 |
13 | Licensed under the [Apache License, Version 2.0](LICENSE)
14 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb.Gettext do
2 | @moduledoc """
3 | A module providing Internationalization with a gettext-based API.
4 |
5 | By using [Gettext](https://hexdocs.pm/gettext),
6 | your module gains a set of macros for translations, for example:
7 |
8 | import RtmpToHlsWeb.Gettext
9 |
10 | # Simple translation
11 | gettext("Here is the string to translate")
12 |
13 | # Plural translation
14 | ngettext("Here is the string to translate",
15 | "Here are the strings to translate",
16 | 3)
17 |
18 | # Domain-based translation
19 | dgettext("errors", "Here is the error message to translate")
20 |
21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
22 | """
23 | use Gettext, otp_app: :rtmp_to_hls
24 | end
25 |
--------------------------------------------------------------------------------
/livebooks/rtmp/README.md:
--------------------------------------------------------------------------------
1 | # Membrane RMTP demo
2 |
3 | Sender livebook shows how to download video and audio from the web using the `Hackney` plugin, and stream it via `RTMP` to the other, receiver livebook.
4 |
5 | Receiver livebook shows how to receive `RTMP` stream mentioned above and play it in the livebook.
6 |
7 | To run the demo, [install Livebook](https://github.com/livebook-dev/livebook#escript) and open both `rtmp_sender.livemd` and `rtmp_receiver.livemd` files there.
8 |
9 | ## Copyright and License
10 |
11 | Copyright 2024, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
12 |
13 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
14 |
15 | Licensed under the [Apache License, Version 2.0](LICENSE)
16 |
--------------------------------------------------------------------------------
/rtsp_to_hls/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where third-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | hls_proxy_api-*.tar
24 |
25 | # Temporary files, for example, from tests.
26 | /tmp/
27 |
28 | # HLS files
29 | *.m3u8
30 | *.m4s
31 | *.mp4
32 | !assets/video.mp4
33 |
34 | # macOS
35 | .DS_Store
36 |
--------------------------------------------------------------------------------
/camera_to_hls_nerves/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application and its
2 | # dependencies.
3 | #
4 | # This configuration file is loaded before any dependency and is restricted to
5 | # this project.
6 | import Config
7 |
8 | # Enable the Nerves integration with Mix
9 | Application.start(:nerves_bootstrap)
10 |
11 | config :camera_to_hls_nerves, target: Mix.target()
12 |
13 | # https://hexdocs.pm/nerves/advanced-configuration.html for details.
14 |
15 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay"
16 |
17 | # Set the SOURCE_DATE_EPOCH date for reproducible builds.
18 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information
19 |
20 | config :nerves, source_date_epoch: "1698662793"
21 |
22 | if Mix.target() == :host do
23 | import_config "host.exs"
24 | else
25 | import_config "target.exs"
26 | end
27 |
--------------------------------------------------------------------------------
/camera_to_hls_nerves/config/host.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # Add configuration that is only needed when running on the host here.
4 |
5 | config :nerves_runtime,
6 | kv_backend:
7 | {Nerves.Runtime.KVBackend.InMemory,
8 | contents: %{
9 | # The KV store on Nerves systems is typically read from UBoot-env, but
10 | # this allows us to use a pre-populated InMemory store when running on
11 | # host for development and testing.
12 | #
13 | # https://hexdocs.pm/nerves_runtime/readme.html#using-nerves_runtime-in-tests
14 | # https://hexdocs.pm/nerves_runtime/readme.html#nerves-system-and-firmware-metadata
15 |
16 | "nerves_fw_active" => "a",
17 | "a.nerves_fw_architecture" => "generic",
18 | "a.nerves_fw_description" => "N/A",
19 | "a.nerves_fw_platform" => "host",
20 | "a.nerves_fw_version" => "0.0.0"
21 | }}
22 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb.Gettext do
2 | @moduledoc """
3 | A module providing Internationalization with a gettext-based API.
4 |
5 | By using [Gettext](https://hexdocs.pm/gettext),
6 | your module gains a set of macros for translations, for example:
7 |
8 | import RtmpToAdaptiveHlsWeb.Gettext
9 |
10 | # Simple translation
11 | gettext("Here is the string to translate")
12 |
13 | # Plural translation
14 | ngettext("Here is the string to translate",
15 | "Here are the strings to translate",
16 | 3)
17 |
18 | # Domain-based translation
19 | dgettext("errors", "Here is the error message to translate")
20 |
21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
22 | """
23 | use Gettext, otp_app: :rtmp_to_adaptive_hls
24 | end
25 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/controllers/error_html.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.ErrorHTML do
2 | @moduledoc """
3 | This module is invoked by your endpoint in case of errors on HTML requests.
4 |
5 | See config/config.exs.
6 | """
7 | use WebrtcLiveViewWeb, :html
8 |
9 | # If you want to customize your error pages,
10 | # uncomment the embed_templates/1 call below
11 | # and add pages to the error directory:
12 | #
13 | # * lib/webrtc_live_view_web/controllers/error_html/404.html.heex
14 | # * lib/webrtc_live_view_web/controllers/error_html/500.html.heex
15 | #
16 | # embed_templates "error_html/*"
17 |
18 | # The default is to render a plain text page based on
19 | # the template name. For example, "404.html" becomes
20 | # "Not Found".
21 | def render(template, _assigns) do
22 | Phoenix.Controller.status_message_from_template(template)
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/mix_audio/README.md:
--------------------------------------------------------------------------------
1 | # Audio mixing
2 |
3 | This script demonstrates mixing two audio tracks
4 |
5 | To run the demo, you need [Elixir installed](https://elixir-lang.org/install.html) on your machine (it's best to use a version manager, like `asdf`). Then, run
6 |
7 | ```bash
8 | elixir mix_audio.exs
9 | ```
10 |
11 | and it will generate `output.aac`, that you can play with VLC or other player.
12 |
13 | Should there be any errors when compiling the script's dependencies, you may need to install [FDK AAC](https://github.com/mstorsjo/fdk-aac), which we use to encode the output stream.
14 |
15 |
16 | ## Copyright and License
17 |
18 | Copyright 2022, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
19 |
20 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
21 |
22 | Licensed under the [Apache License, Version 2.0](LICENSE)
23 |
--------------------------------------------------------------------------------
/rtmp_to_hls/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | rtmp_to_hls-*.tar
24 |
25 | # Ignore assets that are produced by build tools.
26 | /priv/static/assets/
27 |
28 | # Ignore digested assets cache.
29 | /priv/static/cache_manifest.json
30 |
31 | # In case you use Node.js/npm, you want to ignore these.
32 | npm-debug.log
33 | /assets/node_modules/
34 |
35 | /output
36 |
37 | # macOS
38 | .DS_Store
39 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | rtmp_to_adaptive_hls-*.tar
24 |
25 | # Ignore assets that are produced by build tools.
26 | /priv/static/assets/
27 |
28 | # Ignore digested assets cache.
29 | /priv/static/cache_manifest.json
30 |
31 | # In case you use Node.js/npm, you want to ignore these.
32 | npm-debug.log
33 | /assets/node_modules/
34 |
35 | /output
36 |
37 | # macOS
38 | .DS_Store
39 |
--------------------------------------------------------------------------------
/rtsp_to_hls/rtsp_to_hls.exs:
--------------------------------------------------------------------------------
1 | require Logger
2 | alias Membrane.Demo.RTSPToHLS
3 |
4 | rtsp_stream_url = "rtsp://localhost:30001"
5 | output_path = "hls_output"
6 | rtp_port = 20000
7 |
8 | # Prepare a clean directory where output files will be put
9 | File.rm_rf!(output_path)
10 | File.mkdir_p(output_path)
11 |
12 | pipeline_options = %{
13 | port: rtp_port,
14 | port_range_size: 5,
15 | output_path: output_path,
16 | stream_url: rtsp_stream_url,
17 | parent_pid: self()
18 | }
19 |
20 | {:ok, _sup, _pid} = Membrane.Pipeline.start_link(RTSPToHLS.Pipeline, pipeline_options)
21 |
22 | # Wait until first chunks of the stream become available
23 | receive do
24 | :track_playable -> :ok
25 | end
26 |
27 | {:ok, _server} =
28 | :inets.start(:httpd,
29 | bind_address: ~c"localhost",
30 | port: 8000,
31 | document_root: ~c".",
32 | server_name: ~c"rtsp_to_hls",
33 | server_root: "/tmp"
34 | )
35 |
36 | Logger.info("Playback available at http://localhost:8000/stream.html")
37 |
38 | Process.sleep(:infinity)
39 |
--------------------------------------------------------------------------------
/webrtc_live_view/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Temporary files, for example, from tests.
23 | /tmp/
24 |
25 | # Ignore package tarball (built via "mix hex.build").
26 | webrtc_live_view-*.tar
27 |
28 | # Ignore assets that are produced by build tools.
29 | /priv/static/assets/
30 |
31 | # Ignore digested assets cache.
32 | /priv/static/cache_manifest.json
33 |
34 | # In case you use Node.js/npm, you want to ignore these.
35 | npm-debug.log
36 | /assets/node_modules/
37 |
38 |
--------------------------------------------------------------------------------
/livebooks/speech_to_text/README.md:
--------------------------------------------------------------------------------
1 | # Membrane Speech To Text demo
2 |
3 | This is a [Livebook](https://livebook.dev) example, that shows how to perform a real-time speech-to-text conversion with the use of the [Membrane Framework](https://github.com/membraneframework) and the [Bumblebee](https://github.com/elixir-nx/bumblebee).
4 |
5 | To run the demo, [install Livebook](https://livebook.dev/#install) and open the `speech_to_text.livemd` file there.
6 | If you're using Livebook Desktop, you can also [click here](https://livebook.dev/run?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmembraneframework%2Fmembrane_demo%2Fmaster%2Fspeech_to_text%2Fspeech_to_text.livemd) instead.
7 |
8 | ## Copyright and License
9 |
10 | Copyright 2022, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
11 |
12 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
13 |
14 | Licensed under the [Apache License, Version 2.0](LICENSE)
15 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view/pipeline.ex:
--------------------------------------------------------------------------------
1 | defmodule WebRTCLiveView.Pipeline do
2 | use Membrane.Pipeline
3 |
4 | @impl true
5 | def handle_init(_ctx, opts) do
6 | spec =
7 | child(:webrtc_source, %Membrane.WebRTC.Source{
8 | allowed_video_codecs: :h264,
9 | signaling: opts[:ingress_signaling]
10 | })
11 | |> via_out(:output, options: [kind: :video])
12 | |> child(%Membrane.Transcoder{output_stream_format: Membrane.RawVideo})
13 | |> child(%Membrane.FFmpeg.SWScale.Converter{format: :RGB})
14 | |> child(WebRTCLiveView.CountoursDrawer)
15 | |> child(%Membrane.FFmpeg.SWScale.Converter{format: :I420})
16 | |> child(%Membrane.Transcoder{
17 | output_stream_format: %Membrane.H264{alignment: :nalu, stream_structure: :annexb}
18 | })
19 | |> via_in(:input, options: [kind: :video])
20 | |> child(:webrtc_sink, %Membrane.WebRTC.Sink{
21 | video_codec: :h264,
22 | signaling: opts[:egress_signaling]
23 | })
24 |
25 | {[spec: spec], %{}}
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls/client_handler.ex:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.RtmpToAdaptiveHls.ClientHandler do
2 | @moduledoc """
3 | An implementation of `Membrane.RTMPServer.ClientHandlerBehaviour` compatible with the
4 | `Membrane.RTMP.Source` element, which also send information about RTMP stream metadata to the `pipeline` process
5 | """
6 |
7 | @behaviour Membrane.RTMPServer.ClientHandler
8 |
9 | @handler Membrane.RTMP.Source.ClientHandlerImpl
10 |
11 | defstruct []
12 |
13 | @impl true
14 | def handle_init(%{pipeline: pid} = opts) do
15 | state = @handler.handle_init(opts)
16 | Map.put(state, :pipeline, pid)
17 | end
18 |
19 | @impl true
20 | defdelegate handle_info(msg, state), to: @handler
21 |
22 | @impl true
23 | defdelegate handle_data_available(payload, state), to: @handler
24 |
25 | @impl true
26 | defdelegate handle_connection_closed(state), to: @handler
27 |
28 | @impl true
29 | defdelegate handle_delete_stream(state), to: @handler
30 |
31 | @impl true
32 | def handle_metadata(message, state) do
33 | send(state.pipeline, message)
34 | state
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/simple_element/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.SimpleElement.MixProject do
2 | use Mix.Project
3 |
4 | @version "0.5.0"
5 | @github_url "https://github.com/membraneframework/membrane-demo"
6 |
7 | def project do
8 | [
9 | app: :membrane_demo_simple_element,
10 | version: @version,
11 | elixir: "~> 1.12",
12 | name: "Membrane Demo Simple Element",
13 | description: "Membrane simple element demo",
14 | homepage_url: "https://membraneframework.org",
15 | source_url: @github_url,
16 | start_permanent: Mix.env() == :prod,
17 | deps: deps()
18 | ]
19 | end
20 |
21 | # Run "mix help compile.app" to learn about applications.
22 | def application do
23 | [
24 | extra_applications: [:logger]
25 | ]
26 | end
27 |
28 | # Run "mix help deps" to learn about dependencies.
29 | defp deps do
30 | [
31 | {:membrane_core, "~> 1.0"},
32 | {:membrane_file_plugin, "~> 0.17.0"},
33 | {:membrane_portaudio_plugin, "~> 0.19.2"},
34 | {:membrane_ffmpeg_swresample_plugin, "~> 0.20.2"},
35 | {:membrane_mp3_mad_plugin, "~> 0.18.3"},
36 | {:dialyxir, "~> 1.2", only: [:dev], runtime: false}
37 | ]
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/simple_pipeline/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.SimplePipeline.MixProject do
2 | use Mix.Project
3 |
4 | @version "0.5.0"
5 | @github_url "https://github.com/membraneframework/membrane-demo"
6 |
7 | def project do
8 | [
9 | app: :membrane_demo_simple_pipeline,
10 | version: @version,
11 | elixir: "~> 1.12",
12 | name: "Membrane Demo Simple Pipeline",
13 | description: "Membrane simple pipeline demo",
14 | homepage_url: "https://membraneframework.org",
15 | source_url: @github_url,
16 | start_permanent: Mix.env() == :prod,
17 | deps: deps()
18 | ]
19 | end
20 |
21 | # Run "mix help compile.app" to learn about applications.
22 | def application do
23 | [
24 | extra_applications: [:logger]
25 | ]
26 | end
27 |
28 | # Run "mix help deps" to learn about dependencies.
29 | defp deps do
30 | [
31 | {:membrane_core, "~> 1.0"},
32 | {:membrane_file_plugin, "~> 0.17.0"},
33 | {:membrane_portaudio_plugin, "~> 0.19.2"},
34 | {:membrane_ffmpeg_swresample_plugin, "~> 0.20.2"},
35 | {:membrane_mp3_mad_plugin, "~> 0.18.3"},
36 | {:dialyxir, "~> 1.2", only: [:dev], runtime: false}
37 | ]
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/rtp/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.RTP.MixProject do
2 | use Mix.Project
3 |
4 | @version "0.1.0"
5 |
6 | def project do
7 | [
8 | app: :membrane_demo_rtp,
9 | version: @version,
10 | elixir: "~> 1.12",
11 | start_permanent: Mix.env() == :prod,
12 | deps: deps()
13 | ]
14 | end
15 |
16 | # Run "mix help compile.app" to learn about applications.
17 | def application do
18 | [
19 | extra_applications: [:logger]
20 | ]
21 | end
22 |
23 | # Run "mix help deps" to learn about dependencies.
24 | defp deps do
25 | [
26 | {:membrane_core, "~> 1.0"},
27 | {:membrane_rtp_plugin, "~> 0.31.0"},
28 | {:membrane_udp_plugin, "~> 0.13.0"},
29 | {:membrane_h264_ffmpeg_plugin, "~> 0.31.6"},
30 | {:membrane_h26x_plugin, "~> 0.10.1"},
31 | {:membrane_rtp_h264_plugin, "~> 0.20.1"},
32 | {:membrane_opus_plugin, "~> 0.20.1"},
33 | {:membrane_rtp_opus_plugin, "~> 0.10.0"},
34 | {:membrane_sdl_plugin, "~> 0.18.2"},
35 | {:membrane_portaudio_plugin, "~> 0.19.2"},
36 | {:membrane_file_plugin, "~> 0.17.0"},
37 | {:membrane_realtimer_plugin, "~> 0.9.0"},
38 | {:ex_libsrtp, "~> 0.7.2"}
39 | ]
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view/application.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveView.Application do
2 | # See https://hexdocs.pm/elixir/Application.html
3 | # for more information on OTP Applications
4 | @moduledoc false
5 |
6 | use Application
7 |
8 | @impl true
9 | def start(_type, _args) do
10 | children = [
11 | WebrtcLiveViewWeb.Telemetry,
12 | {DNSCluster, query: Application.get_env(:webrtc_live_view, :dns_cluster_query) || :ignore},
13 | {Phoenix.PubSub, name: WebrtcLiveView.PubSub},
14 | # Start a worker by calling: WebrtcLiveView.Worker.start_link(arg)
15 | # {WebrtcLiveView.Worker, arg},
16 | # Start to serve requests, typically the last entry
17 | WebrtcLiveViewWeb.Endpoint
18 | ]
19 |
20 | # See https://hexdocs.pm/elixir/Supervisor.html
21 | # for other strategies and supported options
22 | opts = [strategy: :one_for_one, name: WebrtcLiveView.Supervisor]
23 | Supervisor.start_link(children, opts)
24 | end
25 |
26 | # Tell Phoenix to update the endpoint configuration
27 | # whenever the application is updated.
28 | @impl true
29 | def config_change(changed, _new, removed) do
30 | WebrtcLiveViewWeb.Endpoint.config_change(changed, removed)
31 | :ok
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/webrtc_live_view/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | import other functionality to make it easier
8 | to build common data structures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | we enable the SQL sandbox, so changes done to the database
12 | are reverted at the end of every test. If you are using
13 | PostgreSQL, you can even run database tests asynchronously
14 | by setting `use WebrtcLiveViewWeb.ConnCase, async: true`, although
15 | this option is not recommended for other databases.
16 | """
17 |
18 | use ExUnit.CaseTemplate
19 |
20 | using do
21 | quote do
22 | # The default endpoint for testing
23 | @endpoint WebrtcLiveViewWeb.Endpoint
24 |
25 | use WebrtcLiveViewWeb, :verified_routes
26 |
27 | # Import conveniences for testing with connections
28 | import Plug.Conn
29 | import Phoenix.ConnTest
30 | import WebrtcLiveViewWeb.ConnCase
31 | end
32 | end
33 |
34 | setup _tags do
35 | {:ok, conn: Phoenix.ConnTest.build_conn()}
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/components/layouts/app.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | v{Application.spec(:phoenix, :vsn)}
9 |
10 |
11 |
25 |
26 |
27 |
28 |
29 | <.flash_group flash={@flash} />
30 | {@inner_content}
31 |
32 |
33 |
--------------------------------------------------------------------------------
/simple_element/lib/membrane_demo/simple_element/pipeline.ex:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.SimpleElement.Pipeline do
2 | @moduledoc """
3 | Pipeline that contains `Membrane.Demo.SimpleElement.Counter` element.
4 | """
5 |
6 | use Membrane.Pipeline
7 |
8 | alias Membrane.{File, FFmpeg, MP3.MAD, PortAudio, Time}
9 |
10 | @impl true
11 | def handle_init(_ctx, path_to_mp3) do
12 | spec =
13 | child(:file, %File.Source{location: path_to_mp3})
14 | |> child(:decoder, MAD.Decoder)
15 | |> child(:converter, %FFmpeg.SWResample.Converter{
16 | output_stream_format: %Membrane.RawAudio{
17 | sample_format: :s16le,
18 | sample_rate: 48000,
19 | channels: 2
20 | }
21 | })
22 | |> via_in(:input, options: [divisor: 10])
23 | |> child(:counter, %Membrane.Demo.SimpleElement.Counter{interval: Time.seconds(5)})
24 | |> child(:sink, PortAudio.Sink)
25 |
26 | {[spec: spec], %{}}
27 | end
28 |
29 | @impl true
30 | def handle_child_notification({:counter, counter_value}, _from, _ctx, state) do
31 | IO.inspect(counter_value, label: "Count of buffers processed:")
32 | {[], state}
33 | end
34 |
35 | @impl true
36 | def handle_child_notification(_notification, _from, _ctx, state) do
37 | {[], state}
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | commands:
3 | build_test:
4 | parameters:
5 | demo:
6 | type: string
7 | steps:
8 | - run: cd "<>" && mix deps.get
9 | - run: cd "<>" && mix deps.compile --all
10 | - run: cd "<>" && mix format --check-formatted
11 | - run: cd "<>" && mix test
12 | build_exs:
13 | parameters:
14 | demo:
15 | type: string
16 | steps:
17 | - run: cd "<>" && mix format --check-formatted
18 | - run: cd "<>" && elixir <>.exs
19 |
20 | jobs:
21 | build:
22 | docker:
23 | - image: membraneframeworklabs/docker_membrane:latest
24 | environment:
25 | MIX_ENV: test
26 | CI: true
27 | working_directory: '~/app'
28 | steps:
29 | - checkout
30 | - build_exs:
31 | demo: camera_to_hls
32 | - build_test:
33 | demo: rtmp_to_hls
34 | - build_test:
35 | demo: rtp
36 | - build_exs:
37 | demo: rtp_to_hls
38 | - build_test:
39 | demo: rtsp_to_hls
40 | - build_test:
41 | demo: simple_element
42 | - build_test:
43 | demo: simple_pipeline
44 | - build_exs:
45 | demo: mix_audio
46 |
--------------------------------------------------------------------------------
/rtmp_to_hls/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 |
7 | # General application configuration
8 | import Config
9 |
10 | # Configures the endpoint
11 | config :rtmp_to_hls, RtmpToHlsWeb.Endpoint,
12 | url: [host: "localhost"],
13 | render_errors: [view: RtmpToHlsWeb.ErrorView, accepts: ~w(html json), layout: false],
14 | pubsub_server: RtmpToHls.PubSub,
15 | live_view: [signing_salt: "wVzAotlb"]
16 |
17 | # Configure esbuild (the version is required)
18 | config :esbuild,
19 | version: "0.12.18",
20 | default: [
21 | args: ~w(js/app.js --bundle --target=es2016 --outdir=../priv/static/assets),
22 | cd: Path.expand("../assets", __DIR__),
23 | env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
24 | ]
25 |
26 | # Configures Elixir's Logger
27 | config :logger, :console,
28 | format: "$time $metadata[$level] $message\n",
29 | metadata: [:request_id]
30 |
31 | # Use Jason for JSON parsing in Phoenix
32 | config :phoenix, :json_library, Jason
33 |
34 | # Import environment specific config. This must remain at the bottom
35 | # of this file so it overrides the configuration defined above.
36 | import_config "#{config_env()}.exs"
37 |
--------------------------------------------------------------------------------
/webrtc_live_view/README.md:
--------------------------------------------------------------------------------
1 | # WebRTC LiveView
2 |
3 | This project demonstrates how to use Membrane WebRTC with dedicated Phoenix LiveViews.
4 |
5 | This example uses [WebRTC plugin](https://github.com/membraneframework/membrane_webrtc_plugin) that is responsible for receiving and sending mutlimedia via WebRTC.
6 |
7 | Membrane modules defined in this project are placed in `lib/webrtc_live_view/pipeline.ex` and `lib/webrtc_live_view/contours_drawer.ex`.
8 | The Phoenix LiveViews dedicated to Membrane WebRTC are used in the file lib/webrtc_live_view_web/live/home.ex.
9 |
10 | ## Running the demo
11 |
12 | To run the demo, you'll need to have [Elixir installed](https://elixir-lang.org/install.html). Then, do the following:
13 |
14 | * Run `mix setup` to install and setup dependencies
15 | * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
16 |
17 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
18 |
19 | ## Copyright and License
20 |
21 | Copyright 2025, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
22 |
23 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
24 |
25 | Licensed under the [Apache License, Version 2.0](LICENSE)
26 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 |
7 | # General application configuration
8 | import Config
9 |
10 | # Configures the endpoint
11 | config :rtmp_to_adaptive_hls, RtmpToAdaptiveHlsWeb.Endpoint,
12 | url: [host: "localhost"],
13 | render_errors: [view: RtmpToAdaptiveHlsWeb.ErrorView, accepts: ~w(html json), layout: false],
14 | pubsub_server: RtmpToAdaptiveHls.PubSub,
15 | live_view: [signing_salt: "wVzAotlb"]
16 |
17 | # Configure esbuild (the version is required)
18 | config :esbuild,
19 | version: "0.12.18",
20 | default: [
21 | args: ~w(js/app.js --bundle --target=es2016 --outdir=../priv/static/assets),
22 | cd: Path.expand("../assets", __DIR__),
23 | env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
24 | ]
25 |
26 | # Configures Elixir's Logger
27 | config :logger, :console,
28 | format: "$time $metadata[$level] $message\n",
29 | metadata: [:request_id]
30 |
31 | # Use Jason for JSON parsing in Phoenix
32 | config :phoenix, :json_library, Jason
33 |
34 | # Import environment specific config. This must remain at the bottom
35 | # of this file so it overrides the configuration defined above.
36 | import_config "#{config_env()}.exs"
37 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.Router do
2 | use WebrtcLiveViewWeb, :router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_live_flash
8 | plug :put_root_layout, html: {WebrtcLiveViewWeb.Layouts, :root}
9 | plug :protect_from_forgery
10 | plug :put_secure_browser_headers
11 | end
12 |
13 | pipeline :api do
14 | plug :accepts, ["json"]
15 | end
16 |
17 | scope "/", WebrtcLiveViewWeb do
18 | pipe_through :browser
19 |
20 | live "/", Live.EchoLive, :index
21 | end
22 |
23 | # Other scopes may use custom stacks.
24 | # scope "/api", WebrtcLiveViewWeb do
25 | # pipe_through :api
26 | # end
27 |
28 | # Enable LiveDashboard in development
29 | if Application.compile_env(:webrtc_live_view, :dev_routes) do
30 | # If you want to use the LiveDashboard in production, you should put
31 | # it behind authentication and allow only admins to access it.
32 | # If your application does not have an admins-only section yet,
33 | # you can use Plug.BasicAuth to set up some basic authentication
34 | # as long as you are also using SSL (which you should anyway).
35 | import Phoenix.LiveDashboard.Router
36 |
37 | scope "/dev" do
38 | pipe_through :browser
39 |
40 | live_dashboard "/dashboard", metrics: WebrtcLiveViewWeb.Telemetry
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/live/home.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.Live.EchoLive do
2 | use WebrtcLiveViewWeb, :live_view
3 |
4 | alias Membrane.WebRTC.Live.{Capture, Player}
5 |
6 | def mount(_params, _session, socket) do
7 | socket =
8 | if connected?(socket) do
9 | ingress_signaling = Membrane.WebRTC.Signaling.new()
10 | egress_signaling = Membrane.WebRTC.Signaling.new()
11 |
12 | {:ok, _task_pid} =
13 | Task.start_link(fn ->
14 | Membrane.Pipeline.start_link(WebRTCLiveView.Pipeline,
15 | ingress_signaling: ingress_signaling,
16 | egress_signaling: egress_signaling
17 | )
18 | end)
19 |
20 | socket
21 | |> Capture.attach(
22 | id: "mediaCapture",
23 | signaling: ingress_signaling,
24 | video?: true,
25 | audio?: false
26 | )
27 | |> Player.attach(
28 | id: "videoPlayer",
29 | signaling: egress_signaling
30 | )
31 | else
32 | socket
33 | end
34 |
35 | {:ok, socket}
36 | end
37 |
38 | def render(assigns) do
39 | ~H"""
40 | Captured stream preview
41 |
42 |
43 | Stream sent by the server
44 |
45 | """
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/rtsp_to_hls/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.RTSPToHLS.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :rtsp_to_hls_demo,
7 | version: "0.1.0",
8 | elixir: "~> 1.13",
9 | elixirc_paths: elixirc_paths(Mix.env()),
10 | compilers: Mix.compilers(),
11 | start_permanent: Mix.env() == :prod,
12 | deps: deps(),
13 | dialyzer: dialyzer()
14 | ]
15 | end
16 |
17 | # Run "mix help compile.app" to learn about applications.
18 | def application do
19 | [
20 | extra_applications: [:logger, :runtime_tools]
21 | ]
22 | end
23 |
24 | defp elixirc_paths(_env), do: ["lib"]
25 |
26 | # Run "mix help deps" to learn about dependencies.
27 | defp deps do
28 | [
29 | {:membrane_core, "~> 1.0"},
30 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
31 | {:dialyxir, ">= 0.0.0", only: :dev, runtime: false},
32 | {:credo, ">= 0.0.0", only: :dev, runtime: false},
33 | {:connection, "~> 1.1"},
34 | {:membrane_http_adaptive_stream_plugin, "~> 0.19.0"},
35 | {:membrane_realtimer_plugin, "~> 0.9.0"},
36 | {:membrane_rtsp_plugin, "~> 0.7.0"},
37 | {:membrane_aac_plugin, "~> 0.19.0"},
38 | {:membrane_rtp_aac_plugin, "~> 0.9.1"},
39 | {:membrane_simple_rtsp_server, "~> 0.1.6"},
40 | {:membrane_mp4_plugin, "~> 0.36.0"},
41 | {:membrane_udp_plugin, "~> 0.14.0"}
42 | ]
43 | end
44 |
45 | defp dialyzer() do
46 | [
47 | flags: [:error_handling]
48 | ]
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :rtmp_to_hls
3 |
4 | # The session will be stored in the cookie and signed,
5 | # this means its contents can be read but not tampered with.
6 | # Set :encryption_salt if you would also like to encrypt it.
7 | @session_options [
8 | store: :cookie,
9 | key: "_rtmp_to_hls_key",
10 | signing_salt: "qyNPZi3z"
11 | ]
12 |
13 | # socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
14 |
15 | # Serve at "/" the static files from "priv/static" directory.
16 | #
17 | # You should set gzip to true if you are running phx.digest
18 | # when deploying your static files in production.
19 | plug Plug.Static,
20 | at: "/",
21 | from: :rtmp_to_hls,
22 | gzip: false,
23 | only: ~w(assets fonts images favicon.ico robots.txt)
24 |
25 | # Code reloading can be explicitly enabled under the
26 | # :code_reloader configuration of your endpoint.
27 | if code_reloading? do
28 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
29 | plug Phoenix.LiveReloader
30 | plug Phoenix.CodeReloader
31 | end
32 |
33 | plug Plug.RequestId
34 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
35 |
36 | plug Plug.Parsers,
37 | parsers: [:urlencoded, :multipart, :json],
38 | pass: ["*/*"],
39 | json_decoder: Phoenix.json_library()
40 |
41 | plug Plug.MethodOverride
42 | plug Plug.Head
43 | plug Plug.Session, @session_options
44 | plug RtmpToHlsWeb.Router
45 | end
46 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/telemetry.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb.Telemetry do
2 | use Supervisor
3 | import Telemetry.Metrics
4 |
5 | def start_link(arg) do
6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
7 | end
8 |
9 | @impl true
10 | def init(_arg) do
11 | children = [
12 | # Telemetry poller will execute the given period measurements
13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
15 | # Add reporters as children of your supervision tree.
16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
17 | ]
18 |
19 | Supervisor.init(children, strategy: :one_for_one)
20 | end
21 |
22 | def metrics do
23 | [
24 | # Phoenix Metrics
25 | summary("phoenix.endpoint.stop.duration",
26 | unit: {:native, :millisecond}
27 | ),
28 | summary("phoenix.router_dispatch.stop.duration",
29 | tags: [:route],
30 | unit: {:native, :millisecond}
31 | ),
32 |
33 | # VM Metrics
34 | summary("vm.memory.total", unit: {:byte, :kilobyte}),
35 | summary("vm.total_run_queue_lengths.total"),
36 | summary("vm.total_run_queue_lengths.cpu"),
37 | summary("vm.total_run_queue_lengths.io")
38 | ]
39 | end
40 |
41 | defp periodic_measurements do
42 | [
43 | # A module, function and arguments to be invoked periodically.
44 | # This function must call :telemetry.execute/3 and a metric must be added above.
45 | # {RtmpToHlsWeb, :count_users, []}
46 | ]
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/webrtc_live_view/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 |
7 | # General application configuration
8 | import Config
9 |
10 | config :webrtc_live_view,
11 | generators: [timestamp_type: :utc_datetime]
12 |
13 | # Configures the endpoint
14 | config :webrtc_live_view, WebrtcLiveViewWeb.Endpoint,
15 | url: [host: "localhost"],
16 | adapter: Bandit.PhoenixAdapter,
17 | render_errors: [
18 | formats: [html: WebrtcLiveViewWeb.ErrorHTML, json: WebrtcLiveViewWeb.ErrorJSON],
19 | layout: false
20 | ],
21 | pubsub_server: WebrtcLiveView.PubSub,
22 | live_view: [signing_salt: "X97dFT34"]
23 |
24 | # Configure esbuild (the version is required)
25 | config :esbuild,
26 | version: "0.17.11",
27 | webrtc_live_view: [
28 | args:
29 | ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
30 | cd: Path.expand("../assets", __DIR__),
31 | env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
32 | ]
33 |
34 | # Configures Elixir's Logger
35 | config :logger, :console,
36 | format: "$time $metadata[$level] $message\n",
37 | metadata: [:request_id],
38 | level: :info
39 |
40 | # Use Jason for JSON parsing in Phoenix
41 | config :phoenix, :json_library, Jason
42 |
43 | # Import environment specific config. This must remain at the bottom
44 | # of this file so it overrides the configuration defined above.
45 | import_config "#{config_env()}.exs"
46 |
--------------------------------------------------------------------------------
/.github/workflows/webrtc_videoroom_master_build.yml:
--------------------------------------------------------------------------------
1 | name: WebRTC Videoroom - Master Build
2 | on:
3 | push:
4 | branches:
5 | - "master"
6 | paths:
7 | - "webrtc/videoroom/**"
8 | - "!webrtc/videoroom/README.md"
9 |
10 | # Allows you to run this workflow manually from the Actions tab
11 | workflow_dispatch:
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v2
18 |
19 | - name: Declare variables
20 | id: vars
21 | shell: bash
22 | run: |
23 | echo "::set-output name=version::$(git describe --tags --always)"
24 |
25 | - name: Cache Docker layers
26 | uses: actions/cache@v2
27 | with:
28 | path: /tmp/.buildx-cache
29 | key: ${{ runner.os }}-buildx-${{ github.sha }}
30 | restore-keys: |
31 | ${{ runner.os }}-buildx
32 |
33 | - name: Login to Docker Hub
34 | uses: docker/login-action@v1
35 | with:
36 | username: ${{ secrets.DOCKER_HUB_USERNAME }}
37 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
38 |
39 | - name: Set up Docker Buildx
40 | id: buildx
41 | uses: docker/setup-buildx-action@v1
42 |
43 | - name: Build and push latest version
44 | id: docker_build_latest
45 | uses: docker/build-push-action@v2
46 | with:
47 | context: webrtc/videoroom
48 | file: webrtc/videoroom/Dockerfile
49 | push: true
50 | build-args: VERSION= ${{ steps.vars.outputs.version }}
51 | tags: membraneframework/demo_webrtc_videoroom:latest
52 |
--------------------------------------------------------------------------------
/camera_to_hls_nerves/lib/camera_to_hls_nerves/pipeline.ex:
--------------------------------------------------------------------------------
1 | defmodule CameraToHlsNerves.Pipeline do
2 | use Membrane.Pipeline
3 |
4 | def start_link(opts) do
5 | Membrane.Pipeline.start_link(__MODULE__, opts)
6 | end
7 |
8 | @impl true
9 | def handle_init(_ctx, _opts) do
10 | spec = [
11 | child(:source, Membrane.Rpicam.Source)
12 | |> child(:parser, Membrane.H264.Parser)
13 | |> via_in(:input,
14 | options: [
15 | encoding: :H264,
16 | track_name: "my_track",
17 | segment_duration: Membrane.Time.seconds(5)
18 | ]
19 | )
20 | |> child(:hls_sink, %Membrane.HTTPAdaptiveStream.SinkBin{
21 | manifest_module: Membrane.HTTPAdaptiveStream.HLS,
22 | storage: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{directory: "/data/output"},
23 | target_window_duration: Membrane.Time.seconds(10)
24 | })
25 | ]
26 |
27 | {[spec: spec], %{}}
28 | end
29 |
30 | @impl true
31 | def handle_child_notification({:track_playable, _track_info}, :hls_sink, _context, state) do
32 | Supervisor.start_child(CameraToHlsNervesSupervisor, %{
33 | id: :hls_server,
34 | start: {:inets, :start, [:httpd, httpd_options(), :stand_alone]}
35 | })
36 |
37 | {[], state}
38 | end
39 |
40 | def handle_child_notification(_notification, _child, _context, state) do
41 | {[], state}
42 | end
43 |
44 | defp httpd_options() do
45 | [
46 | bind_address: ~c"0.0.0.0",
47 | port: 8000,
48 | document_root: ~c".",
49 | server_name: ~c"camera_to_hls_nerves",
50 | server_root: ~c"/"
51 | ]
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :rtmp_to_adaptive_hls
3 |
4 | # The session will be stored in the cookie and signed,
5 | # this means its contents can be read but not tampered with.
6 | # Set :encryption_salt if you would also like to encrypt it.
7 | @session_options [
8 | store: :cookie,
9 | key: "_rtmp_to_adaptive_hls_key",
10 | signing_salt: "qyNPZi3z"
11 | ]
12 |
13 | # socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
14 |
15 | # Serve at "/" the static files from "priv/static" directory.
16 | #
17 | # You should set gzip to true if you are running phx.digest
18 | # when deploying your static files in production.
19 | plug Plug.Static,
20 | at: "/",
21 | from: :rtmp_to_adaptive_hls,
22 | gzip: false,
23 | only: ~w(assets fonts images favicon.ico robots.txt)
24 |
25 | # Code reloading can be explicitly enabled under the
26 | # :code_reloader configuration of your endpoint.
27 | if code_reloading? do
28 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
29 | plug Phoenix.LiveReloader
30 | plug Phoenix.CodeReloader
31 | end
32 |
33 | plug Plug.RequestId
34 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
35 |
36 | plug Plug.Parsers,
37 | parsers: [:urlencoded, :multipart, :json],
38 | pass: ["*/*"],
39 | json_decoder: Phoenix.json_library()
40 |
41 | plug Plug.MethodOverride
42 | plug Plug.Head
43 | plug Plug.Session, @session_options
44 | plug RtmpToAdaptiveHlsWeb.Router
45 | end
46 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view/contours_drawer.ex:
--------------------------------------------------------------------------------
1 | defmodule WebRTCLiveView.CountoursDrawer do
2 | use Membrane.Filter
3 |
4 | alias Evision.{ColorConversionCodes, Constant}
5 |
6 | def_input_pad :input, accepted_format: %Membrane.RawVideo{pixel_format: :RGB}
7 | def_output_pad :output, accepted_format: %Membrane.RawVideo{pixel_format: :RGB}
8 |
9 | @impl true
10 | def handle_buffer(:input, buffer, ctx, state) do
11 | %{height: height, width: width} = ctx.pads.input.stream_format
12 |
13 | {:ok, image} =
14 | Vix.Vips.Image.new_from_binary(buffer.payload, width, height, 3, :VIPS_FORMAT_UCHAR)
15 |
16 | {:ok, image} = Image.to_evision(image)
17 |
18 | grayscale = Evision.cvtColor(image, ColorConversionCodes.cv_COLOR_BGR2GRAY())
19 | flags = Bitwise.bor(Constant.cv_THRESH_BINARY(), Constant.cv_THRESH_OTSU())
20 | {_ok, bw} = Evision.threshold(grayscale, 50, 255, flags)
21 |
22 | {contours, _} =
23 | Evision.findContours(
24 | bw,
25 | Constant.cv_RETR_LIST(),
26 | Constant.cv_CHAIN_APPROX_NONE()
27 | )
28 |
29 | contours =
30 | Enum.filter(contours, fn c ->
31 | trunc(Evision.contourArea(c)) in 100..200_000
32 | end)
33 |
34 | {:ok, image} =
35 | image
36 | |> Evision.drawContours(contours, _index = -1, _edge_color = {0, 0, 255}, thickness: 2)
37 | |> Image.from_evision()
38 |
39 | {:ok, payload} =
40 | image |> Image.flatten!() |> Image.to_colorspace!(:srgb) |> Vix.Vips.Image.write_to_binary()
41 |
42 | buffer = %Membrane.Buffer{buffer | payload: payload}
43 | {[buffer: {:output, buffer}], state}
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/telemetry.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb.Telemetry do
2 | use Supervisor
3 | import Telemetry.Metrics
4 |
5 | def start_link(arg) do
6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
7 | end
8 |
9 | @impl true
10 | def init(_arg) do
11 | children = [
12 | # Telemetry poller will execute the given period measurements
13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
15 | # Add reporters as children of your supervision tree.
16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
17 | ]
18 |
19 | Supervisor.init(children, strategy: :one_for_one)
20 | end
21 |
22 | def metrics do
23 | [
24 | # Phoenix Metrics
25 | summary("phoenix.endpoint.stop.duration",
26 | unit: {:native, :millisecond}
27 | ),
28 | summary("phoenix.router_dispatch.stop.duration",
29 | tags: [:route],
30 | unit: {:native, :millisecond}
31 | ),
32 |
33 | # VM Metrics
34 | summary("vm.memory.total", unit: {:byte, :kilobyte}),
35 | summary("vm.total_run_queue_lengths.total"),
36 | summary("vm.total_run_queue_lengths.cpu"),
37 | summary("vm.total_run_queue_lengths.io")
38 | ]
39 | end
40 |
41 | defp periodic_measurements do
42 | [
43 | # A module, function and arguments to be invoked periodically.
44 | # This function must call :telemetry.execute/3 and a metric must be added above.
45 | # {RtmpToAdaptiveHlsWeb, :count_users, []}
46 | ]
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/rtp/lib/membrane_demo_rtp/receive_pipeline.ex:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.RTP.ReceivePipeline do
2 | use Membrane.Pipeline
3 |
4 | require Logger
5 |
6 | alias Membrane.{H264, Opus, RTP, UDP}
7 |
8 | @local_ip {127, 0, 0, 1}
9 |
10 | @impl true
11 | def handle_init(_ctx, opts) do
12 | %{audio_port: audio_port, video_port: video_port, secure?: secure?, srtp_key: srtp_key} = opts
13 |
14 | srtp =
15 | if secure? do
16 | [%ExLibSRTP.Policy{ssrc: :any_inbound, key: srtp_key}]
17 | else
18 | false
19 | end
20 |
21 | spec =
22 | {[
23 | child(:video_src, %UDP.Source{
24 | local_port_no: video_port,
25 | local_address: @local_ip
26 | })
27 | |> child(:video_rtp_demuxer, %RTP.Demuxer{srtp: srtp})
28 | |> via_out(:output, options: [stream_id: {:encoding_name, :H264}])
29 | |> child(:video_depayloader, RTP.H264.Depayloader)
30 | |> child(:video_parser, %H264.Parser{
31 | generate_best_effort_timestamps: %{framerate: {30, 1}}
32 | })
33 | |> child(:video_decoder, H264.FFmpeg.Decoder)
34 | |> child(:video_player, Membrane.SDL.Player),
35 | child(:audio_src, %UDP.Source{
36 | local_port_no: audio_port,
37 | local_address: @local_ip
38 | })
39 | |> child(:audio_rtp_demuxer, %RTP.Demuxer{srtp: srtp})
40 | |> via_out(:output, options: [stream_id: {:encoding_name, :opus}])
41 | |> child(:audio_depayloader, RTP.Opus.Depayloader)
42 | |> child(:audio_decoder, Opus.Decoder)
43 | |> child(:audio_player, Membrane.PortAudio.Sink)
44 | ], stream_sync: :sinks}
45 |
46 | {[spec: spec], %{}}
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web/views/error_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb.ErrorHelpers do
2 | @moduledoc """
3 | Conveniences for translating and building error messages.
4 | """
5 |
6 | use Phoenix.HTML
7 |
8 | @doc """
9 | Generates tag for inlined form input errors.
10 | """
11 | def error_tag(form, field) do
12 | Enum.map(Keyword.get_values(form.errors, field), fn error ->
13 | content_tag(:span, translate_error(error),
14 | class: "invalid-feedback",
15 | phx_feedback_for: input_name(form, field)
16 | )
17 | end)
18 | end
19 |
20 | @doc """
21 | Translates an error message using gettext.
22 | """
23 | def translate_error({msg, opts}) do
24 | # When using gettext, we typically pass the strings we want
25 | # to translate as a static argument:
26 | #
27 | # # Translate "is invalid" in the "errors" domain
28 | # dgettext("errors", "is invalid")
29 | #
30 | # # Translate the number of files with plural rules
31 | # dngettext("errors", "1 file", "%{count} files", count)
32 | #
33 | # Because the error messages we show in our forms and APIs
34 | # are defined inside Ecto, we need to translate them dynamically.
35 | # This requires us to call the Gettext module passing our gettext
36 | # backend as first argument.
37 | #
38 | # Note we use the "errors" domain, which means translations
39 | # should be written to the errors.po file. The :count option is
40 | # set by Ecto and indicates we should also apply plural rules.
41 | if count = opts[:count] do
42 | Gettext.dngettext(RtmpToHlsWeb.Gettext, "errors", msg, msg, count, opts)
43 | else
44 | Gettext.dgettext(RtmpToHlsWeb.Gettext, "errors", msg, opts)
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/rtp_to_hls/README.md:
--------------------------------------------------------------------------------
1 | # RTP to HLS
2 |
3 | This project demonstrates handling RTP streams and converting them to HLS streams.
4 |
5 | The whole idea has been described in [this blog post](https://blog.swmansion.com/live-video-streaming-in-elixir-made-simple-with-membrane-fc5b2083982d).
6 |
7 | ## Running the demo
8 |
9 | To run the demo, you'll need to have [Elixir installed](https://elixir-lang.org/install.html). Then, run
10 |
11 | ```shell
12 | elixir rtp_to_hls.exs
13 | ```
14 |
15 | After a while, the server will start listening for UDP connections on port 5000. Then, start the RTP stream with
16 |
17 | ```shell
18 | elixir send.exs
19 | ```
20 |
21 | When the server prints that playback is available, visit `http://localhost:8000/stream.html` and you should see the stream there. The stream can be also played with players other than the browser, like `vlc` or `ffplay`, for example
22 |
23 | ```bash
24 | ffplay http://localhost:8000/output/index.m3u8
25 | ```
26 |
27 | The RTP stream can be sent with other tools as well, for example with [GStreamer](https://gstreamer.freedesktop.org/):
28 |
29 | ```shell
30 | gst-launch-1.0 -v audiotestsrc ! audio/x-raw,rate=44100 ! faac ! rtpmp4gpay pt=127 ! udpsink host=127.0.0.1 port=5000 \
31 | videotestsrc ! video/x-raw,format=I420 ! x264enc key-int-max=10 tune=zerolatency ! rtph264pay pt=96 ! udpsink host=127.0.0.1 port=5000
32 | ```
33 |
34 |
35 | ## Copyright and License
36 |
37 | Copyright 2020, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
38 |
39 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
40 |
41 | Licensed under the [Apache License, Version 2.0](LICENSE)
42 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls/application.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHls.Application do
2 | # See https://hexdocs.pm/elixir/Application.html
3 | # for more information on OTP Applications
4 | @moduledoc false
5 |
6 | use Application
7 |
8 | alias Membrane.RTMP.Source.TcpServer
9 |
10 | @port 9006
11 | @local_ip {127, 0, 0, 1}
12 |
13 | @impl true
14 | def start(_type, _args) do
15 | File.mkdir_p("output")
16 |
17 | tcp_server_options = %TcpServer{
18 | port: @port,
19 | listen_options: [
20 | :binary,
21 | packet: :raw,
22 | active: false,
23 | ip: @local_ip
24 | ],
25 | socket_handler: fn socket ->
26 | {:ok, _sup, pid} =
27 | Membrane.Pipeline.start_link(Membrane.Demo.RtmpToHls, socket: socket)
28 |
29 | {:ok, pid}
30 | end
31 | }
32 |
33 | children = [
34 | # Start the Tcp Server
35 | # Membrane.Demo.RtmpToHls,
36 | %{
37 | id: TcpServer,
38 | start: {TcpServer, :start_link, [tcp_server_options]}
39 | },
40 | # Start the Telemetry supervisor
41 | RtmpToHlsWeb.Telemetry,
42 | # Start the PubSub system
43 | {Phoenix.PubSub, name: RtmpToHls.PubSub},
44 | # Start the Endpoint (http/https)
45 | RtmpToHlsWeb.Endpoint
46 | ]
47 |
48 | # See https://hexdocs.pm/elixir/Supervisor.html
49 | # for other strategies and supported options
50 | opts = [strategy: :one_for_one, name: RtmpToHls.Supervisor]
51 | Supervisor.start_link(children, opts)
52 | end
53 |
54 | # Tell Phoenix to update the endpoint configuration
55 | # whenever the application is updated.
56 | @impl true
57 | def config_change(changed, _new, removed) do
58 | RtmpToHlsWeb.Endpoint.config_change(changed, removed)
59 | :ok
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls/pipeline.ex:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.RtmpToHls do
2 | use Membrane.Pipeline
3 |
4 | alias Membrane.RTMP.SourceBin
5 |
6 | @impl true
7 | def handle_init(_context, socket: socket) do
8 | structure = [
9 | child(:src, %SourceBin{socket: socket})
10 | |> via_out(:audio)
11 | |> via_in(Pad.ref(:input, :audio),
12 | options: [encoding: :AAC, segment_duration: Membrane.Time.seconds(4)]
13 | )
14 | |> child(:sink, %Membrane.HTTPAdaptiveStream.SinkBin{
15 | manifest_module: Membrane.HTTPAdaptiveStream.HLS,
16 | target_window_duration: :infinity,
17 | persist?: false,
18 | storage: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{directory: "output"}
19 | }),
20 | get_child(:src)
21 | |> via_out(:video)
22 | |> via_in(Pad.ref(:input, :video),
23 | options: [encoding: :H264, segment_duration: Membrane.Time.seconds(4)]
24 | )
25 | |> get_child(:sink)
26 | ]
27 |
28 | {[spec: structure], %{socket: socket}}
29 | end
30 |
31 | @impl true
32 | def handle_child_notification(
33 | {:socket_control_needed, _socket, _source} = notification,
34 | :src,
35 | _ctx,
36 | state
37 | ) do
38 | send(self(), notification)
39 | {[], state}
40 | end
41 |
42 | @impl true
43 | def handle_child_notification(_notification, _child, _ctx, state) do
44 | {[], state}
45 | end
46 |
47 | @impl true
48 | def handle_info({:socket_control_needed, socket, source} = notification, _ctx, state) do
49 | case SourceBin.pass_control(socket, source) do
50 | :ok ->
51 | :ok
52 |
53 | {:error, :not_owner} ->
54 | Process.send_after(self(), notification, 200)
55 | end
56 |
57 | {[], state}
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/rtp/lib/membrane_demo_rtp/send_pipeline.ex:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.RTP.SendPipeline do
2 | use Membrane.Pipeline
3 |
4 | alias Membrane.{File, H264, Opus, RTP, UDP}
5 |
6 | @impl true
7 | def handle_init(_ctx, opts) do
8 | %{
9 | audio_port: audio_port,
10 | video_port: video_port,
11 | secure?: secure?,
12 | srtp_key: srtp_key
13 | } = opts
14 |
15 | srtp =
16 | if secure? do
17 | [%ExLibSRTP.Policy{ssrc: :any_inbound, key: srtp_key}]
18 | else
19 | false
20 | end
21 |
22 | spec = [
23 | child(:video_src, %File.Source{
24 | location: "samples/video.h264"
25 | })
26 | |> child(:video_parser, %H264.Parser{
27 | generate_best_effort_timestamps: %{framerate: {30, 1}},
28 | output_alignment: :nalu
29 | })
30 | |> child(:video_payloader, RTP.H264.Payloader)
31 | |> child(:video_rtp_muxer, %RTP.Muxer{srtp: srtp})
32 | |> child(:video_realtimer, Membrane.Realtimer)
33 | |> child(:video_sink, %UDP.Sink{
34 | destination_port_no: video_port,
35 | destination_address: {127, 0, 0, 1}
36 | }),
37 | child(:audio_src, %File.Source{
38 | location: "samples/audio.opus"
39 | })
40 | |> child(:audio_parser, %Opus.Parser{
41 | input_delimitted?: true,
42 | delimitation: :undelimit,
43 | generate_best_effort_timestamps?: true
44 | })
45 | |> child(:audio_payloader, RTP.Opus.Payloader)
46 | |> child(:audio_rtp_muxer, %RTP.Muxer{srtp: srtp})
47 | |> child(:audio_realtimer, Membrane.Realtimer)
48 | |> child(:audio_sink, %UDP.Sink{
49 | destination_port_no: audio_port,
50 | destination_address: {127, 0, 0, 1}
51 | })
52 | ]
53 |
54 | {[spec: spec], %{}}
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web/views/error_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb.ErrorHelpers do
2 | @moduledoc """
3 | Conveniences for translating and building error messages.
4 | """
5 |
6 | use Phoenix.HTML
7 |
8 | @doc """
9 | Generates tag for inlined form input errors.
10 | """
11 | def error_tag(form, field) do
12 | Enum.map(Keyword.get_values(form.errors, field), fn error ->
13 | content_tag(:span, translate_error(error),
14 | class: "invalid-feedback",
15 | phx_feedback_for: input_name(form, field)
16 | )
17 | end)
18 | end
19 |
20 | @doc """
21 | Translates an error message using gettext.
22 | """
23 | def translate_error({msg, opts}) do
24 | # When using gettext, we typically pass the strings we want
25 | # to translate as a static argument:
26 | #
27 | # # Translate "is invalid" in the "errors" domain
28 | # dgettext("errors", "is invalid")
29 | #
30 | # # Translate the number of files with plural rules
31 | # dngettext("errors", "1 file", "%{count} files", count)
32 | #
33 | # Because the error messages we show in our forms and APIs
34 | # are defined inside Ecto, we need to translate them dynamically.
35 | # This requires us to call the Gettext module passing our gettext
36 | # backend as first argument.
37 | #
38 | # Note we use the "errors" domain, which means translations
39 | # should be written to the errors.po file. The :count option is
40 | # set by Ecto and indicates we should also apply plural rules.
41 | if count = opts[:count] do
42 | Gettext.dngettext(RtmpToAdaptiveHlsWeb.Gettext, "errors", msg, msg, count, opts)
43 | else
44 | Gettext.dgettext(RtmpToAdaptiveHlsWeb.Gettext, "errors", msg, opts)
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/simple_pipeline/lib/membrane_demo/simple_pipeline.ex:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.SimplePipeline do
2 | @moduledoc """
3 | Sample Membrane pipeline that will play an `.mp3` file.
4 | """
5 |
6 | use Membrane.Pipeline
7 |
8 | @doc """
9 | In order to play `.mp3` file we need to read it first.
10 |
11 | In membrane every entry point to data flow is called `Source`. Since we want
12 | to play a `file`, we will use `File.Source`.
13 |
14 | Next problem that arises is the fact that we are reading MPEG Layer 3 frames
15 | not raw audio. To deal with that we need to use `Filter` called decoder. It
16 | takes `.mp3` frames and yields RAW audio data.
17 |
18 | There is one tiny problem here though. Decoder returns `%Raw{format: :s24le}`
19 | data, but PortAudio (module that actually talks with the audio driver of your
20 | computer) wants `%Raw{format: :s16le, sample_rate: 48000, channels: 2}`.
21 |
22 | That's where `SWResample.Converter` comes into play. It will consume data that
23 | doesn't suite our needs and will yield data in format we want.
24 | """
25 | @impl true
26 | def handle_init(_ctx, path_to_mp3) do
27 | # Setup the flow of the data
28 | # Stream from file
29 | spec =
30 | child(:file, %Membrane.File.Source{location: path_to_mp3})
31 | # Decode frames
32 | |> child(:decoder, Membrane.MP3.MAD.Decoder)
33 | # Convert Raw :s24le to Raw :s16le
34 | |> child(:converter, %Membrane.FFmpeg.SWResample.Converter{
35 | output_stream_format: %Membrane.RawAudio{
36 | sample_format: :s16le,
37 | sample_rate: 48000,
38 | channels: 2
39 | }
40 | })
41 | # Stream data into PortAudio to play it on speakers.
42 | |> child(:portaudio, Membrane.PortAudio.Sink)
43 |
44 | {[spec: spec], %{}}
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :webrtc_live_view
3 |
4 | # The session will be stored in the cookie and signed,
5 | # this means its contents can be read but not tampered with.
6 | # Set :encryption_salt if you would also like to encrypt it.
7 | @session_options [
8 | store: :cookie,
9 | key: "_webrtc_live_view_key",
10 | signing_salt: "RxBv85K8",
11 | same_site: "Lax"
12 | ]
13 |
14 | socket "/live", Phoenix.LiveView.Socket,
15 | websocket: [connect_info: [session: @session_options]],
16 | longpoll: [connect_info: [session: @session_options]]
17 |
18 | # Serve at "/" the static files from "priv/static" directory.
19 | #
20 | # You should set gzip to true if you are running phx.digest
21 | # when deploying your static files in production.
22 | plug Plug.Static,
23 | at: "/",
24 | from: :webrtc_live_view,
25 | gzip: false,
26 | only: WebrtcLiveViewWeb.static_paths()
27 |
28 | # Code reloading can be explicitly enabled under the
29 | # :code_reloader configuration of your endpoint.
30 | if code_reloading? do
31 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
32 | plug Phoenix.LiveReloader
33 | plug Phoenix.CodeReloader
34 | end
35 |
36 | plug Phoenix.LiveDashboard.RequestLogger,
37 | param_key: "request_logger",
38 | cookie_key: "request_logger"
39 |
40 | plug Plug.RequestId
41 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
42 |
43 | plug Plug.Parsers,
44 | parsers: [:urlencoded, :multipart, :json],
45 | pass: ["*/*"],
46 | json_decoder: Phoenix.json_library()
47 |
48 | plug Plug.MethodOverride
49 | plug Plug.Head
50 | plug Plug.Session, @session_options
51 | plug WebrtcLiveViewWeb.Router
52 | end
53 |
--------------------------------------------------------------------------------
/rtmp_to_hls/assets/js/app.js:
--------------------------------------------------------------------------------
1 | // We import the CSS which is extracted to its own file by esbuild.
2 | // Remove this line if you add a your own CSS build pipeline (e.g postcss).
3 | import "../css/app.css"
4 |
5 | // If you want to use Phoenix channels, run `mix help phx.gen.channel`
6 | // to get started and then uncomment the line below.
7 | // import "./user_socket.js"
8 |
9 | // You can include dependencies in two ways.
10 | //
11 | // The simplest option is to put them in assets/vendor and
12 | // import them using relative paths:
13 | //
14 | // import "./vendor/some-package.js"
15 | //
16 | // Alternatively, you can `npm install some-package` and import
17 | // them using a path starting with the package name:
18 | //
19 | // import "some-package"
20 | //
21 |
22 | // Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
23 | import "phoenix_html"
24 | // Establish Phoenix Socket and LiveView configuration.
25 | // import {Socket} from "phoenix"
26 | // import {LiveSocket} from "phoenix_live_view"
27 | // import topbar from "../vendor/topbar"
28 |
29 | // let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
30 | // let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
31 |
32 | // Show progress bar on live navigation and form submits
33 | // topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
34 | // window.addEventListener("phx:page-loading-start", info => topbar.show())
35 | // window.addEventListener("phx:page-loading-stop", info => topbar.hide())
36 |
37 | // connect if there are any LiveViews on the page
38 | // liveSocket.connect()
39 |
40 | // expose liveSocket on window for web console debug logs and latency simulation:
41 | // >> liveSocket.enableDebug()
42 | // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
43 | // >> liveSocket.disableLatencySim()
44 | // window.liveSocket = liveSocket
45 |
--------------------------------------------------------------------------------
/rtmp_to_hls/config/runtime.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # config/runtime.exs is executed for all environments, including
4 | # during releases. It is executed after compilation and before the
5 | # system starts, so it is typically used to load production configuration
6 | # and secrets from environment variables or elsewhere. Do not define
7 | # any compile-time configuration in here, as it won't be applied.
8 | # The block below contains prod specific runtime configuration.
9 | if config_env() == :prod do
10 | # The secret key base is used to sign/encrypt cookies and other secrets.
11 | # A default value is used in config/dev.exs and config/test.exs but you
12 | # want to use a different value for prod and you most likely don't want
13 | # to check this value into version control, so we use an environment
14 | # variable instead.
15 | secret_key_base =
16 | System.get_env("SECRET_KEY_BASE") ||
17 | raise """
18 | environment variable SECRET_KEY_BASE is missing.
19 | You can generate one by calling: mix phx.gen.secret
20 | """
21 |
22 | config :rtmp_to_hls, RtmpToHlsWeb.Endpoint,
23 | http: [
24 | # Enable IPv6 and bind on all interfaces.
25 | # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
26 | # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
27 | # for details about using IPv6 vs IPv4 and loopback vs public addresses.
28 | ip: {0, 0, 0, 0, 0, 0, 0, 0},
29 | port: String.to_integer(System.get_env("PORT") || "4000")
30 | ],
31 | secret_key_base: secret_key_base
32 |
33 | # ## Using releases
34 | #
35 | # If you are doing OTP releases, you need to instruct Phoenix
36 | # to start each relevant endpoint:
37 | #
38 | # config :rtmp_to_hls, RtmpToHlsWeb.Endpoint, server: true
39 | #
40 | # Then you can assemble a release by calling `mix release`.
41 | # See `mix help release` for more information.
42 | end
43 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/assets/js/app.js:
--------------------------------------------------------------------------------
1 | // We import the CSS which is extracted to its own file by esbuild.
2 | // Remove this line if you add a your own CSS build pipeline (e.g postcss).
3 | import "../css/app.css"
4 |
5 | // If you want to use Phoenix channels, run `mix help phx.gen.channel`
6 | // to get started and then uncomment the line below.
7 | // import "./user_socket.js"
8 |
9 | // You can include dependencies in two ways.
10 | //
11 | // The simplest option is to put them in assets/vendor and
12 | // import them using relative paths:
13 | //
14 | // import "./vendor/some-package.js"
15 | //
16 | // Alternatively, you can `npm install some-package` and import
17 | // them using a path starting with the package name:
18 | //
19 | // import "some-package"
20 | //
21 |
22 | // Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
23 | import "phoenix_html"
24 | // Establish Phoenix Socket and LiveView configuration.
25 | // import {Socket} from "phoenix"
26 | // import {LiveSocket} from "phoenix_live_view"
27 | // import topbar from "../vendor/topbar"
28 |
29 | // let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
30 | // let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
31 |
32 | // Show progress bar on live navigation and form submits
33 | // topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
34 | // window.addEventListener("phx:page-loading-start", info => topbar.show())
35 | // window.addEventListener("phx:page-loading-stop", info => topbar.hide())
36 |
37 | // connect if there are any LiveViews on the page
38 | // liveSocket.connect()
39 |
40 | // expose liveSocket on window for web console debug logs and latency simulation:
41 | // >> liveSocket.enableDebug()
42 | // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
43 | // >> liveSocket.disableLatencySim()
44 | // window.liveSocket = liveSocket
45 |
--------------------------------------------------------------------------------
/.github/workflows/webrtc_videoroom_tag_build.yml:
--------------------------------------------------------------------------------
1 | name: WebRTC Videoroom - Tag Build
2 | on:
3 | push:
4 | tags:
5 | - "*.*.*"
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 |
13 | - name: Declare variables
14 | id: vars
15 | shell: bash
16 | run: |
17 | echo "::set-output name=version::$(git describe --tags --always)"
18 |
19 | - name: Get tag
20 | id: tag
21 | uses: dawidd6/action-get-tag@v1
22 |
23 | - name: Cache Docker layers
24 | uses: actions/cache@v2
25 | with:
26 | path: /tmp/.buildx-cache
27 | key: ${{ runner.os }}-buildx-${{ github.sha }}
28 | restore-keys: |
29 | ${{ runner.os }}-buildx
30 |
31 | - name: Login to Docker Hub
32 | uses: docker/login-action@v1
33 | with:
34 | username: ${{ secrets.DOCKER_HUB_USERNAME }}
35 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
36 |
37 | - name: Set up Docker Buildx
38 | id: buildx
39 | uses: docker/setup-buildx-action@v1
40 |
41 | - name: Build and push tagged version
42 | id: docker_build_tag
43 | uses: docker/build-push-action@v2
44 | with:
45 | context: webrtc/videoroom
46 | file: webrtc/videoroom/Dockerfile
47 | push: true
48 | build-args: VERSION= ${{ steps.vars.outputs.version }}
49 | tags: membraneframework/demo_webrtc_videoroom:${{ steps.tag.outputs.tag }}
50 |
51 | - name: Build and push latest version
52 | id: docker_build_latest
53 | uses: docker/build-push-action@v2
54 | with:
55 | context: webrtc/videoroom
56 | file: webrtc/videoroom/Dockerfile
57 | push: true
58 | build-args: VERSION= ${{ steps.vars.outputs.version }}
59 | tags: membraneframework/demo_webrtc_videoroom:latest
60 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/config/runtime.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # config/runtime.exs is executed for all environments, including
4 | # during releases. It is executed after compilation and before the
5 | # system starts, so it is typically used to load production configuration
6 | # and secrets from environment variables or elsewhere. Do not define
7 | # any compile-time configuration in here, as it won't be applied.
8 | # The block below contains prod specific runtime configuration.
9 | if config_env() == :prod do
10 | # The secret key base is used to sign/encrypt cookies and other secrets.
11 | # A default value is used in config/dev.exs and config/test.exs but you
12 | # want to use a different value for prod and you most likely don't want
13 | # to check this value into version control, so we use an environment
14 | # variable instead.
15 | secret_key_base =
16 | System.get_env("SECRET_KEY_BASE") ||
17 | raise """
18 | environment variable SECRET_KEY_BASE is missing.
19 | You can generate one by calling: mix phx.gen.secret
20 | """
21 |
22 | config :rtmp_to_adaptive_hls, RtmpToAdaptiveHlsWeb.Endpoint,
23 | http: [
24 | # Enable IPv6 and bind on all interfaces.
25 | # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
26 | # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
27 | # for details about using IPv6 vs IPv4 and loopback vs public addresses.
28 | ip: {0, 0, 0, 0, 0, 0, 0, 0},
29 | port: String.to_integer(System.get_env("PORT") || "4000")
30 | ],
31 | secret_key_base: secret_key_base
32 |
33 | # ## Using releases
34 | #
35 | # If you are doing OTP releases, you need to instruct Phoenix
36 | # to start each relevant endpoint:
37 | #
38 | # config :rtmp_to_adaptive_hls, RtmpToAdaptiveHlsWeb.Endpoint, server: true
39 | #
40 | # Then you can assemble a release by calling `mix release`.
41 | # See `mix help release` for more information.
42 | end
43 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls/application.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHls.Application do
2 | # See https://hexdocs.pm/elixir/Application.html
3 | # for more information on OTP Applications
4 | @moduledoc false
5 |
6 | use Application
7 |
8 | @port 9006
9 | @local_ip {127, 0, 0, 1}
10 |
11 | @impl true
12 | def start(_type, _args) do
13 | File.mkdir_p("output")
14 |
15 | rtmp_server_options = %{
16 | port: @port,
17 | listen_options: [
18 | :binary,
19 | packet: :raw,
20 | active: false,
21 | ip: @local_ip
22 | ],
23 | handle_new_client: fn client_ref, app, stream_key ->
24 | {:ok, _sup, pid} =
25 | Membrane.Pipeline.start_link(Membrane.Demo.RtmpToAdaptiveHls, %{
26 | client_ref: client_ref,
27 | app: app,
28 | stream_key: stream_key
29 | })
30 |
31 | {Membrane.Demo.RtmpToAdaptiveHls.ClientHandler, %{pipeline: pid}}
32 | end
33 | }
34 |
35 | children = [
36 | # Start the RTMP server
37 | %{
38 | id: Membrane.RTMPServer,
39 | start: {Membrane.RTMPServer, :start_link, [rtmp_server_options]}
40 | },
41 | # Start the Telemetry supervisor
42 | RtmpToAdaptiveHlsWeb.Telemetry,
43 | # Start the PubSub system
44 | {Phoenix.PubSub, name: RtmpToAdaptiveHls.PubSub},
45 | # Start the Endpoint (http/https)
46 | RtmpToAdaptiveHlsWeb.Endpoint
47 | ]
48 |
49 | # See https://hexdocs.pm/elixir/Supervisor.html
50 | # for other strategies and supported options
51 | opts = [strategy: :one_for_one, name: RtmpToAdaptiveHls.Supervisor]
52 | Supervisor.start_link(children, opts)
53 | end
54 |
55 | # Tell Phoenix to update the endpoint configuration
56 | # whenever the application is updated.
57 | @impl true
58 | def config_change(changed, _new, removed) do
59 | RtmpToAdaptiveHlsWeb.Endpoint.config_change(changed, removed)
60 | :ok
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/rtmp_to_hls/assets/css/app.css:
--------------------------------------------------------------------------------
1 | /* This file is for your main application CSS */
2 | @import "./phoenix.css";
3 |
4 | /* Alerts and form errors used by phx.new */
5 | .alert {
6 | padding: 15px;
7 | margin-bottom: 20px;
8 | border: 1px solid transparent;
9 | border-radius: 4px;
10 | }
11 | .alert-info {
12 | color: #31708f;
13 | background-color: #d9edf7;
14 | border-color: #bce8f1;
15 | }
16 | .alert-warning {
17 | color: #8a6d3b;
18 | background-color: #fcf8e3;
19 | border-color: #faebcc;
20 | }
21 | .alert-danger {
22 | color: #a94442;
23 | background-color: #f2dede;
24 | border-color: #ebccd1;
25 | }
26 | .alert p {
27 | margin-bottom: 0;
28 | }
29 | .alert:empty {
30 | display: none;
31 | }
32 | .invalid-feedback {
33 | color: #a94442;
34 | display: block;
35 | margin: -1rem 0 2rem;
36 | }
37 |
38 | /* LiveView specific classes for your customization */
39 | .phx-no-feedback.invalid-feedback,
40 | .phx-no-feedback .invalid-feedback {
41 | display: none;
42 | }
43 |
44 | .phx-click-loading {
45 | opacity: 0.5;
46 | transition: opacity 1s ease-out;
47 | }
48 |
49 | .phx-disconnected{
50 | cursor: wait;
51 | }
52 | .phx-disconnected *{
53 | pointer-events: none;
54 | }
55 |
56 | .phx-modal {
57 | opacity: 1!important;
58 | position: fixed;
59 | z-index: 1;
60 | left: 0;
61 | top: 0;
62 | width: 100%;
63 | height: 100%;
64 | overflow: auto;
65 | background-color: rgb(0,0,0);
66 | background-color: rgba(0,0,0,0.4);
67 | }
68 |
69 | .phx-modal-content {
70 | background-color: #fefefe;
71 | margin: 15vh auto;
72 | padding: 20px;
73 | border: 1px solid #888;
74 | width: 80%;
75 | }
76 |
77 | .phx-modal-close {
78 | color: #aaa;
79 | float: right;
80 | font-size: 28px;
81 | font-weight: bold;
82 | }
83 |
84 | .phx-modal-close:hover,
85 | .phx-modal-close:focus {
86 | color: black;
87 | text-decoration: none;
88 | cursor: pointer;
89 | }
90 |
91 | #player {
92 | width: 100%;
93 | height: auto;
94 | margin: auto auto;
95 | }
--------------------------------------------------------------------------------
/rtp/README.md:
--------------------------------------------------------------------------------
1 | # RTP
2 |
3 | This project demonstrates handling RTP in Membrane.
4 |
5 | This example uses [RTP plugin](https://github.com/membraneframework/membrane_rtp_plugin) that is responsible for receiving and sending RTP streams.
6 |
7 | ## Running the demo
8 |
9 | To run the demo, you'll need to have [Elixir installed](https://elixir-lang.org/install.html). Then, do the following:
10 |
11 | - Open a terminal in the project directory
12 | - Type `mix deps.get` to download dependencies
13 | - Type `mix run receive.exs` to run the receiving pipeline
14 | - Wait until the script runs
15 | - Open another terminal in the project directory
16 | - Type `mix run send.exs` to run the sending pipeline
17 |
18 | You should be able to see a player showing an example video.
19 |
20 | The sender pipeline (run with `send.exs`) takes sample audio and video files and sends them via RTP.
21 | The receiving pipeline (run with `receive.exs`) receives the audio and video streams and plays them.
22 |
23 | If you wish to stream using SRTP, add a `--secure` flag when running both `receive.exs` and `send.exs`.
24 |
25 | You can also use another tool, like [GStreamer](https://gstreamer.freedesktop.org/), to send the stream. In this case, you only need to start the receiving pipeline:
26 |
27 | ```shell
28 | mix run receive.exs
29 | ```
30 |
31 | and launch GStreamer:
32 |
33 | ```shell
34 | gst-launch-1.0 -v audiotestsrc ! audio/x-raw,rate=48000,channels=2 ! opusenc ! rtpopuspay pt=120 ! udpsink host=127.0.0.1 port=5002\
35 | videotestsrc ! video/x-raw,format=I420 ! x264enc key-int-max=10 tune=zerolatency ! rtph264pay pt=96 ! udpsink host=127.0.0.1 port=5000
36 | ```
37 |
38 | ## Copyright and License
39 |
40 | Copyright 2018, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
41 |
42 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
43 |
44 | Licensed under the [Apache License, Version 2.0](LICENSE)
45 |
--------------------------------------------------------------------------------
/rtmp_to_hls/config/prod.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # For production, don't forget to configure the url host
4 | # to something meaningful, Phoenix uses this information
5 | # when generating URLs.
6 | #
7 | # Note we also include the path to a cache manifest
8 | # containing the digested version of static files. This
9 | # manifest is generated by the `mix phx.digest` task,
10 | # which you should run after static files are built and
11 | # before starting your production server.
12 | config :rtmp_to_hls, RtmpToHlsWeb.Endpoint,
13 | url: [host: "example.com", port: 80],
14 | cache_static_manifest: "priv/static/cache_manifest.json"
15 |
16 | # Do not print debug messages in production
17 | config :logger, level: :info
18 |
19 | # ## SSL Support
20 | #
21 | # To get SSL working, you will need to add the `https` key
22 | # to the previous section and set your `:url` port to 443:
23 | #
24 | # config :rtmp_to_hls, RtmpToHlsWeb.Endpoint,
25 | # ...,
26 | # url: [host: "example.com", port: 443],
27 | # https: [
28 | # ...,
29 | # port: 443,
30 | # cipher_suite: :strong,
31 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
32 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
33 | # ]
34 | #
35 | # The `cipher_suite` is set to `:strong` to support only the
36 | # latest and more secure SSL ciphers. This means old browsers
37 | # and clients may not be supported. You can set it to
38 | # `:compatible` for wider support.
39 | #
40 | # `:keyfile` and `:certfile` expect an absolute path to the key
41 | # and cert in disk or a relative path inside priv, for example
42 | # "priv/ssl/server.key". For all supported SSL configuration
43 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
44 | #
45 | # We also recommend setting `force_ssl` in your endpoint, ensuring
46 | # no data is ever sent via http, always redirecting to https:
47 | #
48 | # config :rtmp_to_hls, RtmpToHlsWeb.Endpoint,
49 | # force_ssl: [hsts: true]
50 | #
51 | # Check `Plug.SSL` for all available options in `force_ssl`.
52 |
--------------------------------------------------------------------------------
/camera_to_hls_nerves/rel/vm.args.eex:
--------------------------------------------------------------------------------
1 | ## Customize flags given to the VM: http://erlang.org/doc/man/erl.html
2 |
3 | ## Do not set -name or -sname here. Prefer configuring them at runtime
4 | ## Configure -setcookie in the mix.exs release section or at runtime
5 |
6 | ## Number of dirty schedulers doing IO work (file, sockets, and others)
7 | ##+SDio 5
8 |
9 | ## Increase number of concurrent ports/sockets
10 | ##+Q 65536
11 |
12 | ## Tweak GC to run more often
13 | ##-env ERL_FULLSWEEP_AFTER 10
14 |
15 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's
16 | ## break handler and possibly exiting the VM.
17 | +Bc
18 |
19 | # Allow time warps so that the Erlang system time can more closely match the
20 | # OS system time.
21 | +C multi_time_warp
22 |
23 | ## Load code at system startup
24 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy
25 | -mode embedded
26 |
27 | # Load code as per the boot script since not using archives
28 | # See https://www.erlang.org/doc/man/init.html#command-line-flags
29 | -code_path_choice strict
30 |
31 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying
32 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt
33 | +sbwt none
34 | +sbwtdcpu none
35 | +sbwtdio none
36 |
37 | ## Save the shell history between reboots
38 | ## See http://erlang.org/doc/man/kernel_app.html for additional options
39 | -kernel shell_history enabled
40 |
41 | ## Enable heartbeat monitoring of the Erlang runtime system
42 | -heart -env HEART_BEAT_TIMEOUT 30
43 |
44 | ## Start the Elixir shell
45 |
46 | -noshell
47 | -user elixir
48 | -run elixir start_iex
49 |
50 |
51 | ## Enable colors in the shell
52 | -elixir ansi_enabled true
53 |
54 | ## Options added after -extra are interpreted as plain arguments and can be
55 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are
56 | ## interpreted by Elixir and anything afterwards is left around for other IEx
57 | ## and user applications.
58 | -extra --no-halt
59 | --
60 | --dot-iex /etc/iex.exs
61 |
--------------------------------------------------------------------------------
/simple_pipeline/README.md:
--------------------------------------------------------------------------------
1 | # Simple Membrane Pipeline
2 |
3 | This demo shows how to create a pipeline that plays an mp3 file.
4 |
5 | ## Prerequisites
6 |
7 | To run the demo, you need [Elixir installed](https://elixir-lang.org/install.html) on your machine (it's best to use a version manager, like `asdf`).
8 |
9 | If you are running the demo on Linux, make sure to have the following dependencies installed in your system:
10 | - portaudio19-dev,
11 | - pkg-config
12 |
13 | On Ubuntu, you can install them with the following command:
14 | ```shell
15 | apt install portaudio19-dev pkg-config
16 | ```
17 |
18 | ## Running the demo
19 |
20 | To run the demo, clone the `membrane_demo` repository and checkout to the demo directory:
21 |
22 | ```shell
23 | git clone https://github.com/membraneframework/membrane_demo
24 | cd membrane_demo/simple_pipeline
25 | ```
26 |
27 | Then you need to download the dependencies of the mix project:
28 |
29 | ```shell
30 | mix deps.get
31 | ```
32 |
33 | You may be asked to install `Hex` and then `rebar3`.
34 |
35 | To start the demo pipeline run `mix run --no-halt run.exs` or type the following commands into an IEx shell (started by `iex -S mix`):
36 |
37 | ```elixir
38 | {:ok, _supervisor, _pid} = Membrane.Pipeline.start_link(Membrane.Demo.SimplePipeline, "sample.mp3")
39 | ```
40 |
41 | Should there be any errors when compiling the script's dependencies, you may need to install the some dependencies manually on your system:
42 | * [PortAudio](https://www.portaudio.com/) - which we use to play the audio
43 | * [FFmpeg](https://ffmpeg.org/) - which we use to resample the audio
44 | * [MAD](https://www.underbit.com/products/mad/) - which is used to decode audio
45 |
46 |
47 | ## Copyright and License
48 |
49 | Copyright 2018, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
50 |
51 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
52 |
53 | Licensed under the [Apache License, Version 2.0](LICENSE)
54 |
--------------------------------------------------------------------------------
/mix_audio/mix_audio.exs:
--------------------------------------------------------------------------------
1 | Logger.configure(level: :info)
2 |
3 | Mix.install([
4 | {:membrane_core, "~> 1.0"},
5 | {:membrane_file_plugin, "~> 0.17.0"},
6 | {:membrane_wav_plugin, "~> 0.10.1"},
7 | {:membrane_audio_mix_plugin, "~> 0.16.0"},
8 | {:membrane_aac_fdk_plugin, "0.18.8"}
9 | ])
10 |
11 | defmodule MixAudio do
12 | @moduledoc """
13 | Mix several .wav files into single .aac file.
14 | """
15 | use Membrane.Pipeline
16 |
17 | @impl true
18 | def handle_init(_ctx, {wav1, wav2}) do
19 | # Setup the flow of the data
20 | spec = [
21 | # Read & parse first WAV
22 | child(%Membrane.File.Source{location: wav1})
23 | |> child(Membrane.WAV.Parser)
24 | |> get_child(:mixer),
25 | # Read & parse second WAV
26 | child(%Membrane.File.Source{location: wav2})
27 | |> child(Membrane.WAV.Parser)
28 | # Offset audio by 2 seconds
29 | |> via_in(:input, options: [offset: Membrane.Time.seconds(2)])
30 | |> get_child(:mixer),
31 | # Spawn the mixer and setup the audio format that it should operate on
32 | child(:mixer, %Membrane.AudioMixer{
33 | stream_format: %Membrane.RawAudio{
34 | channels: 1,
35 | sample_rate: 16_000,
36 | sample_format: :s16le
37 | }
38 | })
39 | # Encode output to AAC
40 | |> child(Membrane.AAC.FDK.Encoder)
41 | # Save the output to file
42 | |> child(:sink, %Membrane.File.Sink{location: "output.aac"})
43 | ]
44 |
45 | {[spec: spec], %{}}
46 | end
47 |
48 | @impl true
49 | def handle_element_end_of_stream(:sink, :input, _ctx, state) do
50 | {[terminate: :normal], state}
51 | end
52 |
53 | @impl true
54 | def handle_element_end_of_stream(_element, _pad, _ctx, state) do
55 | {[], state}
56 | end
57 | end
58 |
59 | {:ok, _supervisor, pipeline} =
60 | Membrane.Pipeline.start_link(MixAudio, {"sound_500f.wav", "sound_1000f.wav"})
61 |
62 | Process.monitor(pipeline)
63 |
64 | # Wait for the pipeline to terminate
65 | receive do
66 | {:DOWN, _monitor, :process, ^pipeline, _reason} -> :ok
67 | end
68 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/assets/css/app.css:
--------------------------------------------------------------------------------
1 | /* This file is for your main application CSS */
2 | @import "./phoenix.css";
3 | @import "https://cdn.vidstack.io/player/theme.css";
4 | @import "https://cdn.vidstack.io/player/video.css";
5 |
6 | /* Alerts and form errors used by phx.new */
7 | .alert {
8 | padding: 15px;
9 | margin-bottom: 20px;
10 | border: 1px solid transparent;
11 | border-radius: 4px;
12 | }
13 | .alert-info {
14 | color: #31708f;
15 | background-color: #d9edf7;
16 | border-color: #bce8f1;
17 | }
18 | .alert-warning {
19 | color: #8a6d3b;
20 | background-color: #fcf8e3;
21 | border-color: #faebcc;
22 | }
23 | .alert-danger {
24 | color: #a94442;
25 | background-color: #f2dede;
26 | border-color: #ebccd1;
27 | }
28 | .alert p {
29 | margin-bottom: 0;
30 | }
31 | .alert:empty {
32 | display: none;
33 | }
34 | .invalid-feedback {
35 | color: #a94442;
36 | display: block;
37 | margin: -1rem 0 2rem;
38 | }
39 |
40 | /* LiveView specific classes for your customization */
41 | .phx-no-feedback.invalid-feedback,
42 | .phx-no-feedback .invalid-feedback {
43 | display: none;
44 | }
45 |
46 | .phx-click-loading {
47 | opacity: 0.5;
48 | transition: opacity 1s ease-out;
49 | }
50 |
51 | .phx-disconnected{
52 | cursor: wait;
53 | }
54 | .phx-disconnected *{
55 | pointer-events: none;
56 | }
57 |
58 | .phx-modal {
59 | opacity: 1!important;
60 | position: fixed;
61 | z-index: 1;
62 | left: 0;
63 | top: 0;
64 | width: 100%;
65 | height: 100%;
66 | overflow: auto;
67 | background-color: rgb(0,0,0);
68 | background-color: rgba(0,0,0,0.4);
69 | }
70 |
71 | .phx-modal-content {
72 | background-color: #fefefe;
73 | margin: 15vh auto;
74 | padding: 20px;
75 | border: 1px solid #888;
76 | width: 80%;
77 | }
78 |
79 | .phx-modal-close {
80 | color: #aaa;
81 | float: right;
82 | font-size: 28px;
83 | font-weight: bold;
84 | }
85 |
86 | .phx-modal-close:hover,
87 | .phx-modal-close:focus {
88 | color: black;
89 | text-decoration: none;
90 | cursor: pointer;
91 | }
92 |
93 | #player {
94 | width: 100%;
95 | height: auto;
96 | margin: auto auto;
97 | }
98 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/config/prod.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # For production, don't forget to configure the url host
4 | # to something meaningful, Phoenix uses this information
5 | # when generating URLs.
6 | #
7 | # Note we also include the path to a cache manifest
8 | # containing the digested version of static files. This
9 | # manifest is generated by the `mix phx.digest` task,
10 | # which you should run after static files are built and
11 | # before starting your production server.
12 | config :rtmp_to_adaptive_hls, RtmpToAdaptiveHlsWeb.Endpoint,
13 | url: [host: "example.com", port: 80],
14 | cache_static_manifest: "priv/static/cache_manifest.json"
15 |
16 | # Do not print debug messages in production
17 | config :logger, level: :info
18 |
19 | # ## SSL Support
20 | #
21 | # To get SSL working, you will need to add the `https` key
22 | # to the previous section and set your `:url` port to 443:
23 | #
24 | # config :rtmp_to_adaptive_hls, RtmpToAdaptiveHlsWeb.Endpoint,
25 | # ...,
26 | # url: [host: "example.com", port: 443],
27 | # https: [
28 | # ...,
29 | # port: 443,
30 | # cipher_suite: :strong,
31 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
32 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
33 | # ]
34 | #
35 | # The `cipher_suite` is set to `:strong` to support only the
36 | # latest and more secure SSL ciphers. This means old browsers
37 | # and clients may not be supported. You can set it to
38 | # `:compatible` for wider support.
39 | #
40 | # `:keyfile` and `:certfile` expect an absolute path to the key
41 | # and cert in disk or a relative path inside priv, for example
42 | # "priv/ssl/server.key". For all supported SSL configuration
43 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
44 | #
45 | # We also recommend setting `force_ssl` in your endpoint, ensuring
46 | # no data is ever sent via http, always redirecting to https:
47 | #
48 | # config :rtmp_to_adaptive_hls, RtmpToAdaptiveHlsWeb.Endpoint,
49 | # force_ssl: [hsts: true]
50 | #
51 | # Check `Plug.SSL` for all available options in `force_ssl`.
52 |
--------------------------------------------------------------------------------
/rtsp_to_hls/lib/pipeline.ex:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.RTSPToHLS.Pipeline do
2 | @moduledoc """
3 | The pipeline which converts the stream to HLS.
4 | """
5 | use Membrane.Pipeline
6 |
7 | require Logger
8 |
9 | @impl true
10 | def handle_init(_context, options) do
11 | spec = [
12 | child(:source, %Membrane.RTSP.Source{
13 | transport: {:udp, options.port, options.port + options.port_range_size},
14 | allowed_media_types: [:video, :audio],
15 | stream_uri: options.stream_url,
16 | on_connection_closed: :send_eos
17 | }),
18 | child(:hls, %Membrane.HTTPAdaptiveStream.SinkBin{
19 | target_window_duration: Membrane.Time.seconds(120),
20 | manifest_module: Membrane.HTTPAdaptiveStream.HLS,
21 | storage: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{
22 | directory: options.output_path
23 | }
24 | })
25 | ]
26 |
27 | {[spec: spec], %{parent_pid: options.parent_pid}}
28 | end
29 |
30 | @impl true
31 | def handle_child_notification({:set_up_tracks, tracks}, :source, _ctx, state) do
32 | track_specs =
33 | Enum.uniq_by(tracks, & &1.type)
34 | |> Enum.filter(&(&1.type in [:audio, :video]))
35 | |> Enum.map(fn track ->
36 | encoding =
37 | case track do
38 | %{type: :audio} -> :AAC
39 | %{type: :video} -> :H264
40 | end
41 |
42 | get_child(:source)
43 | |> via_out(Pad.ref(:output, track.control_path))
44 | |> via_in(:input,
45 | options: [encoding: encoding, segment_duration: Membrane.Time.seconds(4)]
46 | )
47 | |> get_child(:hls)
48 | end)
49 |
50 | {[spec: track_specs], state}
51 | end
52 |
53 | @impl true
54 | def handle_child_notification({:track_playable, _ref}, :hls, _ctx, state) do
55 | send(state.parent_pid, :track_playable)
56 | {[], state}
57 | end
58 |
59 | @impl true
60 | def handle_child_notification(notification, _element, _ctx, state) do
61 | Logger.warning("Ignoring notification #{inspect(notification)}")
62 | {[], state}
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/webrtc_live_view/assets/js/app.js:
--------------------------------------------------------------------------------
1 | // If you want to use Phoenix channels, run `mix help phx.gen.channel`
2 | // to get started and then uncomment the line below.
3 | // import "./user_socket.js"
4 |
5 | // You can include dependencies in two ways.
6 | //
7 | // The simplest option is to put them in assets/vendor and
8 | // import them using relative paths:
9 | //
10 | // import "../vendor/some-package.js"
11 | //
12 | // Alternatively, you can `npm install some-package --prefix assets` and import
13 | // them using a path starting with the package name:
14 | //
15 | // import "some-package"
16 | //
17 |
18 | // Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
19 | import "phoenix_html";
20 | // Establish Phoenix Socket and LiveView configuration.
21 | import { Socket } from "phoenix";
22 | import { LiveSocket } from "phoenix_live_view";
23 | import topbar from "../vendor/topbar";
24 | import { createCaptureHook, createPlayerHook } from "membrane_webrtc_plugin";
25 |
26 | const iceServers = [{ urls: "stun:stun.l.google.com:19302" }];
27 |
28 | let hooks = {};
29 | hooks.Capture = createCaptureHook(iceServers);
30 | hooks.Player = createPlayerHook(iceServers);
31 |
32 | let csrfToken = document
33 | .querySelector("meta[name='csrf-token']")
34 | .getAttribute("content");
35 | let liveSocket = new LiveSocket("/live", Socket, {
36 | longPollFallbackMs: 2500,
37 | params: { _csrf_token: csrfToken },
38 | hooks: hooks,
39 | });
40 |
41 | // Show progress bar on live navigation and form submits
42 | topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" });
43 | window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300));
44 | window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide());
45 |
46 | // connect if there are any LiveViews on the page
47 | liveSocket.connect();
48 |
49 | // expose liveSocket on window for web console debug logs and latency simulation:
50 | // >> liveSocket.enableDebug()
51 | // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
52 | // >> liveSocket.disableLatencySim()
53 | window.liveSocket = liveSocket;
54 |
--------------------------------------------------------------------------------
/rtmp_to_hls/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHls.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :rtmp_to_hls,
7 | version: "0.1.0",
8 | elixir: "~> 1.12",
9 | elixirc_paths: elixirc_paths(Mix.env()),
10 | compilers: Mix.compilers(),
11 | start_permanent: Mix.env() == :prod,
12 | aliases: aliases(),
13 | deps: deps()
14 | ]
15 | end
16 |
17 | # Configuration for the OTP application.
18 | #
19 | # Type `mix help compile.app` for more information.
20 | def application do
21 | [
22 | mod: {RtmpToHls.Application, []},
23 | extra_applications: [:logger, :runtime_tools, :inets]
24 | ]
25 | end
26 |
27 | # Specifies which paths to compile per environment.
28 | defp elixirc_paths(:test), do: ["lib", "test/support"]
29 | defp elixirc_paths(_), do: ["lib"]
30 |
31 | # Specifies your project dependencies.
32 | #
33 | # Type `mix help deps` for examples and options.
34 | defp deps do
35 | [
36 | {:phoenix, "~> 1.6.0"},
37 | {:phoenix_html, "~> 3.0"},
38 | {:phoenix_live_reload, "~> 1.2", only: :dev},
39 | {:phoenix_live_view, "~> 0.16.0"},
40 | {:floki, ">= 0.30.0", only: :test},
41 | {:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
42 | {:telemetry_metrics, "~> 0.6"},
43 | {:telemetry_poller, "~> 1.0"},
44 | {:telemetry, "~> 1.0", override: true},
45 | {:gettext, "~> 0.18"},
46 | {:jason, "~> 1.2"},
47 | {:plug_cowboy, "~> 2.5"},
48 |
49 | ## Membrane deps
50 | {:membrane_core, "~> 1.0"},
51 | {:membrane_rtmp_plugin, "~> 0.23.2"},
52 | {:membrane_http_adaptive_stream_plugin, "~> 0.18.4"}
53 | ]
54 | end
55 |
56 | # Aliases are shortcuts or tasks specific to the current project.
57 | # For example, to install project dependencies and perform other setup tasks, run:
58 | #
59 | # $ mix setup
60 | #
61 | # See the documentation for `Mix` for more info on aliases.
62 | defp aliases do
63 | [
64 | setup: ["deps.get"],
65 | "assets.deploy": ["esbuild default --minify", "phx.digest"]
66 | ]
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/livebooks/playing_mp3_file/playing_mp3_file.livemd:
--------------------------------------------------------------------------------
1 | # Playing MP3 File
2 |
3 | ```elixir
4 | File.cd(__DIR__)
5 | Logger.configure(level: :error)
6 |
7 | Mix.install([
8 | {:membrane_core, "~> 1.0"},
9 | {:membrane_file_plugin, "~> 0.17.0"},
10 | {:membrane_mp3_mad_plugin, "~> 0.18.3"},
11 | {:membrane_ffmpeg_swresample_plugin, "~> 0.20.2"},
12 | {:membrane_aac_fdk_plugin, "~> 0.18.8"},
13 | {:membrane_kino_plugin, github: "membraneframework-labs/membrane_kino_plugin", tag: "v0.3.2"}
14 | ])
15 | ```
16 |
17 | ## Description
18 |
19 | This is example of loading `MP3` audio from the file, transcoding it to the `AAC` codec, and playing it via `Membrane.Kino.Player`.
20 |
21 | ## Pipeline definition
22 |
23 | Defines simple linear pipeline of the given structure:
24 |
25 | 1. Load `MP3` file from the file.
26 | 2. Transcode `MP3` to `AAC` (it is required by `Kino.Player`):
27 | 1. Decode `MP3` format to `RawAudio`,
28 | 2. Change `sample_format` from `s24le` to `s16le` (it is required by `FDK.Encoder`),
29 | 3. Encode it to `AAC` format.
30 | 3. Fill in audio stream to the player via `:audio` input pad.
31 |
32 | ```elixir
33 | import Membrane.ChildrenSpec,
34 | only: [{:child, 2}, {:child, 3}, {:via_in, 2}]
35 |
36 | alias Membrane.{
37 | File,
38 | MP3,
39 | FFmpeg,
40 | RawAudio,
41 | AAC,
42 | Kino
43 | }
44 |
45 | audio_path = "./assets/sample.mp3"
46 | kino = Membrane.Kino.Player.new(audio: true)
47 |
48 | spec =
49 | child(:file_source, %File.Source{location: audio_path})
50 | |> child(:decoder_mp3, MP3.MAD.Decoder)
51 | |> child(:converter, %FFmpeg.SWResample.Converter{
52 | input_stream_format: %RawAudio{channels: 2, sample_format: :s24le, sample_rate: 48_000},
53 | output_stream_format: %RawAudio{channels: 2, sample_format: :s16le, sample_rate: 44_100}
54 | })
55 | |> child(:encoder_aac, AAC.FDK.Encoder)
56 | |> via_in(:audio)
57 | |> child(:player, %Kino.Player.Sink{kino: kino})
58 |
59 | :ok
60 | ```
61 |
62 | ## Player
63 |
64 | Run pipeline:
65 |
66 | ```elixir
67 | alias Membrane.RCPipeline, as: RC
68 |
69 | pipeline = RC.start!()
70 | RC.exec_actions(pipeline, spec: spec)
71 |
72 | kino
73 | ```
74 |
--------------------------------------------------------------------------------
/camera_to_hls/README.md:
--------------------------------------------------------------------------------
1 | # Camera feed to HLS
2 |
3 | This script demonstrates capturing camera video and broadcasting it via HLS.
4 |
5 | To run the demo, you need [Elixir installed](https://elixir-lang.org/install.html) on your machine (it's best to use a version manager, like `asdf`). Then, run
6 |
7 | ```bash
8 | elixir camera_to_hls.exs
9 | ```
10 |
11 | and when it prints that playback is available, visit `http://localhost:8000/stream.html`. You should see the stream from the camera there. The stream can be also played with players other than the browser, like `vlc` or `ffplay`, for example
12 |
13 | ```bash
14 | ffplay http://localhost:8000/output/index.m3u8
15 | ```
16 |
17 | Should there be any errors when compiling the script's dependencies, you may need to install [FFmpeg](https://ffmpeg.org/), which we use to encode the stream from the camera.
18 |
19 |
20 | You might be asked to grant access to your camera, as some operating systems require that. In case of the absence of a physical camera, it is necessary to use a virtual camera (e.g. OBS, [see how to set up the virtual camera in OBS](https://obsproject.com/kb/virtual-camera-guide)).
21 |
22 | For an example of serving HLS within a Phoenix project, see the [rtmp_to_hls demo](../rtmp_to_hls/).
23 |
24 |
25 |
26 | Debugging
27 |
28 |
29 | ### Camera not found
30 | If you have a camera but you still receiving the error
31 | ```
32 | [video4linux2,v4l2 @ 0x745cf00b0440] Cannot open video device default: No such file or directory
33 | ```
34 | The [Membrane CameraCapture Plugin](https://github.com/membraneframework/membrane_camera_capture_plugin) isn't able to find the default camera.
35 | You can find it manually with `v4l2-ctl --list-devices` and then manually set it on the CameraCapture Plugin.
36 | ```diff
37 | - child(:source, Membrane.CameraCapture)
38 | + child(:source, %Membrane.CameraCapture{device: "/dev/video0"})
39 | ```
40 |
41 |
42 | ## Copyright and License
43 |
44 | Copyright 2022, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
45 |
46 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
47 |
48 | Licensed under the [Apache License, Version 2.0](LICENSE)
49 |
--------------------------------------------------------------------------------
/webrtc_live_view/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveView.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :webrtc_live_view,
7 | version: "0.1.0",
8 | elixir: "~> 1.14",
9 | elixirc_paths: elixirc_paths(Mix.env()),
10 | start_permanent: Mix.env() == :prod,
11 | aliases: aliases(),
12 | deps: deps()
13 | ]
14 | end
15 |
16 | # Configuration for the OTP application.
17 | #
18 | # Type `mix help compile.app` for more information.
19 | def application do
20 | [
21 | mod: {WebrtcLiveView.Application, []},
22 | extra_applications: [:logger, :runtime_tools]
23 | ]
24 | end
25 |
26 | # Specifies which paths to compile per environment.
27 | defp elixirc_paths(:test), do: ["lib", "test/support"]
28 | defp elixirc_paths(_), do: ["lib"]
29 |
30 | # Specifies your project dependencies.
31 | #
32 | # Type `mix help deps` for examples and options.
33 | defp deps do
34 | [
35 | {:membrane_webrtc_plugin, "~> 0.25.3"},
36 | {:membrane_transcoder_plugin, "~> 0.2.1"},
37 | {:membrane_ffmpeg_swscale_plugin, "~> 0.16.2"},
38 | {:evision, "~> 0.2.11"},
39 | {:vix, "~> 0.33.0"},
40 | {:image, "~> 0.59.0"},
41 | {:phoenix, "~> 1.7.21"},
42 | {:phoenix_html, "~> 4.1"},
43 | {:phoenix_live_reload, "~> 1.2", only: :dev},
44 | {:phoenix_live_view, "~> 1.0"},
45 | {:floki, ">= 0.30.0", only: :test},
46 | {:phoenix_live_dashboard, "~> 0.8.3"},
47 | {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
48 | {:telemetry_metrics, "~> 1.0"},
49 | {:telemetry_poller, "~> 1.0"},
50 | {:jason, "~> 1.2"},
51 | {:dns_cluster, "~> 0.1.1"},
52 | {:bandit, "~> 1.5"}
53 | ]
54 | end
55 |
56 | # Aliases are shortcuts or tasks specific to the current project.
57 | # For example, to install project dependencies and perform other setup tasks, run:
58 | #
59 | # $ mix setup
60 | #
61 | # See the documentation for `Mix` for more info on aliases.
62 | defp aliases do
63 | [
64 | setup: ["deps.get", "assets.setup", "assets.build"],
65 | "assets.setup": ["esbuild.install --if-missing"],
66 | "assets.build": ["esbuild webrtc_live_view"],
67 | "assets.deploy": [
68 | "esbuild webrtc_live_view --minify",
69 | "phx.digest"
70 | ]
71 | ]
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/camera_to_hls_nerves/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule CameraToHlsNerves.MixProject do
2 | use Mix.Project
3 |
4 | @app :camera_to_hls_nerves
5 | @version "0.1.0"
6 | @all_targets [
7 | :rpi4
8 | ]
9 |
10 | def project do
11 | [
12 | app: @app,
13 | version: @version,
14 | elixir: "~> 1.11",
15 | archives: [nerves_bootstrap: "~> 1.12"],
16 | start_permanent: Mix.env() == :prod,
17 | deps: deps(),
18 | releases: [{@app, release()}],
19 | preferred_cli_target: [run: :host, test: :host]
20 | ]
21 | end
22 |
23 | # Run "mix help compile.app" to learn about applications.
24 | def application do
25 | [
26 | mod: {CameraToHlsNerves.Application, []},
27 | extra_applications: [:logger, :runtime_tools, :inets]
28 | ]
29 | end
30 |
31 | # Run "mix help deps" to learn about dependencies.
32 | defp deps do
33 | [
34 | # Dependencies for all targets
35 | {:nerves, "~> 1.10", runtime: false},
36 | {:shoehorn, "~> 0.9.1"},
37 | {:ring_logger, "~> 0.10.0"},
38 | {:toolshed, "~> 0.3.0"},
39 | {:membrane_rpicam_plugin, "~> 0.1.5"},
40 | {:membrane_h26x_plugin, "~> 0.10.1"},
41 | {:membrane_http_adaptive_stream_plugin, "~> 0.18.4"},
42 |
43 | # Allow Nerves.Runtime on host to support development, testing and CI.
44 | # See config/host.exs for usage.
45 | {:nerves_runtime, "~> 0.13.0"},
46 |
47 | # Dependencies for all targets except :host
48 | {:nerves_pack, "~> 0.7.0", targets: @all_targets},
49 |
50 | # Dependencies for specific targets
51 | # NOTE: It's generally low risk and recommended to follow minor version
52 | # bumps to Nerves systems. Since these include Linux kernel and Erlang
53 | # version updates, please review their release notes in case
54 | # changes to your application are needed.
55 | {:nerves_system_rpi4, "~> 1.24", runtime: false, targets: :rpi4}
56 | ]
57 | end
58 |
59 | def release do
60 | [
61 | overwrite: true,
62 | # Erlang distribution is not started automatically.
63 | # See https://hexdocs.pm/nerves_pack/readme.html#erlang-distribution
64 | cookie: "#{@app}_cookie",
65 | include_erts: &Nerves.Release.erts/0,
66 | steps: [&Nerves.Release.init/1, :assemble],
67 | strip_beams: Mix.env() == :prod or [keep: ["Docs"]]
68 | ]
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHls.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :rtmp_to_adaptive_hls,
7 | version: "0.1.0",
8 | elixir: "~> 1.12",
9 | elixirc_paths: elixirc_paths(Mix.env()),
10 | compilers: Mix.compilers(),
11 | start_permanent: Mix.env() == :prod,
12 | aliases: aliases(),
13 | deps: deps()
14 | ]
15 | end
16 |
17 | # Configuration for the OTP application.
18 | #
19 | # Type `mix help compile.app` for more information.
20 | def application do
21 | [
22 | mod: {RtmpToAdaptiveHls.Application, []},
23 | extra_applications: [:logger, :runtime_tools, :inets]
24 | ]
25 | end
26 |
27 | # Specifies which paths to compile per environment.
28 | defp elixirc_paths(:test), do: ["lib", "test/support"]
29 | defp elixirc_paths(_), do: ["lib"]
30 |
31 | # Specifies your project dependencies.
32 | #
33 | # Type `mix help deps` for examples and options.
34 | defp deps do
35 | [
36 | {:phoenix, "~> 1.6.0"},
37 | {:phoenix_html, "~> 3.0"},
38 | {:phoenix_live_reload, "~> 1.2", only: :dev},
39 | {:phoenix_live_view, "~> 0.16.0"},
40 | {:floki, ">= 0.30.0", only: :test},
41 | {:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
42 | {:telemetry_metrics, "~> 0.6"},
43 | {:telemetry_poller, "~> 1.0"},
44 | {:telemetry, "~> 1.0", override: true},
45 | {:gettext, "~> 0.18"},
46 | {:jason, "~> 1.2"},
47 | {:plug_cowboy, "~> 2.5"},
48 |
49 | ## Membrane deps
50 | {:membrane_core, "~> 1.0"},
51 | {:membrane_framerate_converter_plugin, "~> 0.8.2"},
52 | {:membrane_ffmpeg_swscale_plugin, "~> 0.15.1"},
53 | {:membrane_h264_ffmpeg_plugin, "~> 0.31.6"},
54 | {:membrane_http_adaptive_stream_plugin, "~> 0.18.5"},
55 | {:membrane_rtmp_plugin, "~> 0.27.2"},
56 | {:membrane_tee_plugin, "~> 0.12.0"}
57 | ]
58 | end
59 |
60 | # Aliases are shortcuts or tasks specific to the current project.
61 | # For example, to install project dependencies and perform other setup tasks, run:
62 | #
63 | # $ mix setup
64 | #
65 | # See the documentation for `Mix` for more info on aliases.
66 | defp aliases do
67 | [
68 | setup: ["deps.get"],
69 | "assets.deploy": ["esbuild default --minify", "phx.digest"]
70 | ]
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/rtmp_to_hls/config/dev.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with esbuild to bundle .js and .css sources.
9 | config :rtmp_to_hls, RtmpToHlsWeb.Endpoint,
10 | # Binding to loopback ipv4 address prevents access from other machines.
11 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
12 | http: [ip: {127, 0, 0, 1}, port: 4000],
13 | check_origin: false,
14 | code_reloader: true,
15 | debug_errors: true,
16 | secret_key_base: "RJ+YNDINYsAkM4evupc/vq5vMR9PPuJihcWgMB1DNMZYH+jrC3GcwpsLdi8mB61m",
17 | watchers: [
18 | # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
19 | esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
20 | ]
21 |
22 | # ## SSL Support
23 | #
24 | # In order to use HTTPS in development, a self-signed
25 | # certificate can be generated by running the following
26 | # Mix task:
27 | #
28 | # mix phx.gen.cert
29 | #
30 | # Note that this task requires Erlang/OTP 20 or later.
31 | # Run `mix help phx.gen.cert` for more information.
32 | #
33 | # The `http:` config above can be replaced with:
34 | #
35 | # https: [
36 | # port: 4001,
37 | # cipher_suite: :strong,
38 | # keyfile: "priv/cert/selfsigned_key.pem",
39 | # certfile: "priv/cert/selfsigned.pem"
40 | # ],
41 | #
42 | # If desired, both `http:` and `https:` keys can be
43 | # configured to run both http and https servers on
44 | # different ports.
45 |
46 | # Watch static and templates for browser reloading.
47 | config :rtmp_to_hls, RtmpToHlsWeb.Endpoint,
48 | live_reload: [
49 | patterns: [
50 | ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
51 | ~r"priv/gettext/.*(po)$",
52 | ~r"lib/rtmp_to_hls_web/(live|views)/.*(ex)$",
53 | ~r"lib/rtmp_to_hls_web/templates/.*(eex)$"
54 | ]
55 | ]
56 |
57 | # Do not include metadata nor timestamps in development logs
58 | config :logger, :console, format: "[$level] $message\n"
59 |
60 | # Set a higher stacktrace during development. Avoid configuring such
61 | # in production as building large stacktraces may be expensive.
62 | config :phoenix, :stacktrace_depth, 20
63 |
64 | # Initialize plugs at runtime for faster development compilation
65 | config :phoenix, :plug_init_mode, :runtime
66 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web/telemetry.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb.Telemetry do
2 | use Supervisor
3 | import Telemetry.Metrics
4 |
5 | def start_link(arg) do
6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
7 | end
8 |
9 | @impl true
10 | def init(_arg) do
11 | children = [
12 | # Telemetry poller will execute the given period measurements
13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
15 | # Add reporters as children of your supervision tree.
16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
17 | ]
18 |
19 | Supervisor.init(children, strategy: :one_for_one)
20 | end
21 |
22 | def metrics do
23 | [
24 | # Phoenix Metrics
25 | summary("phoenix.endpoint.start.system_time",
26 | unit: {:native, :millisecond}
27 | ),
28 | summary("phoenix.endpoint.stop.duration",
29 | unit: {:native, :millisecond}
30 | ),
31 | summary("phoenix.router_dispatch.start.system_time",
32 | tags: [:route],
33 | unit: {:native, :millisecond}
34 | ),
35 | summary("phoenix.router_dispatch.exception.duration",
36 | tags: [:route],
37 | unit: {:native, :millisecond}
38 | ),
39 | summary("phoenix.router_dispatch.stop.duration",
40 | tags: [:route],
41 | unit: {:native, :millisecond}
42 | ),
43 | summary("phoenix.socket_connected.duration",
44 | unit: {:native, :millisecond}
45 | ),
46 | sum("phoenix.socket_drain.count"),
47 | summary("phoenix.channel_joined.duration",
48 | unit: {:native, :millisecond}
49 | ),
50 | summary("phoenix.channel_handled_in.duration",
51 | tags: [:event],
52 | unit: {:native, :millisecond}
53 | ),
54 |
55 | # VM Metrics
56 | summary("vm.memory.total", unit: {:byte, :kilobyte}),
57 | summary("vm.total_run_queue_lengths.total"),
58 | summary("vm.total_run_queue_lengths.cpu"),
59 | summary("vm.total_run_queue_lengths.io")
60 | ]
61 | end
62 |
63 | defp periodic_measurements do
64 | [
65 | # A module, function and arguments to be invoked periodically.
66 | # This function must call :telemetry.execute/3 and a metric must be added above.
67 | # {WebrtcLiveViewWeb, :count_users, []}
68 | ]
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/simple_element/lib/membrane_demo/simple_element/counter.ex:
--------------------------------------------------------------------------------
1 | defmodule Membrane.Demo.SimpleElement.Counter do
2 | @moduledoc """
3 | Membrane element counting incoming buffers.
4 |
5 | Count of buffers divided by `divisor` (passed via `:input` pad options)
6 | is sent as a `{:counter, number}` notification once every `interval`
7 | (passed via element options).
8 | """
9 | use Membrane.Filter
10 |
11 | def_options interval: [
12 | spec: Membrane.Time.non_neg_t(),
13 | default: 1000,
14 | description: """
15 | Amount of the time in milliseconds, telling how often
16 | the count of buffers should be sent and zeroed.
17 | """
18 | ]
19 |
20 | def_input_pad :input,
21 | availability: :always,
22 | flow_control: :manual,
23 | demand_unit: :bytes,
24 | accepted_format: _any,
25 | options: [
26 | divisor: [
27 | type: :integer,
28 | default: 1,
29 | description: "Number by which the counter will be divided before sending notification"
30 | ]
31 | ]
32 |
33 | def_output_pad :output,
34 | availability: :always,
35 | flow_control: :manual,
36 | accepted_format: _any
37 |
38 | @impl true
39 | def handle_init(_ctx, %__MODULE{interval: interval}) do
40 | state = %{
41 | interval: interval,
42 | counter: 0
43 | }
44 |
45 | {[], state}
46 | end
47 |
48 | @impl true
49 | def handle_terminate_request(_ctx, state) do
50 | {[stop_timer: :timer, terminate: :normal], %{state | counter: 0}}
51 | end
52 |
53 | @impl true
54 | def handle_playing(_ctx, state) do
55 | {[start_timer: {:timer, state.interval}], state}
56 | end
57 |
58 | @impl true
59 | def handle_demand(:output, size, :bytes, _context, state) do
60 | {[demand: {:input, size}], state}
61 | end
62 |
63 | @impl true
64 | def handle_buffer(:input, %Membrane.Buffer{} = buffer, _context, state) do
65 | state = %{state | counter: state.counter + 1}
66 | {[buffer: {:output, buffer}], state}
67 | end
68 |
69 | @impl true
70 | def handle_tick(:timer, ctx, state) do
71 | # create the term to send
72 | notification = {
73 | :counter,
74 | div(state.counter, ctx.pads.input.options.divisor)
75 | }
76 |
77 | # reset the counter
78 | new_state = %{state | counter: 0}
79 |
80 | {[notify_parent: notification], new_state}
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/config/dev.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with esbuild to bundle .js and .css sources.
9 | config :rtmp_to_adaptive_hls, RtmpToAdaptiveHlsWeb.Endpoint,
10 | # Binding to loopback ipv4 address prevents access from other machines.
11 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
12 | http: [ip: {127, 0, 0, 1}, port: 4000],
13 | check_origin: false,
14 | code_reloader: true,
15 | debug_errors: true,
16 | secret_key_base: "RJ+YNDINYsAkM4evupc/vq5vMR9PPuJihcWgMB1DNMZYH+jrC3GcwpsLdi8mB61m",
17 | watchers: [
18 | # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
19 | esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
20 | ]
21 |
22 | # ## SSL Support
23 | #
24 | # In order to use HTTPS in development, a self-signed
25 | # certificate can be generated by running the following
26 | # Mix task:
27 | #
28 | # mix phx.gen.cert
29 | #
30 | # Note that this task requires Erlang/OTP 20 or later.
31 | # Run `mix help phx.gen.cert` for more information.
32 | #
33 | # The `http:` config above can be replaced with:
34 | #
35 | # https: [
36 | # port: 4001,
37 | # cipher_suite: :strong,
38 | # keyfile: "priv/cert/selfsigned_key.pem",
39 | # certfile: "priv/cert/selfsigned.pem"
40 | # ],
41 | #
42 | # If desired, both `http:` and `https:` keys can be
43 | # configured to run both http and https servers on
44 | # different ports.
45 |
46 | # Watch static and templates for browser reloading.
47 | config :rtmp_to_adaptive_hls, RtmpToAdaptiveHlsWeb.Endpoint,
48 | live_reload: [
49 | patterns: [
50 | ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
51 | ~r"priv/gettext/.*(po)$",
52 | ~r"lib/rtmp_to_adaptive_hls_web/(live|views)/.*(ex)$",
53 | ~r"lib/rtmp_to_adaptive_hls_web/templates/.*(eex)$"
54 | ]
55 | ]
56 |
57 | # Do not include metadata nor timestamps in development logs
58 | config :logger, :console, format: "[$level] $message\n"
59 |
60 | # Set a higher stacktrace during development. Avoid configuring such
61 | # in production as building large stacktraces may be expensive.
62 | config :phoenix, :stacktrace_depth, 20
63 |
64 | # Initialize plugs at runtime for faster development compilation
65 | config :phoenix, :plug_init_mode, :runtime
66 |
--------------------------------------------------------------------------------
/rtmp_to_hls/assets/css/phoenix.css:
--------------------------------------------------------------------------------
1 | /* Includes some default style for the starter application.
2 | * This can be safely deleted to start fresh.
3 | */
4 |
5 | /* Milligram v1.4.1 https://milligram.github.io
6 | * Copyright (c) 2020 CJ Patoilo Licensed under the MIT license
7 | */
8 |
9 | html {
10 | background-color: #0b0c22;
11 | color: white;
12 | font-family: "Poppins", sans-serif;
13 | }
14 |
15 | a {
16 | text-decoration: none;
17 | color: inherit;
18 | }
19 |
20 | /* General style */
21 | h1{font-size: 3.6rem; line-height: 1.25}
22 | h2{font-size: 2.8rem; line-height: 1.3}
23 | h3{font-size: 2.2rem; letter-spacing: -.08rem; line-height: 1.35}
24 | h4{font-size: 1.8rem; letter-spacing: -.05rem; line-height: 1.5}
25 | h5{font-size: 1.6rem; letter-spacing: 0; line-height: 1.4}
26 | h6{font-size: 1.4rem; letter-spacing: 0; line-height: 1.2}
27 | pre{padding: 1em;}
28 |
29 | .container{
30 | margin: 0 auto;
31 | max-width: 80.0rem;
32 | padding: 0 2.0rem;
33 | position: relative;
34 | width: 100%
35 | }
36 | select {
37 | width: auto;
38 | }
39 |
40 | /* Phoenix promo and logo */
41 | .phx-hero {
42 | text-align: center;
43 | border-bottom: 1px solid #e3e3e3;
44 | border-radius: 6px;
45 | padding: 3em 3em 1em;
46 | margin-bottom: 3rem;
47 | font-weight: 200;
48 | font-size: 120%;
49 | }
50 | .phx-hero input {
51 | background: #ffffff;
52 | }
53 | .phx-logo {
54 | min-width: 300px;
55 | margin: 1rem;
56 | display: block;
57 | }
58 | .phx-logo img {
59 | width: auto;
60 | display: block;
61 | }
62 |
63 | /* Headers */
64 | header {
65 | width: 100%;
66 | margin-bottom: 2rem;
67 | }
68 | header section {
69 | align-items: center;
70 | display: flex;
71 | flex-direction: column;
72 | justify-content: space-between;
73 | }
74 | header section :first-child {
75 | order: 2;
76 | }
77 | header section :last-child {
78 | order: 1;
79 | }
80 | header nav ul,
81 | header nav li {
82 | margin: 0;
83 | padding: 0;
84 | display: block;
85 | text-align: right;
86 | white-space: nowrap;
87 | }
88 | header nav ul {
89 | margin: 1rem;
90 | margin-top: 0;
91 | }
92 | header nav a {
93 | display: block;
94 | }
95 |
96 | @media (min-width: 40.0rem) { /* Small devices (landscape phones, 576px and up) */
97 | header section {
98 | flex-direction: row;
99 | }
100 | header nav ul {
101 | margin: 1rem;
102 | }
103 | .phx-logo {
104 | flex-basis: 527px;
105 | margin: 2rem 1rem;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/assets/css/phoenix.css:
--------------------------------------------------------------------------------
1 | /* Includes some default style for the starter application.
2 | * This can be safely deleted to start fresh.
3 | */
4 |
5 | /* Milligram v1.4.1 https://milligram.github.io
6 | * Copyright (c) 2020 CJ Patoilo Licensed under the MIT license
7 | */
8 |
9 | html {
10 | background-color: #0b0c22;
11 | color: white;
12 | font-family: "Poppins", sans-serif;
13 | }
14 |
15 | a {
16 | text-decoration: none;
17 | color: inherit;
18 | }
19 |
20 | /* General style */
21 | h1{font-size: 3.6rem; line-height: 1.25}
22 | h2{font-size: 2.8rem; line-height: 1.3}
23 | h3{font-size: 2.2rem; letter-spacing: -.08rem; line-height: 1.35}
24 | h4{font-size: 1.8rem; letter-spacing: -.05rem; line-height: 1.5}
25 | h5{font-size: 1.6rem; letter-spacing: 0; line-height: 1.4}
26 | h6{font-size: 1.4rem; letter-spacing: 0; line-height: 1.2}
27 | pre{padding: 1em;}
28 |
29 | .container{
30 | margin: 0 auto;
31 | max-width: 80.0rem;
32 | padding: 0 2.0rem;
33 | position: relative;
34 | width: 100%
35 | }
36 | select {
37 | width: auto;
38 | }
39 |
40 | /* Phoenix promo and logo */
41 | .phx-hero {
42 | text-align: center;
43 | border-bottom: 1px solid #e3e3e3;
44 | border-radius: 6px;
45 | padding: 3em 3em 1em;
46 | margin-bottom: 3rem;
47 | font-weight: 200;
48 | font-size: 120%;
49 | }
50 | .phx-hero input {
51 | background: #ffffff;
52 | }
53 | .phx-logo {
54 | min-width: 300px;
55 | margin: 1rem;
56 | display: block;
57 | }
58 | .phx-logo img {
59 | width: auto;
60 | display: block;
61 | }
62 |
63 | /* Headers */
64 | header {
65 | width: 100%;
66 | margin-bottom: 2rem;
67 | }
68 | header section {
69 | align-items: center;
70 | display: flex;
71 | flex-direction: column;
72 | justify-content: space-between;
73 | }
74 | header section :first-child {
75 | order: 2;
76 | }
77 | header section :last-child {
78 | order: 1;
79 | }
80 | header nav ul,
81 | header nav li {
82 | margin: 0;
83 | padding: 0;
84 | display: block;
85 | text-align: right;
86 | white-space: nowrap;
87 | }
88 | header nav ul {
89 | margin: 1rem;
90 | margin-top: 0;
91 | }
92 | header nav a {
93 | display: block;
94 | }
95 |
96 | @media (min-width: 40.0rem) { /* Small devices (landscape phones, 576px and up) */
97 | header section {
98 | flex-direction: row;
99 | }
100 | header nav ul {
101 | margin: 1rem;
102 | }
103 | .phx-logo {
104 | flex-basis: 527px;
105 | margin: 2rem 1rem;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/simple_element/README.md:
--------------------------------------------------------------------------------
1 | # Simple Membrane Element
2 |
3 | This demo shows how to create a simple Membrane element and plug it into a pipeline.
4 |
5 | ## Prerequisites
6 |
7 | To run the demo, you need [Elixir installed](https://elixir-lang.org/install.html) on your machine (it's best to use a version manager, like `asdf`).
8 |
9 | If you are running the demo on Linux, make sure to have the following dependencies installed in your system:
10 | - portaudio19-dev,
11 | - pkg-config
12 |
13 | On Ubuntu, you can install them with the following command:
14 | ```shell
15 | apt install portaudio19-dev pkg-config
16 | ```
17 |
18 | ## Running the demo
19 |
20 | To run the demo, clone the `membrane_demo` repository and checkout to the demo directory:
21 |
22 | ```shell
23 | git clone https://github.com/membraneframework/membrane_demo
24 | cd membrane_demo/simple_element
25 | ```
26 |
27 | Then you need to download the dependencies of the mix project:
28 |
29 | ```shell
30 | mix deps.get
31 | ```
32 |
33 | You may be asked to install `Hex` and then `rebar3`.
34 |
35 | To start the demo pipeline run `mix run --no-halt run.exs` or type the following commands into an IEx shell (started by `iex -S mix`):
36 |
37 | ```elixir
38 | Membrane.Pipeline.start_link(Membrane.Demo.SimpleElement.Pipeline, "sample.mp3")
39 | ```
40 |
41 | You should hear the audio sample playing and see the number of buffers processed being periodically printed to the console.
42 |
43 | Should there be any errors when compiling the script's dependencies, you may need to install the some dependencies manually on your system:
44 | * [PortAudio](https://www.portaudio.com/) - which we use to play the audio
45 | * [FFmpeg](https://ffmpeg.org/) - which we use to resample the audio
46 | * [MAD](https://www.underbit.com/products/mad/) - which is used to decode audio
47 |
48 | ## How it works
49 |
50 | The pipeline takes a sample mp3 file, decodes it, and plays the audio.
51 | The simple `counter` element is responsible for counting the number of buffers
52 | passing through it and periodically prints the number of buffers processed to the console.
53 |
54 | The element is plugged in just before the audio player element in the pipeline.
55 |
56 | ## Copyright and License
57 |
58 | Copyright 2018, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
59 |
60 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
61 |
62 | Licensed under the [Apache License, Version 2.0](LICENSE)
63 |
--------------------------------------------------------------------------------
/webrtc_live_view/config/dev.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we can use it
8 | # to bundle .js and .css sources.
9 | config :webrtc_live_view, WebrtcLiveViewWeb.Endpoint,
10 | # Binding to loopback ipv4 address prevents access from other machines.
11 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
12 | http: [ip: {127, 0, 0, 1}, port: 4000],
13 | check_origin: false,
14 | code_reloader: true,
15 | debug_errors: true,
16 | secret_key_base: "hvkMsB0coySlkK38GdeYwpOMBEFJFmK/ogj8SD791OFVAxlk89y1fOGkumXlYgIH",
17 | watchers: [
18 | esbuild: {Esbuild, :install_and_run, [:webrtc_live_view, ~w(--sourcemap=inline --watch)]}
19 | ]
20 |
21 | # ## SSL Support
22 | #
23 | # In order to use HTTPS in development, a self-signed
24 | # certificate can be generated by running the following
25 | # Mix task:
26 | #
27 | # mix phx.gen.cert
28 | #
29 | # Run `mix help phx.gen.cert` for more information.
30 | #
31 | # The `http:` config above can be replaced with:
32 | #
33 | # https: [
34 | # port: 4001,
35 | # cipher_suite: :strong,
36 | # keyfile: "priv/cert/selfsigned_key.pem",
37 | # certfile: "priv/cert/selfsigned.pem"
38 | # ],
39 | #
40 | # If desired, both `http:` and `https:` keys can be
41 | # configured to run both http and https servers on
42 | # different ports.
43 |
44 | # Watch static and templates for browser reloading.
45 | config :webrtc_live_view, WebrtcLiveViewWeb.Endpoint,
46 | live_reload: [
47 | patterns: [
48 | ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",
49 | ~r"lib/webrtc_live_view_web/(controllers|live|components)/.*(ex|heex)$"
50 | ]
51 | ]
52 |
53 | # Enable dev routes for dashboard and mailbox
54 | config :webrtc_live_view, dev_routes: true
55 |
56 | # Do not include metadata nor timestamps in development logs
57 | config :logger, :console, format: "[$level] $message\n"
58 |
59 | # Set a higher stacktrace during development. Avoid configuring such
60 | # in production as building large stacktraces may be expensive.
61 | config :phoenix, :stacktrace_depth, 20
62 |
63 | # Initialize plugs at runtime for faster development compilation
64 | config :phoenix, :plug_init_mode, :runtime
65 |
66 | config :phoenix_live_view,
67 | # Include HEEx debug annotations as HTML comments in rendered markup
68 | debug_heex_annotations: true,
69 | # Enable helpful, but potentially expensive runtime checks
70 | enable_expensive_runtime_checks: true
71 |
--------------------------------------------------------------------------------
/rtsp_to_hls/README.md:
--------------------------------------------------------------------------------
1 | # RTSP to HLS
2 |
3 | This demo demonstrates receiving the RTSP stream and converting it to the HLS stream.
4 |
5 | ## Prerequisites and running the demo
6 |
7 | Below is the instruction for the installation of required dependencies and how to run this demo on various operating systems:
8 |
9 | ### Prerequisites
10 | Make sure you have Elixir installed on your machine. For installation details, see: https://elixir-lang.org/install.html
11 |
12 | ### Running the demo
13 |
14 | To run the demo, clone the `membrane_demo` repository and checkout to the demo directory:
15 |
16 | ```shell
17 | git clone https://github.com/membraneframework/membrane_demo
18 | cd membrane_demo/rtsp_to_hls
19 | ```
20 |
21 | Then you need to download the dependencies of the mix project:
22 |
23 | ```shell
24 | mix deps.get
25 | ```
26 | You may be asked to install `Hex` and then `rebar3`.
27 |
28 |
29 | Now you need to compile the project:
30 |
31 | ```shell
32 | mix compile
33 | ```
34 |
35 | ### Running simple RTSP server
36 | We will use a custom implementation of a simple RTSP server that you will be able to run with the `server.exs` script.
37 | That script will setup a server listening for RTSP connection on `rtsp://localhost:8554`.
38 |
39 | You can run the server with the following command:
40 | ```shell
41 | mix run server.exs
42 | ```
43 |
44 | ### Running RTSP to HLS conversion
45 | After you have launched the RTSP server, you can run the pipeline that performs RTSP to HLS conversion:
46 |
47 | ```shell
48 | mix run rtsp_to_hls.exs
49 | ```
50 |
51 | By default the pipeline is configured to connect to the RTSP server launched in the previous step of this manual.
52 | You can change the default pipeline configuration in the `rtsp_to_hls.exs` script implementation by modification of the following
53 | attributes:
54 |
55 | ##### rtsp_to_hls.exs
56 |
57 | ```elixir
58 | rtsp_stream_url = "rtsp://localhost:8554/livestream"
59 | output_path = "hls_output"
60 | rtp_port = 20000
61 | ```
62 |
63 | After a moment the pipeline will start generating HLS output files (by default in the `hls_output` directory).
64 | Once that happens, an HTTP server is started and now you can watch the stream by visiting the following page in your browser:
65 | ```
66 | http://localhost:8000/stream.html
67 | ```
68 |
69 | Copyright 2022, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
70 |
71 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
72 |
73 | Licensed under the [Apache License, Version 2.0](LICENSE)
74 |
--------------------------------------------------------------------------------
/simple_element/.gitignore:
--------------------------------------------------------------------------------
1 | compile_commands.json
2 | .elixir_ls
3 |
4 | # Created by https://www.gitignore.io/api/c,vim,linux,macos,elixir,windows,visualstudiocode
5 |
6 | bundlex.sh
7 | bundlex.bat
8 |
9 | ### C ###
10 | # Prerequisites
11 | *.d
12 |
13 | # Object files
14 | *.o
15 | *.ko
16 | *.obj
17 | *.elf
18 |
19 | # Linker output
20 | *.ilk
21 | *.map
22 | *.exp
23 |
24 | # Precompiled Headers
25 | *.gch
26 | *.pch
27 |
28 | # Libraries
29 | *.lib
30 | *.a
31 | *.la
32 | *.lo
33 |
34 | # Shared objects (inc. Windows DLLs)
35 | *.dll
36 | *.so
37 | *.so.*
38 | *.dylib
39 |
40 | # Executables
41 | *.exe
42 | *.out
43 | *.app
44 | *.i*86
45 | *.x86_64
46 | *.hex
47 |
48 | # Debug files
49 | *.dSYM/
50 | *.su
51 | *.idb
52 | *.pdb
53 |
54 | # Kernel Module Compile Results
55 | *.mod*
56 | *.cmd
57 | .tmp_versions/
58 | modules.order
59 | Module.symvers
60 | Mkfile.old
61 | dkms.conf
62 |
63 | ### Elixir ###
64 | /_build
65 | /cover
66 | /deps
67 | /doc
68 | /.fetch
69 | erl_crash.dump
70 | *.ez
71 | *.beam
72 |
73 | ### Elixir Patch ###
74 | ### Linux ###
75 | *~
76 |
77 | # temporary files which can be created if a process still has a handle open of a deleted file
78 | .fuse_hidden*
79 |
80 | # KDE directory preferences
81 | .directory
82 |
83 | # Linux trash folder which might appear on any partition or disk
84 | .Trash-*
85 |
86 | # .nfs files are created when an open file is removed but is still being accessed
87 | .nfs*
88 |
89 | ### macOS ###
90 | *.DS_Store
91 | .AppleDouble
92 | .LSOverride
93 |
94 | # Icon must end with two \r
95 | Icon
96 |
97 | # Thumbnails
98 | ._*
99 |
100 | # Files that might appear in the root of a volume
101 | .DocumentRevisions-V100
102 | .fseventsd
103 | .Spotlight-V100
104 | .TemporaryItems
105 | .Trashes
106 | .VolumeIcon.icns
107 | .com.apple.timemachine.donotpresent
108 |
109 | # Directories potentially created on remote AFP share
110 | .AppleDB
111 | .AppleDesktop
112 | Network Trash Folder
113 | Temporary Items
114 | .apdisk
115 |
116 | ### Vim ###
117 | # swap
118 | .sw[a-p]
119 | .*.sw[a-p]
120 | # session
121 | Session.vim
122 | # temporary
123 | .netrwhist
124 | # auto-generated tag files
125 | tags
126 |
127 | ### VisualStudioCode ###
128 | .vscode/*
129 | !.vscode/settings.json
130 | !.vscode/tasks.json
131 | !.vscode/launch.json
132 | !.vscode/extensions.json
133 | .history
134 |
135 | ### Windows ###
136 | # Windows thumbnail cache files
137 | Thumbs.db
138 | ehthumbs.db
139 | ehthumbs_vista.db
140 |
141 | # Folder config file
142 | Desktop.ini
143 |
144 | # Recycle Bin used on file shares
145 | $RECYCLE.BIN/
146 |
147 | # Windows Installer files
148 | *.cab
149 | *.msi
150 | *.msm
151 | *.msp
152 |
153 | # Windows shortcuts
154 | *.lnk
155 |
156 |
157 | # End of https://www.gitignore.io/api/c,vim,linux,macos,elixir,windows,visualstudiocode
158 |
--------------------------------------------------------------------------------
/simple_pipeline/.gitignore:
--------------------------------------------------------------------------------
1 | compile_commands.json
2 | .elixir_ls
3 |
4 | # Created by https://www.gitignore.io/api/c,vim,linux,macos,elixir,windows,visualstudiocode
5 |
6 | bundlex.sh
7 | bundlex.bat
8 |
9 | ### C ###
10 | # Prerequisites
11 | *.d
12 |
13 | # Object files
14 | *.o
15 | *.ko
16 | *.obj
17 | *.elf
18 |
19 | # Linker output
20 | *.ilk
21 | *.map
22 | *.exp
23 |
24 | # Precompiled Headers
25 | *.gch
26 | *.pch
27 |
28 | # Libraries
29 | *.lib
30 | *.a
31 | *.la
32 | *.lo
33 |
34 | # Shared objects (inc. Windows DLLs)
35 | *.dll
36 | *.so
37 | *.so.*
38 | *.dylib
39 |
40 | # Executables
41 | *.exe
42 | *.out
43 | *.app
44 | *.i*86
45 | *.x86_64
46 | *.hex
47 |
48 | # Debug files
49 | *.dSYM/
50 | *.su
51 | *.idb
52 | *.pdb
53 |
54 | # Kernel Module Compile Results
55 | *.mod*
56 | *.cmd
57 | .tmp_versions/
58 | modules.order
59 | Module.symvers
60 | Mkfile.old
61 | dkms.conf
62 |
63 | ### Elixir ###
64 | /_build
65 | /cover
66 | /deps
67 | /doc
68 | /.fetch
69 | erl_crash.dump
70 | *.ez
71 | *.beam
72 |
73 | ### Elixir Patch ###
74 | ### Linux ###
75 | *~
76 |
77 | # temporary files which can be created if a process still has a handle open of a deleted file
78 | .fuse_hidden*
79 |
80 | # KDE directory preferences
81 | .directory
82 |
83 | # Linux trash folder which might appear on any partition or disk
84 | .Trash-*
85 |
86 | # .nfs files are created when an open file is removed but is still being accessed
87 | .nfs*
88 |
89 | ### macOS ###
90 | *.DS_Store
91 | .AppleDouble
92 | .LSOverride
93 |
94 | # Icon must end with two \r
95 | Icon
96 |
97 | # Thumbnails
98 | ._*
99 |
100 | # Files that might appear in the root of a volume
101 | .DocumentRevisions-V100
102 | .fseventsd
103 | .Spotlight-V100
104 | .TemporaryItems
105 | .Trashes
106 | .VolumeIcon.icns
107 | .com.apple.timemachine.donotpresent
108 |
109 | # Directories potentially created on remote AFP share
110 | .AppleDB
111 | .AppleDesktop
112 | Network Trash Folder
113 | Temporary Items
114 | .apdisk
115 |
116 | ### Vim ###
117 | # swap
118 | .sw[a-p]
119 | .*.sw[a-p]
120 | # session
121 | Session.vim
122 | # temporary
123 | .netrwhist
124 | # auto-generated tag files
125 | tags
126 |
127 | ### VisualStudioCode ###
128 | .vscode/*
129 | !.vscode/settings.json
130 | !.vscode/tasks.json
131 | !.vscode/launch.json
132 | !.vscode/extensions.json
133 | .history
134 |
135 | ### Windows ###
136 | # Windows thumbnail cache files
137 | Thumbs.db
138 | ehthumbs.db
139 | ehthumbs_vista.db
140 |
141 | # Folder config file
142 | Desktop.ini
143 |
144 | # Recycle Bin used on file shares
145 | $RECYCLE.BIN/
146 |
147 | # Windows Installer files
148 | *.cab
149 | *.msi
150 | *.msm
151 | *.msp
152 |
153 | # Windows shortcuts
154 | *.lnk
155 |
156 |
157 | # End of https://www.gitignore.io/api/c,vim,linux,macos,elixir,windows,visualstudiocode
158 |
--------------------------------------------------------------------------------
/camera_to_hls/camera_to_hls.exs:
--------------------------------------------------------------------------------
1 | require Logger
2 | Logger.configure(level: :info)
3 |
4 | Mix.install([
5 | {:membrane_core, "~> 1.0"},
6 | {:membrane_camera_capture_plugin, "~> 0.7.2"},
7 | {:membrane_ffmpeg_swscale_plugin, "~> 0.15.1"},
8 | {:membrane_h264_ffmpeg_plugin, "~> 0.31.6"},
9 | {:membrane_http_adaptive_stream_plugin, "~> 0.18.4"}
10 | ])
11 |
12 | defmodule CameraToHls do
13 | use Membrane.Pipeline
14 |
15 | @impl true
16 | def handle_init(_ctx, _opts) do
17 | # Captures video from the camera (raw video, depending on camera/os)
18 | spec =
19 | child(:source, Membrane.CameraCapture)
20 | # Converts pixel format to I420 (this is still a raw video)
21 | |> child(:converter, %Membrane.FFmpeg.SWScale.PixelFormatConverter{format: :I420})
22 | # Takes raw video in I420 pixel format and encodes it into H264.
23 | # The baseline profile is usually most suitable for live streaming
24 | |> child(:encoder, %Membrane.H264.FFmpeg.Encoder{profile: :baseline})
25 | # Muxes H264 into CMAF (short, mp4-like chunks) and generates
26 | # an HLS playlist.
27 | |> via_in(:input,
28 | options: [
29 | encoding: :H264,
30 | track_name: "my_track",
31 | segment_duration: Membrane.Time.seconds(5)
32 | ]
33 | )
34 | |> child(:hls_sink, %Membrane.HTTPAdaptiveStream.SinkBin{
35 | manifest_module: Membrane.HTTPAdaptiveStream.HLS,
36 | storage: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{directory: "output"},
37 | target_window_duration: Membrane.Time.seconds(10)
38 | })
39 |
40 | {[spec: spec], %{}}
41 | end
42 |
43 | @impl true
44 | def handle_child_notification({:track_playable, _track_info}, :hls_sink, _context, state) do
45 | send(:script, :playlist_ready)
46 | {[], state}
47 | end
48 |
49 | @impl true
50 | def handle_child_notification(_notification, _child, _context, state) do
51 | {[], state}
52 | end
53 | end
54 |
55 | # On CI we just check if the script compiles
56 | if System.get_env("CI") == "true" do
57 | Logger.info("CI=true, exiting")
58 | exit(:normal)
59 | end
60 |
61 | File.rm_rf!("output")
62 | File.mkdir!("output")
63 |
64 | Process.register(self(), :script)
65 |
66 | Logger.info("Starting the pipeline")
67 | {:ok, _supervisor, _pipeline} = Membrane.Pipeline.start_link(CameraToHls)
68 |
69 | Logger.info("Waiting for the playlist to be ready")
70 |
71 | receive do
72 | :playlist_ready -> :ok
73 | end
74 |
75 | Logger.info("Starting HTTP server")
76 | :ok = :inets.start()
77 |
78 | {:ok, _server} =
79 | :inets.start(:httpd,
80 | bind_address: ~c"localhost",
81 | port: 8000,
82 | document_root: ~c".",
83 | server_name: ~c"camera_to_hls",
84 | server_root: "/tmp"
85 | )
86 |
87 | Logger.info("Playback available at http://localhost:8000/stream.html")
88 |
89 | Process.sleep(:infinity)
90 |
--------------------------------------------------------------------------------
/rtmp_to_hls/lib/rtmp_to_hls_web.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToHlsWeb do
2 | @moduledoc """
3 | The entrypoint for defining your web interface, such
4 | as controllers, views, channels and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use RtmpToHlsWeb, :controller
9 | use RtmpToHlsWeb, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below. Instead, define any helper function in modules
17 | and import those modules here.
18 | """
19 |
20 | def controller do
21 | quote do
22 | use Phoenix.Controller, namespace: RtmpToHlsWeb
23 |
24 | import Plug.Conn
25 | import RtmpToHlsWeb.Gettext
26 | alias RtmpToHlsWeb.Router.Helpers, as: Routes
27 | end
28 | end
29 |
30 | def view do
31 | quote do
32 | use Phoenix.View,
33 | root: "lib/rtmp_to_hls_web/templates",
34 | namespace: RtmpToHlsWeb
35 |
36 | # Import convenience functions from controllers
37 | import Phoenix.Controller,
38 | only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
39 |
40 | # Include shared imports and aliases for views
41 | unquote(view_helpers())
42 | end
43 | end
44 |
45 | def live_view do
46 | quote do
47 | use Phoenix.LiveView,
48 | layout: {RtmpToHlsWeb.LayoutView, "live.html"}
49 |
50 | unquote(view_helpers())
51 | end
52 | end
53 |
54 | def live_component do
55 | quote do
56 | use Phoenix.LiveComponent
57 |
58 | unquote(view_helpers())
59 | end
60 | end
61 |
62 | def router do
63 | quote do
64 | use Phoenix.Router
65 |
66 | import Plug.Conn
67 | import Phoenix.Controller
68 | import Phoenix.LiveView.Router
69 | end
70 | end
71 |
72 | def channel do
73 | quote do
74 | use Phoenix.Channel
75 | import RtmpToHlsWeb.Gettext
76 | end
77 | end
78 |
79 | defp view_helpers do
80 | quote do
81 | # Use all HTML functionality (forms, tags, etc)
82 | use Phoenix.HTML
83 |
84 | # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
85 | import Phoenix.LiveView.Helpers
86 |
87 | # Import basic rendering functionality (render, render_layout, etc)
88 | import Phoenix.View
89 |
90 | import RtmpToHlsWeb.ErrorHelpers
91 | import RtmpToHlsWeb.Gettext
92 | alias RtmpToHlsWeb.Router.Helpers, as: Routes
93 | end
94 | end
95 |
96 | @doc """
97 | When used, dispatch to the appropriate controller/view/etc.
98 | """
99 | defmacro __using__(which) when is_atom(which) do
100 | apply(__MODULE__, which, [])
101 | end
102 | end
103 |
--------------------------------------------------------------------------------
/rtmp_to_adaptive_hls/lib/rtmp_to_adaptive_hls_web.ex:
--------------------------------------------------------------------------------
1 | defmodule RtmpToAdaptiveHlsWeb do
2 | @moduledoc """
3 | The entrypoint for defining your web interface, such
4 | as controllers, views, channels and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use RtmpToAdaptiveHlsWeb, :controller
9 | use RtmpToAdaptiveHlsWeb, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below. Instead, define any helper function in modules
17 | and import those modules here.
18 | """
19 |
20 | def controller do
21 | quote do
22 | use Phoenix.Controller, namespace: RtmpToAdaptiveHlsWeb
23 |
24 | import Plug.Conn
25 | import RtmpToAdaptiveHlsWeb.Gettext
26 | alias RtmpToAdaptiveHlsWeb.Router.Helpers, as: Routes
27 | end
28 | end
29 |
30 | def view do
31 | quote do
32 | use Phoenix.View,
33 | root: "lib/rtmp_to_adaptive_hls_web/templates",
34 | namespace: RtmpToAdaptiveHlsWeb
35 |
36 | # Import convenience functions from controllers
37 | import Phoenix.Controller,
38 | only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
39 |
40 | # Include shared imports and aliases for views
41 | unquote(view_helpers())
42 | end
43 | end
44 |
45 | def live_view do
46 | quote do
47 | use Phoenix.LiveView,
48 | layout: {RtmpToAdaptiveHlsWeb.LayoutView, "live.html"}
49 |
50 | unquote(view_helpers())
51 | end
52 | end
53 |
54 | def live_component do
55 | quote do
56 | use Phoenix.LiveComponent
57 |
58 | unquote(view_helpers())
59 | end
60 | end
61 |
62 | def router do
63 | quote do
64 | use Phoenix.Router
65 |
66 | import Plug.Conn
67 | import Phoenix.Controller
68 | import Phoenix.LiveView.Router
69 | end
70 | end
71 |
72 | def channel do
73 | quote do
74 | use Phoenix.Channel
75 | import RtmpToAdaptiveHlsWeb.Gettext
76 | end
77 | end
78 |
79 | defp view_helpers do
80 | quote do
81 | # Use all HTML functionality (forms, tags, etc)
82 | use Phoenix.HTML
83 |
84 | # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
85 | import Phoenix.LiveView.Helpers
86 |
87 | # Import basic rendering functionality (render, render_layout, etc)
88 | import Phoenix.View
89 |
90 | import RtmpToAdaptiveHlsWeb.ErrorHelpers
91 | import RtmpToAdaptiveHlsWeb.Gettext
92 | alias RtmpToAdaptiveHlsWeb.Router.Helpers, as: Routes
93 | end
94 | end
95 |
96 | @doc """
97 | When used, dispatch to the appropriate controller/view/etc.
98 | """
99 | defmacro __using__(which) when is_atom(which) do
100 | apply(__MODULE__, which, [])
101 | end
102 | end
103 |
--------------------------------------------------------------------------------
/webrtc_live_view/priv/static/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/rtp_to_hls/send.exs:
--------------------------------------------------------------------------------
1 | require Logger
2 | Logger.configure(level: :info)
3 |
4 | Mix.install([
5 | {:membrane_core, "~> 1.0"},
6 | {:membrane_hackney_plugin, "~> 0.11.0"},
7 | {:membrane_realtimer_plugin, "~> 0.9.0"},
8 | {:membrane_h26x_plugin, "~> 0.10.0"},
9 | {:membrane_funnel_plugin, "~> 0.9.0"},
10 | {:membrane_mp4_plugin, "~> 0.34.1"},
11 | {:membrane_rtp_plugin, "~> 0.31.0"},
12 | {:membrane_rtp_h264_plugin, "~> 0.20.0"},
13 | {:membrane_rtp_aac_plugin, "~> 0.9.1"},
14 | {:membrane_udp_plugin, "~> 0.14.0"},
15 | {:membrane_aac_plugin, "~> 0.19.0"}
16 | ])
17 |
18 | defmodule SendRTP do
19 | use Membrane.Pipeline
20 |
21 | @samples_url "https://raw.githubusercontent.com/membraneframework/static/gh-pages/samples/big-buck-bunny/"
22 |
23 | @mp4_url @samples_url <> "bun33s.mp4"
24 |
25 | @impl true
26 | def handle_init(_ctx, _opts) do
27 | spec =
28 | child(:video_src, %Membrane.Hackney.Source{
29 | location: @mp4_url,
30 | hackney_opts: [follow_redirect: true]
31 | })
32 | |> child(:mp4, Membrane.MP4.Demuxer.ISOM)
33 |
34 | {[spec: spec], %{}}
35 | end
36 |
37 | @impl true
38 | def handle_child_notification({:new_tracks, tracks}, :mp4, _ctx, state) do
39 | audio_ssrc = 1234
40 | video_ssrc = 1235
41 | {audio_id, _format} = Enum.find(tracks, fn {_id, %format{}} -> format == Membrane.AAC end)
42 | {video_id, _format} = Enum.find(tracks, fn {_id, %format{}} -> format == Membrane.H264 end)
43 |
44 | spec = [
45 | get_child(:mp4)
46 | |> via_out(Pad.ref(:output, video_id))
47 | |> child(:video_parser, %Membrane.H264.Parser{
48 | output_stream_structure: :annexb,
49 | output_alignment: :nalu
50 | })
51 | |> child(:video_realtimer, Membrane.Realtimer)
52 | |> child(:video_payloader, Membrane.RTP.H264.Payloader)
53 | |> child(:rtp_muxer, Membrane.RTP.Muxer)
54 | |> child(:udp, %Membrane.UDP.Sink{
55 | destination_port_no: 5000,
56 | destination_address: {127, 0, 0, 1}
57 | }),
58 | get_child(:mp4)
59 | |> via_out(Pad.ref(:output, audio_id))
60 | |> child(:audio_parser, %Membrane.AAC.Parser{out_encapsulation: :none})
61 | |> child(:audio_realtimer, Membrane.Realtimer)
62 | |> child(:audio_payloader, %Membrane.RTP.AAC.Payloader{frames_per_packet: 1, mode: :hbr})
63 | |> get_child(:rtp_muxer)
64 | ]
65 |
66 | {[spec: spec], state}
67 | end
68 |
69 | @impl true
70 | def handle_child_notification(_notification, _child, _ctx, state) do
71 | {[], state}
72 | end
73 |
74 | @impl true
75 | def handle_element_end_of_stream(:udp, :input, _ctx, state) do
76 | {[terminate: :normal], state}
77 | end
78 |
79 | @impl true
80 | def handle_element_end_of_stream(_element, _pad, _ctx, state) do
81 | {[], state}
82 | end
83 | end
84 |
85 | {:ok, supervisor, _pipeline} = Membrane.Pipeline.start_link(SendRTP)
86 |
87 | monitor = Process.monitor(supervisor)
88 |
89 | receive do
90 | {:DOWN, ^monitor, _kind, _pid, :normal} -> :ok
91 | end
92 |
--------------------------------------------------------------------------------
/webrtc_live_view/lib/webrtc_live_view_web.ex:
--------------------------------------------------------------------------------
1 | defmodule WebrtcLiveViewWeb do
2 | @moduledoc """
3 | The entrypoint for defining your web interface, such
4 | as controllers, components, channels, and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use WebrtcLiveViewWeb, :controller
9 | use WebrtcLiveViewWeb, :html
10 |
11 | The definitions below will be executed for every controller,
12 | component, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below. Instead, define additional modules and import
17 | those modules here.
18 | """
19 |
20 | def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)
21 |
22 | def router do
23 | quote do
24 | use Phoenix.Router, helpers: false
25 |
26 | # Import common connection and controller functions to use in pipelines
27 | import Plug.Conn
28 | import Phoenix.Controller
29 | import Phoenix.LiveView.Router
30 | end
31 | end
32 |
33 | def channel do
34 | quote do
35 | use Phoenix.Channel
36 | end
37 | end
38 |
39 | def controller do
40 | quote do
41 | use Phoenix.Controller,
42 | formats: [:html, :json],
43 | layouts: [html: WebrtcLiveViewWeb.Layouts]
44 |
45 | import Plug.Conn
46 |
47 | unquote(verified_routes())
48 | end
49 | end
50 |
51 | def live_view do
52 | quote do
53 | use Phoenix.LiveView,
54 | layout: {WebrtcLiveViewWeb.Layouts, :app}
55 |
56 | unquote(html_helpers())
57 | end
58 | end
59 |
60 | def live_component do
61 | quote do
62 | use Phoenix.LiveComponent
63 |
64 | unquote(html_helpers())
65 | end
66 | end
67 |
68 | def html do
69 | quote do
70 | use Phoenix.Component
71 |
72 | # Import convenience functions from controllers
73 | import Phoenix.Controller,
74 | only: [get_csrf_token: 0, view_module: 1, view_template: 1]
75 |
76 | # Include general helpers for rendering HTML
77 | unquote(html_helpers())
78 | end
79 | end
80 |
81 | defp html_helpers do
82 | quote do
83 | # HTML escaping functionality
84 | import Phoenix.HTML
85 | # Core UI components
86 | import WebrtcLiveViewWeb.CoreComponents
87 |
88 | # Shortcut for generating JS commands
89 | alias Phoenix.LiveView.JS
90 |
91 | # Routes generation with the ~p sigil
92 | unquote(verified_routes())
93 | end
94 | end
95 |
96 | def verified_routes do
97 | quote do
98 | use Phoenix.VerifiedRoutes,
99 | endpoint: WebrtcLiveViewWeb.Endpoint,
100 | router: WebrtcLiveViewWeb.Router,
101 | statics: WebrtcLiveViewWeb.static_paths()
102 | end
103 | end
104 |
105 | @doc """
106 | When used, dispatch to the appropriate controller/live_view/etc.
107 | """
108 | defmacro __using__(which) when is_atom(which) do
109 | apply(__MODULE__, which, [])
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/rtmp_to_hls/README.md:
--------------------------------------------------------------------------------
1 | # RTMP to HLS
2 |
3 | This demo is an application that is capable of receiving an RTMP stream, converting it to HLS, and publishing via an HTTP server so that it can be played with a web player.
4 |
5 | ## Possible use cases
6 |
7 | The application presented in this demo could be used in the following scenario.
8 | There is one person, the so-called "streamer", and multiple "viewers", who want to see the stream of multimedia sent by the streamer.
9 | The streamer sends multimedia using RTMP, a protocol supported by popular streaming software (i.e. OBS). Such an RTMP stream is then converted into HLS and published by the HTTP server, which is capable of handling many HTTP requests. The viewers can then play the multimedia delivered with HTTP. Such a solution scales well because the streamer doesn't have a direct connection with any of the viewers.
10 |
11 | ## Architecture of the solution
12 |
13 | The system is divided into two parts:
14 |
15 | - the server, which is responsible for receiving the RTMP stream, converting it into HLS, and then publishing the created files with an HTTP server,
16 | - the client, responsible for playing the incoming HLS stream.
17 |
18 | ### Server
19 |
20 | The internal architecture of the server is presented below:
21 | 
22 |
23 | ### Client
24 |
25 | The client is just a simple JavaScript application using the [HLS.js](https://github.com/video-dev/hls.js/) web player.
26 |
27 | ## Running the demo
28 |
29 | To run the demo, you'll need to have [Elixir installed](https://elixir-lang.org/install.html). Then, download the dependencies and start the server:
30 |
31 | ```shell
32 | mix deps.get
33 | mix phx.server
34 | ```
35 |
36 | The server will be waiting for an RTMP stream on `localhost:9006`, and the client of the application will be available on `localhost:4000`.
37 |
38 | ## Exemplary stream generation with OBS
39 |
40 | You can send RTMP stream onto `localhost:9006` with your favorite streaming tool. Below we present how to generate an RTMP stream with
41 | [OBS](https://obsproject.com).
42 | Once you have OBS installed, you can perform the following steps:
43 |
44 | 1. Open the OBS application
45 | 2. Open the `Settings` windows
46 | 3. Go to the `Stream` tab, set `Service` to `Custom...` and then set the value in the `Server` field to `rtmp://localhost:9006` (the address where the server is waiting for the stream)
47 | 4. Go to the `Output`, set output mode to `Advanced`, and set `Keyframe Interval` to 2 seconds.
48 | 5. Finally, you can go back to the main window and start streaming with the `Start Streaming` button.
49 |
50 | Below you can see how to set the appropriate settings (steps 2. and 3. from the list of steps above):
51 |
52 | 
53 |
54 | ## Copyright and License
55 |
56 | Copyright 2018, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
57 |
58 | [](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane)
59 |
60 | Licensed under the [Apache License, Version 2.0](LICENSE)
61 |
--------------------------------------------------------------------------------
/livebooks/audio_mixer/audio_mixer.livemd:
--------------------------------------------------------------------------------
1 | # Mixing audio files
2 |
3 | ```elixir
4 | File.cd(__DIR__)
5 | Logger.configure(level: :error)
6 |
7 | Mix.install([
8 | {:membrane_core, "~> 1.0"},
9 | {:membrane_audio_mix_plugin, "~> 0.16.0"},
10 | {:membrane_file_plugin, "~> 0.17.0"},
11 | {:membrane_mp3_mad_plugin, "~> 0.18.2"},
12 | {:membrane_ffmpeg_swresample_plugin, "~> 0.20.2"},
13 | {:membrane_aac_fdk_plugin, "~> 0.18.8"},
14 | {:membrane_kino_plugin, github: "membraneframework-labs/membrane_kino_plugin", tag: "v0.3.2"},
15 | {:membrane_tee_plugin, "~> 0.12.0"}
16 | ])
17 | ```
18 |
19 | ## Description
20 |
21 | This is an example of mixing multiple short "beep" sound into background music, one by one, every second.
22 |
23 | ## Pipeline definition
24 |
25 | Define all constants.
26 |
27 | ```elixir
28 | n_beeps = 30
29 | beep_filepath = "./assets/beep.aac"
30 | background_filepath = "./assets/sample.mp3"
31 | :ok
32 | ```
33 |
34 | The file's "beep" sound input is decoded from `AAC` and split into separate inputs using the `Tee` element. These inputs are then filled into the mixer with corresponding time offsets.
35 |
36 | ```elixir
37 | import Membrane.ChildrenSpec
38 |
39 | alias Membrane.{File, AAC, Tee, Time}
40 |
41 | beep_audio_input =
42 | child({:file_source, :beep}, %File.Source{location: beep_filepath})
43 | |> child({:decoder_aac, :beep}, AAC.FDK.Decoder)
44 | |> child(:beeps, Tee.PushOutput)
45 |
46 | beeps_split =
47 | for i <- 1..n_beeps do
48 | get_child(:beeps)
49 | |> via_in(:input, options: [offset: Time.seconds(i)])
50 | |> get_child(:mixer)
51 | end
52 |
53 | :ok
54 | ```
55 |
56 | The background music is loaded from a file and then decoded from the `MP3` format to the appropriate `Raw Audio` format.
57 |
58 | All mixer inputs must be of the same format.
59 |
60 | ```elixir
61 | import Membrane.ChildrenSpec
62 |
63 | alias Membrane.{File, RawAudio, MP3}
64 | alias Membrane.FFmpeg.SWResample.Converter
65 |
66 | background_audio_input =
67 | child(:file_source, %File.Source{location: background_filepath})
68 | |> child(:decoder_mp3, MP3.MAD.Decoder)
69 | |> child(:converter, %Converter{
70 | input_stream_format: %RawAudio{channels: 2, sample_format: :s24le, sample_rate: 48_000},
71 | output_stream_format: %RawAudio{channels: 1, sample_format: :s16le, sample_rate: 44_100}
72 | })
73 | |> get_child(:mixer)
74 |
75 | :ok
76 | ```
77 |
78 | Mixer is created and directly connected to audio input of the player.
79 |
80 | ```elixir
81 | import Membrane.ChildrenSpec
82 |
83 | alias Membrane.{AudioMixer, AAC, Kino}
84 |
85 | kino = Membrane.Kino.Player.new(audio: true)
86 |
87 | mixer_output =
88 | child(:mixer, Membrane.AudioMixer)
89 | |> child(:encoder_aac, AAC.FDK.Encoder)
90 | |> via_in(:audio)
91 | |> child(:player, %Kino.Player.Sink{kino: kino})
92 |
93 | :ok
94 | ```
95 |
96 | Whole pipeline structure.
97 |
98 | ```elixir
99 | spec = beeps_split ++ [beep_audio_input, background_audio_input, mixer_output]
100 | :ok
101 | ```
102 |
103 | ## Playing audio
104 |
105 | ```elixir
106 | alias Membrane.RCPipeline, as: RC
107 |
108 | pipeline = RC.start!()
109 | RC.exec_actions(pipeline, spec: spec)
110 |
111 | kino
112 | ```
113 |
--------------------------------------------------------------------------------
/rtp/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/c,vim,linux,macos,elixir,windows,visualstudiocode
3 | # Edit at https://www.gitignore.io/?templates=c,vim,linux,macos,elixir,windows,visualstudiocode
4 |
5 | ### C ###
6 | # Prerequisites
7 | *.d
8 |
9 | # Object files
10 | *.o
11 | *.ko
12 | *.obj
13 | *.elf
14 |
15 | # Linker output
16 | *.ilk
17 | *.map
18 | *.exp
19 |
20 | # Precompiled Headers
21 | *.gch
22 | *.pch
23 |
24 | # Libraries
25 | *.lib
26 | *.a
27 | *.la
28 | *.lo
29 |
30 | # Shared objects (inc. Windows DLLs)
31 | *.dll
32 | *.so
33 | *.so.*
34 | *.dylib
35 |
36 | # Executables
37 | *.exe
38 | *.out
39 | *.app
40 | *.i*86
41 | *.x86_64
42 | *.hex
43 |
44 | # Debug files
45 | *.dSYM/
46 | *.su
47 | *.idb
48 | *.pdb
49 |
50 | # Kernel Module Compile Results
51 | *.mod*
52 | *.cmd
53 | .tmp_versions/
54 | modules.order
55 | Module.symvers
56 | Mkfile.old
57 | dkms.conf
58 |
59 | ### Elixir ###
60 | /_build
61 | /cover
62 | /deps
63 | /doc
64 | /.fetch
65 | erl_crash.dump
66 | *.ez
67 | *.beam
68 | /config/*.secret.exs
69 | .elixir_ls/
70 |
71 | ### Elixir Patch ###
72 |
73 | ### Linux ###
74 | *~
75 |
76 | # temporary files which can be created if a process still has a handle open of a deleted file
77 | .fuse_hidden*
78 |
79 | # KDE directory preferences
80 | .directory
81 |
82 | # Linux trash folder which might appear on any partition or disk
83 | .Trash-*
84 |
85 | # .nfs files are created when an open file is removed but is still being accessed
86 | .nfs*
87 |
88 | ### macOS ###
89 | # General
90 | .DS_Store
91 | .AppleDouble
92 | .LSOverride
93 |
94 | # Icon must end with two \r
95 | Icon
96 |
97 | # Thumbnails
98 | ._*
99 |
100 | # Files that might appear in the root of a volume
101 | .DocumentRevisions-V100
102 | .fseventsd
103 | .Spotlight-V100
104 | .TemporaryItems
105 | .Trashes
106 | .VolumeIcon.icns
107 | .com.apple.timemachine.donotpresent
108 |
109 | # Directories potentially created on remote AFP share
110 | .AppleDB
111 | .AppleDesktop
112 | Network Trash Folder
113 | Temporary Items
114 | .apdisk
115 |
116 | ### Vim ###
117 | # Swap
118 | [._]*.s[a-v][a-z]
119 | [._]*.sw[a-p]
120 | [._]s[a-rt-v][a-z]
121 | [._]ss[a-gi-z]
122 | [._]sw[a-p]
123 |
124 | # Session
125 | Session.vim
126 | Sessionx.vim
127 |
128 | # Temporary
129 | .netrwhist
130 |
131 | # Auto-generated tag files
132 | tags
133 |
134 | # Persistent undo
135 | [._]*.un~
136 |
137 | # Coc configuration directory
138 | .vim
139 |
140 | ### VisualStudioCode ###
141 | .vscode/*
142 | !.vscode/settings.json
143 | !.vscode/tasks.json
144 | !.vscode/launch.json
145 | !.vscode/extensions.json
146 |
147 | ### VisualStudioCode Patch ###
148 | # Ignore all local history of files
149 | .history
150 |
151 | ### Windows ###
152 | # Windows thumbnail cache files
153 | Thumbs.db
154 | Thumbs.db:encryptable
155 | ehthumbs.db
156 | ehthumbs_vista.db
157 |
158 | # Dump file
159 | *.stackdump
160 |
161 | # Folder config file
162 | [Dd]esktop.ini
163 |
164 | # Recycle Bin used on file shares
165 | $RECYCLE.BIN/
166 |
167 | # Windows Installer files
168 | *.cab
169 | *.msi
170 | *.msix
171 | *.msm
172 | *.msp
173 |
174 | # Windows shortcuts
175 | *.lnk
176 |
177 | # End of https://www.gitignore.io/api/c,vim,linux,macos,elixir,windows,visualstudiocode
178 |
--------------------------------------------------------------------------------
/rtp_to_hls/rtp_to_hls.exs:
--------------------------------------------------------------------------------
1 | require Logger
2 | Logger.configure(level: :info)
3 |
4 | Mix.install([
5 | {:membrane_core, "~> 1.0"},
6 | {:membrane_udp_plugin, "~> 0.13.0"},
7 | {:membrane_rtp_plugin, "~> 0.31.0"},
8 | {:membrane_rtp_h264_plugin, "~> 0.20.0"},
9 | {:membrane_rtp_aac_plugin, "~> 0.9.1"},
10 | {:membrane_aac_plugin, "~> 0.19.1"},
11 | {:membrane_http_adaptive_stream_plugin, "~> 0.18.4"},
12 | {:membrane_fake_plugin, "~> 0.11.0"},
13 | {:membrane_h26x_plugin, "~> 0.10.0"}
14 | ])
15 |
16 | defmodule RTPToHLS do
17 | use Membrane.Pipeline
18 |
19 | require Logger
20 |
21 | @impl true
22 | def handle_init(_ctx, %{port: port}) do
23 | spec = [
24 | child(:app_source, %Membrane.UDP.Source{local_port_no: port})
25 | |> child(:rtp_demuxer, Membrane.RTP.Demuxer)
26 | |> via_out(:output, options: [stream_id: {:payload_type, 96}])
27 | |> child(:rtp_h264_depayloader, Membrane.RTP.H264.Depayloader)
28 | |> via_in(Pad.ref(:input, :video),
29 | options: [encoding: :H264, segment_duration: Membrane.Time.seconds(4)]
30 | )
31 | |> child(:hls, %Membrane.HTTPAdaptiveStream.SinkBin{
32 | manifest_module: Membrane.HTTPAdaptiveStream.HLS,
33 | target_window_duration: Membrane.Time.seconds(15),
34 | storage: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{directory: "output"}
35 | }),
36 | get_child(:rtp_demuxer)
37 | |> via_out(:output, options: [stream_id: {:payload_type, 127}])
38 | |> child(:rtp_aac_depayloader, %Membrane.RTP.AAC.Depayloader{mode: :hbr})
39 | |> child(:aac_parser, %Membrane.AAC.Parser{
40 | audio_specific_config: Base.decode16!("1210"),
41 | out_encapsulation: :none
42 | })
43 | |> via_in(Pad.ref(:input, :audio),
44 | options: [encoding: :AAC, segment_duration: Membrane.Time.seconds(4)]
45 | )
46 | |> get_child(:hls)
47 | ]
48 |
49 | {[spec: spec], %{}}
50 | end
51 |
52 | @impl true
53 | def handle_child_notification({:track_playable, _track_info}, :hls, _context, state) do
54 | send(:script, :playlist_ready)
55 | {[], state}
56 | end
57 |
58 | @impl true
59 | def handle_child_notification(_notification, _element, _ctx, state) do
60 | {[], state}
61 | end
62 | end
63 |
64 | File.rm_rf!("output")
65 | File.mkdir!("output")
66 |
67 | Process.register(self(), :script)
68 |
69 | Logger.info("Starting the pipeline")
70 | input_port = 5000
71 | {:ok, _supervisor, _pipeline} = Membrane.Pipeline.start_link(RTPToHLS, %{port: input_port})
72 |
73 | if System.get_env("CI") == "true" do
74 | # Wait to catch potential pipeline setup errors
75 | Process.sleep(1000)
76 | Logger.info("CI=true, exiting")
77 | exit(:normal)
78 | end
79 |
80 | Logger.info("Waiting for the RTP stream on port #{input_port}")
81 |
82 | receive do
83 | :playlist_ready -> :ok
84 | end
85 |
86 | Logger.info("Starting HTTP server")
87 | :ok = :inets.start()
88 |
89 | {:ok, _server} =
90 | :inets.start(:httpd,
91 | bind_address: ~c"localhost",
92 | port: 8000,
93 | document_root: ~c".",
94 | server_name: ~c"rtp_to_hls",
95 | server_root: "/tmp"
96 | )
97 |
98 | Logger.info("Playback available at http://localhost:8000/stream.html")
99 |
100 | Process.sleep(:infinity)
101 |
--------------------------------------------------------------------------------