From be9250d53a68f7be4f1c6fd77d980ba62cbceec5 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 6 Feb 2025 17:26:01 +0100 Subject: [PATCH 01/45] wip --- assets/index.js | 4 + assets/player.js | 31 +++++ assets/publisher.js | 324 +++++++++++++++++++++++++++++++++++++++++++ lib/webrtc/player.ex | 221 +++++++++++++++++++++++++++++ mach_demo.exs | 83 +++++++++++ mix.exs | 10 +- mix.lock | 127 +++++++++++++++-- 7 files changed, 787 insertions(+), 13 deletions(-) create mode 100644 assets/index.js create mode 100644 assets/player.js create mode 100644 assets/publisher.js create mode 100644 lib/webrtc/player.ex create mode 100644 mach_demo.exs diff --git a/assets/index.js b/assets/index.js new file mode 100644 index 0000000..895c721 --- /dev/null +++ b/assets/index.js @@ -0,0 +1,4 @@ +import { createPublisherHook } from "./publisher.js"; +import { createPlayerHook } from "./player.js"; + +export { createPublisherHook, createPlayerHook }; diff --git a/assets/player.js b/assets/player.js new file mode 100644 index 0000000..893b56a --- /dev/null +++ b/assets/player.js @@ -0,0 +1,31 @@ +export function createPlayerHook(iceServers = []) { + return { + async mounted() { + this.pc = new RTCPeerConnection({ iceServers: iceServers }); + + this.pc.onicecandidate = (ev) => { + // this.pushEventTo(this.el, "ice", JSON.stringify(ev.candidate)); + message = JSON.stringify({ type: "ice_candidate", data: ev.candidate }); + this.pushEventTo(this.el, "webrtc_singaling", message); + }; + + this.pc.ontrack = (ev) => { + if (!this.el.srcObject) { + this.el.srcObject = ev.streams[0]; + } + }; + this.pc.addTransceiver("audio", { direction: "recvonly" }); + this.pc.addTransceiver("video", { direction: "recvonly" }); + + const offer = await this.pc.createOffer(); + await this.pc.setLocalDescription(offer); + + const eventName = "answer" + "-" + this.el.id; + this.handleEvent(eventName, async (answer) => { + await this.pc.setRemoteDescription(answer); + }); + + this.pushEventTo(this.el, "offer", offer); + }, + }; +} diff --git a/assets/publisher.js b/assets/publisher.js new file mode 100644 index 0000000..baef439 --- /dev/null +++ b/assets/publisher.js @@ -0,0 +1,324 @@ +export function createPublisherHook(iceServers = []) { + return { + async mounted() { + const view = this; + + view.handleEvent("start-streaming", () => view.startStreaming(view)); + view.handleEvent("stop-streaming", () => view.stopStreaming(view)); + + view.audioDevices = document.getElementById("lex-audio-devices"); + view.videoDevices = document.getElementById("lex-video-devices"); + + view.echoCancellation = document.getElementById("lex-echo-cancellation"); + view.autoGainControl = document.getElementById("lex-auto-gain-control"); + view.noiseSuppression = document.getElementById("lex-noise-suppression"); + + view.width = document.getElementById("lex-width"); + view.height = document.getElementById("lex-height"); + view.fps = document.getElementById("lex-fps"); + view.bitrate = document.getElementById("lex-bitrate"); + + view.previewPlayer = document.getElementById("lex-preview-player"); + + view.audioBitrate = document.getElementById("lex-audio-bitrate"); + view.videoBitrate = document.getElementById("lex-video-bitrate"); + view.packetLoss = document.getElementById("lex-packet-loss"); + view.status = document.getElementById("lex-status"); + view.time = document.getElementById("lex-time"); + + view.audioApplyButton = document.getElementById("lex-audio-apply-button"); + view.videoApplyButton = document.getElementById("lex-video-apply-button"); + view.button = document.getElementById("lex-button"); + + view.audioDevices.onchange = function () { + view.setupStream(view); + }; + + view.videoDevices.onchange = function () { + view.setupStream(view); + }; + + view.audioApplyButton.onclick = function () { + view.setupStream(view); + }; + + view.videoApplyButton.onclick = function () { + view.setupStream(view); + }; + + // handle remote events + view.handleEvent(`answer-${view.el.id}`, async (answer) => { + if (view.pc) { + await view.pc.setRemoteDescription(answer); + } else { + console.warn("Received SDP cnswer but there is no PC. Ignoring."); + } + }); + + view.handleEvent(`ice-${view.el.id}`, async (cand) => { + if (view.pc) { + await view.pc.addIceCandidate(JSON.parse(cand)); + } else { + console.warn("Received ICE candidate but there is no PC. Ignoring."); + } + }); + + try { + await view.findDevices(view); + try { + await view.setupStream(view); + view.button.disabled = false; + view.audioApplyButton.disabled = false; + view.videoApplyButton.disabled = false; + } catch (error) { + console.error("Couldn't setup stream, reason:", error.stack); + } + } catch (error) { + console.error("Couldn't find audio and/or video devices, reason: ", error.stack); + } + }, + + disableControls(view) { + view.audioDevices.disabled = true; + view.videoDevices.disabled = true; + view.echoCancellation.disabled = true; + view.autoGainControl.disabled = true; + view.noiseSuppression.disabled = true; + view.width.disabled = true; + view.height.disabled = true; + view.fps.disabled = true; + view.audioApplyButton.disabled = true; + view.videoApplyButton.disabled = true; + view.bitrate.disabled = true; + }, + + enableControls(view) { + view.audioDevices.disabled = false; + view.videoDevices.disabled = false; + view.echoCancellation.disabled = false; + view.autoGainControl.disabled = false; + view.noiseSuppression.disabled = false; + view.width.disabled = false; + view.height.disabled = false; + view.fps.disabled = false; + view.audioApplyButton.disabled = false; + view.videoApplyButton.disabled = false; + view.bitrate.disabled = false; + }, + + async findDevices(view) { + // ask for permissions + view.localStream = await navigator.mediaDevices.getUserMedia({ + video: true, + audio: true, + }); + + console.log(`Obtained stream with id: ${view.localStream.id}`); + + // enumerate devices + const devices = await navigator.mediaDevices.enumerateDevices(); + devices.forEach((device) => { + if (device.kind === "videoinput") { + view.videoDevices.options[view.videoDevices.options.length] = new Option( + device.label, + device.deviceId + ); + } else if (device.kind === "audioinput") { + view.audioDevices.options[view.audioDevices.options.length] = new Option( + device.label, + device.deviceId + ); + } + }); + + // for some reasons, firefox loses labels after closing the stream + // so we close it after filling audio/video devices selects + view.closeStream(view); + }, + + closeStream(view) { + if (view.localStream != undefined) { + console.log(`Closing stream with id: ${view.localStream.id}`); + view.localStream.getTracks().forEach((track) => track.stop()); + view.localStream = undefined; + } + }, + + async setupStream(view) { + if (view.localStream != undefined) { + view.closeStream(view); + } + + const videoDevice = view.videoDevices.value; + const audioDevice = view.audioDevices.value; + + console.log(`Setting up stream: audioDevice: ${audioDevice}, videoDevice: ${videoDevice}`); + + view.localStream = await navigator.mediaDevices.getUserMedia({ + video: { + deviceId: { exact: videoDevice }, + width: view.width.value, + height: view.height.value, + frameRate: view.fps.value, + }, + audio: { + deviceId: { exact: audioDevice }, + echoCancellation: view.echoCancellation.checked, + autoGainControl: view.autoGainControl.checked, + noiseSuppression: view.noiseSuppression.checked, + }, + }); + + console.log(`Obtained stream with id: ${view.localStream.id}`); + + view.previewPlayer.srcObject = view.localStream; + }, + + async startStreaming(view) { + view.disableControls(view); + + view.pc = new RTCPeerConnection({ iceServers: iceServers }); + + // handle local events + view.pc.onconnectionstatechange = () => { + if (view.pc.connectionState === "connected") { + view.startTime = new Date(); + view.status.classList.remove("bg-red-500"); + // TODO use tailwind + view.status.style.backgroundColor = "rgb(34, 197, 94)"; + + view.statsIntervalId = setInterval(async function () { + if (!view.pc) { + clearInterval(view.statsIntervalId); + view.statsIntervalId = undefined; + return; + } + + view.time.innerText = view.toHHMMSS(new Date() - view.startTime); + + const stats = await view.pc.getStats(null); + let bitrate; + + stats.forEach((report) => { + if (report.type === "outbound-rtp" && report.kind === "video") { + if (!view.lastVideoReport) { + bitrate = (report.bytesSent * 8) / 1000; + } else { + const timeDiff = (report.timestamp - view.lastVideoReport.timestamp) / 1000; + if (timeDiff == 0) { + // this should never happen as we are getting stats every second + bitrate = 0; + } else { + bitrate = ((report.bytesSent - view.lastVideoReport.bytesSent) * 8) / timeDiff; + } + } + + view.videoBitrate.innerText = (bitrate / 1000).toFixed(); + view.lastVideoReport = report; + } else if (report.type === "outbound-rtp" && report.kind === "audio") { + if (!view.lastAudioReport) { + bitrate = report.bytesSent; + } else { + const timeDiff = (report.timestamp - view.lastAudioReport.timestamp) / 1000; + if (timeDiff == 0) { + // this should never happen as we are getting stats every second + bitrate = 0; + } else { + bitrate = ((report.bytesSent - view.lastAudioReport.bytesSent) * 8) / timeDiff; + } + } + + view.audioBitrate.innerText = (bitrate / 1000).toFixed(); + view.lastAudioReport = report; + } + }); + + // calculate packet loss + if (!view.lastAudioReport || !view.lastVideoReport) { + view.packetLoss.innerText = 0; + } else { + const packetsSent = + view.lastVideoReport.packetsSent + view.lastAudioReport.packetsSent; + const rtxPacketsSent = + view.lastVideoReport.retransmittedPacketsSent + + view.lastAudioReport.retransmittedPacketsSent; + const nackReceived = view.lastVideoReport.nackCount + view.lastAudioReport.nackCount; + + if (nackReceived == 0) { + view.packetLoss.innerText = 0; + } else { + view.packetLoss.innerText = ( + (nackReceived / (packetsSent - rtxPacketsSent)) * + 100 + ).toFixed(); + } + } + }, 1000); + } else if (view.pc.connectionState === "failed") { + view.pushEvent("stop-streaming", { reason: "failed" }); + view.stopStreaming(view); + } + }; + + view.pc.onicecandidate = (ev) => { + view.pushEventTo(view.el, "ice", JSON.stringify(ev.candidate)); + }; + + view.pc.addTrack(view.localStream.getAudioTracks()[0], view.localStream); + view.pc.addTrack(view.localStream.getVideoTracks()[0], view.localStream); + + // set max bitrate + view.pc + .getSenders() + .filter((sender) => sender.track.kind === "video") + .forEach(async (sender) => { + const params = sender.getParameters(); + params.encodings[0].maxBitrate = view.bitrate.value * 1024; + await sender.setParameters(params); + }); + + const offer = await view.pc.createOffer(); + await view.pc.setLocalDescription(offer); + + view.pushEventTo(view.el, "offer", offer); + }, + + stopStreaming(view) { + if (view.pc) { + view.pc.close(); + view.pc = undefined; + } + + view.resetStats(view); + + view.enableControls(view); + }, + + resetStats(view) { + view.startTime = undefined; + view.lastAudioReport = undefined; + view.lastVideoReport = undefined; + view.audioBitrate.innerText = 0; + view.videoBitrate.innerText = 0; + view.packetLoss.innerText = 0; + view.time.innerText = "00:00:00"; + view.status.style.backgroundColor = "rgb(239, 68, 68)"; + }, + + toHHMMSS(milliseconds) { + // Calculate hours + let hours = Math.floor(milliseconds / (1000 * 60 * 60)); + // Calculate minutes, subtracting the hours part + let minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); + // Calculate seconds, subtracting the hours and minutes parts + let seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); + + // Formatting each unit to always have at least two digits + hours = hours < 10 ? "0" + hours : hours; + minutes = minutes < 10 ? "0" + minutes : minutes; + seconds = seconds < 10 ? "0" + seconds : seconds; + + return hours + ":" + minutes + ":" + seconds; + }, + }; +} diff --git a/lib/webrtc/player.ex b/lib/webrtc/player.ex new file mode 100644 index 0000000..6d206a7 --- /dev/null +++ b/lib/webrtc/player.ex @@ -0,0 +1,221 @@ +defmodule LiveExWebRTC.Player do + @moduledoc ~S''' + Component for sending and playing audio and video via WebRTC from a Phoenix app to a browser (browser subscribes). + + It: + * renders a single HTMLVideoElement + * creates WebRTC PeerConnection both on the server and client side + * connects those two peer connections negotiating a single audio and a single video track + * attaches audio and video on the client side to the HTMLVideoElement + * subscribes to the configured PubSub where it expects audio and video packets and sends them to the client side. + + When `LiveExWebRTC.Publisher` is used, audio an video packets are delivered automatically, + assuming both components are configured with the same PubSub. + + If `LiveExWebRTC.Publisher` is not used, you should send packets to the + `streams:audio:#{publisher_id}` and `streams:video:#{publisher_id}` topics. + + Keyframe requests are sent under `publishers:#{publisher_id}` topic. + + ## JavaScript Hook + + Player live view requires JavaScript hook to be registered under `Player` name. + The hook can be created using `createPlayerHook` function. + For example: + + ```javascript + import { createPlayerHook } from "live_ex_webrtc"; + let Hooks = {}; + const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; + Hooks.Player = createPlayerHook(iceServers); + let liveSocket = new LiveSocket("/live", Socket, { + // ... + hooks: Hooks + }); + ``` + + ## Examples + + ```elixir + defmodule LiveTwitchWeb.StreamViewerLive do + use LiveTwitchWeb, :live_view + + alias LiveExWebRTC.Player + + @impl true + def render(assigns) do + ~H""" + + """ + end + + @impl true + def mount(_params, _session, socket) do + socket = Player.attach(socket, id: "player", publisher_id: "publisher", pubsub: LiveTwitch.PubSub) + {:ok, socket} + end + end + ``` + ''' + use Phoenix.LiveView + + alias LiveExWebRTC.Player + alias Membrane.WebRTC.SignalingChannel + + # @type on_connected() :: (publisher_id :: String.t() -> any()) + + # @type on_packet() :: + # (publisher_id :: String.t(), + # packet_type :: :audio | :video, + # packet :: ExRTP.Packet.t(), + # socket :: Phoenix.LiveView.Socket.t() -> + # packet :: ExRTP.Packet.t()) + + @type t() :: struct() + + defstruct [:video?, :audio?, :ice_servers, id: nil, signaling_channel: nil] + + # publisher_id: nil, + # pubsub: nil, + # pc: nil, + # audio_track_id: nil, + # video_track_id: nil, + # on_packet: nil, + # on_connected: nil, + # ice_servers: nil, + # ice_ip_filter: nil, + # ice_port_range: nil, + # audio_codecs: nil, + # video_codecs: nil, + # pc_genserver_opts: nil + + alias ExWebRTC.{ICECandidate, MediaStreamTrack, PeerConnection, SessionDescription} + alias ExRTCP.Packet.PayloadFeedback.PLI + alias Phoenix.PubSub + + attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") + + attr(:player, __MODULE__, + required: true, + doc: """ + Player struct. It is used to pass player id and publisher id to the newly created live view via live view session. + This data is then used to do a handshake between parent live view and child live view during which child live view receives + the whole Player struct. + """ + ) + + attr(:class, :string, default: nil, doc: "CSS/Tailwind classes for styling HTMLVideoElement") + + @doc """ + Helper function for rendering Player live view. + """ + def live_render(assigns) do + ~H""" + <%= live_render(@socket, __MODULE__, id: "#{@player.id}-lv", session: %{"class" => @class}) %> + """ + end + + @doc """ + Attaches required hooks and creates `t:t/0` struct. + + Created struct is saved in socket's assigns and has to be passed to `LiveExWebRTC.Player.live_render/1`. + + Options: + * `id` - player id. This is typically your user id (if there is users database). + It is used to identify live view and generated HTML video player. + * `publisher_id` - publisher id that this player is going to subscribe to. + * `pubsub` - a pubsub that player live view will subscribe to for audio and video packets. See module doc for more. + * `on_connected` - callback called when the underlying peer connection changes its state to the `:connected`. See `t:on_connected/0`. + * `on_packet` - callback called for each audio and video RTP packet. Can be used to modify the packet before sending via WebRTC to the other side. See `t:on_packet/0`. + * `ice_servers` - a list of `t:ExWebRTC.PeerConnection.Configuration.ice_server/0`, + * `ice_ip_filter` - `t:ExICE.ICEAgent.ip_filter/0`, + * `ice_port_range` - `t:Enumerable.t(non_neg_integer())/1`, + * `audio_codecs` - a list of `t:ExWebRTC.RTPCodecParameters.t/0`, + * `video_codecs` - a list of `t:ExWebRTC.RTPCodecParameters.t/0`, + * `pc_genserver_opts` - `t:GenServer.options/0` for the underlying `ExWebRTC.PeerConnection` process. + * `class` - a list of CSS/Tailwind classes that will be applied to the HTMLVideoPlayer. Defaults to "". + """ + @spec attach(Phoenix.LiveView.Socket.t(), Keyword.t()) :: Phoenix.LiveView.Socket.t() + def attach(socket, opts) do + player = + opts + |> Keyword.validate!([ + :id, + :signaling_channel, + video?: true, + audio?: true, + ice_servers: [%{urls: "stun:stun.l.google.com:19302"}] + ]) + |> struct!(__MODULE__) + + socket + |> assign(player: player) + |> attach_hook(:handshake, :handle_info, &handshake/2) + end + + defp handshake({__MODULE__, {:connected, ref, child_pid, _meta}}, socket) do + # child live view is connected, send it player struct + send(child_pid, {ref, socket.assigns.player}) + {:halt, socket} + end + + defp handshake(_msg, socket) do + {:cont, socket} + end + + ## CALLBACKS + + @impl true + def render(%{player: nil} = assigns) do + ~H""" + """ + end + + @impl true + def render(assigns) do + ~H""" + + """ + end + + # todo: simplify the function below later, but for now it should work fine + @impl true + def mount(_params, %{"class" => class}, socket) do + socket = assign(socket, class: class, player: nil) + + if connected?(socket) do + ref = make_ref() + send(socket.parent_pid, {__MODULE__, {:connected, ref, self(), %{}}}) + + socket = + receive do + {^ref, %Player{} = player} -> + assign(socket, player: player) + after + 5000 -> exit(:timeout) + end + + {:ok, socket} + else + {:ok, socket} + end + end + + @impl true + def handle_info({SignalingChannel, _pid, message, _metadata}, socket) do + socket + |> push_event("webrtc_signaling", Jason.encode!(message)) + + {:noreply, socket} + end + + @impl true + def handle_event("webrtc_signaling", message, socket) do + message = Jason.decode!(message) + + socket.assigns.signaling_channel + |> SignalingChannel.signal(message) + + {:noreply, socket} + end +end diff --git a/mach_demo.exs b/mach_demo.exs new file mode 100644 index 0000000..f454be3 --- /dev/null +++ b/mach_demo.exs @@ -0,0 +1,83 @@ +Application.put_env(:sample, Example.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 5001], + server: true, + live_view: [signing_salt: "aaaaaaaa"], + secret_key_base: String.duplicate("a", 64) +) + +# Mix.install([ +# {:plug_cowboy, "~> 2.5"}, +# {:jason, "~> 1.0"}, +# {:phoenix, "~> 1.7.0"}, +# {:phoenix_live_view, "~> 0.19.0"} +# ]) + +defmodule Example.ErrorView do + def render(template, _), do: Phoenix.Controller.status_message_from_template(template) +end + +defmodule Example.HomeLive do + use Phoenix.LiveView, layout: {__MODULE__, :live} + + def mount(_params, _session, socket) do + {:ok, assign(socket, :count, 0)} + end + + defp phx_vsn, do: Application.spec(:phoenix, :vsn) + defp lv_vsn, do: Application.spec(:phoenix_live_view, :vsn) + + def render("live.html", assigns) do + ~H""" + + + + + <%= @inner_content %> + """ + end + + def render(assigns) do + ~H""" + <%= @count %> + + + """ + end + + def handle_event("inc", _params, socket) do + {:noreply, assign(socket, :count, socket.assigns.count + 1)} + end + + def handle_event("dec", _params, socket) do + {:noreply, assign(socket, :count, socket.assigns.count - 1)} + end +end + +defmodule Example.Router do + use Phoenix.Router + import Phoenix.LiveView.Router + + pipeline :browser do + plug(:accepts, ["html"]) + end + + scope "/", Example do + pipe_through(:browser) + + live("/", HomeLive, :index) + end +end + +defmodule Example.Endpoint do + use Phoenix.Endpoint, otp_app: :sample + socket("/live", Phoenix.LiveView.Socket) + plug(Example.Router) +end + +{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one) +Process.sleep(:infinity) diff --git a/mix.exs b/mix.exs index 611ad1c..e7b3f8f 100644 --- a/mix.exs +++ b/mix.exs @@ -1,4 +1,4 @@ -defmodule Membrane.Template.Mixfile do +defmodule Boombox.Live.Mixfile do use Mix.Project @version "0.1.0" @@ -37,7 +37,13 @@ defmodule Membrane.Template.Mixfile do defp deps do [ - {:membrane_core, "~> 1.0"}, + # {:boombox, "~> 0.1.0"}, + {:boombox, path: "../boombox"}, + # {:membrane_webrtc_plugin, "~> 0.23.2"}, + {:plug_cowboy, "~> 2.5"}, + {:phoenix_live_view, "~> 1.0"}, + {:phoenix, "~> 1.7"}, + {:jason, "~> 1.0"}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, {:dialyxir, ">= 0.0.0", only: :dev, runtime: false}, {:credo, ">= 0.0.0", only: :dev, runtime: false} diff --git a/mix.lock b/mix.lock index 8d15140..5dc0aa8 100644 --- a/mix.lock +++ b/mix.lock @@ -1,21 +1,126 @@ %{ - "bunch": {:hex, :bunch, "1.6.0", "4775f8cdf5e801c06beed3913b0bd53fceec9d63380cdcccbda6be125a6cfd54", [:mix], [], "hexpm", "ef4e9abf83f0299d599daed3764d19e8eac5d27a5237e5e4d5e2c129cfeb9a22"}, + "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, + "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, + "boombox": {:hex, :boombox, "0.1.0", "86433412c72c3fc6255cba223e968a857680b0d58225dffa3fa05c1eb10113b2", [:mix], [{:burrito, "~> 1.0", [hex: :burrito, repo: "hexpm", optional: true]}, {:image, "~> 0.54.0", [hex: :image, repo: "hexpm", optional: false]}, {:membrane_aac_fdk_plugin, "~> 0.18.0", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.20.0", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swscale_plugin, "~> 0.16.0", [hex: :membrane_ffmpeg_swscale_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.32.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_hackney_plugin, "~> 0.11.0", [hex: :membrane_hackney_plugin, repo: "hexpm", optional: false]}, {:membrane_http_adaptive_stream_plugin, "~> 0.18.5", [hex: :membrane_http_adaptive_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_mp4_plugin, "~> 0.35.2", [hex: :membrane_mp4_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.20.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_realtimer_plugin, "~> 0.9.0", [hex: :membrane_realtimer_plugin, repo: "hexpm", optional: false]}, {:membrane_rtmp_plugin, "~> 0.25.0", [hex: :membrane_rtmp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.29.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp_plugin, "~> 0.3.0", [hex: :membrane_rtsp_plugin, repo: "hexpm", optional: false]}, {:membrane_webrtc_plugin, "~> 0.22.0", [hex: :membrane_webrtc_plugin, repo: "hexpm", optional: false]}], "hexpm", "a1baaebbff25bd77a12261a29ef28e8e348bc3f1773c2665d781064575ffb7e8"}, + "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, + "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, + "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, + "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, + "corsica": {:hex, :corsica, "2.1.3", "dccd094ffce38178acead9ae743180cdaffa388f35f0461ba1e8151d32e190e6", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "616c08f61a345780c2cf662ff226816f04d8868e12054e68963e95285b5be8bc"}, + "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "crc": {:hex, :crc, "0.10.5", "ee12a7c056ac498ef2ea985ecdc9fa53c1bfb4e53a484d9f17ff94803707dfd8", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3e673b6495a9525c5c641585af1accba59a1eb33de697bedf341e247012c2c7f"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.35", "437773ca9384edf69830e26e9e7b2e0d22d2596c4a6b17094a3b29f01ea65bb8", [:mix], [], "hexpm", "8652ba3cb85608d0d7aa2d21b45c6fad4ddc9a1f9a1f1b30ca3a246f0acc33f6"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, + "ex_dtls": {:hex, :ex_dtls, "0.15.2", "6c8c0f8eb67525216551bd3e0322ab33c9d851d56ef3e065efab4fd277a8fbb9", [:mix], [{:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "6b852bc926bbdc9c1b9c4ecc6cfc73a89d4e106042802cefea2c1503072a9f2a"}, + "ex_ice": {:hex, :ex_ice, "0.8.5", "65de6fa7516f767a8eef4145d66cbd1ed0af10999ecfd1e4c698e90d3c2b7f86", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.1.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "cc52cdf531288959dc8a4353602079188d88319fe1635ea1619fa612d31b8b3b"}, + "ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"}, + "ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"}, + "ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"}, + "ex_sdp": {:hex, :ex_sdp, "1.1.1", "1a7b049491e5ec02dad9251c53d960835dc5631321ae978ec331831f3e4f6d5f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "1b13a72ac9c5c695b8824dbdffc671be8cbb4c0d1ccb4ff76a04a6826759f233"}, + "ex_stun": {:hex, :ex_stun, "0.2.0", "feb1fc7db0356406655b2a617805e6c712b93308c8ea2bf0ba1197b1f0866deb", [:mix], [], "hexpm", "1e01ba8290082ccbf37acaa5190d1f69b51edd6de2026a8d6d51368b29d115d0"}, + "ex_turn": {:hex, :ex_turn, "0.1.1", "e796b6c27aaf39e24ab9bad2044b426ed41ecc5ee8515087fe64f8ec9967e41b", [:mix], [{:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "832f2bba6f200a859e14cb8658b0932f841b0cf290491cca10cccf5367152547"}, + "ex_webrtc": {:hex, :ex_webrtc, "0.4.1", "834054e38630f91a1c62e5d77ecf3394c0be64e9fd94be45e3021ea53baef6f9", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.15.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:ex_ice, "~> 0.8.0", [hex: :ex_ice, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.7.1", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}], "hexpm", "be81984cf1624ccd1b50fa257558829db967764786e47ecc3da0cacbdcf1448d"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "membrane_core": {:hex, :membrane_core, "1.0.0", "1b543aefd952283be1f2a215a1db213aa4d91222722ba03cd35280622f1905ee", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "352c90fd0a29942143c4bf7a727cc05c632e323f50a1a4e99321b1e8982f1533"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "heap": {:hex, :heap, "2.0.2", "d98cb178286cfeb5edbcf17785e2d20af73ca57b5a2cf4af584118afbcf917eb", [:mix], [], "hexpm", "ba9ea2fe99eb4bcbd9a8a28eaf71cbcac449ca1d8e71731596aace9028c9d429"}, + "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "image": {:hex, :image, "0.54.4", "332cd64ca47938447dffee97b05a5e4203f2a45e8918537ab0fb971fa3c9debb", [:mix], [{:bumblebee, "~> 0.3", [hex: :bumblebee, repo: "hexpm", optional: true]}, {:evision, "~> 0.1.33 or ~> 0.2", [hex: :evision, repo: "hexpm", optional: true]}, {:exla, "~> 0.5", [hex: :exla, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:kino, "~> 0.13", [hex: :kino, repo: "hexpm", optional: true]}, {:nx, "~> 0.7", [hex: :nx, repo: "hexpm", optional: true]}, {:nx_image, "~> 0.1", [hex: :nx_image, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.1 or ~> 3.2 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:rustler, "> 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:scholar, "~> 0.3", [hex: :scholar, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}, {:vix, "~> 0.23", [hex: :vix, repo: "hexpm", optional: false]}], "hexpm", "4d66ee976c30ec181a54b99791354a4ae990521d64811cb2daed39c4cd95860b"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, + "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.11", "6bdcde2eaeafcce136f21fb17effdcdaf9c73107771c8d2a661ca5d0b616ea12", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "dac3f0c839f33603dc2fdad8b9fbebd5ca578c9ca28ea65249ebb15c96c0fd31"}, + "membrane_aac_format": {:hex, :membrane_aac_format, "0.8.0", "515631eabd6e584e0e9af2cea80471fee6246484dbbefc4726c1d93ece8e0838", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}], "hexpm", "a30176a94491033ed32be45e51d509fc70a5ee6e751f12fd6c0d60bd637013f6"}, + "membrane_aac_plugin": {:hex, :membrane_aac_plugin, "0.19.0", "58a15efaaa4f2cc91b968464cfd269244e035efdd983aac2e3ddeb176fcf0585", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "eb7e786e650608ee205f4eebff4c1df3677e545acf09802458f77f64f9942fe9"}, + "membrane_cmaf_format": {:hex, :membrane_cmaf_format, "0.7.1", "9ea858faefdcb181cdfa8001be827c35c5f854e9809ad57d7062cff1f0f703fd", [:mix], [], "hexpm", "3c7b4ed2a986e27f6f336d2f19e9442cb31d93b3142fc024c019572faca54a73"}, + "membrane_common_c": {:hex, :membrane_common_c, "0.16.0", "caf3f29d2f5a1d32d8c2c122866110775866db2726e4272be58e66dfdf4bce40", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "a3c7e91de1ce1f8b23b9823188a5d13654d317235ea0ca781c05353ed3be9b1c"}, + "membrane_core": {:hex, :membrane_core, "1.1.2", "3ca206893e1d3739a24d5092d21c06fcb4db326733a1798f9788fc53abb74829", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a989fd7e0516a7e66f5fb63950b1027315b7f8c8d82d8d685e178b0fb780901b"}, + "membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.20.2", "2e669f0b25418d10b51a73bc52d2e12e4a3a26b416c5c1199d852c3f781a18b3", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:mockery, "~> 2.1", [hex: :mockery, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "6c8d3bcd61d568dd94cabb9b45f29e8926e0076e4432d8f419378e004e02147c"}, + "membrane_ffmpeg_swscale_plugin": {:hex, :membrane_ffmpeg_swscale_plugin, "0.16.2", "581909312d6d12ed560ee99caa1b1674a339760ab2ad6835d243326806c23da1", [:mix], [{:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "46c185dacff1e1b404d0ceb74d0d5224f0931fe1e8b951cc3776ebd099e39afc"}, + "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.2", "650e134c2345d946f930082fac8bac9f5aba785a7817d38a9a9da41ffc56fa92", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "df50c6040004cd7b901cf057bd7e99c875bbbd6ae574efc93b2c753c96f43b9d"}, + "membrane_flv_plugin": {:hex, :membrane_flv_plugin, "0.12.0", "d715ad405af86dcaf4b2f479e34088e1f6738c7280366828e1066b39d2aa493a", [:mix], [{:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}], "hexpm", "a317872d6d394e550c7bfd8979f12a3a1cc1e89b547d75360321025b403d3279"}, + "membrane_funnel_plugin": {:hex, :membrane_funnel_plugin, "0.9.2", "2b2e840dbb232ce29aaff2d55bd329d9978766518dbeb6e8dba7aba7115fadcc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "865ac9d84f86698e2cfeb7904d3b12ab74855a38ca651a880db1505965fa77cc"}, + "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.32.5", "30542fb5d6d36961a51906549b4338f4fc66a304bf92e7c7123e2b9971e3502d", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "8c80e11b9ec9ca23d44304ed7bb3daf665e98b91b2488608ee5718a88182e363"}, + "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, + "membrane_h265_ffmpeg_plugin": {:hex, :membrane_h265_ffmpeg_plugin, "0.4.2", "6dcd932fc2c65a851ab7a44f3996cc9613163bdf23b00568c87c9c0754a64edf", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.1", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "d7eb02110a47a9c11f1432f95b775ba7ce5e962dbe8fc07b421bdd2ce5283b2d"}, + "membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"}, + "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.2", "caf2790d8c107df35f8d456b45f4e09fb9c56ce6c7669a3a03f7d59972e6ed82", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}], "hexpm", "becf1ac4a589adecd850137ccd61a33058f686083a514a7e39fcd721bcf9fb2e"}, + "membrane_hackney_plugin": {:hex, :membrane_hackney_plugin, "0.11.0", "54b368333a23394e7cac2f4d6b701bf8c5ee6614670a31f4ebe009b5e691a5c1", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "2b28fd1be3c889d5824d7d985598386c7673828c88f49a91221df3626af8a998"}, + "membrane_http_adaptive_stream_plugin": {:hex, :membrane_http_adaptive_stream_plugin, "0.18.6", "d417f54da56f7a704200baf018cc0e6222e4c649672adac21e2b321b66c3a958", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_mp4_plugin, "~> 0.35.0", [hex: :membrane_mp4_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "7ca36002d0d74254537afb7aae31541d57a36b33c1abe114e6dd56cf7566bcb2"}, + "membrane_mp4_format": {:hex, :membrane_mp4_format, "0.8.0", "8c6e7d68829228117d333b4fbb030e7be829aab49dd8cb047fdc664db1812e6a", [:mix], [], "hexpm", "148dea678a1f82ccfd44dbde6f936d2f21255f496cb45a22cc6eec427f025522"}, + "membrane_mp4_plugin": {:hex, :membrane_mp4_plugin, "0.35.2", "cbedb5272ef1c8f7d9cd3c44f820a90306469b1dc84b8db30ff55bb6195b7cb2", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_cmaf_format, "~> 0.7.0", [hex: :membrane_cmaf_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_mp4_format, "~> 0.8.0", [hex: :membrane_mp4_format, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.1", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}], "hexpm", "8afd4e7779a742dd56c23f1f23053933d1b0b34d397ad368a2f56f995edb2fe0"}, + "membrane_opus_format": {:hex, :membrane_opus_format, "0.3.0", "3804d9916058b7cfa2baa0131a644d8186198d64f52d592ae09e0942513cb4c2", [:mix], [], "hexpm", "8fc89c97be50de23ded15f2050fe603dcce732566fe6fdd15a2de01cb6b81afe"}, + "membrane_opus_plugin": {:hex, :membrane_opus_plugin, "0.20.5", "aa344bb9931c8e22b2286778cce0658e0d4aa071a503c18c55e1b161e17ab337", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "94fd4447b6576780afc6144dbb0520b43bd399c86a10bf5df1fa878a91798cf6"}, + "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, + "membrane_raw_audio_format": {:hex, :membrane_raw_audio_format, "0.12.0", "b574cd90f69ce2a8b6201b0ccf0826ca28b0fbc8245b8078d9f11cef65f7d5d5", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "6e6c98e3622a2b9df19eab50ba65d7eb45949b1ba306fa8423df6cdb12fd0b44"}, + "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.1", "d7344499c2d80f236a7ef962b5490c651341a501052ee43dec56cf0319fa3936", [:mix], [], "hexpm", "9920b7d445b5357608a364fec5685acdfce85334c647f745045237a0d296c442"}, + "membrane_realtimer_plugin": {:hex, :membrane_realtimer_plugin, "0.9.0", "27210d5e32a5e8bfd101c41e4d8c1876e873a52cc129ebfbee4d0ccbea1cbd21", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "b2e96d62135ee57ef9a5fdea94b3a9ab1198e5ea8ee248391b89c671125d1b51"}, + "membrane_rtmp_plugin": {:hex, :membrane_rtmp_plugin, "0.27.3", "2ca6705668e207a2af1dfd459ec94cebf43bd40338b1b176af1b7ecfb7f5017e", [:mix], [{:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_flv_plugin, "~> 0.12.0", [hex: :membrane_flv_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "e3b3a858cee514aae1cd78b5c85a24b68edb15183e7aa909180c0ce7fb92ab10"}, + "membrane_rtp_aac_plugin": {:hex, :membrane_rtp_aac_plugin, "0.9.4", "355efe237151b304a479a8f0db12043aea2528718d045cb596cbfb85f64ff20a", [:mix], [{:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "f72d12b88b57a3c93eeea19c02c95c878c4b09883dbec703ae3d1557d3af44c0"}, + "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.10.0", "e7f62b1f522647a64d88b60f635476dcb71db61dccda32ffa21ecdb965314c91", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "493245bb45faf3a08343e29281e2aadac72b795d48d64bb805ea8daa31cfd414"}, + "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.20.2", "ab84db505d3102a9cdc300f137c78245ef3982a7ec545838f9544b6b0a2ca1ba", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "27f38c49544d1acf6f7c3f8770a7893f90813a31e8a26461e112a3d3142aff46"}, + "membrane_rtp_h265_plugin": {:hex, :membrane_rtp_h265_plugin, "0.5.2", "970155229e97311b13df8ffb705ee208df83de6d084b43bc77c35e692774616a", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "f8c34c0db3d13d84709fa0b41d4944fcbca50be1fbd292553bfd53512e9a0a26"}, + "membrane_rtp_opus_plugin": {:hex, :membrane_rtp_opus_plugin, "0.10.0", "5797c9b5f09a81c35edc13583eafdfa86b71ac5b2a9d399e95598e27c23ec1b6", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "988cf18ad1e5eb876ec93ae476f035bbec0aff2ca33127b1009217bd258e4845"}, + "membrane_rtp_plugin": {:hex, :membrane_rtp_plugin, "0.30.0", "f7dbc0c5e163edf4ec67b89b1028a7b99ea56e87f8cc01d01b01ec0470287128", [:mix], [{:bimap, "~> 1.2", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.6.0 or ~> 0.7.0", [hex: :ex_libsrtp, repo: "hexpm", optional: true]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:heap, "~> 2.0.2", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.1", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "9600e39c35428c3a86c469ee3cae5a60d259da3ee7e58f94b04866da6d719016"}, + "membrane_rtp_vp8_plugin": {:hex, :membrane_rtp_vp8_plugin, "0.9.4", "8eb7e90576e79ccfefe6cf54f982afd8109027b148e17e701e5fbabe485a7b53", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.4.0 or ~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}], "hexpm", "0fd14c81d3200fc83908f3d72ece3faed207e34c1f59a85b2b7625ec02fb30dd"}, + "membrane_rtsp": {:hex, :membrane_rtsp, "0.10.1", "c6cb8549daab155175896f677b049a0f65c3a650e93e603714ec94a836148722", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.17.0 or ~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.4.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ff382b38c09150ff43c7ec7a2a62df41b2bab42793633dcc40ef9b6e51ac70b9"}, + "membrane_rtsp_plugin": {:hex, :membrane_rtsp_plugin, "0.6.1", "56f11bbd74e47fe7240df44ffc2b17537bc295a4286e08ce3232a905347edfea", [:mix], [{:ex_sdp, "~> 1.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_aac_plugin, "~> 0.9.1", [hex: :membrane_rtp_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_h265_plugin, "~> 0.5.2", [hex: :membrane_rtp_h265_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.10.0", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:membrane_tcp_plugin, "~> 0.6.0", [hex: :membrane_tcp_plugin, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.14.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}], "hexpm", "80b6aedd27b4d0e2c99097b5b7d841855ff6fcaf7a11ef85d8e57f7b83a4d8c1"}, + "membrane_tcp_plugin": {:hex, :membrane_tcp_plugin, "0.6.0", "1f8dba5525504fb2d49070932f24113d1b26c7e5429c700671ed80433ac83f2f", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "820440f5a8181a96cff461ad2d5ed426d47eacfdd7764dd9596dad68ad892d3d"}, + "membrane_tee_plugin": {:hex, :membrane_tee_plugin, "0.12.0", "f94989b4080ef4b7937d74c1a14d3379577c7bd4c6d06e5a2bb41c351ad604d4", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "0d61c9ed5e68e5a75d54200e1c6df5739c0bcb52fee0974183ad72446a179887"}, + "membrane_telemetry_metrics": {:hex, :membrane_telemetry_metrics, "0.1.1", "57917e72012f9ebe124eab54f29ca74c9d9eb3ae2207f55c95618ee51280eb4f", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "69392966e6bd51937244758c2b3d835c5ff47d8953d25431a9d37059737afc11"}, + "membrane_timestamp_queue": {:hex, :membrane_timestamp_queue, "0.2.2", "1c831b2273d018a6548654aa9f7fa7c4b683f71d96ffe164934ef55f9d11f693", [:mix], [{:heap, "~> 2.0", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7c830e760baaced0988421671cd2c83c7cda8d1bd2b61fd05332711675d1204f"}, + "membrane_transcoder_plugin": {:hex, :membrane_transcoder_plugin, "0.1.2", "5d5546971289109423c1d02c4a45bfce7133293d76f5850824663a4613e5a728", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.0", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.20.0", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.1", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.32.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_ffmpeg_plugin, "~> 0.4.2", [hex: :membrane_h265_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.20.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.2", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vpx_plugin, "~> 0.3.0", [hex: :membrane_vpx_plugin, repo: "hexpm", optional: false]}], "hexpm", "50297b922fdd39520125120782bf679f26be8965b31565b470fb51b6ef908873"}, + "membrane_udp_plugin": {:hex, :membrane_udp_plugin, "0.14.0", "d533ee5f6fcdd0551ad690045cdb6c1a76307a155d9255cc4a4606f85774bc37", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "902d1a7aa228ec377482d53a605b100e20e0b6e59196f94f94147bb62b23c47e"}, + "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, + "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, + "membrane_vpx_plugin": {:hex, :membrane_vpx_plugin, "0.3.0", "60404d1b1511b4c62ba6bbf7b6212570f1732ba477015c4072e0aa33e18a8809", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vp9_format, "~> 0.5.0", [hex: :membrane_vp9_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "effa7762bbf73efd8d21d0978bce79538414719284194db97672afbce665b56a"}, + "membrane_webrtc_plugin": {:git, "https://github.com/membraneframework/membrane_webrtc_plugin.git", "8c1567c212f6ce4dfb06b6d5ed183b37d435336f", []}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "mockery": {:hex, :mockery, "2.3.3", "3dba87bd0422a513e6af6e0d811383f38f82ac6be5d3d285a5fcca9c299bd0ac", [:mix], [], "hexpm", "17282be00613286254298117cd25e607a39f15ac03b41c631f60e52f5b5ec974"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"}, + "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.2", "e7b1dd68c86326e2c45cc81da41e332cc8aa7228a7161e2c811dcd7f1dd14db1", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8a40265b0cd7d3a35f136dfa3cc048e3b198fc3718763411a78c323a44ebebee"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, - "ratio": {:hex, :ratio, "3.0.2", "60a5976872a4dc3d873ecc57eed1738589e99d1094834b9c935b118231297cfb", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "3a13ed5a30ad0bfd7e4a86bf86d93d2b5a06f5904417d38d3f3ea6406cdfc7bb"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"}, + "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, + "shmex": {:hex, :shmex, "0.5.1", "81dd209093416bf6608e66882cb7e676089307448a1afd4fc906c1f7e5b94cf4", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "c29f8286891252f64c4e1dac40b217d960f7d58def597c4e606ff8fbe71ceb80"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, + "thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "unifex": {:hex, :unifex, "1.2.1", "6841c170a6e16509fac30b19e4e0a19937c33155a59088b50c15fc2c36251b6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "8c9d2e3c48df031e9995dd16865bab3df402c0295ba3a31f38274bb5314c7d37"}, + "vix": {:hex, :vix, "0.33.0", "cd98084529fd8fe3d2336f157db6de03b297fb096508d820068117d58eadb6f1", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "9acde72b27bdfeadeb51f790f7a6cc0d06cf555718c05cf57e43c5cf93d8471b"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, + "zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"}, } From b8b612edbbc2d1924d9368b1cb6dad9ea70cacd7 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 7 Feb 2025 16:57:16 +0100 Subject: [PATCH 02/45] wip --- assets/player.js | 95 ++++++++++++++++++++++++++++++++++++++++-------- mach_demo.exs | 9 ++++- 2 files changed, 86 insertions(+), 18 deletions(-) diff --git a/assets/player.js b/assets/player.js index 893b56a..fe51ffd 100644 --- a/assets/player.js +++ b/assets/player.js @@ -1,31 +1,94 @@ -export function createPlayerHook(iceServers = []) { +export function createPlayerHook(iceServers = [{ urls: "stun:stun.l.google.com:19302" }]) { return { async mounted() { + // const videoPlayer = document.getElementById("videoPlayer"); + // videoPlayer.srcObject = new MediaStream(); + this.pc = new RTCPeerConnection({ iceServers: iceServers }); this.pc.onicecandidate = (ev) => { - // this.pushEventTo(this.el, "ice", JSON.stringify(ev.candidate)); message = JSON.stringify({ type: "ice_candidate", data: ev.candidate }); this.pushEventTo(this.el, "webrtc_singaling", message); }; - this.pc.ontrack = (ev) => { - if (!this.el.srcObject) { - this.el.srcObject = ev.streams[0]; - } - }; - this.pc.addTransceiver("audio", { direction: "recvonly" }); - this.pc.addTransceiver("video", { direction: "recvonly" }); + // pc.ontrack = (event) => videoPlayer.srcObject.addTrack(event.track); - const offer = await this.pc.createOffer(); - await this.pc.setLocalDescription(offer); + // this.pc.ontrack = (ev) => { + // if (!this.el.srcObject) { + // this.el.srcObject = ev.streams[0]; + // } + // }; - const eventName = "answer" + "-" + this.el.id; - this.handleEvent(eventName, async (answer) => { - await this.pc.setRemoteDescription(answer); - }); + // this.pc.addTransceiver("audio", { direction: "recvonly" }); + // this.pc.addTransceiver("video", { direction: "recvonly" }); + + // const offer = await this.pc.createOffer(); + // await this.pc.setLocalDescription(offer); + + // const eventName = "answer" + "-" + this.el.id; + // this.handleEvent(eventName, async (answer) => { + // await this.pc.setRemoteDescription(answer); + // }); + + // this.pushEventTo(this.el, "offer", offer); + + this.handleEvent("webrtc_singaling", async (event) => { + const { type, data } = JSON.parse(event); - this.pushEventTo(this.el, "offer", offer); + switch (type) { + case "sdp_offer": + console.log("Received SDP offer:", data); + await pc.setRemoteDescription(data); + const answer = await pc.createAnswer(); + await pc.setLocalDescription(answer); + // ws.send(JSON.stringify({ type: "sdp_answer", data: answer })); + message = JSON.stringify({ type: "sdp_answer", data: answer }); + this.pushEventTo(this.el, "webrtc_signaling", message); + console.log("Sent SDP answer:", answer); + break; + case "ice_candidate": + console.log("Recieved ICE candidate:", data); + await pc.addIceCandidate(data); + } + }); }, }; } + +// const videoPlayer = document.getElementById("videoPlayer"); +// const pcConfig = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] }; +// const proto = window.location.protocol === "https:" ? "wss:" : "ws:"; +// const ws = new WebSocket(`${proto}//${window.location.hostname}:8829`); +// ws.onopen = () => start_connection(ws); +// ws.onclose = (event) => console.log("WebSocket connection was terminated:", event); + +// const start_connection = async (ws) => { +// videoPlayer.srcObject = new MediaStream(); + +// const pc = new RTCPeerConnection(pcConfig); +// pc.ontrack = (event) => videoPlayer.srcObject.addTrack(event.track); +// pc.onicecandidate = (event) => { +// if (event.candidate === null) return; + +// console.log("Sent ICE candidate:", event.candidate); +// ws.send(JSON.stringify({ type: "ice_candidate", data: event.candidate })); +// }; + +// ws.onmessage = async (event) => { +// const { type, data } = JSON.parse(event.data); + +// switch (type) { +// case "sdp_offer": +// console.log("Received SDP offer:", data); +// await pc.setRemoteDescription(data); +// const answer = await pc.createAnswer(); +// await pc.setLocalDescription(answer); +// ws.send(JSON.stringify({ type: "sdp_answer", data: answer })); +// console.log("Sent SDP answer:", answer); +// break; +// case "ice_candidate": +// console.log("Recieved ICE candidate:", data); +// await pc.addIceCandidate(data); +// } +// }; +// }; diff --git a/mach_demo.exs b/mach_demo.exs index f454be3..58ee5af 100644 --- a/mach_demo.exs +++ b/mach_demo.exs @@ -31,8 +31,13 @@ defmodule Example.HomeLive do + <%= @inner_content %> + """ + end + + def render(%{capture: %Capture{}, player: %Player{}} = assigns) do + ~H""" + + + """ + end + + def render(assigns) do + ~H""" + """ + end +end + +defmodule Example.Router do + use Phoenix.Router + import Phoenix.LiveView.Router + + pipeline :browser do + plug(:accepts, ["html"]) + end + + scope "/", Example do + pipe_through(:browser) + + live("/", HomeLive, :index) + end +end + +defmodule Example.Endpoint do + use Phoenix.Endpoint, otp_app: :sample + socket("/live", Phoenix.LiveView.Socket) + plug(Example.Router) +end + +{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one) +Process.sleep(:infinity) diff --git a/lib/webrtc/capture.ex b/lib/webrtc/capture.ex new file mode 100644 index 0000000..9ff58a1 --- /dev/null +++ b/lib/webrtc/capture.ex @@ -0,0 +1,195 @@ +defmodule Boombox.Live.Capture do + @moduledoc ~S''' + Component for sending and playing audio and video via WebRTC from a Phoenix app to a browser (browser subscribes). + + It: + * renders a single HTMLVideoElement + * creates WebRTC PeerConnection both on the server and client side + * connects those two peer connections negotiating a single audio and a single video track + * attaches audio and video on the client side to the HTMLVideoElement + * subscribes to the configured PubSub where it expects audio and video packets and sends them to the client side. + + When `LiveExWebRTC.Publisher` is used, audio an video packets are delivered automatically, + assuming both components are configured with the same PubSub. + + If `LiveExWebRTC.Publisher` is not used, you should send packets to the + `streams:audio:#{publisher_id}` and `streams:video:#{publisher_id}` topics. + + Keyframe requests are sent under `publishers:#{publisher_id}` topic. + + ## JavaScript Hook + + Player live view requires JavaScript hook to be registered under `Player` name. + The hook can be created using `createPlayerHook` function. + For example: + + ```javascript + import { createPlayerHook } from "live_ex_webrtc"; + let Hooks = {}; + const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; + Hooks.Player = createPlayerHook(iceServers); + let liveSocket = new LiveSocket("/live", Socket, { + // ... + hooks: Hooks + }); + ``` + + ## Examples + + ```elixir + defmodule LiveTwitchWeb.StreamViewerLive do + use LiveTwitchWeb, :live_view + + alias LiveExWebRTC.Player + + @impl true + def render(assigns) do + ~H""" + + """ + end + + @impl true + def mount(_params, _session, socket) do + socket = Player.attach(socket, id: "player", publisher_id: "publisher", pubsub: LiveTwitch.PubSub) + {:ok, socket} + end + end + ``` + ''' + use Phoenix.LiveView + + alias Membrane.WebRTC.SignalingChannel + + @type t() :: struct() + + defstruct [:ice_servers, id: nil, signaling_channel: nil, video?: true, audio?: true] + + attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") + + attr(:capture, __MODULE__, required: true) + + attr(:class, :string, default: nil, doc: "CSS/Tailwind classes for styling HTMLVideoElement") + + @doc """ + Helper function for rendering Capture live view. + """ + def live_render(assigns) do + ~H""" + <%= live_render(@socket, __MODULE__, id: "#{@capture.id}-lv", session: %{"class" => @class}) %> + """ + end + + @doc """ + Attaches required hooks and creates `t:t/0` struct. + + Created struct is saved in socket's assigns and has to be passed to `LiveExWebRTC.Player.live_render/1`. + + Options: + * `id` - capture id. This is typically your user id (if there is users database). + It is used to identify live view and generated HTML video player. + * `class` - a list of CSS/Tailwind classes that will be applied to the HTMLVideoPlayer. Defaults to "". + """ + @spec attach(Phoenix.LiveView.Socket.t(), Keyword.t()) :: Phoenix.LiveView.Socket.t() + def attach(socket, opts) do + opts = + opts + |> Keyword.validate!([ + :id, + :signaling_channel, + video?: true, + audio?: true, + ice_servers: [%{urls: "stun:stun.l.google.com:19302"}] + ]) + + capture = struct!(__MODULE__, opts) + + socket + |> assign(capture: capture) + |> attach_hook(:capture_handshake, :handle_info, &handshake/2) + end + + defp handshake({__MODULE__, {:connected, ref, child_pid, _meta}}, socket) do + # child live view is connected, send it capture struct + send(child_pid, {ref, socket.assigns.capture}) + {:halt, socket} + end + + defp handshake(_msg, socket) do + {:cont, socket} + end + + ## CALLBACKS + + @impl true + def render(%{capture: nil} = assigns) do + ~H""" + NOT RENDERED {inspect(self())} + """ + end + + @impl true + def render(assigns) do + ~H""" + RENDERED {inspect(self())} + + """ + end + + # todo: simplify the function below later, but for now it should work fine + @impl true + def mount(_params, %{"class" => class}, socket) do + socket = assign(socket, class: class, capture: nil) + + if connected?(socket) do + ref = make_ref() + send(socket.parent_pid, {__MODULE__, {:connected, ref, self(), %{}}}) + + socket = + receive do + {^ref, %__MODULE__{} = capture} -> + IO.inspect(capture, label: "CAPTURE MOUNT") + SignalingChannel.register_peer(capture.signaling_channel, message_format: :json_data) + + media_constraints = %{ + "audio" => inspect(capture.audio?), + "video" => inspect(capture.video?) + } + + socket + |> assign(capture: capture) + |> push_event("media_constraints-#{capture.id}", media_constraints) + after + 5000 -> exit(:timeout) + end + + {:ok, socket} + else + {:ok, socket} + end + end + + @impl true + def handle_info({SignalingChannel, _pid, message, _metadata}, socket) do + # IO.inspect(message, label: "SIGNALING BOOMBOX -> BROWSER") + + {:noreply, + socket + |> push_event("webrtc_signaling-#{socket.assigns.capture.id}", message)} + end + + @impl true + def handle_event("webrtc_signaling", message, socket) do + # |> IO.inspect(label: "SIGNALING BROWSER -> BOOMBOX") + message = Jason.decode!(message) + + if message["data"] do + SignalingChannel.signal( + socket.assigns.capture.signaling_channel, + message + ) + end + + {:noreply, socket} + end +end diff --git a/lib/webrtc/player.ex b/lib/webrtc/player.ex index a0e06ff..71c092c 100644 --- a/lib/webrtc/player.ex +++ b/lib/webrtc/player.ex @@ -113,7 +113,7 @@ defmodule Boombox.Live.Player do socket |> assign(player: player) - |> attach_hook(:handshake, :handle_info, &handshake/2) + |> attach_hook(:player_handshake, :handle_info, &handshake/2) end defp handshake({__MODULE__, {:connected, ref, child_pid, _meta}}, socket) do @@ -190,6 +190,7 @@ defmodule Boombox.Live.Player do message ) end + {:noreply, socket} end end diff --git a/player_demo.exs b/player_demo.exs index 16ba55b..c6062c9 100644 --- a/player_demo.exs +++ b/player_demo.exs @@ -168,14 +168,6 @@ defmodule Example.HomeLive do ~H""" """ end - - def handle_event("inc", _params, socket) do - {:noreply, assign(socket, :count, socket.assigns.count + 1)} - end - - def handle_event("dec", _params, socket) do - {:noreply, assign(socket, :count, socket.assigns.count - 1)} - end end defmodule Example.Router do From 8beb69e577f0f686fb8e852b1865a880f3a46c72 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 13 Feb 2025 14:38:06 +0100 Subject: [PATCH 10/45] Logs refactor --- capture_and_play_demo.exs | 2 +- lib/{ => boombox_live}/webrtc/capture.ex | 2 +- lib/{ => boombox_live}/webrtc/player.ex | 2 +- lib/membrane_template.ex | 2 -- player_demo.exs | 28 +++++++++--------------- 5 files changed, 13 insertions(+), 23 deletions(-) rename lib/{ => boombox_live}/webrtc/capture.ex (99%) rename lib/{ => boombox_live}/webrtc/player.ex (99%) delete mode 100644 lib/membrane_template.ex diff --git a/capture_and_play_demo.exs b/capture_and_play_demo.exs index a66f2f1..bbc7a14 100644 --- a/capture_and_play_demo.exs +++ b/capture_and_play_demo.exs @@ -33,7 +33,7 @@ end defmodule Example.HomeLive do use Phoenix.LiveView, layout: {__MODULE__, :live} - alias Boombox.Live.{Capture, Player} + alias Boombox.Live.WebRTC.{Capture, Player} def mount(_params, _session, socket) do socket = diff --git a/lib/webrtc/capture.ex b/lib/boombox_live/webrtc/capture.ex similarity index 99% rename from lib/webrtc/capture.ex rename to lib/boombox_live/webrtc/capture.ex index 9ff58a1..f6fea72 100644 --- a/lib/webrtc/capture.ex +++ b/lib/boombox_live/webrtc/capture.ex @@ -1,4 +1,4 @@ -defmodule Boombox.Live.Capture do +defmodule Boombox.Live.WebRTC.Capture do @moduledoc ~S''' Component for sending and playing audio and video via WebRTC from a Phoenix app to a browser (browser subscribes). diff --git a/lib/webrtc/player.ex b/lib/boombox_live/webrtc/player.ex similarity index 99% rename from lib/webrtc/player.ex rename to lib/boombox_live/webrtc/player.ex index 71c092c..e329b28 100644 --- a/lib/webrtc/player.ex +++ b/lib/boombox_live/webrtc/player.ex @@ -1,4 +1,4 @@ -defmodule Boombox.Live.Player do +defmodule Boombox.Live.WebRTC.Player do @moduledoc ~S''' Component for sending and playing audio and video via WebRTC from a Phoenix app to a browser (browser subscribes). diff --git a/lib/membrane_template.ex b/lib/membrane_template.ex deleted file mode 100644 index c6882fb..0000000 --- a/lib/membrane_template.ex +++ /dev/null @@ -1,2 +0,0 @@ -defmodule Membrane.Template do -end diff --git a/player_demo.exs b/player_demo.exs index c6062c9..bf7caa6 100644 --- a/player_demo.exs +++ b/player_demo.exs @@ -33,7 +33,7 @@ end defmodule Example.HomeLive do use Phoenix.LiveView, layout: {__MODULE__, :live} - alias Boombox.Live.Player + alias Boombox.Live.WebRTC.Player def mount(_params, _session, socket) do socket = @@ -42,8 +42,6 @@ defmodule Example.HomeLive do {:ok, boombox_pid} = Task.start_link(fn -> - # Boombox.run(input: "../output.mp4", output: {:webrtc, signaling_channel}) - overlay = Req.get!("https://avatars.githubusercontent.com/u/25247695?s=200&v=4").body |> Vix.Vips.Image.new_from_buffer() @@ -98,32 +96,26 @@ defmodule Example.HomeLive do function createPlayerHook(iceServers = [{ urls: "stun:stun.l.google.com:19302" }]) { return { async mounted() { - console.log("MOUNTED") - this.pc = new RTCPeerConnection({ iceServers: iceServers }); - this.el.srcObject = new MediaStream() + this.el.srcObject = new MediaStream(); this.pc.ontrack = (event) => { - console.log("NEW TRACK", this.el) this.el.srcObject.addTrack(event.track); - } + }; - this.pc.onicecandidate = (ev) => { - console.log("NEW BROWSER ICE CANDIDATE") - message = JSON.stringify({ type: "ice_candidate", data: ev.candidate }); + this.pc.onicecandidate = (event) => { + console.log("[" + this.el.id + "] Sent ICE candidate:", event.candidate); + message = JSON.stringify({ type: "ice_candidate", data: event.candidate }); this.pushEventTo(this.el, "webrtc_signaling", message); }; - const eventName = "webrtc_signaling-" + this.el.id + const eventName = "webrtc_signaling-" + this.el.id; this.handleEvent(eventName, async (event) => { - - console.log("NEW SIGNALING MESSAGE", event) - const { type, data } = event; switch (type) { case "sdp_offer": - console.log("Received SDP offer:", data); + console.log("[" + this.el.id + "] Received SDP offer:", data); await this.pc.setRemoteDescription(data); const answer = await this.pc.createAnswer(); @@ -131,11 +123,11 @@ defmodule Example.HomeLive do message = JSON.stringify({ type: "sdp_answer", data: answer }); this.pushEventTo(this.el, "webrtc_signaling", message); - console.log("Sent SDP answer:", answer); + console.log("[" + this.el.id + "] Sent SDP answer:", answer); break; case "ice_candidate": - console.log("Recieved ICE candidate:", data); + console.log("[" + this.el.id + "] Recieved ICE candidate:", data); await this.pc.addIceCandidate(data); } }); From f6f434dba7e9ca37b19a4c655ab4f77ddeeebce6 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 13 Feb 2025 14:55:05 +0100 Subject: [PATCH 11/45] Refactor logs --- capture_and_play_demo.exs | 2 -- lib/boombox_live/webrtc/capture.ex | 21 +++++++++++++++------ lib/boombox_live/webrtc/player.ex | 20 ++++++++++++++------ player_demo.exs | 2 -- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/capture_and_play_demo.exs b/capture_and_play_demo.exs index bbc7a14..44fce41 100644 --- a/capture_and_play_demo.exs +++ b/capture_and_play_demo.exs @@ -49,8 +49,6 @@ defmodule Example.HomeLive do ) end) - IO.inspect(socket, label: "PARENT MOUNT") - _debug_task = Task.start_link(fn -> Debugger.debug(boombox_pid) end) socket diff --git a/lib/boombox_live/webrtc/capture.ex b/lib/boombox_live/webrtc/capture.ex index f6fea72..053b07b 100644 --- a/lib/boombox_live/webrtc/capture.ex +++ b/lib/boombox_live/webrtc/capture.ex @@ -61,6 +61,8 @@ defmodule Boombox.Live.WebRTC.Capture do alias Membrane.WebRTC.SignalingChannel + require Logger + @type t() :: struct() defstruct [:ice_servers, id: nil, signaling_channel: nil, video?: true, audio?: true] @@ -124,14 +126,12 @@ defmodule Boombox.Live.WebRTC.Capture do @impl true def render(%{capture: nil} = assigns) do ~H""" - NOT RENDERED {inspect(self())} """ end @impl true def render(assigns) do ~H""" - RENDERED {inspect(self())} """ end @@ -148,8 +148,10 @@ defmodule Boombox.Live.WebRTC.Capture do socket = receive do {^ref, %__MODULE__{} = capture} -> - IO.inspect(capture, label: "CAPTURE MOUNT") - SignalingChannel.register_peer(capture.signaling_channel, message_format: :json_data) + Logger.metadata([module: __MODULE__, id: capture.id]) + + capture.signaling_channel + |> SignalingChannel.register_peer(message_format: :json_data) media_constraints = %{ "audio" => inspect(capture.audio?), @@ -171,7 +173,9 @@ defmodule Boombox.Live.WebRTC.Capture do @impl true def handle_info({SignalingChannel, _pid, message, _metadata}, socket) do - # IO.inspect(message, label: "SIGNALING BOOMBOX -> BROWSER") + Logger.info(""" + #{log_prefix(socket.assigns.capture.id)} Sent WebRTC signaling message: #{inspect(message, pretty: true)} + """) {:noreply, socket @@ -180,9 +184,12 @@ defmodule Boombox.Live.WebRTC.Capture do @impl true def handle_event("webrtc_signaling", message, socket) do - # |> IO.inspect(label: "SIGNALING BROWSER -> BOOMBOX") message = Jason.decode!(message) + Logger.info(""" + #{log_prefix(socket.assigns.capture.id)} Received WebRTC signaling message: #{inspect(message, pretty: true)} + """) + if message["data"] do SignalingChannel.signal( socket.assigns.capture.signaling_channel, @@ -192,4 +199,6 @@ defmodule Boombox.Live.WebRTC.Capture do {:noreply, socket} end + + defp log_prefix(id), do: [module: __MODULE__, id: id] |> inspect() end diff --git a/lib/boombox_live/webrtc/player.ex b/lib/boombox_live/webrtc/player.ex index e329b28..e85ac42 100644 --- a/lib/boombox_live/webrtc/player.ex +++ b/lib/boombox_live/webrtc/player.ex @@ -61,6 +61,8 @@ defmodule Boombox.Live.WebRTC.Player do alias Membrane.WebRTC.SignalingChannel + require Logger + @type t() :: struct() defstruct [:video?, :audio?, :ice_servers, id: nil, signaling_channel: nil] @@ -148,8 +150,6 @@ defmodule Boombox.Live.WebRTC.Player do def mount(_params, %{"class" => class}, socket) do socket = assign(socket, class: class, player: nil) - IO.inspect({self(), connected?(socket)}, label: "MOUNT BEGIN") - if connected?(socket) do ref = make_ref() send(socket.parent_pid, {__MODULE__, {:connected, ref, self(), %{}}}) @@ -157,8 +157,8 @@ defmodule Boombox.Live.WebRTC.Player do socket = receive do {^ref, %__MODULE__{} = player} -> - IO.inspect(player, label: "PLAYER MOUNT") - SignalingChannel.register_peer(player.signaling_channel, message_format: :json_data) + player.signaling_channel + |> SignalingChannel.register_peer(message_format: :json_data) socket |> assign(player: player) after @@ -173,7 +173,9 @@ defmodule Boombox.Live.WebRTC.Player do @impl true def handle_info({SignalingChannel, _pid, message, _metadata}, socket) do - IO.inspect(message, label: "SIGNALING BOOMBOX -> BROWSER") + Logger.info(""" + #{log_prefix(socket.assigns.player.id)} Sent WebRTC signaling message: #{inspect(message, pretty: true)} + """) {:noreply, socket @@ -182,7 +184,11 @@ defmodule Boombox.Live.WebRTC.Player do @impl true def handle_event("webrtc_signaling", message, socket) do - message = Jason.decode!(message) |> IO.inspect(label: "SIGNALING BROWSER -> BOOMBOX") + message = Jason.decode!(message) + + Logger.info(""" + #{log_prefix(socket.assigns.player.id)} Received WebRTC signaling message: #{inspect(message, pretty: true)} + """) if message["data"] do SignalingChannel.signal( @@ -193,4 +199,6 @@ defmodule Boombox.Live.WebRTC.Player do {:noreply, socket} end + + defp log_prefix(id), do: [module: __MODULE__, id: id] |> inspect() end diff --git a/player_demo.exs b/player_demo.exs index bf7caa6..14fd331 100644 --- a/player_demo.exs +++ b/player_demo.exs @@ -69,8 +69,6 @@ defmodule Example.HomeLive do ) end) - IO.inspect(socket, label: "PARENT MOUNT") - _debug_task = Task.start_link(fn -> Debugger.debug(boombox_pid) end) socket From 16f45333deaad6c3e6bbcbe14d7be001ee58ee2f Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 13 Feb 2025 16:41:49 +0100 Subject: [PATCH 12/45] Celanup demo --- capture_and_play_demo.exs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/capture_and_play_demo.exs b/capture_and_play_demo.exs index 44fce41..beeed3c 100644 --- a/capture_and_play_demo.exs +++ b/capture_and_play_demo.exs @@ -16,20 +16,6 @@ defmodule Example.ErrorView do def render(template, _), do: Phoenix.Controller.status_message_from_template(template) end -defmodule Debugger do - def debug(pid) do - if Process.alive?(pid) do - IO.puts("ALIVE #{inspect(pid)} #{self() |> inspect()}") - else - IO.puts("NOT ALIVE #{inspect(pid)} #{self() |> inspect()}") - end - - Process.sleep(1000) - - debug(pid) - end -end - defmodule Example.HomeLive do use Phoenix.LiveView, layout: {__MODULE__, :live} @@ -49,8 +35,6 @@ defmodule Example.HomeLive do ) end) - _debug_task = Task.start_link(fn -> Debugger.debug(boombox_pid) end) - socket |> Capture.attach( id: "mediaCapture", @@ -62,11 +46,6 @@ defmodule Example.HomeLive do id: "videoPlayer", signaling_channel: egress_signaling ) - |> assign( - ingress_signaling: ingress_signaling, - egress_signaling: egress_signaling, - boombox: boombox_pid - ) else socket end From 83eaf4951569dd1905247eb2863721dfc9a8723e Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 17 Feb 2025 17:32:18 +0100 Subject: [PATCH 13/45] Refactor live views, add example project --- example_project/.formatter.exs | 5 + example_project/.gitignore | 37 ++++ example_project/README.md | 18 ++ example_project/assets/css/app.css | 5 + example_project/assets/js/app.js | 44 +++++ example_project/assets/tailwind.config.js | 74 ++++++++ example_project/assets/vendor/topbar.js | 165 ++++++++++++++++++ example_project/config/config.exs | 56 ++++++ example_project/config/dev.exs | 71 ++++++++ example_project/config/prod.exs | 15 ++ example_project/config/runtime.exs | 84 +++++++++ example_project/config/test.exs | 18 ++ example_project/lib/example_project.ex | 9 + .../lib/example_project/application.ex | 33 ++++ example_project/lib/example_project_web.ex | 111 ++++++++++++ .../example_project_web/components/layouts.ex | 14 ++ .../components/layouts/app.html.heex | 4 + .../components/layouts/root.html.heex | 17 ++ .../lib/example_project_web/endpoint.ex | 52 ++++++ .../live_views/home_live.ex | 19 ++ .../lib/example_project_web/router.ex | 16 ++ example_project/mix.exs | 76 ++++++++ example_project/mix.lock | 28 +++ example_project/priv/static/favicon.ico | Bin 0 -> 152 bytes example_project/priv/static/images/logo.svg | 6 + example_project/priv/static/robots.txt | 5 + .../controllers/error_html_test.exs | 14 ++ .../controllers/error_json_test.exs | 12 ++ .../controllers/page_controller_test.exs | 8 + example_project/test/support/conn_case.ex | 37 ++++ example_project/test/test_helper.exs | 1 + lib/boombox_live/webrtc/capture.ex | 28 +-- lib/boombox_live/webrtc/player.ex | 24 ++- lib/boombox_live/webrtc/utils.ex | 30 ++++ 34 files changed, 1119 insertions(+), 17 deletions(-) create mode 100644 example_project/.formatter.exs create mode 100644 example_project/.gitignore create mode 100644 example_project/README.md create mode 100644 example_project/assets/css/app.css create mode 100644 example_project/assets/js/app.js create mode 100644 example_project/assets/tailwind.config.js create mode 100644 example_project/assets/vendor/topbar.js create mode 100644 example_project/config/config.exs create mode 100644 example_project/config/dev.exs create mode 100644 example_project/config/prod.exs create mode 100644 example_project/config/runtime.exs create mode 100644 example_project/config/test.exs create mode 100644 example_project/lib/example_project.ex create mode 100644 example_project/lib/example_project/application.ex create mode 100644 example_project/lib/example_project_web.ex create mode 100644 example_project/lib/example_project_web/components/layouts.ex create mode 100644 example_project/lib/example_project_web/components/layouts/app.html.heex create mode 100644 example_project/lib/example_project_web/components/layouts/root.html.heex create mode 100644 example_project/lib/example_project_web/endpoint.ex create mode 100644 example_project/lib/example_project_web/live_views/home_live.ex create mode 100644 example_project/lib/example_project_web/router.ex create mode 100644 example_project/mix.exs create mode 100644 example_project/mix.lock create mode 100644 example_project/priv/static/favicon.ico create mode 100644 example_project/priv/static/images/logo.svg create mode 100644 example_project/priv/static/robots.txt create mode 100644 example_project/test/example_project_web/controllers/error_html_test.exs create mode 100644 example_project/test/example_project_web/controllers/error_json_test.exs create mode 100644 example_project/test/example_project_web/controllers/page_controller_test.exs create mode 100644 example_project/test/support/conn_case.ex create mode 100644 example_project/test/test_helper.exs create mode 100644 lib/boombox_live/webrtc/utils.ex diff --git a/example_project/.formatter.exs b/example_project/.formatter.exs new file mode 100644 index 0000000..e945e12 --- /dev/null +++ b/example_project/.formatter.exs @@ -0,0 +1,5 @@ +[ + import_deps: [:phoenix], + plugins: [Phoenix.LiveView.HTMLFormatter], + inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] +] diff --git a/example_project/.gitignore b/example_project/.gitignore new file mode 100644 index 0000000..d039d2b --- /dev/null +++ b/example_project/.gitignore @@ -0,0 +1,37 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Temporary files, for example, from tests. +/tmp/ + +# Ignore package tarball (built via "mix hex.build"). +example_project-*.tar + +# Ignore assets that are produced by build tools. +/priv/static/assets/ + +# Ignore digested assets cache. +/priv/static/cache_manifest.json + +# In case you use Node.js/npm, you want to ignore these. +npm-debug.log +/assets/node_modules/ + diff --git a/example_project/README.md b/example_project/README.md new file mode 100644 index 0000000..9ff00e5 --- /dev/null +++ b/example_project/README.md @@ -0,0 +1,18 @@ +# ExampleProject + +To start your Phoenix server: + + * Run `mix setup` to install and setup dependencies + * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + + * Official website: https://www.phoenixframework.org/ + * Guides: https://hexdocs.pm/phoenix/overview.html + * Docs: https://hexdocs.pm/phoenix + * Forum: https://elixirforum.com/c/phoenix-forum + * Source: https://github.com/phoenixframework/phoenix diff --git a/example_project/assets/css/app.css b/example_project/assets/css/app.css new file mode 100644 index 0000000..378c8f9 --- /dev/null +++ b/example_project/assets/css/app.css @@ -0,0 +1,5 @@ +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + +/* This file is for your main application CSS */ diff --git a/example_project/assets/js/app.js b/example_project/assets/js/app.js new file mode 100644 index 0000000..d5e278a --- /dev/null +++ b/example_project/assets/js/app.js @@ -0,0 +1,44 @@ +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" +import topbar from "../vendor/topbar" + +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +let liveSocket = new LiveSocket("/live", Socket, { + longPollFallbackMs: 2500, + params: {_csrf_token: csrfToken} +}) + +// Show progress bar on live navigation and form submits +topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) +window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) +window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket + diff --git a/example_project/assets/tailwind.config.js b/example_project/assets/tailwind.config.js new file mode 100644 index 0000000..e766447 --- /dev/null +++ b/example_project/assets/tailwind.config.js @@ -0,0 +1,74 @@ +// See the Tailwind configuration guide for advanced usage +// https://tailwindcss.com/docs/configuration + +const plugin = require("tailwindcss/plugin") +const fs = require("fs") +const path = require("path") + +module.exports = { + content: [ + "./js/**/*.js", + "../lib/example_project_web.ex", + "../lib/example_project_web/**/*.*ex" + ], + theme: { + extend: { + colors: { + brand: "#FD4F00", + } + }, + }, + plugins: [ + require("@tailwindcss/forms"), + // Allows prefixing tailwind classes with LiveView classes to add rules + // only when LiveView classes are applied, for example: + // + //
+ // + plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])), + plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])), + plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])), + + // Embeds Heroicons (https://heroicons.com) into your app.css bundle + // See your `CoreComponents.icon/1` for more information. + // + plugin(function({matchComponents, theme}) { + let iconsDir = path.join(__dirname, "../deps/heroicons/optimized") + let values = {} + let icons = [ + ["", "/24/outline"], + ["-solid", "/24/solid"], + ["-mini", "/20/solid"], + ["-micro", "/16/solid"] + ] + icons.forEach(([suffix, dir]) => { + fs.readdirSync(path.join(iconsDir, dir)).forEach(file => { + let name = path.basename(file, ".svg") + suffix + values[name] = {name, fullPath: path.join(iconsDir, dir, file)} + }) + }) + matchComponents({ + "hero": ({name, fullPath}) => { + let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") + let size = theme("spacing.6") + if (name.endsWith("-mini")) { + size = theme("spacing.5") + } else if (name.endsWith("-micro")) { + size = theme("spacing.4") + } + return { + [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, + "-webkit-mask": `var(--hero-${name})`, + "mask": `var(--hero-${name})`, + "mask-repeat": "no-repeat", + "background-color": "currentColor", + "vertical-align": "middle", + "display": "inline-block", + "width": size, + "height": size + } + } + }, {values}) + }) + ] +} diff --git a/example_project/assets/vendor/topbar.js b/example_project/assets/vendor/topbar.js new file mode 100644 index 0000000..4195727 --- /dev/null +++ b/example_project/assets/vendor/topbar.js @@ -0,0 +1,165 @@ +/** + * @license MIT + * topbar 2.0.0, 2023-02-04 + * https://buunguyen.github.io/topbar + * Copyright (c) 2021 Buu Nguyen + */ +(function (window, document) { + "use strict"; + + // https://gist.github.com/paulirish/1579671 + (function () { + var lastTime = 0; + var vendors = ["ms", "moz", "webkit", "o"]; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x] + "RequestAnimationFrame"]; + window.cancelAnimationFrame = + window[vendors[x] + "CancelAnimationFrame"] || + window[vendors[x] + "CancelRequestAnimationFrame"]; + } + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function (callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + })(); + + var canvas, + currentProgress, + showing, + progressTimerId = null, + fadeTimerId = null, + delayTimerId = null, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false); + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); + else elem["on" + type] = handler; + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth; + canvas.height = options.barThickness * 5; // need space for shadow + + var ctx = canvas.getContext("2d"); + ctx.shadowBlur = options.shadowBlur; + ctx.shadowColor = options.shadowColor; + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]); + ctx.lineWidth = options.barThickness; + ctx.beginPath(); + ctx.moveTo(0, options.barThickness / 2); + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ); + ctx.strokeStyle = lineGradient; + ctx.stroke(); + }, + createCanvas = function () { + canvas = document.createElement("canvas"); + var style = canvas.style; + style.position = "fixed"; + style.top = style.left = style.right = style.margin = style.padding = 0; + style.zIndex = 100001; + style.display = "none"; + if (options.className) canvas.classList.add(options.className); + document.body.appendChild(canvas); + addEvent(window, "resize", repaint); + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key]; + }, + show: function (delay) { + if (showing) return; + if (delay) { + if (delayTimerId) return; + delayTimerId = setTimeout(() => topbar.show(), delay); + } else { + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); + } + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress; + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to); + } + currentProgress = to > 1 ? 1 : to; + repaint(); + return currentProgress; + }, + hide: function () { + clearTimeout(delayTimerId); + delayTimerId = null; + if (!showing) return; + showing = false; + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId); + progressTimerId = null; + } + (function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05; + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none"; + fadeTimerId = null; + return; + } + } + fadeTimerId = window.requestAnimationFrame(loop); + })(); + }, + }; + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar; + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar; + }); + } else { + this.topbar = topbar; + } +}.call(this, window, document)); diff --git a/example_project/config/config.exs b/example_project/config/config.exs new file mode 100644 index 0000000..92386a0 --- /dev/null +++ b/example_project/config/config.exs @@ -0,0 +1,56 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +config :example_project, + generators: [timestamp_type: :utc_datetime] + +# Configures the endpoint +config :example_project, ExampleProjectWeb.Endpoint, + url: [host: "localhost"], + adapter: Bandit.PhoenixAdapter, + render_errors: [ + formats: [html: ExampleProjectWeb.ErrorHTML, json: ExampleProjectWeb.ErrorJSON], + layout: false + ], + pubsub_server: ExampleProject.PubSub, + live_view: [signing_salt: "mjeuZD0H"] + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.17.11", + example_project: [ + args: + ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ] + +# Configure tailwind (the version is required) +config :tailwind, + version: "3.4.3", + example_project: [ + args: ~w( + --config=tailwind.config.js + --input=css/app.css + --output=../priv/static/assets/app.css + ), + cd: Path.expand("../assets", __DIR__) + ] + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/example_project/config/dev.exs b/example_project/config/dev.exs new file mode 100644 index 0000000..6893142 --- /dev/null +++ b/example_project/config/dev.exs @@ -0,0 +1,71 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we can use it +# to bundle .js and .css sources. +# Binding to loopback ipv4 address prevents access from other machines. +config :example_project, ExampleProjectWeb.Endpoint, + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4000], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "0PrhqMtCh4XT+kBypnvBe9sR3SiOUuhVtmxfyekYHusrqZmBO3PSWEJcr0jxj6WT", + watchers: [ + esbuild: {Esbuild, :install_and_run, [:example_project, ~w(--sourcemap=inline --watch)]}, + tailwind: {Tailwind, :install_and_run, [:example_project, ~w(--watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :example_project, ExampleProjectWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"lib/example_project_web/(controllers|live|components)/.*(ex|heex)$" + ] + ] + +# Enable dev routes for dashboard and mailbox +config :example_project, dev_routes: true + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime + +config :phoenix_live_view, + # Include HEEx debug annotations as HTML comments in rendered markup + debug_heex_annotations: true, + # Enable helpful, but potentially expensive runtime checks + enable_expensive_runtime_checks: true diff --git a/example_project/config/prod.exs b/example_project/config/prod.exs new file mode 100644 index 0000000..8acfd85 --- /dev/null +++ b/example_project/config/prod.exs @@ -0,0 +1,15 @@ +import Config + +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix assets.deploy` task, +# which you should run after static files are built and +# before starting your production server. +config :example_project, ExampleProjectWeb.Endpoint, + cache_static_manifest: "priv/static/cache_manifest.json" + +# Do not print debug messages in production +config :logger, level: :info + +# Runtime production configuration, including reading +# of environment variables, is done on config/runtime.exs. diff --git a/example_project/config/runtime.exs b/example_project/config/runtime.exs new file mode 100644 index 0000000..07c04f3 --- /dev/null +++ b/example_project/config/runtime.exs @@ -0,0 +1,84 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. + +# ## Using releases +# +# If you use `mix release`, you need to explicitly enable the server +# by passing the PHX_SERVER=true when you start it: +# +# PHX_SERVER=true bin/example_project start +# +# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` +# script that automatically sets the env var above. +if System.get_env("PHX_SERVER") do + config :example_project, ExampleProjectWeb.Endpoint, server: true +end + +if config_env() == :prod do + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + host = System.get_env("PHX_HOST") || "example.com" + port = String.to_integer(System.get_env("PORT") || "4000") + + config :example_project, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") + + config :example_project, ExampleProjectWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0 + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0}, + port: port + ], + secret_key_base: secret_key_base + + # ## SSL Support + # + # To get SSL working, you will need to add the `https` key + # to your endpoint configuration: + # + # config :example_project, ExampleProjectWeb.Endpoint, + # https: [ + # ..., + # port: 443, + # cipher_suite: :strong, + # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), + # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") + # ] + # + # The `cipher_suite` is set to `:strong` to support only the + # latest and more secure SSL ciphers. This means old browsers + # and clients may not be supported. You can set it to + # `:compatible` for wider support. + # + # `:keyfile` and `:certfile` expect an absolute path to the key + # and cert in disk or a relative path inside priv, for example + # "priv/ssl/server.key". For all supported SSL configuration + # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 + # + # We also recommend setting `force_ssl` in your config/prod.exs, + # ensuring no data is ever sent via http, always redirecting to https: + # + # config :example_project, ExampleProjectWeb.Endpoint, + # force_ssl: [hsts: true] + # + # Check `Plug.SSL` for all available options in `force_ssl`. +end diff --git a/example_project/config/test.exs b/example_project/config/test.exs new file mode 100644 index 0000000..f43df7e --- /dev/null +++ b/example_project/config/test.exs @@ -0,0 +1,18 @@ +import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :example_project, ExampleProjectWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "IWAvaHFOK0m2MvwpUQhGTPiKUPNIXs19LxW+Lts3kQISgoMMEWOW+haMYQnIK1by", + server: false + +# Print only warnings and errors during test +config :logger, level: :warning + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime + +# Enable helpful, but potentially expensive runtime checks +config :phoenix_live_view, + enable_expensive_runtime_checks: true diff --git a/example_project/lib/example_project.ex b/example_project/lib/example_project.ex new file mode 100644 index 0000000..ef1a176 --- /dev/null +++ b/example_project/lib/example_project.ex @@ -0,0 +1,9 @@ +defmodule ExampleProject do + @moduledoc """ + ExampleProject keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/example_project/lib/example_project/application.ex b/example_project/lib/example_project/application.ex new file mode 100644 index 0000000..0957c3f --- /dev/null +++ b/example_project/lib/example_project/application.ex @@ -0,0 +1,33 @@ +defmodule ExampleProject.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + ExampleProjectWeb.Telemetry, + {DNSCluster, query: Application.get_env(:example_project, :dns_cluster_query) || :ignore}, + {Phoenix.PubSub, name: ExampleProject.PubSub}, + # Start a worker by calling: ExampleProject.Worker.start_link(arg) + # {ExampleProject.Worker, arg}, + # Start to serve requests, typically the last entry + ExampleProjectWeb.Endpoint + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: ExampleProject.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + ExampleProjectWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/example_project/lib/example_project_web.ex b/example_project/lib/example_project_web.ex new file mode 100644 index 0000000..dfed80f --- /dev/null +++ b/example_project/lib/example_project_web.ex @@ -0,0 +1,111 @@ +defmodule ExampleProjectWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, components, channels, and so on. + + This can be used in your application as: + + use ExampleProjectWeb, :controller + use ExampleProjectWeb, :html + + The definitions below will be executed for every controller, + component, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define additional modules and import + those modules here. + """ + + def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + + def router do + quote do + use Phoenix.Router, helpers: false + + # Import common connection and controller functions to use in pipelines + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + end + end + + def controller do + quote do + use Phoenix.Controller, + formats: [:html, :json], + layouts: [html: ExampleProjectWeb.Layouts] + + import Plug.Conn + + unquote(verified_routes()) + end + end + + def live_view do + quote do + use Phoenix.LiveView, + layout: {ExampleProjectWeb.Layouts, :app} + + unquote(html_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(html_helpers()) + end + end + + def html do + quote do + use Phoenix.Component + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_csrf_token: 0, view_module: 1, view_template: 1] + + # Include general helpers for rendering HTML + unquote(html_helpers()) + end + end + + defp html_helpers do + quote do + # HTML escaping functionality + import Phoenix.HTML + # Core UI components + import ExampleProjectWeb.CoreComponents + + # Shortcut for generating JS commands + alias Phoenix.LiveView.JS + + # Routes generation with the ~p sigil + unquote(verified_routes()) + end + end + + def verified_routes do + quote do + use Phoenix.VerifiedRoutes, + endpoint: ExampleProjectWeb.Endpoint, + router: ExampleProjectWeb.Router, + statics: ExampleProjectWeb.static_paths() + end + end + + @doc """ + When used, dispatch to the appropriate controller/live_view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/example_project/lib/example_project_web/components/layouts.ex b/example_project/lib/example_project_web/components/layouts.ex new file mode 100644 index 0000000..ebdca43 --- /dev/null +++ b/example_project/lib/example_project_web/components/layouts.ex @@ -0,0 +1,14 @@ +defmodule ExampleProjectWeb.Layouts do + @moduledoc """ + This module holds different layouts used by your application. + + See the `layouts` directory for all templates available. + The "root" layout is a skeleton rendered as part of the + application router. The "app" layout is set as the default + layout on both `use ExampleProjectWeb, :controller` and + `use ExampleProjectWeb, :live_view`. + """ + use ExampleProjectWeb, :html + + embed_templates "layouts/*" +end diff --git a/example_project/lib/example_project_web/components/layouts/app.html.heex b/example_project/lib/example_project_web/components/layouts/app.html.heex new file mode 100644 index 0000000..3fbe272 --- /dev/null +++ b/example_project/lib/example_project_web/components/layouts/app.html.heex @@ -0,0 +1,4 @@ +
+ <.flash_group flash={@flash} /> + {@inner_content} +
diff --git a/example_project/lib/example_project_web/components/layouts/root.html.heex b/example_project/lib/example_project_web/components/layouts/root.html.heex new file mode 100644 index 0000000..2f9ed93 --- /dev/null +++ b/example_project/lib/example_project_web/components/layouts/root.html.heex @@ -0,0 +1,17 @@ + + + + + + + <.live_title default="ExampleProject" suffix=" · Phoenix Framework"> + {assigns[:page_title]} + + + + + + {@inner_content} + + diff --git a/example_project/lib/example_project_web/endpoint.ex b/example_project/lib/example_project_web/endpoint.ex new file mode 100644 index 0000000..3dbc810 --- /dev/null +++ b/example_project/lib/example_project_web/endpoint.ex @@ -0,0 +1,52 @@ +defmodule ExampleProjectWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :example_project + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_example_project_key", + signing_salt: "qTrQh+Y1", + same_site: "Lax" + ] + + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [session: @session_options]], + longpoll: [connect_info: [session: @session_options]] + + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phx.digest + # when deploying your static files in production. + plug Plug.Static, + at: "/", + from: :example_project, + gzip: false, + only: ExampleProjectWeb.static_paths() + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader + end + + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + + plug Plug.MethodOverride + plug Plug.Head + plug Plug.Session, @session_options + plug ExampleProjectWeb.Router +end diff --git a/example_project/lib/example_project_web/live_views/home_live.ex b/example_project/lib/example_project_web/live_views/home_live.ex new file mode 100644 index 0000000..f714b15 --- /dev/null +++ b/example_project/lib/example_project_web/live_views/home_live.ex @@ -0,0 +1,19 @@ +# Copyright 2025 feliks +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +defmodule ExampleProject.HomeLive do + + use ExampleProjectWeb, :live_view +end diff --git a/example_project/lib/example_project_web/router.ex b/example_project/lib/example_project_web/router.ex new file mode 100644 index 0000000..fa4554a --- /dev/null +++ b/example_project/lib/example_project_web/router.ex @@ -0,0 +1,16 @@ +defmodule ExampleProjectWeb.Router do + use ExampleProjectWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, html: {ExampleProjectWeb.Layouts, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end +end diff --git a/example_project/mix.exs b/example_project/mix.exs new file mode 100644 index 0000000..2d92df8 --- /dev/null +++ b/example_project/mix.exs @@ -0,0 +1,76 @@ +defmodule ExampleProject.MixProject do + use Mix.Project + + def project do + [ + app: :example_project, + version: "0.1.0", + elixir: "~> 1.14", + elixirc_paths: elixirc_paths(Mix.env()), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {ExampleProject.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.7.19"}, + {:phoenix_html, "~> 4.1"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 1.0.0"}, + {:floki, ">= 0.30.0", only: :test}, + {:phoenix_live_dashboard, "~> 0.8.3"}, + {:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, + {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, + {:heroicons, + github: "tailwindlabs/heroicons", + tag: "v2.1.1", + sparse: "optimized", + app: false, + compile: false, + depth: 1}, + {:telemetry_metrics, "~> 1.0"}, + {:telemetry_poller, "~> 1.0"}, + {:jason, "~> 1.2"}, + {:dns_cluster, "~> 0.1.1"}, + {:bandit, "~> 1.5"} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to install project dependencies and perform other setup tasks, run: + # + # $ mix setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + setup: ["deps.get", "assets.setup", "assets.build"], + "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"], + "assets.build": ["tailwind example_project", "esbuild example_project"], + "assets.deploy": [ + "tailwind example_project --minify", + "esbuild example_project --minify", + "phx.digest" + ] + ] + end +end diff --git a/example_project/mix.lock b/example_project/mix.lock new file mode 100644 index 0000000..ba7ca2e --- /dev/null +++ b/example_project/mix.lock @@ -0,0 +1,28 @@ +%{ + "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, + "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, + "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, + "esbuild": {:hex, :esbuild, "0.9.0", "f043eeaca4932ca8e16e5429aebd90f7766f31ac160a25cbd9befe84f2bc068f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b415027f71d5ab57ef2be844b2a10d0c1b5a492d431727f43937adce22ba45ae"}, + "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, + "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, + "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]}, + "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "phoenix": {:hex, :phoenix, "1.7.19", "36617efe5afbd821099a8b994ff4618a340a5bfb25531a1802c4d4c634017a57", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "ba4dc14458278773f905f8ae6c2ec743d52c3a35b6b353733f64f02dfe096cd6"}, + "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.4", "327491b033e79db2f887b065c5a2993228449091883d74cfa1baa12f8c98d5eb", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9865316ddf8d78f382d63af278d20436b52d262b60239956817a61279514366"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, + "thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, +} diff --git a/example_project/priv/static/favicon.ico b/example_project/priv/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7f372bfc21cdd8cb47585339d5fa4d9dd424402f GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=@t!V@Ar*{oFEH`~d50E!_s``s q?{G*w(7?#d#v@^nKnY_HKaYb01EZMZjMqTJ89ZJ6T-G@yGywoKK_h|y literal 0 HcmV?d00001 diff --git a/example_project/priv/static/images/logo.svg b/example_project/priv/static/images/logo.svg new file mode 100644 index 0000000..9f26bab --- /dev/null +++ b/example_project/priv/static/images/logo.svg @@ -0,0 +1,6 @@ + diff --git a/example_project/priv/static/robots.txt b/example_project/priv/static/robots.txt new file mode 100644 index 0000000..26e06b5 --- /dev/null +++ b/example_project/priv/static/robots.txt @@ -0,0 +1,5 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/example_project/test/example_project_web/controllers/error_html_test.exs b/example_project/test/example_project_web/controllers/error_html_test.exs new file mode 100644 index 0000000..12099fd --- /dev/null +++ b/example_project/test/example_project_web/controllers/error_html_test.exs @@ -0,0 +1,14 @@ +defmodule ExampleProjectWeb.ErrorHTMLTest do + use ExampleProjectWeb.ConnCase, async: true + + # Bring render_to_string/4 for testing custom views + import Phoenix.Template + + test "renders 404.html" do + assert render_to_string(ExampleProjectWeb.ErrorHTML, "404", "html", []) == "Not Found" + end + + test "renders 500.html" do + assert render_to_string(ExampleProjectWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" + end +end diff --git a/example_project/test/example_project_web/controllers/error_json_test.exs b/example_project/test/example_project_web/controllers/error_json_test.exs new file mode 100644 index 0000000..27d1bd3 --- /dev/null +++ b/example_project/test/example_project_web/controllers/error_json_test.exs @@ -0,0 +1,12 @@ +defmodule ExampleProjectWeb.ErrorJSONTest do + use ExampleProjectWeb.ConnCase, async: true + + test "renders 404" do + assert ExampleProjectWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} + end + + test "renders 500" do + assert ExampleProjectWeb.ErrorJSON.render("500.json", %{}) == + %{errors: %{detail: "Internal Server Error"}} + end +end diff --git a/example_project/test/example_project_web/controllers/page_controller_test.exs b/example_project/test/example_project_web/controllers/page_controller_test.exs new file mode 100644 index 0000000..54de386 --- /dev/null +++ b/example_project/test/example_project_web/controllers/page_controller_test.exs @@ -0,0 +1,8 @@ +defmodule ExampleProjectWeb.PageControllerTest do + use ExampleProjectWeb.ConnCase + + test "GET /", %{conn: conn} do + conn = get(conn, ~p"/") + assert html_response(conn, 200) =~ "Peace of mind from prototype to production" + end +end diff --git a/example_project/test/support/conn_case.ex b/example_project/test/support/conn_case.ex new file mode 100644 index 0000000..58e0e34 --- /dev/null +++ b/example_project/test/support/conn_case.ex @@ -0,0 +1,37 @@ +defmodule ExampleProjectWeb.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use ExampleProjectWeb.ConnCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # The default endpoint for testing + @endpoint ExampleProjectWeb.Endpoint + + use ExampleProjectWeb, :verified_routes + + # Import conveniences for testing with connections + import Plug.Conn + import Phoenix.ConnTest + import ExampleProjectWeb.ConnCase + end + end + + setup _tags do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/example_project/test/test_helper.exs b/example_project/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/example_project/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/lib/boombox_live/webrtc/capture.ex b/lib/boombox_live/webrtc/capture.ex index 053b07b..1238603 100644 --- a/lib/boombox_live/webrtc/capture.ex +++ b/lib/boombox_live/webrtc/capture.ex @@ -78,7 +78,7 @@ defmodule Boombox.Live.WebRTC.Capture do """ def live_render(assigns) do ~H""" - <%= live_render(@socket, __MODULE__, id: "#{@capture.id}-lv", session: %{"class" => @class}) %> + <%= live_render(@socket, __MODULE__, id: "#{@capture.id}-lv", session: %{"class" => @class, "id" => @capture.id}) %> """ end @@ -106,14 +106,25 @@ defmodule Boombox.Live.WebRTC.Capture do capture = struct!(__MODULE__, opts) + all_captures = + socket.assigns + |> Map.get(__MODULE__, %{}) + |> Map.put(capture.id, capture) + socket - |> assign(capture: capture) + |> assign(__MODULE__, all_captures) |> attach_hook(:capture_handshake, :handle_info, &handshake/2) end - defp handshake({__MODULE__, {:connected, ref, child_pid, _meta}}, socket) do + defp handshake({__MODULE__, {:connected, capture_id, child_pid, _meta}}, socket) do # child live view is connected, send it capture struct - send(child_pid, {ref, socket.assigns.capture}) + capture = + socket.assings + |> Map.fetch!(__MODULE__) + |> Map.fetch!(capture_id) + + send(child_pid, capture) + {:halt, socket} end @@ -138,18 +149,15 @@ defmodule Boombox.Live.WebRTC.Capture do # todo: simplify the function below later, but for now it should work fine @impl true - def mount(_params, %{"class" => class}, socket) do + def mount(_params, %{"class" => class, "id" => id}, socket) do socket = assign(socket, class: class, capture: nil) if connected?(socket) do - ref = make_ref() - send(socket.parent_pid, {__MODULE__, {:connected, ref, self(), %{}}}) + send(socket.parent_pid, {__MODULE__, {:connected, id, self(), %{}}}) socket = receive do - {^ref, %__MODULE__{} = capture} -> - Logger.metadata([module: __MODULE__, id: capture.id]) - + %__MODULE__{} = capture -> capture.signaling_channel |> SignalingChannel.register_peer(message_format: :json_data) diff --git a/lib/boombox_live/webrtc/player.ex b/lib/boombox_live/webrtc/player.ex index e85ac42..57b9dfe 100644 --- a/lib/boombox_live/webrtc/player.ex +++ b/lib/boombox_live/webrtc/player.ex @@ -85,7 +85,7 @@ defmodule Boombox.Live.WebRTC.Player do """ def live_render(assigns) do ~H""" - <%= live_render(@socket, __MODULE__, id: "#{@player.id}-lv", session: %{"class" => @class}) %> + <%= live_render(@socket, __MODULE__, id: "#{@player.id}-lv", session: %{"class" => @class, "id" => @player.id}) %> """ end @@ -113,14 +113,25 @@ defmodule Boombox.Live.WebRTC.Player do player = struct!(__MODULE__, opts) + all_players = + socket.assigns + |> Map.get(__MODULE__, %{}) + |> Map.put(player.id, player) + socket - |> assign(player: player) + |> assign(__MODULE__, all_players) |> attach_hook(:player_handshake, :handle_info, &handshake/2) end - defp handshake({__MODULE__, {:connected, ref, child_pid, _meta}}, socket) do + defp handshake({__MODULE__, {:connected, id, child_pid, _meta}}, socket) do # child live view is connected, send it player struct - send(child_pid, {ref, socket.assigns.player}) + player = + socket.assigns + |> Map.fetch!(__MODULE__) + |> Map.fetch!(id) + + send(child_pid, player) + {:halt, socket} end @@ -147,16 +158,15 @@ defmodule Boombox.Live.WebRTC.Player do # todo: simplify the function below later, but for now it should work fine @impl true - def mount(_params, %{"class" => class}, socket) do + def mount(_params, %{"class" => class, "id" => id}, socket) do socket = assign(socket, class: class, player: nil) if connected?(socket) do - ref = make_ref() send(socket.parent_pid, {__MODULE__, {:connected, ref, self(), %{}}}) socket = receive do - {^ref, %__MODULE__{} = player} -> + %__MODULE__{} = player -> player.signaling_channel |> SignalingChannel.register_peer(message_format: :json_data) diff --git a/lib/boombox_live/webrtc/utils.ex b/lib/boombox_live/webrtc/utils.ex new file mode 100644 index 0000000..ec0e7e1 --- /dev/null +++ b/lib/boombox_live/webrtc/utils.ex @@ -0,0 +1,30 @@ +defmodule Boombox.Live.WebRTC.Utils do + @moduledoc false + + defmacro log_prefix(id) do + quote do + [module: __MODULE__, id: unquote(id)] + |> inspect() + end + end + + defmacro assing_struct(socket, id, struct) do + quote do + map = + unquote(socket).assigns + |> Map.get(__MODULE__, %{}) + |> Map.put(unquote(id), unquote(struct)) + + unquote(socket) + |> Phoenix.Socket.assign(__MODULE__, map) + end + end + + defmacro get_struct(socket, id) do + quote do + unquote(socket).assigns + |> Map.get(__MODULE__) + |> Map.get(unquote(id)) + end + end +end From 43aaeef9e9db0ac5a43633a573975ea480070d5e Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 18 Feb 2025 12:23:37 +0100 Subject: [PATCH 14/45] Fix bugs in components and demo --- capture_and_play_demo.exs | 27 +++++++++++++++++---------- lib/boombox_live/webrtc/capture.ex | 6 ++++-- lib/boombox_live/webrtc/player.ex | 10 ++++++---- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/capture_and_play_demo.exs b/capture_and_play_demo.exs index beeed3c..840b8a1 100644 --- a/capture_and_play_demo.exs +++ b/capture_and_play_demo.exs @@ -27,7 +27,7 @@ defmodule Example.HomeLive do ingress_signaling = Membrane.WebRTC.SignalingChannel.new() egress_signaling = Membrane.WebRTC.SignalingChannel.new() - {:ok, boombox_pid} = + {:ok, _boombox_pid} = Task.start_link(fn -> Boombox.run( input: {:webrtc, ingress_signaling}, @@ -35,16 +35,23 @@ defmodule Example.HomeLive do ) end) + socket = + socket + |> Capture.attach( + id: "mediaCapture", + signaling_channel: ingress_signaling, + audio?: false, + video?: true + ) + |> Player.attach( + id: "videoPlayer", + signaling_channel: egress_signaling + ) + socket - |> Capture.attach( - id: "mediaCapture", - signaling_channel: ingress_signaling, - audio?: false, - video?: true - ) - |> Player.attach( - id: "videoPlayer", - signaling_channel: egress_signaling + |> assign( + capture: Capture.get_attached(socket, "mediaCapture"), + player: Player.get_attached(socket, "videoPlayer") ) else socket diff --git a/lib/boombox_live/webrtc/capture.ex b/lib/boombox_live/webrtc/capture.ex index 1238603..6afc2de 100644 --- a/lib/boombox_live/webrtc/capture.ex +++ b/lib/boombox_live/webrtc/capture.ex @@ -113,13 +113,16 @@ defmodule Boombox.Live.WebRTC.Capture do socket |> assign(__MODULE__, all_captures) + |> detach_hook(:capture_handshake, :handle_info) |> attach_hook(:capture_handshake, :handle_info, &handshake/2) end + def get_attached(socket, id), do: socket.assigns[__MODULE__][id] + defp handshake({__MODULE__, {:connected, capture_id, child_pid, _meta}}, socket) do # child live view is connected, send it capture struct capture = - socket.assings + socket.assigns |> Map.fetch!(__MODULE__) |> Map.fetch!(capture_id) @@ -147,7 +150,6 @@ defmodule Boombox.Live.WebRTC.Capture do """ end - # todo: simplify the function below later, but for now it should work fine @impl true def mount(_params, %{"class" => class, "id" => id}, socket) do socket = assign(socket, class: class, capture: nil) diff --git a/lib/boombox_live/webrtc/player.ex b/lib/boombox_live/webrtc/player.ex index 57b9dfe..0dab1a6 100644 --- a/lib/boombox_live/webrtc/player.ex +++ b/lib/boombox_live/webrtc/player.ex @@ -120,9 +120,12 @@ defmodule Boombox.Live.WebRTC.Player do socket |> assign(__MODULE__, all_players) + |> detach_hook(:player_handshake, :handle_info) |> attach_hook(:player_handshake, :handle_info, &handshake/2) end + def get_attached(socket, id), do: socket.assigns[__MODULE__][id] + defp handshake({__MODULE__, {:connected, id, child_pid, _meta}}, socket) do # child live view is connected, send it player struct player = @@ -144,25 +147,24 @@ defmodule Boombox.Live.WebRTC.Player do @impl true def render(%{player: nil} = assigns) do ~H""" - NOT RENDERED {inspect(self())} """ end @impl true def render(assigns) do ~H""" - RENDERED {inspect(self())} """ end - # todo: simplify the function below later, but for now it should work fine @impl true def mount(_params, %{"class" => class, "id" => id}, socket) do + IO.inspect(self(), label: "DUPA") + socket = assign(socket, class: class, player: nil) if connected?(socket) do - send(socket.parent_pid, {__MODULE__, {:connected, ref, self(), %{}}}) + send(socket.parent_pid, {__MODULE__, {:connected, id, self(), %{}}}) socket = receive do From a512cc5133d857bafd602e3ae710a1d073c01ab1 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 18 Feb 2025 12:44:01 +0100 Subject: [PATCH 15/45] Add path to .. to esbuild in example project --- example_project/assets/js/app.js | 25 +++++++++---------- example_project/config/config.exs | 2 +- .../live_views/home_live.ex | 16 ------------ 3 files changed, 13 insertions(+), 30 deletions(-) diff --git a/example_project/assets/js/app.js b/example_project/assets/js/app.js index d5e278a..3f45b27 100644 --- a/example_project/assets/js/app.js +++ b/example_project/assets/js/app.js @@ -16,29 +16,28 @@ // // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. -import "phoenix_html" +import "phoenix_html"; // Establish Phoenix Socket and LiveView configuration. -import {Socket} from "phoenix" -import {LiveSocket} from "phoenix_live_view" -import topbar from "../vendor/topbar" +import { Socket } from "phoenix"; +import { LiveSocket } from "phoenix_live_view"; +import topbar from "../vendor/topbar"; -let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content"); let liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, - params: {_csrf_token: csrfToken} -}) + params: { _csrf_token: csrfToken }, +}); // Show progress bar on live navigation and form submits -topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) -window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) -window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) +topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" }); +window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300)); +window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide()); // connect if there are any LiveViews on the page -liveSocket.connect() +liveSocket.connect(); // expose liveSocket on window for web console debug logs and latency simulation: // >> liveSocket.enableDebug() // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() -window.liveSocket = liveSocket - +window.liveSocket = liveSocket; diff --git a/example_project/config/config.exs b/example_project/config/config.exs index 92386a0..1a563c7 100644 --- a/example_project/config/config.exs +++ b/example_project/config/config.exs @@ -28,7 +28,7 @@ config :esbuild, args: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), cd: Path.expand("../assets", __DIR__), - env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + env: %{"NODE_PATH" => Enum.map_join(["../deps", "../.."], ":", &Path.expand(&1, __DIR__))} ] # Configure tailwind (the version is required) diff --git a/example_project/lib/example_project_web/live_views/home_live.ex b/example_project/lib/example_project_web/live_views/home_live.ex index f714b15..cdb57b3 100644 --- a/example_project/lib/example_project_web/live_views/home_live.ex +++ b/example_project/lib/example_project_web/live_views/home_live.ex @@ -1,19 +1,3 @@ -# Copyright 2025 feliks -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - defmodule ExampleProject.HomeLive do - use ExampleProjectWeb, :live_view end From c5b853b06ae0ada91d52ccdde842b2616a76933a Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 18 Feb 2025 12:47:14 +0100 Subject: [PATCH 16/45] Add JS hooks --- example_project/assets/js/app.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/example_project/assets/js/app.js b/example_project/assets/js/app.js index 3f45b27..2f47b86 100644 --- a/example_project/assets/js/app.js +++ b/example_project/assets/js/app.js @@ -22,10 +22,18 @@ import { Socket } from "phoenix"; import { LiveSocket } from "phoenix_live_view"; import topbar from "../vendor/topbar"; +import { createPublisherHook, createPlayerHook } from "live_ex_webrtc"; + +let Hooks = {}; +const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; +Hooks.Publisher = createPublisherHook(iceServers); +Hooks.Player = createPlayerHook(iceServers); + let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content"); let liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, params: { _csrf_token: csrfToken }, + hooks: Hooks, }); // Show progress bar on live navigation and form submits From 82340a59e22b1b1d3a26d73914966772bd327a62 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 18 Feb 2025 12:50:47 +0100 Subject: [PATCH 17/45] Add live view module content --- .../live_views/home_live.ex | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/example_project/lib/example_project_web/live_views/home_live.ex b/example_project/lib/example_project_web/live_views/home_live.ex index cdb57b3..0434304 100644 --- a/example_project/lib/example_project_web/live_views/home_live.ex +++ b/example_project/lib/example_project_web/live_views/home_live.ex @@ -1,3 +1,56 @@ defmodule ExampleProject.HomeLive do use ExampleProjectWeb, :live_view + + alias Boombox.Live.WebRTC.{Capture, Player} + + def mount(_params, _session, socket) do + socket = + if connected?(socket) do + ingress_signaling = Membrane.WebRTC.SignalingChannel.new() + egress_signaling = Membrane.WebRTC.SignalingChannel.new() + + {:ok, _boombox_pid} = + Task.start_link(fn -> + Boombox.run( + input: {:webrtc, ingress_signaling}, + output: {:webrtc, egress_signaling} + ) + end) + + socket = + socket + |> Capture.attach( + id: "mediaCapture", + signaling_channel: ingress_signaling, + audio?: false, + video?: true + ) + |> Player.attach( + id: "videoPlayer", + signaling_channel: egress_signaling + ) + + socket + |> assign( + capture: Capture.get_attached(socket, "mediaCapture"), + player: Player.get_attached(socket, "videoPlayer") + ) + else + socket + end + + {:ok, socket} + end + + def render(%{capture: %Capture{}, player: %Player{}} = assigns) do + ~H""" + + + """ + end + + def render(assigns) do + ~H""" + """ + end end From aa449dfab8a9803fff477e371d55c2cf7515a246 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 18 Feb 2025 15:56:13 +0100 Subject: [PATCH 18/45] Make example project work wip --- .github/workflows/fetch_changes.yml | 2 +- README.md | 14 +- assets/capture.js | 2 +- assets/player.js | 2 +- capture_and_play_demo.exs | 2 +- example_project/mix.lock | 28 ---- .../.formatter.exs | 0 .../.gitignore | 0 .../README.md | 0 .../assets/css/app.css | 0 .../assets/js/app.js | 11 +- .../assets/tailwind.config.js | 0 .../assets/vendor/topbar.js | 0 .../config/config.exs | 2 +- .../config/dev.exs | 0 .../config/prod.exs | 0 .../config/runtime.exs | 0 .../config/test.exs | 0 .../lib/example_project.ex | 0 .../lib/example_project/application.ex | 1 - .../lib/example_project_web.ex | 2 +- .../example_project_web/components/layouts.ex | 0 .../components/layouts/app.html.heex | 1 - .../components/layouts/root.html.heex | 0 .../lib/example_project_web/endpoint.ex | 0 .../live_views/home_live.ex | 13 +- .../lib/example_project_web/router.ex | 2 + .../mix.exs | 2 + example_project_sc/mix.lock | 123 ++++++++++++++++++ .../priv/static/favicon.ico | Bin .../priv/static/images/logo.svg | 0 .../priv/static/robots.txt | 0 .../controllers/error_html_test.exs | 0 .../controllers/error_json_test.exs | 0 .../controllers/page_controller_test.exs | 0 .../test/support/conn_case.ex | 0 .../test/test_helper.exs | 0 mix.exs | 4 +- package.json | 12 ++ 39 files changed, 173 insertions(+), 50 deletions(-) delete mode 100644 example_project/mix.lock rename {example_project => example_project_sc}/.formatter.exs (100%) rename {example_project => example_project_sc}/.gitignore (100%) rename {example_project => example_project_sc}/README.md (100%) rename {example_project => example_project_sc}/assets/css/app.css (100%) rename {example_project => example_project_sc}/assets/js/app.js (88%) rename {example_project => example_project_sc}/assets/tailwind.config.js (100%) rename {example_project => example_project_sc}/assets/vendor/topbar.js (100%) rename {example_project => example_project_sc}/config/config.exs (94%) rename {example_project => example_project_sc}/config/dev.exs (100%) rename {example_project => example_project_sc}/config/prod.exs (100%) rename {example_project => example_project_sc}/config/runtime.exs (100%) rename {example_project => example_project_sc}/config/test.exs (100%) rename {example_project => example_project_sc}/lib/example_project.ex (100%) rename {example_project => example_project_sc}/lib/example_project/application.ex (96%) rename {example_project => example_project_sc}/lib/example_project_web.ex (98%) rename {example_project => example_project_sc}/lib/example_project_web/components/layouts.ex (100%) rename {example_project => example_project_sc}/lib/example_project_web/components/layouts/app.html.heex (50%) rename {example_project => example_project_sc}/lib/example_project_web/components/layouts/root.html.heex (100%) rename {example_project => example_project_sc}/lib/example_project_web/endpoint.ex (100%) rename {example_project => example_project_sc}/lib/example_project_web/live_views/home_live.ex (82%) rename {example_project => example_project_sc}/lib/example_project_web/router.ex (90%) rename {example_project => example_project_sc}/mix.exs (96%) create mode 100644 example_project_sc/mix.lock rename {example_project => example_project_sc}/priv/static/favicon.ico (100%) rename {example_project => example_project_sc}/priv/static/images/logo.svg (100%) rename {example_project => example_project_sc}/priv/static/robots.txt (100%) rename {example_project => example_project_sc}/test/example_project_web/controllers/error_html_test.exs (100%) rename {example_project => example_project_sc}/test/example_project_web/controllers/error_json_test.exs (100%) rename {example_project => example_project_sc}/test/example_project_web/controllers/page_controller_test.exs (100%) rename {example_project => example_project_sc}/test/support/conn_case.ex (100%) rename {example_project => example_project_sc}/test/test_helper.exs (100%) create mode 100644 package.json diff --git a/.github/workflows/fetch_changes.yml b/.github/workflows/fetch_changes.yml index 7c9c0e9..7fb6d52 100644 --- a/.github/workflows/fetch_changes.yml +++ b/.github/workflows/fetch_changes.yml @@ -32,7 +32,7 @@ jobs: # Runs a set of commands using the runners shell - name: Add remote run: | - git remote add source git@github.com:membraneframework/membrane_template_plugin.git + git remote add source git@github.com:membraneframework/boombox_live.git git remote update echo "CURRENT_BRANCH=$(git branch --show-current)" >> $GITHUB_ENV diff --git a/README.md b/README.md index e057e4c..aade3a1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Membrane Template Plugin -[![Hex.pm](https://img.shields.io/hexpm/v/membrane_template_plugin.svg)](https://hex.pm/packages/membrane_template_plugin) -[![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/membrane_template_plugin) -[![CircleCI](https://circleci.com/gh/membraneframework/membrane_template_plugin.svg?style=svg)](https://circleci.com/gh/membraneframework/membrane_template_plugin) +[![Hex.pm](https://img.shields.io/hexpm/v/boombox_live.svg)](https://hex.pm/packages/boombox_live) +[![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/boombox_live) +[![CircleCI](https://circleci.com/gh/membraneframework/boombox_live.svg?style=svg)](https://circleci.com/gh/membraneframework/boombox_live) This repository contains a template for new plugins. @@ -12,12 +12,12 @@ It's a part of the [Membrane Framework](https://membrane.stream). ## Installation -The package can be installed by adding `membrane_template_plugin` to your list of dependencies in `mix.exs`: +The package can be installed by adding `boombox_live` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:membrane_template_plugin, "~> 0.1.0"} + {:boombox_live, "~> 0.1.0"} ] end ``` @@ -28,8 +28,8 @@ TODO ## Copyright and License -Copyright 2020, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_template_plugin) +Copyright 2020, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=boombox_live) -[![Software Mansion](https://logo.swmansion.com/logo?color=white&variant=desktop&width=200&tag=membrane-github)](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_template_plugin) +[![Software Mansion](https://logo.swmansion.com/logo?color=white&variant=desktop&width=200&tag=membrane-github)](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=boombox_live) Licensed under the [Apache License, Version 2.0](LICENSE) diff --git a/assets/capture.js b/assets/capture.js index 8e6bec0..0e6714e 100644 --- a/assets/capture.js +++ b/assets/capture.js @@ -1,4 +1,4 @@ -function createCaptureHook(iceServers = [{ urls: "stun:stun.l.google.com:19302" }]) { +export function createCaptureHook(iceServers = [{ urls: "stun:stun.l.google.com:19302" }]) { return { async mounted() { this.handleEvent("media_constraints-" + this.el.id, async (mediaConstraints) => { diff --git a/assets/player.js b/assets/player.js index de6e174..54ca7a0 100644 --- a/assets/player.js +++ b/assets/player.js @@ -1,4 +1,4 @@ -function createPlayerHook(iceServers = [{ urls: "stun:stun.l.google.com:19302" }]) { +export function createPlayerHook(iceServers = [{ urls: "stun:stun.l.google.com:19302" }]) { return { async mounted() { this.pc = new RTCPeerConnection({ iceServers: iceServers }); diff --git a/capture_and_play_demo.exs b/capture_and_play_demo.exs index 840b8a1..39e130c 100644 --- a/capture_and_play_demo.exs +++ b/capture_and_play_demo.exs @@ -184,7 +184,7 @@ defmodule Example.HomeLive do def render(%{capture: %Capture{}, player: %Player{}} = assigns) do ~H""" - + """ end diff --git a/example_project/mix.lock b/example_project/mix.lock deleted file mode 100644 index ba7ca2e..0000000 --- a/example_project/mix.lock +++ /dev/null @@ -1,28 +0,0 @@ -%{ - "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, - "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, - "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, - "esbuild": {:hex, :esbuild, "0.9.0", "f043eeaca4932ca8e16e5429aebd90f7766f31ac160a25cbd9befe84f2bc068f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b415027f71d5ab57ef2be844b2a10d0c1b5a492d431727f43937adce22ba45ae"}, - "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, - "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, - "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]}, - "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, - "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, - "phoenix": {:hex, :phoenix, "1.7.19", "36617efe5afbd821099a8b994ff4618a340a5bfb25531a1802c4d4c634017a57", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "ba4dc14458278773f905f8ae6c2ec743d52c3a35b6b353733f64f02dfe096cd6"}, - "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, - "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"}, - "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.4", "327491b033e79db2f887b065c5a2993228449091883d74cfa1baa12f8c98d5eb", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9865316ddf8d78f382d63af278d20436b52d262b60239956817a61279514366"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, - "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, - "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, - "tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, - "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, - "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, - "thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"}, - "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, -} diff --git a/example_project/.formatter.exs b/example_project_sc/.formatter.exs similarity index 100% rename from example_project/.formatter.exs rename to example_project_sc/.formatter.exs diff --git a/example_project/.gitignore b/example_project_sc/.gitignore similarity index 100% rename from example_project/.gitignore rename to example_project_sc/.gitignore diff --git a/example_project/README.md b/example_project_sc/README.md similarity index 100% rename from example_project/README.md rename to example_project_sc/README.md diff --git a/example_project/assets/css/app.css b/example_project_sc/assets/css/app.css similarity index 100% rename from example_project/assets/css/app.css rename to example_project_sc/assets/css/app.css diff --git a/example_project/assets/js/app.js b/example_project_sc/assets/js/app.js similarity index 88% rename from example_project/assets/js/app.js rename to example_project_sc/assets/js/app.js index 2f47b86..e41b41d 100644 --- a/example_project/assets/js/app.js +++ b/example_project_sc/assets/js/app.js @@ -22,11 +22,15 @@ import { Socket } from "phoenix"; import { LiveSocket } from "phoenix_live_view"; import topbar from "../vendor/topbar"; -import { createPublisherHook, createPlayerHook } from "live_ex_webrtc"; +console.log("A"); + +import { createCaptureHook, createPlayerHook } from "boombox_live"; + +console.log("B"); let Hooks = {}; const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; -Hooks.Publisher = createPublisherHook(iceServers); +Hooks.Capture = createCaptureHook(iceServers); Hooks.Player = createPlayerHook(iceServers); let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content"); @@ -43,9 +47,10 @@ window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide()); // connect if there are any LiveViews on the page liveSocket.connect(); +console.log("LiveView WebSocket connected"); // expose liveSocket on window for web console debug logs and latency simulation: -// >> liveSocket.enableDebug() +// liveSocket.enableDebug(); // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() window.liveSocket = liveSocket; diff --git a/example_project/assets/tailwind.config.js b/example_project_sc/assets/tailwind.config.js similarity index 100% rename from example_project/assets/tailwind.config.js rename to example_project_sc/assets/tailwind.config.js diff --git a/example_project/assets/vendor/topbar.js b/example_project_sc/assets/vendor/topbar.js similarity index 100% rename from example_project/assets/vendor/topbar.js rename to example_project_sc/assets/vendor/topbar.js diff --git a/example_project/config/config.exs b/example_project_sc/config/config.exs similarity index 94% rename from example_project/config/config.exs rename to example_project_sc/config/config.exs index 1a563c7..3b55888 100644 --- a/example_project/config/config.exs +++ b/example_project_sc/config/config.exs @@ -28,7 +28,7 @@ config :esbuild, args: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), cd: Path.expand("../assets", __DIR__), - env: %{"NODE_PATH" => Enum.map_join(["../deps", "../.."], ":", &Path.expand(&1, __DIR__))} + env: %{"NODE_PATH" => Enum.map_join(["../deps", "../../.."], ":", &Path.expand(&1, __DIR__))} ] # Configure tailwind (the version is required) diff --git a/example_project/config/dev.exs b/example_project_sc/config/dev.exs similarity index 100% rename from example_project/config/dev.exs rename to example_project_sc/config/dev.exs diff --git a/example_project/config/prod.exs b/example_project_sc/config/prod.exs similarity index 100% rename from example_project/config/prod.exs rename to example_project_sc/config/prod.exs diff --git a/example_project/config/runtime.exs b/example_project_sc/config/runtime.exs similarity index 100% rename from example_project/config/runtime.exs rename to example_project_sc/config/runtime.exs diff --git a/example_project/config/test.exs b/example_project_sc/config/test.exs similarity index 100% rename from example_project/config/test.exs rename to example_project_sc/config/test.exs diff --git a/example_project/lib/example_project.ex b/example_project_sc/lib/example_project.ex similarity index 100% rename from example_project/lib/example_project.ex rename to example_project_sc/lib/example_project.ex diff --git a/example_project/lib/example_project/application.ex b/example_project_sc/lib/example_project/application.ex similarity index 96% rename from example_project/lib/example_project/application.ex rename to example_project_sc/lib/example_project/application.ex index 0957c3f..45343cb 100644 --- a/example_project/lib/example_project/application.ex +++ b/example_project_sc/lib/example_project/application.ex @@ -8,7 +8,6 @@ defmodule ExampleProject.Application do @impl true def start(_type, _args) do children = [ - ExampleProjectWeb.Telemetry, {DNSCluster, query: Application.get_env(:example_project, :dns_cluster_query) || :ignore}, {Phoenix.PubSub, name: ExampleProject.PubSub}, # Start a worker by calling: ExampleProject.Worker.start_link(arg) diff --git a/example_project/lib/example_project_web.ex b/example_project_sc/lib/example_project_web.ex similarity index 98% rename from example_project/lib/example_project_web.ex rename to example_project_sc/lib/example_project_web.ex index dfed80f..6fa0969 100644 --- a/example_project/lib/example_project_web.ex +++ b/example_project_sc/lib/example_project_web.ex @@ -83,7 +83,7 @@ defmodule ExampleProjectWeb do # HTML escaping functionality import Phoenix.HTML # Core UI components - import ExampleProjectWeb.CoreComponents + # import ExampleProjectWeb.CoreComponents # Shortcut for generating JS commands alias Phoenix.LiveView.JS diff --git a/example_project/lib/example_project_web/components/layouts.ex b/example_project_sc/lib/example_project_web/components/layouts.ex similarity index 100% rename from example_project/lib/example_project_web/components/layouts.ex rename to example_project_sc/lib/example_project_web/components/layouts.ex diff --git a/example_project/lib/example_project_web/components/layouts/app.html.heex b/example_project_sc/lib/example_project_web/components/layouts/app.html.heex similarity index 50% rename from example_project/lib/example_project_web/components/layouts/app.html.heex rename to example_project_sc/lib/example_project_web/components/layouts/app.html.heex index 3fbe272..ef79d41 100644 --- a/example_project/lib/example_project_web/components/layouts/app.html.heex +++ b/example_project_sc/lib/example_project_web/components/layouts/app.html.heex @@ -1,4 +1,3 @@
- <.flash_group flash={@flash} /> {@inner_content}
diff --git a/example_project/lib/example_project_web/components/layouts/root.html.heex b/example_project_sc/lib/example_project_web/components/layouts/root.html.heex similarity index 100% rename from example_project/lib/example_project_web/components/layouts/root.html.heex rename to example_project_sc/lib/example_project_web/components/layouts/root.html.heex diff --git a/example_project/lib/example_project_web/endpoint.ex b/example_project_sc/lib/example_project_web/endpoint.ex similarity index 100% rename from example_project/lib/example_project_web/endpoint.ex rename to example_project_sc/lib/example_project_web/endpoint.ex diff --git a/example_project/lib/example_project_web/live_views/home_live.ex b/example_project_sc/lib/example_project_web/live_views/home_live.ex similarity index 82% rename from example_project/lib/example_project_web/live_views/home_live.ex rename to example_project_sc/lib/example_project_web/live_views/home_live.ex index 0434304..fdb66af 100644 --- a/example_project/lib/example_project_web/live_views/home_live.ex +++ b/example_project_sc/lib/example_project_web/live_views/home_live.ex @@ -1,9 +1,11 @@ -defmodule ExampleProject.HomeLive do +defmodule ExampleProjectWeb.HomeLive do use ExampleProjectWeb, :live_view alias Boombox.Live.WebRTC.{Capture, Player} def mount(_params, _session, socket) do + IO.inspect(socket, label: "MOUNT") + socket = if connected?(socket) do ingress_signaling = Membrane.WebRTC.SignalingChannel.new() @@ -44,13 +46,20 @@ defmodule ExampleProject.HomeLive do def render(%{capture: %Capture{}, player: %Player{}} = assigns) do ~H""" - + + + """ end def render(assigns) do ~H""" + """ end end diff --git a/example_project/lib/example_project_web/router.ex b/example_project_sc/lib/example_project_web/router.ex similarity index 90% rename from example_project/lib/example_project_web/router.ex rename to example_project_sc/lib/example_project_web/router.ex index fa4554a..e50c386 100644 --- a/example_project/lib/example_project_web/router.ex +++ b/example_project_sc/lib/example_project_web/router.ex @@ -13,4 +13,6 @@ defmodule ExampleProjectWeb.Router do pipeline :api do plug :accepts, ["json"] end + + live "/", ExampleProjectWeb.HomeLive end diff --git a/example_project/mix.exs b/example_project_sc/mix.exs similarity index 96% rename from example_project/mix.exs rename to example_project_sc/mix.exs index 2d92df8..877803c 100644 --- a/example_project/mix.exs +++ b/example_project_sc/mix.exs @@ -32,6 +32,8 @@ defmodule ExampleProject.MixProject do # Type `mix help deps` for examples and options. defp deps do [ + {:boombox, path: "../../boombox"}, + {:boombox_live, path: ".."}, {:phoenix, "~> 1.7.19"}, {:phoenix_html, "~> 4.1"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, diff --git a/example_project_sc/mix.lock b/example_project_sc/mix.lock new file mode 100644 index 0000000..4e7f4e8 --- /dev/null +++ b/example_project_sc/mix.lock @@ -0,0 +1,123 @@ +%{ + "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, + "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, + "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, + "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, + "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, + "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, + "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, + "corsica": {:hex, :corsica, "2.1.3", "dccd094ffce38178acead9ae743180cdaffa388f35f0461ba1e8151d32e190e6", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "616c08f61a345780c2cf662ff226816f04d8868e12054e68963e95285b5be8bc"}, + "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"}, + "crc": {:hex, :crc, "0.10.5", "ee12a7c056ac498ef2ea985ecdc9fa53c1bfb4e53a484d9f17ff94803707dfd8", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3e673b6495a9525c5c641585af1accba59a1eb33de697bedf341e247012c2c7f"}, + "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, + "esbuild": {:hex, :esbuild, "0.9.0", "f043eeaca4932ca8e16e5429aebd90f7766f31ac160a25cbd9befe84f2bc068f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b415027f71d5ab57ef2be844b2a10d0c1b5a492d431727f43937adce22ba45ae"}, + "ex_dtls": {:hex, :ex_dtls, "0.16.0", "3ae38025ccc77f6db573e2e391602fa9bbc02253c137d8d2d59469a66cbe806b", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2a4e30d74c6ddf95cc5b796423293c06a0da295454c3823819808ff031b4b361"}, + "ex_ice": {:hex, :ex_ice, "0.9.3", "46700963acaba72737032500b6ee298a4effa7ad7189ab48887be5e9f4fe2107", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "4fd98d20a39ab70a62dd301c44e87437d479292c528ec7f21522ebfe0654b9cb"}, + "ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"}, + "ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"}, + "ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"}, + "ex_sdp": {:hex, :ex_sdp, "1.1.1", "1a7b049491e5ec02dad9251c53d960835dc5631321ae978ec331831f3e4f6d5f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "1b13a72ac9c5c695b8824dbdffc671be8cbb4c0d1ccb4ff76a04a6826759f233"}, + "ex_stun": {:hex, :ex_stun, "0.2.0", "feb1fc7db0356406655b2a617805e6c712b93308c8ea2bf0ba1197b1f0866deb", [:mix], [], "hexpm", "1e01ba8290082ccbf37acaa5190d1f69b51edd6de2026a8d6d51368b29d115d0"}, + "ex_turn": {:hex, :ex_turn, "0.2.0", "4e1f9b089e9a5ee44928d12370cc9ea7a89b84b2f6256832de65271212eb80de", [:mix], [{:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "08e884f0af2c4a147e3f8cd4ffe33e3452a256389f0956e55a8c4d75bf0e74cd"}, + "ex_webrtc": {:hex, :ex_webrtc, "0.8.1", "e507d1b3d89e9c8b74e3fef5f4070d57e20a3f77061b7439b1af1877d6577793", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.16.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:ex_ice, "~> 0.9.0", [hex: :ex_ice, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.7.1", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:ex_sctp, "0.1.2", [hex: :ex_sctp, repo: "hexpm", optional: true]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}], "hexpm", "2c5563cdaf998b5beed3c79b0feeaa1430f30118b21ab59dd18289d72177adf0"}, + "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "heap": {:hex, :heap, "2.0.2", "d98cb178286cfeb5edbcf17785e2d20af73ca57b5a2cf4af584118afbcf917eb", [:mix], [], "hexpm", "ba9ea2fe99eb4bcbd9a8a28eaf71cbcac449ca1d8e71731596aace9028c9d429"}, + "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]}, + "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "image": {:hex, :image, "0.54.4", "332cd64ca47938447dffee97b05a5e4203f2a45e8918537ab0fb971fa3c9debb", [:mix], [{:bumblebee, "~> 0.3", [hex: :bumblebee, repo: "hexpm", optional: true]}, {:evision, "~> 0.1.33 or ~> 0.2", [hex: :evision, repo: "hexpm", optional: true]}, {:exla, "~> 0.5", [hex: :exla, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:kino, "~> 0.13", [hex: :kino, repo: "hexpm", optional: true]}, {:nx, "~> 0.7", [hex: :nx, repo: "hexpm", optional: true]}, {:nx_image, "~> 0.1", [hex: :nx_image, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.1 or ~> 3.2 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:rustler, "> 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:scholar, "~> 0.3", [hex: :scholar, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}, {:vix, "~> 0.23", [hex: :vix, repo: "hexpm", optional: false]}], "hexpm", "4d66ee976c30ec181a54b99791354a4ae990521d64811cb2daed39c4cd95860b"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"}, + "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.11", "6bdcde2eaeafcce136f21fb17effdcdaf9c73107771c8d2a661ca5d0b616ea12", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "dac3f0c839f33603dc2fdad8b9fbebd5ca578c9ca28ea65249ebb15c96c0fd31"}, + "membrane_aac_format": {:hex, :membrane_aac_format, "0.8.0", "515631eabd6e584e0e9af2cea80471fee6246484dbbefc4726c1d93ece8e0838", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}], "hexpm", "a30176a94491033ed32be45e51d509fc70a5ee6e751f12fd6c0d60bd637013f6"}, + "membrane_aac_plugin": {:hex, :membrane_aac_plugin, "0.19.0", "58a15efaaa4f2cc91b968464cfd269244e035efdd983aac2e3ddeb176fcf0585", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "eb7e786e650608ee205f4eebff4c1df3677e545acf09802458f77f64f9942fe9"}, + "membrane_cmaf_format": {:hex, :membrane_cmaf_format, "0.7.1", "9ea858faefdcb181cdfa8001be827c35c5f854e9809ad57d7062cff1f0f703fd", [:mix], [], "hexpm", "3c7b4ed2a986e27f6f336d2f19e9442cb31d93b3142fc024c019572faca54a73"}, + "membrane_common_c": {:hex, :membrane_common_c, "0.16.0", "caf3f29d2f5a1d32d8c2c122866110775866db2726e4272be58e66dfdf4bce40", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "a3c7e91de1ce1f8b23b9823188a5d13654d317235ea0ca781c05353ed3be9b1c"}, + "membrane_core": {:hex, :membrane_core, "1.1.2", "3ca206893e1d3739a24d5092d21c06fcb4db326733a1798f9788fc53abb74829", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a989fd7e0516a7e66f5fb63950b1027315b7f8c8d82d8d685e178b0fb780901b"}, + "membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.20.2", "2e669f0b25418d10b51a73bc52d2e12e4a3a26b416c5c1199d852c3f781a18b3", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:mockery, "~> 2.1", [hex: :mockery, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "6c8d3bcd61d568dd94cabb9b45f29e8926e0076e4432d8f419378e004e02147c"}, + "membrane_ffmpeg_swscale_plugin": {:hex, :membrane_ffmpeg_swscale_plugin, "0.16.2", "581909312d6d12ed560ee99caa1b1674a339760ab2ad6835d243326806c23da1", [:mix], [{:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "46c185dacff1e1b404d0ceb74d0d5224f0931fe1e8b951cc3776ebd099e39afc"}, + "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.2", "650e134c2345d946f930082fac8bac9f5aba785a7817d38a9a9da41ffc56fa92", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "df50c6040004cd7b901cf057bd7e99c875bbbd6ae574efc93b2c753c96f43b9d"}, + "membrane_flv_plugin": {:hex, :membrane_flv_plugin, "0.12.0", "d715ad405af86dcaf4b2f479e34088e1f6738c7280366828e1066b39d2aa493a", [:mix], [{:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}], "hexpm", "a317872d6d394e550c7bfd8979f12a3a1cc1e89b547d75360321025b403d3279"}, + "membrane_funnel_plugin": {:hex, :membrane_funnel_plugin, "0.9.2", "2b2e840dbb232ce29aaff2d55bd329d9978766518dbeb6e8dba7aba7115fadcc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "865ac9d84f86698e2cfeb7904d3b12ab74855a38ca651a880db1505965fa77cc"}, + "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.32.5", "30542fb5d6d36961a51906549b4338f4fc66a304bf92e7c7123e2b9971e3502d", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "8c80e11b9ec9ca23d44304ed7bb3daf665e98b91b2488608ee5718a88182e363"}, + "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, + "membrane_h265_ffmpeg_plugin": {:hex, :membrane_h265_ffmpeg_plugin, "0.4.2", "6dcd932fc2c65a851ab7a44f3996cc9613163bdf23b00568c87c9c0754a64edf", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.1", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "d7eb02110a47a9c11f1432f95b775ba7ce5e962dbe8fc07b421bdd2ce5283b2d"}, + "membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"}, + "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.2", "caf2790d8c107df35f8d456b45f4e09fb9c56ce6c7669a3a03f7d59972e6ed82", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}], "hexpm", "becf1ac4a589adecd850137ccd61a33058f686083a514a7e39fcd721bcf9fb2e"}, + "membrane_hackney_plugin": {:hex, :membrane_hackney_plugin, "0.11.0", "54b368333a23394e7cac2f4d6b701bf8c5ee6614670a31f4ebe009b5e691a5c1", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "2b28fd1be3c889d5824d7d985598386c7673828c88f49a91221df3626af8a998"}, + "membrane_http_adaptive_stream_plugin": {:hex, :membrane_http_adaptive_stream_plugin, "0.18.6", "d417f54da56f7a704200baf018cc0e6222e4c649672adac21e2b321b66c3a958", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_mp4_plugin, "~> 0.35.0", [hex: :membrane_mp4_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "7ca36002d0d74254537afb7aae31541d57a36b33c1abe114e6dd56cf7566bcb2"}, + "membrane_mp4_format": {:hex, :membrane_mp4_format, "0.8.0", "8c6e7d68829228117d333b4fbb030e7be829aab49dd8cb047fdc664db1812e6a", [:mix], [], "hexpm", "148dea678a1f82ccfd44dbde6f936d2f21255f496cb45a22cc6eec427f025522"}, + "membrane_mp4_plugin": {:hex, :membrane_mp4_plugin, "0.35.2", "cbedb5272ef1c8f7d9cd3c44f820a90306469b1dc84b8db30ff55bb6195b7cb2", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_cmaf_format, "~> 0.7.0", [hex: :membrane_cmaf_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_mp4_format, "~> 0.8.0", [hex: :membrane_mp4_format, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.1", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}], "hexpm", "8afd4e7779a742dd56c23f1f23053933d1b0b34d397ad368a2f56f995edb2fe0"}, + "membrane_opus_format": {:hex, :membrane_opus_format, "0.3.0", "3804d9916058b7cfa2baa0131a644d8186198d64f52d592ae09e0942513cb4c2", [:mix], [], "hexpm", "8fc89c97be50de23ded15f2050fe603dcce732566fe6fdd15a2de01cb6b81afe"}, + "membrane_opus_plugin": {:hex, :membrane_opus_plugin, "0.20.5", "aa344bb9931c8e22b2286778cce0658e0d4aa071a503c18c55e1b161e17ab337", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "94fd4447b6576780afc6144dbb0520b43bd399c86a10bf5df1fa878a91798cf6"}, + "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, + "membrane_raw_audio_format": {:hex, :membrane_raw_audio_format, "0.12.0", "b574cd90f69ce2a8b6201b0ccf0826ca28b0fbc8245b8078d9f11cef65f7d5d5", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "6e6c98e3622a2b9df19eab50ba65d7eb45949b1ba306fa8423df6cdb12fd0b44"}, + "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.1", "d7344499c2d80f236a7ef962b5490c651341a501052ee43dec56cf0319fa3936", [:mix], [], "hexpm", "9920b7d445b5357608a364fec5685acdfce85334c647f745045237a0d296c442"}, + "membrane_realtimer_plugin": {:hex, :membrane_realtimer_plugin, "0.9.0", "27210d5e32a5e8bfd101c41e4d8c1876e873a52cc129ebfbee4d0ccbea1cbd21", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "b2e96d62135ee57ef9a5fdea94b3a9ab1198e5ea8ee248391b89c671125d1b51"}, + "membrane_rtmp_plugin": {:hex, :membrane_rtmp_plugin, "0.27.3", "2ca6705668e207a2af1dfd459ec94cebf43bd40338b1b176af1b7ecfb7f5017e", [:mix], [{:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_flv_plugin, "~> 0.12.0", [hex: :membrane_flv_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "e3b3a858cee514aae1cd78b5c85a24b68edb15183e7aa909180c0ce7fb92ab10"}, + "membrane_rtp_aac_plugin": {:hex, :membrane_rtp_aac_plugin, "0.9.4", "355efe237151b304a479a8f0db12043aea2528718d045cb596cbfb85f64ff20a", [:mix], [{:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "f72d12b88b57a3c93eeea19c02c95c878c4b09883dbec703ae3d1557d3af44c0"}, + "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.10.0", "e7f62b1f522647a64d88b60f635476dcb71db61dccda32ffa21ecdb965314c91", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "493245bb45faf3a08343e29281e2aadac72b795d48d64bb805ea8daa31cfd414"}, + "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.20.2", "ab84db505d3102a9cdc300f137c78245ef3982a7ec545838f9544b6b0a2ca1ba", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "27f38c49544d1acf6f7c3f8770a7893f90813a31e8a26461e112a3d3142aff46"}, + "membrane_rtp_h265_plugin": {:hex, :membrane_rtp_h265_plugin, "0.5.2", "970155229e97311b13df8ffb705ee208df83de6d084b43bc77c35e692774616a", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "f8c34c0db3d13d84709fa0b41d4944fcbca50be1fbd292553bfd53512e9a0a26"}, + "membrane_rtp_opus_plugin": {:hex, :membrane_rtp_opus_plugin, "0.10.0", "5797c9b5f09a81c35edc13583eafdfa86b71ac5b2a9d399e95598e27c23ec1b6", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "988cf18ad1e5eb876ec93ae476f035bbec0aff2ca33127b1009217bd258e4845"}, + "membrane_rtp_plugin": {:hex, :membrane_rtp_plugin, "0.30.0", "f7dbc0c5e163edf4ec67b89b1028a7b99ea56e87f8cc01d01b01ec0470287128", [:mix], [{:bimap, "~> 1.2", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.6.0 or ~> 0.7.0", [hex: :ex_libsrtp, repo: "hexpm", optional: true]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:heap, "~> 2.0.2", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.1", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "9600e39c35428c3a86c469ee3cae5a60d259da3ee7e58f94b04866da6d719016"}, + "membrane_rtp_vp8_plugin": {:hex, :membrane_rtp_vp8_plugin, "0.9.4", "8eb7e90576e79ccfefe6cf54f982afd8109027b148e17e701e5fbabe485a7b53", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.4.0 or ~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}], "hexpm", "0fd14c81d3200fc83908f3d72ece3faed207e34c1f59a85b2b7625ec02fb30dd"}, + "membrane_rtsp": {:hex, :membrane_rtsp, "0.10.1", "c6cb8549daab155175896f677b049a0f65c3a650e93e603714ec94a836148722", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.17.0 or ~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.4.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ff382b38c09150ff43c7ec7a2a62df41b2bab42793633dcc40ef9b6e51ac70b9"}, + "membrane_rtsp_plugin": {:hex, :membrane_rtsp_plugin, "0.6.1", "56f11bbd74e47fe7240df44ffc2b17537bc295a4286e08ce3232a905347edfea", [:mix], [{:ex_sdp, "~> 1.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_aac_plugin, "~> 0.9.1", [hex: :membrane_rtp_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_h265_plugin, "~> 0.5.2", [hex: :membrane_rtp_h265_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.10.0", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:membrane_tcp_plugin, "~> 0.6.0", [hex: :membrane_tcp_plugin, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.14.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}], "hexpm", "80b6aedd27b4d0e2c99097b5b7d841855ff6fcaf7a11ef85d8e57f7b83a4d8c1"}, + "membrane_tcp_plugin": {:hex, :membrane_tcp_plugin, "0.6.0", "1f8dba5525504fb2d49070932f24113d1b26c7e5429c700671ed80433ac83f2f", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "820440f5a8181a96cff461ad2d5ed426d47eacfdd7764dd9596dad68ad892d3d"}, + "membrane_tee_plugin": {:hex, :membrane_tee_plugin, "0.12.0", "f94989b4080ef4b7937d74c1a14d3379577c7bd4c6d06e5a2bb41c351ad604d4", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "0d61c9ed5e68e5a75d54200e1c6df5739c0bcb52fee0974183ad72446a179887"}, + "membrane_telemetry_metrics": {:hex, :membrane_telemetry_metrics, "0.1.1", "57917e72012f9ebe124eab54f29ca74c9d9eb3ae2207f55c95618ee51280eb4f", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "69392966e6bd51937244758c2b3d835c5ff47d8953d25431a9d37059737afc11"}, + "membrane_timestamp_queue": {:hex, :membrane_timestamp_queue, "0.2.2", "1c831b2273d018a6548654aa9f7fa7c4b683f71d96ffe164934ef55f9d11f693", [:mix], [{:heap, "~> 2.0", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7c830e760baaced0988421671cd2c83c7cda8d1bd2b61fd05332711675d1204f"}, + "membrane_transcoder_plugin": {:hex, :membrane_transcoder_plugin, "0.1.2", "5d5546971289109423c1d02c4a45bfce7133293d76f5850824663a4613e5a728", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.0", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.20.0", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.1", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.32.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_ffmpeg_plugin, "~> 0.4.2", [hex: :membrane_h265_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.20.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.2", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vpx_plugin, "~> 0.3.0", [hex: :membrane_vpx_plugin, repo: "hexpm", optional: false]}], "hexpm", "50297b922fdd39520125120782bf679f26be8965b31565b470fb51b6ef908873"}, + "membrane_udp_plugin": {:hex, :membrane_udp_plugin, "0.14.0", "d533ee5f6fcdd0551ad690045cdb6c1a76307a155d9255cc4a4606f85774bc37", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "902d1a7aa228ec377482d53a605b100e20e0b6e59196f94f94147bb62b23c47e"}, + "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, + "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, + "membrane_vpx_plugin": {:hex, :membrane_vpx_plugin, "0.3.0", "60404d1b1511b4c62ba6bbf7b6212570f1732ba477015c4072e0aa33e18a8809", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vp9_format, "~> 0.5.0", [hex: :membrane_vp9_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "effa7762bbf73efd8d21d0978bce79538414719284194db97672afbce665b56a"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "mockery": {:hex, :mockery, "2.3.3", "3dba87bd0422a513e6af6e0d811383f38f82ac6be5d3d285a5fcca9c299bd0ac", [:mix], [], "hexpm", "17282be00613286254298117cd25e607a39f15ac03b41c631f60e52f5b5ec974"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "phoenix": {:hex, :phoenix, "1.7.19", "36617efe5afbd821099a8b994ff4618a340a5bfb25531a1802c4d4c634017a57", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "ba4dc14458278773f905f8ae6c2ec743d52c3a35b6b353733f64f02dfe096cd6"}, + "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.4", "327491b033e79db2f887b065c5a2993228449091883d74cfa1baa12f8c98d5eb", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9865316ddf8d78f382d63af278d20436b52d262b60239956817a61279514366"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, + "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, + "ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"}, + "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, + "shmex": {:hex, :shmex, "0.5.1", "81dd209093416bf6608e66882cb7e676089307448a1afd4fc906c1f7e5b94cf4", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "c29f8286891252f64c4e1dac40b217d960f7d58def597c4e606ff8fbe71ceb80"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"}, + "tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, + "thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "unifex": {:hex, :unifex, "1.2.1", "6841c170a6e16509fac30b19e4e0a19937c33155a59088b50c15fc2c36251b6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "8c9d2e3c48df031e9995dd16865bab3df402c0295ba3a31f38274bb5314c7d37"}, + "vix": {:hex, :vix, "0.33.0", "cd98084529fd8fe3d2336f157db6de03b297fb096508d820068117d58eadb6f1", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "9acde72b27bdfeadeb51f790f7a6cc0d06cf555718c05cf57e43c5cf93d8471b"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, + "zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"}, +} diff --git a/example_project/priv/static/favicon.ico b/example_project_sc/priv/static/favicon.ico similarity index 100% rename from example_project/priv/static/favicon.ico rename to example_project_sc/priv/static/favicon.ico diff --git a/example_project/priv/static/images/logo.svg b/example_project_sc/priv/static/images/logo.svg similarity index 100% rename from example_project/priv/static/images/logo.svg rename to example_project_sc/priv/static/images/logo.svg diff --git a/example_project/priv/static/robots.txt b/example_project_sc/priv/static/robots.txt similarity index 100% rename from example_project/priv/static/robots.txt rename to example_project_sc/priv/static/robots.txt diff --git a/example_project/test/example_project_web/controllers/error_html_test.exs b/example_project_sc/test/example_project_web/controllers/error_html_test.exs similarity index 100% rename from example_project/test/example_project_web/controllers/error_html_test.exs rename to example_project_sc/test/example_project_web/controllers/error_html_test.exs diff --git a/example_project/test/example_project_web/controllers/error_json_test.exs b/example_project_sc/test/example_project_web/controllers/error_json_test.exs similarity index 100% rename from example_project/test/example_project_web/controllers/error_json_test.exs rename to example_project_sc/test/example_project_web/controllers/error_json_test.exs diff --git a/example_project/test/example_project_web/controllers/page_controller_test.exs b/example_project_sc/test/example_project_web/controllers/page_controller_test.exs similarity index 100% rename from example_project/test/example_project_web/controllers/page_controller_test.exs rename to example_project_sc/test/example_project_web/controllers/page_controller_test.exs diff --git a/example_project/test/support/conn_case.ex b/example_project_sc/test/support/conn_case.ex similarity index 100% rename from example_project/test/support/conn_case.ex rename to example_project_sc/test/support/conn_case.ex diff --git a/example_project/test/test_helper.exs b/example_project_sc/test/test_helper.exs similarity index 100% rename from example_project/test/test_helper.exs rename to example_project_sc/test/test_helper.exs diff --git a/mix.exs b/mix.exs index e7b3f8f..67a9cb7 100644 --- a/mix.exs +++ b/mix.exs @@ -2,11 +2,11 @@ defmodule Boombox.Live.Mixfile do use Mix.Project @version "0.1.0" - @github_url "https://github.com/membraneframework/membrane_template_plugin" + @github_url "https://github.com/membraneframework/boombox_live" def project do [ - app: :membrane_template_plugin, + app: :boombox_live, version: @version, elixir: "~> 1.13", elixirc_paths: elixirc_paths(Mix.env()), diff --git a/package.json b/package.json new file mode 100644 index 0000000..7c9b6e6 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "boombox_live", + "version": "0.1.0", + "description": "Phoenix Live Components for Boombox", + "main": "./assets/index.js", + "repository": { + "type": "git", + "url": "git://github.com/membraneframework/boombox_live.git" + }, + "license": "Apache-2.0", + "homepage": "https://github.com/membraneframework/boombox_live" +} From 9040ff071e43a8ad776198d214cbac8c7b94a507 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 18 Feb 2025 15:58:00 +0100 Subject: [PATCH 19/45] Create new example project --- example_project/.formatter.exs | 5 + example_project/.gitignore | 37 + example_project/README.md | 18 + example_project/assets/js/app.js | 44 ++ example_project/assets/vendor/topbar.js | 165 +++++ example_project/config/config.exs | 44 ++ example_project/config/dev.exs | 71 ++ example_project/config/prod.exs | 15 + example_project/config/runtime.exs | 84 +++ example_project/config/test.exs | 18 + example_project/lib/example_project.ex | 9 + .../lib/example_project/application.ex | 33 + example_project/lib/example_project_web.ex | 116 +++ .../components/core_components.ex | 676 ++++++++++++++++++ .../example_project_web/components/layouts.ex | 14 + .../components/layouts/app.html.heex | 32 + .../components/layouts/root.html.heex | 17 + .../controllers/error_html.ex | 24 + .../controllers/error_json.ex | 21 + .../controllers/page_controller.ex | 9 + .../controllers/page_html.ex | 10 + .../controllers/page_html/home.html.heex | 223 ++++++ .../lib/example_project_web/endpoint.ex | 48 ++ .../lib/example_project_web/gettext.ex | 25 + .../lib/example_project_web/router.ex | 27 + .../lib/example_project_web/telemetry.ex | 69 ++ example_project/mix.exs | 67 ++ example_project/mix.lock | 27 + .../priv/gettext/en/LC_MESSAGES/errors.po | 11 + example_project/priv/gettext/errors.pot | 10 + example_project/priv/static/favicon.ico | Bin 0 -> 152 bytes example_project/priv/static/images/logo.svg | 6 + example_project/priv/static/robots.txt | 5 + .../controllers/error_html_test.exs | 14 + .../controllers/error_json_test.exs | 12 + .../controllers/page_controller_test.exs | 8 + example_project/test/support/conn_case.ex | 37 + example_project/test/test_helper.exs | 1 + 38 files changed, 2052 insertions(+) create mode 100644 example_project/.formatter.exs create mode 100644 example_project/.gitignore create mode 100644 example_project/README.md create mode 100644 example_project/assets/js/app.js create mode 100644 example_project/assets/vendor/topbar.js create mode 100644 example_project/config/config.exs create mode 100644 example_project/config/dev.exs create mode 100644 example_project/config/prod.exs create mode 100644 example_project/config/runtime.exs create mode 100644 example_project/config/test.exs create mode 100644 example_project/lib/example_project.ex create mode 100644 example_project/lib/example_project/application.ex create mode 100644 example_project/lib/example_project_web.ex create mode 100644 example_project/lib/example_project_web/components/core_components.ex create mode 100644 example_project/lib/example_project_web/components/layouts.ex create mode 100644 example_project/lib/example_project_web/components/layouts/app.html.heex create mode 100644 example_project/lib/example_project_web/components/layouts/root.html.heex create mode 100644 example_project/lib/example_project_web/controllers/error_html.ex create mode 100644 example_project/lib/example_project_web/controllers/error_json.ex create mode 100644 example_project/lib/example_project_web/controllers/page_controller.ex create mode 100644 example_project/lib/example_project_web/controllers/page_html.ex create mode 100644 example_project/lib/example_project_web/controllers/page_html/home.html.heex create mode 100644 example_project/lib/example_project_web/endpoint.ex create mode 100644 example_project/lib/example_project_web/gettext.ex create mode 100644 example_project/lib/example_project_web/router.ex create mode 100644 example_project/lib/example_project_web/telemetry.ex create mode 100644 example_project/mix.exs create mode 100644 example_project/mix.lock create mode 100644 example_project/priv/gettext/en/LC_MESSAGES/errors.po create mode 100644 example_project/priv/gettext/errors.pot create mode 100644 example_project/priv/static/favicon.ico create mode 100644 example_project/priv/static/images/logo.svg create mode 100644 example_project/priv/static/robots.txt create mode 100644 example_project/test/example_project_web/controllers/error_html_test.exs create mode 100644 example_project/test/example_project_web/controllers/error_json_test.exs create mode 100644 example_project/test/example_project_web/controllers/page_controller_test.exs create mode 100644 example_project/test/support/conn_case.ex create mode 100644 example_project/test/test_helper.exs diff --git a/example_project/.formatter.exs b/example_project/.formatter.exs new file mode 100644 index 0000000..e945e12 --- /dev/null +++ b/example_project/.formatter.exs @@ -0,0 +1,5 @@ +[ + import_deps: [:phoenix], + plugins: [Phoenix.LiveView.HTMLFormatter], + inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] +] diff --git a/example_project/.gitignore b/example_project/.gitignore new file mode 100644 index 0000000..d039d2b --- /dev/null +++ b/example_project/.gitignore @@ -0,0 +1,37 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Temporary files, for example, from tests. +/tmp/ + +# Ignore package tarball (built via "mix hex.build"). +example_project-*.tar + +# Ignore assets that are produced by build tools. +/priv/static/assets/ + +# Ignore digested assets cache. +/priv/static/cache_manifest.json + +# In case you use Node.js/npm, you want to ignore these. +npm-debug.log +/assets/node_modules/ + diff --git a/example_project/README.md b/example_project/README.md new file mode 100644 index 0000000..9ff00e5 --- /dev/null +++ b/example_project/README.md @@ -0,0 +1,18 @@ +# ExampleProject + +To start your Phoenix server: + + * Run `mix setup` to install and setup dependencies + * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + + * Official website: https://www.phoenixframework.org/ + * Guides: https://hexdocs.pm/phoenix/overview.html + * Docs: https://hexdocs.pm/phoenix + * Forum: https://elixirforum.com/c/phoenix-forum + * Source: https://github.com/phoenixframework/phoenix diff --git a/example_project/assets/js/app.js b/example_project/assets/js/app.js new file mode 100644 index 0000000..d5e278a --- /dev/null +++ b/example_project/assets/js/app.js @@ -0,0 +1,44 @@ +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" +import topbar from "../vendor/topbar" + +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +let liveSocket = new LiveSocket("/live", Socket, { + longPollFallbackMs: 2500, + params: {_csrf_token: csrfToken} +}) + +// Show progress bar on live navigation and form submits +topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) +window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) +window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket + diff --git a/example_project/assets/vendor/topbar.js b/example_project/assets/vendor/topbar.js new file mode 100644 index 0000000..4195727 --- /dev/null +++ b/example_project/assets/vendor/topbar.js @@ -0,0 +1,165 @@ +/** + * @license MIT + * topbar 2.0.0, 2023-02-04 + * https://buunguyen.github.io/topbar + * Copyright (c) 2021 Buu Nguyen + */ +(function (window, document) { + "use strict"; + + // https://gist.github.com/paulirish/1579671 + (function () { + var lastTime = 0; + var vendors = ["ms", "moz", "webkit", "o"]; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x] + "RequestAnimationFrame"]; + window.cancelAnimationFrame = + window[vendors[x] + "CancelAnimationFrame"] || + window[vendors[x] + "CancelRequestAnimationFrame"]; + } + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function (callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + })(); + + var canvas, + currentProgress, + showing, + progressTimerId = null, + fadeTimerId = null, + delayTimerId = null, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false); + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); + else elem["on" + type] = handler; + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth; + canvas.height = options.barThickness * 5; // need space for shadow + + var ctx = canvas.getContext("2d"); + ctx.shadowBlur = options.shadowBlur; + ctx.shadowColor = options.shadowColor; + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]); + ctx.lineWidth = options.barThickness; + ctx.beginPath(); + ctx.moveTo(0, options.barThickness / 2); + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ); + ctx.strokeStyle = lineGradient; + ctx.stroke(); + }, + createCanvas = function () { + canvas = document.createElement("canvas"); + var style = canvas.style; + style.position = "fixed"; + style.top = style.left = style.right = style.margin = style.padding = 0; + style.zIndex = 100001; + style.display = "none"; + if (options.className) canvas.classList.add(options.className); + document.body.appendChild(canvas); + addEvent(window, "resize", repaint); + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key]; + }, + show: function (delay) { + if (showing) return; + if (delay) { + if (delayTimerId) return; + delayTimerId = setTimeout(() => topbar.show(), delay); + } else { + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); + } + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress; + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to); + } + currentProgress = to > 1 ? 1 : to; + repaint(); + return currentProgress; + }, + hide: function () { + clearTimeout(delayTimerId); + delayTimerId = null; + if (!showing) return; + showing = false; + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId); + progressTimerId = null; + } + (function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05; + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none"; + fadeTimerId = null; + return; + } + } + fadeTimerId = window.requestAnimationFrame(loop); + })(); + }, + }; + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar; + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar; + }); + } else { + this.topbar = topbar; + } +}.call(this, window, document)); diff --git a/example_project/config/config.exs b/example_project/config/config.exs new file mode 100644 index 0000000..9dadc1c --- /dev/null +++ b/example_project/config/config.exs @@ -0,0 +1,44 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +config :example_project, + generators: [timestamp_type: :utc_datetime] + +# Configures the endpoint +config :example_project, ExampleProjectWeb.Endpoint, + url: [host: "localhost"], + adapter: Bandit.PhoenixAdapter, + render_errors: [ + formats: [html: ExampleProjectWeb.ErrorHTML, json: ExampleProjectWeb.ErrorJSON], + layout: false + ], + pubsub_server: ExampleProject.PubSub, + live_view: [signing_salt: "+TWYHjZu"] + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.17.11", + example_project: [ + args: + ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ] + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/example_project/config/dev.exs b/example_project/config/dev.exs new file mode 100644 index 0000000..b323f26 --- /dev/null +++ b/example_project/config/dev.exs @@ -0,0 +1,71 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we can use it +# to bundle .js and .css sources. +# Binding to loopback ipv4 address prevents access from other machines. +config :example_project, ExampleProjectWeb.Endpoint, + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4000], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "q5zn/L1BnrCev7xxgSkgCgilInhLIiGMGh8IjH6roOgrxsTJ7hJIvIH/IY2lHe3y", + watchers: [ + esbuild: {Esbuild, :install_and_run, [:example_project, ~w(--sourcemap=inline --watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :example_project, ExampleProjectWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/example_project_web/(controllers|live|components)/.*(ex|heex)$" + ] + ] + +# Enable dev routes for dashboard and mailbox +config :example_project, dev_routes: true + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime + +config :phoenix_live_view, + # Include HEEx debug annotations as HTML comments in rendered markup + debug_heex_annotations: true, + # Enable helpful, but potentially expensive runtime checks + enable_expensive_runtime_checks: true diff --git a/example_project/config/prod.exs b/example_project/config/prod.exs new file mode 100644 index 0000000..8acfd85 --- /dev/null +++ b/example_project/config/prod.exs @@ -0,0 +1,15 @@ +import Config + +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix assets.deploy` task, +# which you should run after static files are built and +# before starting your production server. +config :example_project, ExampleProjectWeb.Endpoint, + cache_static_manifest: "priv/static/cache_manifest.json" + +# Do not print debug messages in production +config :logger, level: :info + +# Runtime production configuration, including reading +# of environment variables, is done on config/runtime.exs. diff --git a/example_project/config/runtime.exs b/example_project/config/runtime.exs new file mode 100644 index 0000000..07c04f3 --- /dev/null +++ b/example_project/config/runtime.exs @@ -0,0 +1,84 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. + +# ## Using releases +# +# If you use `mix release`, you need to explicitly enable the server +# by passing the PHX_SERVER=true when you start it: +# +# PHX_SERVER=true bin/example_project start +# +# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` +# script that automatically sets the env var above. +if System.get_env("PHX_SERVER") do + config :example_project, ExampleProjectWeb.Endpoint, server: true +end + +if config_env() == :prod do + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + host = System.get_env("PHX_HOST") || "example.com" + port = String.to_integer(System.get_env("PORT") || "4000") + + config :example_project, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") + + config :example_project, ExampleProjectWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0 + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0}, + port: port + ], + secret_key_base: secret_key_base + + # ## SSL Support + # + # To get SSL working, you will need to add the `https` key + # to your endpoint configuration: + # + # config :example_project, ExampleProjectWeb.Endpoint, + # https: [ + # ..., + # port: 443, + # cipher_suite: :strong, + # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), + # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") + # ] + # + # The `cipher_suite` is set to `:strong` to support only the + # latest and more secure SSL ciphers. This means old browsers + # and clients may not be supported. You can set it to + # `:compatible` for wider support. + # + # `:keyfile` and `:certfile` expect an absolute path to the key + # and cert in disk or a relative path inside priv, for example + # "priv/ssl/server.key". For all supported SSL configuration + # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 + # + # We also recommend setting `force_ssl` in your config/prod.exs, + # ensuring no data is ever sent via http, always redirecting to https: + # + # config :example_project, ExampleProjectWeb.Endpoint, + # force_ssl: [hsts: true] + # + # Check `Plug.SSL` for all available options in `force_ssl`. +end diff --git a/example_project/config/test.exs b/example_project/config/test.exs new file mode 100644 index 0000000..402a222 --- /dev/null +++ b/example_project/config/test.exs @@ -0,0 +1,18 @@ +import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :example_project, ExampleProjectWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "NOw9umqMS7gzFHxbPLvAI2Z9f/2iAOmS54sZ/bpjElo+WynkIdf1SeGWKuNJvT3p", + server: false + +# Print only warnings and errors during test +config :logger, level: :warning + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime + +# Enable helpful, but potentially expensive runtime checks +config :phoenix_live_view, + enable_expensive_runtime_checks: true diff --git a/example_project/lib/example_project.ex b/example_project/lib/example_project.ex new file mode 100644 index 0000000..ef1a176 --- /dev/null +++ b/example_project/lib/example_project.ex @@ -0,0 +1,9 @@ +defmodule ExampleProject do + @moduledoc """ + ExampleProject keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/example_project/lib/example_project/application.ex b/example_project/lib/example_project/application.ex new file mode 100644 index 0000000..0957c3f --- /dev/null +++ b/example_project/lib/example_project/application.ex @@ -0,0 +1,33 @@ +defmodule ExampleProject.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + ExampleProjectWeb.Telemetry, + {DNSCluster, query: Application.get_env(:example_project, :dns_cluster_query) || :ignore}, + {Phoenix.PubSub, name: ExampleProject.PubSub}, + # Start a worker by calling: ExampleProject.Worker.start_link(arg) + # {ExampleProject.Worker, arg}, + # Start to serve requests, typically the last entry + ExampleProjectWeb.Endpoint + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: ExampleProject.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + ExampleProjectWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/example_project/lib/example_project_web.ex b/example_project/lib/example_project_web.ex new file mode 100644 index 0000000..3c7d713 --- /dev/null +++ b/example_project/lib/example_project_web.ex @@ -0,0 +1,116 @@ +defmodule ExampleProjectWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, components, channels, and so on. + + This can be used in your application as: + + use ExampleProjectWeb, :controller + use ExampleProjectWeb, :html + + The definitions below will be executed for every controller, + component, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define additional modules and import + those modules here. + """ + + def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + + def router do + quote do + use Phoenix.Router, helpers: false + + # Import common connection and controller functions to use in pipelines + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + end + end + + def controller do + quote do + use Phoenix.Controller, + formats: [:html, :json], + layouts: [html: ExampleProjectWeb.Layouts] + + use Gettext, backend: ExampleProjectWeb.Gettext + + import Plug.Conn + + unquote(verified_routes()) + end + end + + def live_view do + quote do + use Phoenix.LiveView, + layout: {ExampleProjectWeb.Layouts, :app} + + unquote(html_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(html_helpers()) + end + end + + def html do + quote do + use Phoenix.Component + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_csrf_token: 0, view_module: 1, view_template: 1] + + # Include general helpers for rendering HTML + unquote(html_helpers()) + end + end + + defp html_helpers do + quote do + # Translation + use Gettext, backend: ExampleProjectWeb.Gettext + + # HTML escaping functionality + import Phoenix.HTML + # Core UI components + import ExampleProjectWeb.CoreComponents + + # Shortcut for generating JS commands + alias Phoenix.LiveView.JS + + # Routes generation with the ~p sigil + unquote(verified_routes()) + end + end + + def verified_routes do + quote do + use Phoenix.VerifiedRoutes, + endpoint: ExampleProjectWeb.Endpoint, + router: ExampleProjectWeb.Router, + statics: ExampleProjectWeb.static_paths() + end + end + + @doc """ + When used, dispatch to the appropriate controller/live_view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/example_project/lib/example_project_web/components/core_components.ex b/example_project/lib/example_project_web/components/core_components.ex new file mode 100644 index 0000000..36f3d9b --- /dev/null +++ b/example_project/lib/example_project_web/components/core_components.ex @@ -0,0 +1,676 @@ +defmodule ExampleProjectWeb.CoreComponents do + @moduledoc """ + Provides core UI components. + + At first glance, this module may seem daunting, but its goal is to provide + core building blocks for your application, such as modals, tables, and + forms. The components consist mostly of markup and are well-documented + with doc strings and declarative assigns. You may customize and style + them in any way you want, based on your application growth and needs. + + The default components use Tailwind CSS, a utility-first CSS framework. + See the [Tailwind CSS documentation](https://tailwindcss.com) to learn + how to customize them or feel free to swap in another framework altogether. + + Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage. + """ + use Phoenix.Component + use Gettext, backend: ExampleProjectWeb.Gettext + + alias Phoenix.LiveView.JS + + @doc """ + Renders a modal. + + ## Examples + + <.modal id="confirm-modal"> + This is a modal. + + + JS commands may be passed to the `:on_cancel` to configure + the closing/cancel event, for example: + + <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}> + This is another modal. + + + """ + attr :id, :string, required: true + attr :show, :boolean, default: false + attr :on_cancel, JS, default: %JS{} + slot :inner_block, required: true + + def modal(assigns) do + ~H""" + + """ + end + + def input(%{type: "select"} = assigns) do + ~H""" +
+ <.label for={@id}>{@label} + + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + def input(%{type: "textarea"} = assigns) do + ~H""" +
+ <.label for={@id}>{@label} + + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + # All other inputs text, datetime-local, url, password, etc. are handled here... + def input(assigns) do + ~H""" +
+ <.label for={@id}>{@label} + + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + @doc """ + Renders a label. + """ + attr :for, :string, default: nil + slot :inner_block, required: true + + def label(assigns) do + ~H""" + + """ + end + + @doc """ + Generates a generic error message. + """ + slot :inner_block, required: true + + def error(assigns) do + ~H""" +

+ <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> + {render_slot(@inner_block)} +

+ """ + end + + @doc """ + Renders a header with title. + """ + attr :class, :string, default: nil + + slot :inner_block, required: true + slot :subtitle + slot :actions + + def header(assigns) do + ~H""" +
+
+

+ {render_slot(@inner_block)} +

+

+ {render_slot(@subtitle)} +

+
+
{render_slot(@actions)}
+
+ """ + end + + @doc ~S""" + Renders a table with generic styling. + + ## Examples + + <.table id="users" rows={@users}> + <:col :let={user} label="id">{user.id} + <:col :let={user} label="username">{user.username} + + """ + attr :id, :string, required: true + attr :rows, :list, required: true + attr :row_id, :any, default: nil, doc: "the function for generating the row id" + attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" + + attr :row_item, :any, + default: &Function.identity/1, + doc: "the function for mapping each row before calling the :col and :action slots" + + slot :col, required: true do + attr :label, :string + end + + slot :action, doc: "the slot for showing user actions in the last table column" + + def table(assigns) do + assigns = + with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do + assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) + end + + ~H""" +
+ + + + + + + + + + + + + +
{col[:label]} + {gettext("Actions")} +
+
+ + + {render_slot(col, @row_item.(row))} + +
+
+
+ + + {render_slot(action, @row_item.(row))} + +
+
+
+ """ + end + + @doc """ + Renders a data list. + + ## Examples + + <.list> + <:item title="Title">{@post.title} + <:item title="Views">{@post.views} + + """ + slot :item, required: true do + attr :title, :string, required: true + end + + def list(assigns) do + ~H""" +
+
+
+
{item.title}
+
{render_slot(item)}
+
+
+
+ """ + end + + @doc """ + Renders a back navigation link. + + ## Examples + + <.back navigate={~p"/posts"}>Back to posts + """ + attr :navigate, :any, required: true + slot :inner_block, required: true + + def back(assigns) do + ~H""" +
+ <.link + navigate={@navigate} + class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700" + > + <.icon name="hero-arrow-left-solid" class="h-3 w-3" /> + {render_slot(@inner_block)} + +
+ """ + end + + @doc """ + Renders a [Heroicon](https://heroicons.com). + + Heroicons come in three styles – outline, solid, and mini. + By default, the outline style is used, but solid and mini may + be applied by using the `-solid` and `-mini` suffix. + + You can customize the size and colors of the icons by setting + width, height, and background color classes. + + Icons are extracted from the `deps/heroicons` directory and bundled within + your compiled app.css by the plugin in your `assets/tailwind.config.js`. + + ## Examples + + <.icon name="hero-x-mark-solid" /> + <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" /> + """ + attr :name, :string, required: true + attr :class, :string, default: nil + + def icon(%{name: "hero-" <> _} = assigns) do + ~H""" + + """ + end + + ## JS Commands + + def show(js \\ %JS{}, selector) do + JS.show(js, + to: selector, + time: 300, + transition: + {"transition-all transform ease-out duration-300", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", + "opacity-100 translate-y-0 sm:scale-100"} + ) + end + + def hide(js \\ %JS{}, selector) do + JS.hide(js, + to: selector, + time: 200, + transition: + {"transition-all transform ease-in duration-200", + "opacity-100 translate-y-0 sm:scale-100", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} + ) + end + + def show_modal(js \\ %JS{}, id) when is_binary(id) do + js + |> JS.show(to: "##{id}") + |> JS.show( + to: "##{id}-bg", + time: 300, + transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"} + ) + |> show("##{id}-container") + |> JS.add_class("overflow-hidden", to: "body") + |> JS.focus_first(to: "##{id}-content") + end + + def hide_modal(js \\ %JS{}, id) do + js + |> JS.hide( + to: "##{id}-bg", + transition: {"transition-all transform ease-in duration-200", "opacity-100", "opacity-0"} + ) + |> hide("##{id}-container") + |> JS.hide(to: "##{id}", transition: {"block", "block", "hidden"}) + |> JS.remove_class("overflow-hidden", to: "body") + |> JS.pop_focus() + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # However the error messages in our forms and APIs are generated + # dynamically, so we need to translate them by calling Gettext + # with our gettext backend as first argument. Translations are + # available in the errors.po file (as we use the "errors" domain). + if count = opts[:count] do + Gettext.dngettext(ExampleProjectWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(ExampleProjectWeb.Gettext, "errors", msg, opts) + end + end + + @doc """ + Translates the errors for a field from a keyword list of errors. + """ + def translate_errors(errors, field) when is_list(errors) do + for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) + end +end diff --git a/example_project/lib/example_project_web/components/layouts.ex b/example_project/lib/example_project_web/components/layouts.ex new file mode 100644 index 0000000..ebdca43 --- /dev/null +++ b/example_project/lib/example_project_web/components/layouts.ex @@ -0,0 +1,14 @@ +defmodule ExampleProjectWeb.Layouts do + @moduledoc """ + This module holds different layouts used by your application. + + See the `layouts` directory for all templates available. + The "root" layout is a skeleton rendered as part of the + application router. The "app" layout is set as the default + layout on both `use ExampleProjectWeb, :controller` and + `use ExampleProjectWeb, :live_view`. + """ + use ExampleProjectWeb, :html + + embed_templates "layouts/*" +end diff --git a/example_project/lib/example_project_web/components/layouts/app.html.heex b/example_project/lib/example_project_web/components/layouts/app.html.heex new file mode 100644 index 0000000..3b3b607 --- /dev/null +++ b/example_project/lib/example_project_web/components/layouts/app.html.heex @@ -0,0 +1,32 @@ +
+
+
+ + + +

+ v{Application.spec(:phoenix, :vsn)} +

+
+ +
+
+
+
+ <.flash_group flash={@flash} /> + {@inner_content} +
+
diff --git a/example_project/lib/example_project_web/components/layouts/root.html.heex b/example_project/lib/example_project_web/components/layouts/root.html.heex new file mode 100644 index 0000000..2f9ed93 --- /dev/null +++ b/example_project/lib/example_project_web/components/layouts/root.html.heex @@ -0,0 +1,17 @@ + + + + + + + <.live_title default="ExampleProject" suffix=" · Phoenix Framework"> + {assigns[:page_title]} + + + + + + {@inner_content} + + diff --git a/example_project/lib/example_project_web/controllers/error_html.ex b/example_project/lib/example_project_web/controllers/error_html.ex new file mode 100644 index 0000000..0823974 --- /dev/null +++ b/example_project/lib/example_project_web/controllers/error_html.ex @@ -0,0 +1,24 @@ +defmodule ExampleProjectWeb.ErrorHTML do + @moduledoc """ + This module is invoked by your endpoint in case of errors on HTML requests. + + See config/config.exs. + """ + use ExampleProjectWeb, :html + + # If you want to customize your error pages, + # uncomment the embed_templates/1 call below + # and add pages to the error directory: + # + # * lib/example_project_web/controllers/error_html/404.html.heex + # * lib/example_project_web/controllers/error_html/500.html.heex + # + # embed_templates "error_html/*" + + # The default is to render a plain text page based on + # the template name. For example, "404.html" becomes + # "Not Found". + def render(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/example_project/lib/example_project_web/controllers/error_json.ex b/example_project/lib/example_project_web/controllers/error_json.ex new file mode 100644 index 0000000..b4174b1 --- /dev/null +++ b/example_project/lib/example_project_web/controllers/error_json.ex @@ -0,0 +1,21 @@ +defmodule ExampleProjectWeb.ErrorJSON do + @moduledoc """ + This module is invoked by your endpoint in case of errors on JSON requests. + + See config/config.exs. + """ + + # If you want to customize a particular status code, + # you may add your own clauses, such as: + # + # def render("500.json", _assigns) do + # %{errors: %{detail: "Internal Server Error"}} + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.json" becomes + # "Not Found". + def render(template, _assigns) do + %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} + end +end diff --git a/example_project/lib/example_project_web/controllers/page_controller.ex b/example_project/lib/example_project_web/controllers/page_controller.ex new file mode 100644 index 0000000..54ad28d --- /dev/null +++ b/example_project/lib/example_project_web/controllers/page_controller.ex @@ -0,0 +1,9 @@ +defmodule ExampleProjectWeb.PageController do + use ExampleProjectWeb, :controller + + def home(conn, _params) do + # The home page is often custom made, + # so skip the default app layout. + render(conn, :home, layout: false) + end +end diff --git a/example_project/lib/example_project_web/controllers/page_html.ex b/example_project/lib/example_project_web/controllers/page_html.ex new file mode 100644 index 0000000..0eb606b --- /dev/null +++ b/example_project/lib/example_project_web/controllers/page_html.ex @@ -0,0 +1,10 @@ +defmodule ExampleProjectWeb.PageHTML do + @moduledoc """ + This module contains pages rendered by PageController. + + See the `page_html` directory for all templates available. + """ + use ExampleProjectWeb, :html + + embed_templates "page_html/*" +end diff --git a/example_project/lib/example_project_web/controllers/page_html/home.html.heex b/example_project/lib/example_project_web/controllers/page_html/home.html.heex new file mode 100644 index 0000000..798d8f7 --- /dev/null +++ b/example_project/lib/example_project_web/controllers/page_html/home.html.heex @@ -0,0 +1,223 @@ + +<.flash_group flash={@flash} /> + +
+
+ +

+ Phoenix Framework + + v{Application.spec(:phoenix, :vsn)} + +

+

+ Peace of mind from prototype to production. +

+

+ Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale. +

+ +
+
diff --git a/example_project/lib/example_project_web/endpoint.ex b/example_project/lib/example_project_web/endpoint.ex new file mode 100644 index 0000000..8eb9395 --- /dev/null +++ b/example_project/lib/example_project_web/endpoint.ex @@ -0,0 +1,48 @@ +defmodule ExampleProjectWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :example_project + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_example_project_key", + signing_salt: "wOcy44Jq", + same_site: "Lax" + ] + + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [session: @session_options]], + longpoll: [connect_info: [session: @session_options]] + + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phx.digest + # when deploying your static files in production. + plug Plug.Static, + at: "/", + from: :example_project, + gzip: false, + only: ExampleProjectWeb.static_paths() + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader + end + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + + plug Plug.MethodOverride + plug Plug.Head + plug Plug.Session, @session_options + plug ExampleProjectWeb.Router +end diff --git a/example_project/lib/example_project_web/gettext.ex b/example_project/lib/example_project_web/gettext.ex new file mode 100644 index 0000000..c90f163 --- /dev/null +++ b/example_project/lib/example_project_web/gettext.ex @@ -0,0 +1,25 @@ +defmodule ExampleProjectWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), your module compiles translations + that you can use in your application. To use this Gettext backend module, + call `use Gettext` and pass it as an option: + + use Gettext, backend: ExampleProjectWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) + + # Domain-based translation + dgettext("errors", "Here is the error message to translate") + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext.Backend, otp_app: :example_project +end diff --git a/example_project/lib/example_project_web/router.ex b/example_project/lib/example_project_web/router.ex new file mode 100644 index 0000000..2f2ee32 --- /dev/null +++ b/example_project/lib/example_project_web/router.ex @@ -0,0 +1,27 @@ +defmodule ExampleProjectWeb.Router do + use ExampleProjectWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, html: {ExampleProjectWeb.Layouts, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", ExampleProjectWeb do + pipe_through :browser + + get "/", PageController, :home + end + + # Other scopes may use custom stacks. + # scope "/api", ExampleProjectWeb do + # pipe_through :api + # end +end diff --git a/example_project/lib/example_project_web/telemetry.ex b/example_project/lib/example_project_web/telemetry.ex new file mode 100644 index 0000000..fe93a7e --- /dev/null +++ b/example_project/lib/example_project_web/telemetry.ex @@ -0,0 +1,69 @@ +defmodule ExampleProjectWeb.Telemetry do + use Supervisor + import Telemetry.Metrics + + def start_link(arg) do + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) + end + + @impl true + def init(_arg) do + children = [ + # Telemetry poller will execute the given period measurements + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + # Add reporters as children of your supervision tree. + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + def metrics do + [ + # Phoenix Metrics + summary("phoenix.endpoint.start.system_time", + unit: {:native, :millisecond} + ), + summary("phoenix.endpoint.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.start.system_time", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.exception.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.socket_connected.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.channel_joined.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.channel_handled_in.duration", + tags: [:event], + unit: {:native, :millisecond} + ), + + # VM Metrics + summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.total_run_queue_lengths.total"), + summary("vm.total_run_queue_lengths.cpu"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp periodic_measurements do + [ + # A module, function and arguments to be invoked periodically. + # This function must call :telemetry.execute/3 and a metric must be added above. + # {ExampleProjectWeb, :count_users, []} + ] + end +end diff --git a/example_project/mix.exs b/example_project/mix.exs new file mode 100644 index 0000000..209c8e1 --- /dev/null +++ b/example_project/mix.exs @@ -0,0 +1,67 @@ +defmodule ExampleProject.MixProject do + use Mix.Project + + def project do + [ + app: :example_project, + version: "0.1.0", + elixir: "~> 1.14", + elixirc_paths: elixirc_paths(Mix.env()), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {ExampleProject.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.7.19"}, + {:phoenix_html, "~> 4.1"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 1.0.0"}, + {:floki, ">= 0.30.0", only: :test}, + {:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, + {:telemetry_metrics, "~> 1.0"}, + {:telemetry_poller, "~> 1.0"}, + {:gettext, "~> 0.26"}, + {:jason, "~> 1.2"}, + {:dns_cluster, "~> 0.1.1"}, + {:bandit, "~> 1.5"} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to install project dependencies and perform other setup tasks, run: + # + # $ mix setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + setup: ["deps.get", "assets.setup", "assets.build"], + "assets.setup": ["esbuild.install --if-missing"], + "assets.build": ["esbuild example_project"], + "assets.deploy": [ + "esbuild example_project --minify", + "phx.digest" + ] + ] + end +end diff --git a/example_project/mix.lock b/example_project/mix.lock new file mode 100644 index 0000000..e23296e --- /dev/null +++ b/example_project/mix.lock @@ -0,0 +1,27 @@ +%{ + "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, + "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, + "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, + "esbuild": {:hex, :esbuild, "0.9.0", "f043eeaca4932ca8e16e5429aebd90f7766f31ac160a25cbd9befe84f2bc068f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b415027f71d5ab57ef2be844b2a10d0c1b5a492d431727f43937adce22ba45ae"}, + "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, + "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, + "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, + "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, + "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "phoenix": {:hex, :phoenix, "1.7.19", "36617efe5afbd821099a8b994ff4618a340a5bfb25531a1802c4d4c634017a57", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "ba4dc14458278773f905f8ae6c2ec743d52c3a35b6b353733f64f02dfe096cd6"}, + "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.4", "327491b033e79db2f887b065c5a2993228449091883d74cfa1baa12f8c98d5eb", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9865316ddf8d78f382d63af278d20436b52d262b60239956817a61279514366"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, + "thousand_island": {:hex, :thousand_island, "1.3.10", "a9971ebab1dfb36e2710a86b37c3f54973fbc9470d892035334415521fb53328", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17ab1f1b13aadb1f4b4c8e5b59c06874d701119fed082884c9c6d38addad254f"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, +} diff --git a/example_project/priv/gettext/en/LC_MESSAGES/errors.po b/example_project/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 0000000..cdec3a1 --- /dev/null +++ b/example_project/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,11 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" diff --git a/example_project/priv/gettext/errors.pot b/example_project/priv/gettext/errors.pot new file mode 100644 index 0000000..d6f47fa --- /dev/null +++ b/example_project/priv/gettext/errors.pot @@ -0,0 +1,10 @@ +## This is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here has no +## effect: edit them in PO (`.po`) files instead. + diff --git a/example_project/priv/static/favicon.ico b/example_project/priv/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7f372bfc21cdd8cb47585339d5fa4d9dd424402f GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=@t!V@Ar*{oFEH`~d50E!_s``s q?{G*w(7?#d#v@^nKnY_HKaYb01EZMZjMqTJ89ZJ6T-G@yGywoKK_h|y literal 0 HcmV?d00001 diff --git a/example_project/priv/static/images/logo.svg b/example_project/priv/static/images/logo.svg new file mode 100644 index 0000000..9f26bab --- /dev/null +++ b/example_project/priv/static/images/logo.svg @@ -0,0 +1,6 @@ + diff --git a/example_project/priv/static/robots.txt b/example_project/priv/static/robots.txt new file mode 100644 index 0000000..26e06b5 --- /dev/null +++ b/example_project/priv/static/robots.txt @@ -0,0 +1,5 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/example_project/test/example_project_web/controllers/error_html_test.exs b/example_project/test/example_project_web/controllers/error_html_test.exs new file mode 100644 index 0000000..12099fd --- /dev/null +++ b/example_project/test/example_project_web/controllers/error_html_test.exs @@ -0,0 +1,14 @@ +defmodule ExampleProjectWeb.ErrorHTMLTest do + use ExampleProjectWeb.ConnCase, async: true + + # Bring render_to_string/4 for testing custom views + import Phoenix.Template + + test "renders 404.html" do + assert render_to_string(ExampleProjectWeb.ErrorHTML, "404", "html", []) == "Not Found" + end + + test "renders 500.html" do + assert render_to_string(ExampleProjectWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" + end +end diff --git a/example_project/test/example_project_web/controllers/error_json_test.exs b/example_project/test/example_project_web/controllers/error_json_test.exs new file mode 100644 index 0000000..27d1bd3 --- /dev/null +++ b/example_project/test/example_project_web/controllers/error_json_test.exs @@ -0,0 +1,12 @@ +defmodule ExampleProjectWeb.ErrorJSONTest do + use ExampleProjectWeb.ConnCase, async: true + + test "renders 404" do + assert ExampleProjectWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} + end + + test "renders 500" do + assert ExampleProjectWeb.ErrorJSON.render("500.json", %{}) == + %{errors: %{detail: "Internal Server Error"}} + end +end diff --git a/example_project/test/example_project_web/controllers/page_controller_test.exs b/example_project/test/example_project_web/controllers/page_controller_test.exs new file mode 100644 index 0000000..54de386 --- /dev/null +++ b/example_project/test/example_project_web/controllers/page_controller_test.exs @@ -0,0 +1,8 @@ +defmodule ExampleProjectWeb.PageControllerTest do + use ExampleProjectWeb.ConnCase + + test "GET /", %{conn: conn} do + conn = get(conn, ~p"/") + assert html_response(conn, 200) =~ "Peace of mind from prototype to production" + end +end diff --git a/example_project/test/support/conn_case.ex b/example_project/test/support/conn_case.ex new file mode 100644 index 0000000..58e0e34 --- /dev/null +++ b/example_project/test/support/conn_case.ex @@ -0,0 +1,37 @@ +defmodule ExampleProjectWeb.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use ExampleProjectWeb.ConnCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # The default endpoint for testing + @endpoint ExampleProjectWeb.Endpoint + + use ExampleProjectWeb, :verified_routes + + # Import conveniences for testing with connections + import Plug.Conn + import Phoenix.ConnTest + import ExampleProjectWeb.ConnCase + end + end + + setup _tags do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/example_project/test/test_helper.exs b/example_project/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/example_project/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() From 791000b2ab247dbcec37d22de1d7583aea1494fb Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 18 Feb 2025 16:11:24 +0100 Subject: [PATCH 20/45] Finally fix example project --- example_project/assets/js/app.js | 33 ++++--- example_project/config/config.exs | 2 +- .../example_project_web/live_views/echo.ex | 64 +++++++++++++ .../lib/example_project_web/router.ex | 15 ++- example_project/mix.exs | 1 + example_project/mix.lock | 95 +++++++++++++++++++ example_project_sc/assets/js/app.js | 4 - 7 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 example_project/lib/example_project_web/live_views/echo.ex diff --git a/example_project/assets/js/app.js b/example_project/assets/js/app.js index d5e278a..ddfe6f3 100644 --- a/example_project/assets/js/app.js +++ b/example_project/assets/js/app.js @@ -16,29 +16,38 @@ // // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. -import "phoenix_html" +import "phoenix_html"; // Establish Phoenix Socket and LiveView configuration. -import {Socket} from "phoenix" -import {LiveSocket} from "phoenix_live_view" -import topbar from "../vendor/topbar" +import { Socket } from "phoenix"; +import { LiveSocket } from "phoenix_live_view"; +import topbar from "../vendor/topbar"; -let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +import { createCaptureHook, createPlayerHook } from "boombox_live"; + +let Hooks = {}; +const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; +Hooks.Capture = createCaptureHook(iceServers); +Hooks.Player = createPlayerHook(iceServers); + +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content"); let liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, - params: {_csrf_token: csrfToken} -}) + params: { _csrf_token: csrfToken }, + hooks: Hooks, +}); // Show progress bar on live navigation and form submits -topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) -window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) -window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) +topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" }); +window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300)); +window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide()); // connect if there are any LiveViews on the page -liveSocket.connect() +liveSocket.connect(); // expose liveSocket on window for web console debug logs and latency simulation: // >> liveSocket.enableDebug() // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() -window.liveSocket = liveSocket +window.liveSocket = liveSocket; +console.log("app.js executed succesfully"); diff --git a/example_project/config/config.exs b/example_project/config/config.exs index 9dadc1c..0eeb1d8 100644 --- a/example_project/config/config.exs +++ b/example_project/config/config.exs @@ -28,7 +28,7 @@ config :esbuild, args: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), cd: Path.expand("../assets", __DIR__), - env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + env: %{"NODE_PATH" => Enum.map_join(["../deps", "../../.."], ":", &Path.expand(&1, __DIR__))} ] # Configures Elixir's Logger diff --git a/example_project/lib/example_project_web/live_views/echo.ex b/example_project/lib/example_project_web/live_views/echo.ex new file mode 100644 index 0000000..c0c9dd6 --- /dev/null +++ b/example_project/lib/example_project_web/live_views/echo.ex @@ -0,0 +1,64 @@ +defmodule ExampleProjectWeb.LiveViews.Echo do + use ExampleProjectWeb, :live_view + alias Boombox.Live.WebRTC.{Capture, Player} + + def mount(_params, _session, socket) do + IO.inspect(socket, label: "MOUNT") + + socket = + if connected?(socket) do + ingress_signaling = Membrane.WebRTC.SignalingChannel.new() + egress_signaling = Membrane.WebRTC.SignalingChannel.new() + + {:ok, _boombox_pid} = + Task.start_link(fn -> + Boombox.run( + input: {:webrtc, ingress_signaling}, + output: {:webrtc, egress_signaling} + ) + end) + + socket = + socket + |> Capture.attach( + id: "mediaCapture", + signaling_channel: ingress_signaling, + audio?: false, + video?: true + ) + |> Player.attach( + id: "videoPlayer", + signaling_channel: egress_signaling + ) + + socket + |> assign( + capture: Capture.get_attached(socket, "mediaCapture"), + player: Player.get_attached(socket, "videoPlayer") + ) + else + socket + end + + {:ok, socket} + end + + def render(%{capture: %Capture{}, player: %Player{}} = assigns) do + ~H""" + + + + + """ + end + + def render(assigns) do + ~H""" + + """ + end +end diff --git a/example_project/lib/example_project_web/router.ex b/example_project/lib/example_project_web/router.ex index 2f2ee32..cbadde3 100644 --- a/example_project/lib/example_project_web/router.ex +++ b/example_project/lib/example_project_web/router.ex @@ -14,14 +14,21 @@ defmodule ExampleProjectWeb.Router do plug :accepts, ["json"] end - scope "/", ExampleProjectWeb do - pipe_through :browser + # scope "/", ExampleProjectWeb do + # pipe_through :browser - get "/", PageController, :home - end + # get "/", PageController, :home + # end # Other scopes may use custom stacks. # scope "/api", ExampleProjectWeb do # pipe_through :api # end + + scope "/", ExampleProjectWeb do + pipe_through(:browser) + + live("/", LiveViews.Echo, :index) + end + end diff --git a/example_project/mix.exs b/example_project/mix.exs index 209c8e1..bbb2f4a 100644 --- a/example_project/mix.exs +++ b/example_project/mix.exs @@ -32,6 +32,7 @@ defmodule ExampleProject.MixProject do # Type `mix help deps` for examples and options. defp deps do [ + {:boombox_live, path: ".."}, {:phoenix, "~> 1.7.19"}, {:phoenix_html, "~> 4.1"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, diff --git a/example_project/mix.lock b/example_project/mix.lock index e23296e..b6e8000 100644 --- a/example_project/mix.lock +++ b/example_project/mix.lock @@ -1,15 +1,98 @@ %{ "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, + "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, + "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, + "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, + "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, + "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, + "corsica": {:hex, :corsica, "2.1.3", "dccd094ffce38178acead9ae743180cdaffa388f35f0461ba1e8151d32e190e6", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "616c08f61a345780c2cf662ff226816f04d8868e12054e68963e95285b5be8bc"}, + "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"}, + "crc": {:hex, :crc, "0.10.5", "ee12a7c056ac498ef2ea985ecdc9fa53c1bfb4e53a484d9f17ff94803707dfd8", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3e673b6495a9525c5c641585af1accba59a1eb33de697bedf341e247012c2c7f"}, "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "esbuild": {:hex, :esbuild, "0.9.0", "f043eeaca4932ca8e16e5429aebd90f7766f31ac160a25cbd9befe84f2bc068f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b415027f71d5ab57ef2be844b2a10d0c1b5a492d431727f43937adce22ba45ae"}, + "ex_dtls": {:hex, :ex_dtls, "0.16.0", "3ae38025ccc77f6db573e2e391602fa9bbc02253c137d8d2d59469a66cbe806b", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2a4e30d74c6ddf95cc5b796423293c06a0da295454c3823819808ff031b4b361"}, + "ex_ice": {:hex, :ex_ice, "0.9.3", "46700963acaba72737032500b6ee298a4effa7ad7189ab48887be5e9f4fe2107", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "4fd98d20a39ab70a62dd301c44e87437d479292c528ec7f21522ebfe0654b9cb"}, + "ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"}, + "ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"}, + "ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"}, + "ex_sdp": {:hex, :ex_sdp, "1.1.1", "1a7b049491e5ec02dad9251c53d960835dc5631321ae978ec331831f3e4f6d5f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "1b13a72ac9c5c695b8824dbdffc671be8cbb4c0d1ccb4ff76a04a6826759f233"}, + "ex_stun": {:hex, :ex_stun, "0.2.0", "feb1fc7db0356406655b2a617805e6c712b93308c8ea2bf0ba1197b1f0866deb", [:mix], [], "hexpm", "1e01ba8290082ccbf37acaa5190d1f69b51edd6de2026a8d6d51368b29d115d0"}, + "ex_turn": {:hex, :ex_turn, "0.2.0", "4e1f9b089e9a5ee44928d12370cc9ea7a89b84b2f6256832de65271212eb80de", [:mix], [{:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "08e884f0af2c4a147e3f8cd4ffe33e3452a256389f0956e55a8c4d75bf0e74cd"}, + "ex_webrtc": {:hex, :ex_webrtc, "0.8.1", "e507d1b3d89e9c8b74e3fef5f4070d57e20a3f77061b7439b1af1877d6577793", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.16.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:ex_ice, "~> 0.9.0", [hex: :ex_ice, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.7.1", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:ex_sctp, "0.1.2", [hex: :ex_sctp, repo: "hexpm", optional: true]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}], "hexpm", "2c5563cdaf998b5beed3c79b0feeaa1430f30118b21ab59dd18289d72177adf0"}, "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "heap": {:hex, :heap, "2.0.2", "d98cb178286cfeb5edbcf17785e2d20af73ca57b5a2cf4af584118afbcf917eb", [:mix], [], "hexpm", "ba9ea2fe99eb4bcbd9a8a28eaf71cbcac449ca1d8e71731596aace9028c9d429"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "image": {:hex, :image, "0.54.4", "332cd64ca47938447dffee97b05a5e4203f2a45e8918537ab0fb971fa3c9debb", [:mix], [{:bumblebee, "~> 0.3", [hex: :bumblebee, repo: "hexpm", optional: true]}, {:evision, "~> 0.1.33 or ~> 0.2", [hex: :evision, repo: "hexpm", optional: true]}, {:exla, "~> 0.5", [hex: :exla, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:kino, "~> 0.13", [hex: :kino, repo: "hexpm", optional: true]}, {:nx, "~> 0.7", [hex: :nx, repo: "hexpm", optional: true]}, {:nx_image, "~> 0.1", [hex: :nx_image, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.1 or ~> 3.2 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:rustler, "> 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:scholar, "~> 0.3", [hex: :scholar, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}, {:vix, "~> 0.23", [hex: :vix, repo: "hexpm", optional: false]}], "hexpm", "4d66ee976c30ec181a54b99791354a4ae990521d64811cb2daed39c4cd95860b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"}, + "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.11", "6bdcde2eaeafcce136f21fb17effdcdaf9c73107771c8d2a661ca5d0b616ea12", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "dac3f0c839f33603dc2fdad8b9fbebd5ca578c9ca28ea65249ebb15c96c0fd31"}, + "membrane_aac_format": {:hex, :membrane_aac_format, "0.8.0", "515631eabd6e584e0e9af2cea80471fee6246484dbbefc4726c1d93ece8e0838", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}], "hexpm", "a30176a94491033ed32be45e51d509fc70a5ee6e751f12fd6c0d60bd637013f6"}, + "membrane_aac_plugin": {:hex, :membrane_aac_plugin, "0.19.0", "58a15efaaa4f2cc91b968464cfd269244e035efdd983aac2e3ddeb176fcf0585", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "eb7e786e650608ee205f4eebff4c1df3677e545acf09802458f77f64f9942fe9"}, + "membrane_cmaf_format": {:hex, :membrane_cmaf_format, "0.7.1", "9ea858faefdcb181cdfa8001be827c35c5f854e9809ad57d7062cff1f0f703fd", [:mix], [], "hexpm", "3c7b4ed2a986e27f6f336d2f19e9442cb31d93b3142fc024c019572faca54a73"}, + "membrane_common_c": {:hex, :membrane_common_c, "0.16.0", "caf3f29d2f5a1d32d8c2c122866110775866db2726e4272be58e66dfdf4bce40", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "a3c7e91de1ce1f8b23b9823188a5d13654d317235ea0ca781c05353ed3be9b1c"}, + "membrane_core": {:hex, :membrane_core, "1.1.2", "3ca206893e1d3739a24d5092d21c06fcb4db326733a1798f9788fc53abb74829", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a989fd7e0516a7e66f5fb63950b1027315b7f8c8d82d8d685e178b0fb780901b"}, + "membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.20.2", "2e669f0b25418d10b51a73bc52d2e12e4a3a26b416c5c1199d852c3f781a18b3", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:mockery, "~> 2.1", [hex: :mockery, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "6c8d3bcd61d568dd94cabb9b45f29e8926e0076e4432d8f419378e004e02147c"}, + "membrane_ffmpeg_swscale_plugin": {:hex, :membrane_ffmpeg_swscale_plugin, "0.16.2", "581909312d6d12ed560ee99caa1b1674a339760ab2ad6835d243326806c23da1", [:mix], [{:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "46c185dacff1e1b404d0ceb74d0d5224f0931fe1e8b951cc3776ebd099e39afc"}, + "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.2", "650e134c2345d946f930082fac8bac9f5aba785a7817d38a9a9da41ffc56fa92", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "df50c6040004cd7b901cf057bd7e99c875bbbd6ae574efc93b2c753c96f43b9d"}, + "membrane_flv_plugin": {:hex, :membrane_flv_plugin, "0.12.0", "d715ad405af86dcaf4b2f479e34088e1f6738c7280366828e1066b39d2aa493a", [:mix], [{:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}], "hexpm", "a317872d6d394e550c7bfd8979f12a3a1cc1e89b547d75360321025b403d3279"}, + "membrane_funnel_plugin": {:hex, :membrane_funnel_plugin, "0.9.2", "2b2e840dbb232ce29aaff2d55bd329d9978766518dbeb6e8dba7aba7115fadcc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "865ac9d84f86698e2cfeb7904d3b12ab74855a38ca651a880db1505965fa77cc"}, + "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.32.5", "30542fb5d6d36961a51906549b4338f4fc66a304bf92e7c7123e2b9971e3502d", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "8c80e11b9ec9ca23d44304ed7bb3daf665e98b91b2488608ee5718a88182e363"}, + "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, + "membrane_h265_ffmpeg_plugin": {:hex, :membrane_h265_ffmpeg_plugin, "0.4.2", "6dcd932fc2c65a851ab7a44f3996cc9613163bdf23b00568c87c9c0754a64edf", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.1", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "d7eb02110a47a9c11f1432f95b775ba7ce5e962dbe8fc07b421bdd2ce5283b2d"}, + "membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"}, + "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.2", "caf2790d8c107df35f8d456b45f4e09fb9c56ce6c7669a3a03f7d59972e6ed82", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}], "hexpm", "becf1ac4a589adecd850137ccd61a33058f686083a514a7e39fcd721bcf9fb2e"}, + "membrane_hackney_plugin": {:hex, :membrane_hackney_plugin, "0.11.0", "54b368333a23394e7cac2f4d6b701bf8c5ee6614670a31f4ebe009b5e691a5c1", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "2b28fd1be3c889d5824d7d985598386c7673828c88f49a91221df3626af8a998"}, + "membrane_http_adaptive_stream_plugin": {:hex, :membrane_http_adaptive_stream_plugin, "0.18.6", "d417f54da56f7a704200baf018cc0e6222e4c649672adac21e2b321b66c3a958", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_mp4_plugin, "~> 0.35.0", [hex: :membrane_mp4_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "7ca36002d0d74254537afb7aae31541d57a36b33c1abe114e6dd56cf7566bcb2"}, + "membrane_mp4_format": {:hex, :membrane_mp4_format, "0.8.0", "8c6e7d68829228117d333b4fbb030e7be829aab49dd8cb047fdc664db1812e6a", [:mix], [], "hexpm", "148dea678a1f82ccfd44dbde6f936d2f21255f496cb45a22cc6eec427f025522"}, + "membrane_mp4_plugin": {:hex, :membrane_mp4_plugin, "0.35.2", "cbedb5272ef1c8f7d9cd3c44f820a90306469b1dc84b8db30ff55bb6195b7cb2", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_cmaf_format, "~> 0.7.0", [hex: :membrane_cmaf_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_mp4_format, "~> 0.8.0", [hex: :membrane_mp4_format, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.1", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}], "hexpm", "8afd4e7779a742dd56c23f1f23053933d1b0b34d397ad368a2f56f995edb2fe0"}, + "membrane_opus_format": {:hex, :membrane_opus_format, "0.3.0", "3804d9916058b7cfa2baa0131a644d8186198d64f52d592ae09e0942513cb4c2", [:mix], [], "hexpm", "8fc89c97be50de23ded15f2050fe603dcce732566fe6fdd15a2de01cb6b81afe"}, + "membrane_opus_plugin": {:hex, :membrane_opus_plugin, "0.20.5", "aa344bb9931c8e22b2286778cce0658e0d4aa071a503c18c55e1b161e17ab337", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "94fd4447b6576780afc6144dbb0520b43bd399c86a10bf5df1fa878a91798cf6"}, + "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, + "membrane_raw_audio_format": {:hex, :membrane_raw_audio_format, "0.12.0", "b574cd90f69ce2a8b6201b0ccf0826ca28b0fbc8245b8078d9f11cef65f7d5d5", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "6e6c98e3622a2b9df19eab50ba65d7eb45949b1ba306fa8423df6cdb12fd0b44"}, + "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.1", "d7344499c2d80f236a7ef962b5490c651341a501052ee43dec56cf0319fa3936", [:mix], [], "hexpm", "9920b7d445b5357608a364fec5685acdfce85334c647f745045237a0d296c442"}, + "membrane_realtimer_plugin": {:hex, :membrane_realtimer_plugin, "0.9.0", "27210d5e32a5e8bfd101c41e4d8c1876e873a52cc129ebfbee4d0ccbea1cbd21", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "b2e96d62135ee57ef9a5fdea94b3a9ab1198e5ea8ee248391b89c671125d1b51"}, + "membrane_rtmp_plugin": {:hex, :membrane_rtmp_plugin, "0.27.3", "2ca6705668e207a2af1dfd459ec94cebf43bd40338b1b176af1b7ecfb7f5017e", [:mix], [{:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_flv_plugin, "~> 0.12.0", [hex: :membrane_flv_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "e3b3a858cee514aae1cd78b5c85a24b68edb15183e7aa909180c0ce7fb92ab10"}, + "membrane_rtp_aac_plugin": {:hex, :membrane_rtp_aac_plugin, "0.9.4", "355efe237151b304a479a8f0db12043aea2528718d045cb596cbfb85f64ff20a", [:mix], [{:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "f72d12b88b57a3c93eeea19c02c95c878c4b09883dbec703ae3d1557d3af44c0"}, + "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.10.0", "e7f62b1f522647a64d88b60f635476dcb71db61dccda32ffa21ecdb965314c91", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "493245bb45faf3a08343e29281e2aadac72b795d48d64bb805ea8daa31cfd414"}, + "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.20.2", "ab84db505d3102a9cdc300f137c78245ef3982a7ec545838f9544b6b0a2ca1ba", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "27f38c49544d1acf6f7c3f8770a7893f90813a31e8a26461e112a3d3142aff46"}, + "membrane_rtp_h265_plugin": {:hex, :membrane_rtp_h265_plugin, "0.5.2", "970155229e97311b13df8ffb705ee208df83de6d084b43bc77c35e692774616a", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "f8c34c0db3d13d84709fa0b41d4944fcbca50be1fbd292553bfd53512e9a0a26"}, + "membrane_rtp_opus_plugin": {:hex, :membrane_rtp_opus_plugin, "0.10.0", "5797c9b5f09a81c35edc13583eafdfa86b71ac5b2a9d399e95598e27c23ec1b6", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "988cf18ad1e5eb876ec93ae476f035bbec0aff2ca33127b1009217bd258e4845"}, + "membrane_rtp_plugin": {:hex, :membrane_rtp_plugin, "0.30.0", "f7dbc0c5e163edf4ec67b89b1028a7b99ea56e87f8cc01d01b01ec0470287128", [:mix], [{:bimap, "~> 1.2", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.6.0 or ~> 0.7.0", [hex: :ex_libsrtp, repo: "hexpm", optional: true]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:heap, "~> 2.0.2", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.1", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "9600e39c35428c3a86c469ee3cae5a60d259da3ee7e58f94b04866da6d719016"}, + "membrane_rtp_vp8_plugin": {:hex, :membrane_rtp_vp8_plugin, "0.9.4", "8eb7e90576e79ccfefe6cf54f982afd8109027b148e17e701e5fbabe485a7b53", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.4.0 or ~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}], "hexpm", "0fd14c81d3200fc83908f3d72ece3faed207e34c1f59a85b2b7625ec02fb30dd"}, + "membrane_rtsp": {:hex, :membrane_rtsp, "0.10.1", "c6cb8549daab155175896f677b049a0f65c3a650e93e603714ec94a836148722", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.17.0 or ~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.4.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ff382b38c09150ff43c7ec7a2a62df41b2bab42793633dcc40ef9b6e51ac70b9"}, + "membrane_rtsp_plugin": {:hex, :membrane_rtsp_plugin, "0.6.1", "56f11bbd74e47fe7240df44ffc2b17537bc295a4286e08ce3232a905347edfea", [:mix], [{:ex_sdp, "~> 1.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_aac_plugin, "~> 0.9.1", [hex: :membrane_rtp_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_h265_plugin, "~> 0.5.2", [hex: :membrane_rtp_h265_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.10.0", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:membrane_tcp_plugin, "~> 0.6.0", [hex: :membrane_tcp_plugin, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.14.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}], "hexpm", "80b6aedd27b4d0e2c99097b5b7d841855ff6fcaf7a11ef85d8e57f7b83a4d8c1"}, + "membrane_tcp_plugin": {:hex, :membrane_tcp_plugin, "0.6.0", "1f8dba5525504fb2d49070932f24113d1b26c7e5429c700671ed80433ac83f2f", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "820440f5a8181a96cff461ad2d5ed426d47eacfdd7764dd9596dad68ad892d3d"}, + "membrane_tee_plugin": {:hex, :membrane_tee_plugin, "0.12.0", "f94989b4080ef4b7937d74c1a14d3379577c7bd4c6d06e5a2bb41c351ad604d4", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "0d61c9ed5e68e5a75d54200e1c6df5739c0bcb52fee0974183ad72446a179887"}, + "membrane_telemetry_metrics": {:hex, :membrane_telemetry_metrics, "0.1.1", "57917e72012f9ebe124eab54f29ca74c9d9eb3ae2207f55c95618ee51280eb4f", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "69392966e6bd51937244758c2b3d835c5ff47d8953d25431a9d37059737afc11"}, + "membrane_timestamp_queue": {:hex, :membrane_timestamp_queue, "0.2.2", "1c831b2273d018a6548654aa9f7fa7c4b683f71d96ffe164934ef55f9d11f693", [:mix], [{:heap, "~> 2.0", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7c830e760baaced0988421671cd2c83c7cda8d1bd2b61fd05332711675d1204f"}, + "membrane_transcoder_plugin": {:hex, :membrane_transcoder_plugin, "0.1.2", "5d5546971289109423c1d02c4a45bfce7133293d76f5850824663a4613e5a728", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.0", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.20.0", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.1", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.32.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_ffmpeg_plugin, "~> 0.4.2", [hex: :membrane_h265_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.20.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.2", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vpx_plugin, "~> 0.3.0", [hex: :membrane_vpx_plugin, repo: "hexpm", optional: false]}], "hexpm", "50297b922fdd39520125120782bf679f26be8965b31565b470fb51b6ef908873"}, + "membrane_udp_plugin": {:hex, :membrane_udp_plugin, "0.14.0", "d533ee5f6fcdd0551ad690045cdb6c1a76307a155d9255cc4a4606f85774bc37", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "902d1a7aa228ec377482d53a605b100e20e0b6e59196f94f94147bb62b23c47e"}, + "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, + "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, + "membrane_vpx_plugin": {:hex, :membrane_vpx_plugin, "0.3.0", "60404d1b1511b4c62ba6bbf7b6212570f1732ba477015c4072e0aa33e18a8809", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vp9_format, "~> 0.5.0", [hex: :membrane_vp9_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "effa7762bbf73efd8d21d0978bce79538414719284194db97672afbce665b56a"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "mockery": {:hex, :mockery, "2.3.3", "3dba87bd0422a513e6af6e0d811383f38f82ac6be5d3d285a5fcca9c299bd0ac", [:mix], [], "hexpm", "17282be00613286254298117cd25e607a39f15ac03b41c631f60e52f5b5ec974"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.19", "36617efe5afbd821099a8b994ff4618a340a5bfb25531a1802c4d4c634017a57", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "ba4dc14458278773f905f8ae6c2ec743d52c3a35b6b353733f64f02dfe096cd6"}, "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, @@ -17,11 +100,23 @@ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, + "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, + "ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"}, + "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, + "shmex": {:hex, :shmex, "0.5.1", "81dd209093416bf6608e66882cb7e676089307448a1afd4fc906c1f7e5b94cf4", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "c29f8286891252f64c4e1dac40b217d960f7d58def597c4e606ff8fbe71ceb80"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, "thousand_island": {:hex, :thousand_island, "1.3.10", "a9971ebab1dfb36e2710a86b37c3f54973fbc9470d892035334415521fb53328", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17ab1f1b13aadb1f4b4c8e5b59c06874d701119fed082884c9c6d38addad254f"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "unifex": {:hex, :unifex, "1.2.1", "6841c170a6e16509fac30b19e4e0a19937c33155a59088b50c15fc2c36251b6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "8c9d2e3c48df031e9995dd16865bab3df402c0295ba3a31f38274bb5314c7d37"}, + "vix": {:hex, :vix, "0.33.0", "cd98084529fd8fe3d2336f157db6de03b297fb096508d820068117d58eadb6f1", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "9acde72b27bdfeadeb51f790f7a6cc0d06cf555718c05cf57e43c5cf93d8471b"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, + "zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"}, } diff --git a/example_project_sc/assets/js/app.js b/example_project_sc/assets/js/app.js index e41b41d..fa3be65 100644 --- a/example_project_sc/assets/js/app.js +++ b/example_project_sc/assets/js/app.js @@ -22,12 +22,8 @@ import { Socket } from "phoenix"; import { LiveSocket } from "phoenix_live_view"; import topbar from "../vendor/topbar"; -console.log("A"); - import { createCaptureHook, createPlayerHook } from "boombox_live"; -console.log("B"); - let Hooks = {}; const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; Hooks.Capture = createCaptureHook(iceServers); From edf33229980b9f4f1692dd8c42d2e3f7f6abef73 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 18 Feb 2025 16:14:04 +0100 Subject: [PATCH 21/45] Comment out phoenix header --- .../components/layouts/app.html.heex | 4 ++-- example_project/lib/example_project_web/router.ex | 15 ++++----------- .../controllers/error_html_test.exs | 3 ++- .../controllers/error_json_test.exs | 4 +++- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/example_project/lib/example_project_web/components/layouts/app.html.heex b/example_project/lib/example_project_web/components/layouts/app.html.heex index 3b3b607..59a702e 100644 --- a/example_project/lib/example_project_web/components/layouts/app.html.heex +++ b/example_project/lib/example_project_web/components/layouts/app.html.heex @@ -1,5 +1,5 @@
- --%>
diff --git a/example_project/lib/example_project_web/router.ex b/example_project/lib/example_project_web/router.ex index cbadde3..fad2b5f 100644 --- a/example_project/lib/example_project_web/router.ex +++ b/example_project/lib/example_project_web/router.ex @@ -14,21 +14,14 @@ defmodule ExampleProjectWeb.Router do plug :accepts, ["json"] end - # scope "/", ExampleProjectWeb do - # pipe_through :browser - - # get "/", PageController, :home - # end - - # Other scopes may use custom stacks. - # scope "/api", ExampleProjectWeb do - # pipe_through :api - # end - scope "/", ExampleProjectWeb do pipe_through(:browser) live("/", LiveViews.Echo, :index) end + # Other scopes may use custom stacks. + # scope "/api", ExampleProjectWeb do + # pipe_through :api + # end end diff --git a/example_project/test/example_project_web/controllers/error_html_test.exs b/example_project/test/example_project_web/controllers/error_html_test.exs index 12099fd..39c90db 100644 --- a/example_project/test/example_project_web/controllers/error_html_test.exs +++ b/example_project/test/example_project_web/controllers/error_html_test.exs @@ -9,6 +9,7 @@ defmodule ExampleProjectWeb.ErrorHTMLTest do end test "renders 500.html" do - assert render_to_string(ExampleProjectWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" + assert render_to_string(ExampleProjectWeb.ErrorHTML, "500", "html", []) == + "Internal Server Error" end end diff --git a/example_project/test/example_project_web/controllers/error_json_test.exs b/example_project/test/example_project_web/controllers/error_json_test.exs index 27d1bd3..91bc3fc 100644 --- a/example_project/test/example_project_web/controllers/error_json_test.exs +++ b/example_project/test/example_project_web/controllers/error_json_test.exs @@ -2,7 +2,9 @@ defmodule ExampleProjectWeb.ErrorJSONTest do use ExampleProjectWeb.ConnCase, async: true test "renders 404" do - assert ExampleProjectWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} + assert ExampleProjectWeb.ErrorJSON.render("404.json", %{}) == %{ + errors: %{detail: "Not Found"} + } end test "renders 500" do From 8ffc2f0b6d2c021366919a11a74f7b3f840deb7c Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 18 Feb 2025 16:22:40 +0100 Subject: [PATCH 22/45] Remove old example project --- example_project_sc/.formatter.exs | 5 - example_project_sc/.gitignore | 37 ---- example_project_sc/README.md | 18 -- example_project_sc/assets/css/app.css | 5 - example_project_sc/assets/js/app.js | 52 ------ example_project_sc/assets/tailwind.config.js | 74 -------- example_project_sc/assets/vendor/topbar.js | 165 ------------------ example_project_sc/config/config.exs | 56 ------ example_project_sc/config/dev.exs | 71 -------- example_project_sc/config/prod.exs | 15 -- example_project_sc/config/runtime.exs | 84 --------- example_project_sc/config/test.exs | 18 -- example_project_sc/lib/example_project.ex | 9 - .../lib/example_project/application.ex | 32 ---- example_project_sc/lib/example_project_web.ex | 111 ------------ .../example_project_web/components/layouts.ex | 14 -- .../components/layouts/app.html.heex | 3 - .../components/layouts/root.html.heex | 17 -- .../lib/example_project_web/endpoint.ex | 52 ------ .../live_views/home_live.ex | 65 ------- .../lib/example_project_web/router.ex | 18 -- example_project_sc/mix.exs | 78 --------- example_project_sc/mix.lock | 123 ------------- example_project_sc/priv/static/favicon.ico | Bin 152 -> 0 bytes .../priv/static/images/logo.svg | 6 - example_project_sc/priv/static/robots.txt | 5 - .../controllers/error_html_test.exs | 14 -- .../controllers/error_json_test.exs | 12 -- .../controllers/page_controller_test.exs | 8 - example_project_sc/test/support/conn_case.ex | 37 ---- example_project_sc/test/test_helper.exs | 1 - 31 files changed, 1205 deletions(-) delete mode 100644 example_project_sc/.formatter.exs delete mode 100644 example_project_sc/.gitignore delete mode 100644 example_project_sc/README.md delete mode 100644 example_project_sc/assets/css/app.css delete mode 100644 example_project_sc/assets/js/app.js delete mode 100644 example_project_sc/assets/tailwind.config.js delete mode 100644 example_project_sc/assets/vendor/topbar.js delete mode 100644 example_project_sc/config/config.exs delete mode 100644 example_project_sc/config/dev.exs delete mode 100644 example_project_sc/config/prod.exs delete mode 100644 example_project_sc/config/runtime.exs delete mode 100644 example_project_sc/config/test.exs delete mode 100644 example_project_sc/lib/example_project.ex delete mode 100644 example_project_sc/lib/example_project/application.ex delete mode 100644 example_project_sc/lib/example_project_web.ex delete mode 100644 example_project_sc/lib/example_project_web/components/layouts.ex delete mode 100644 example_project_sc/lib/example_project_web/components/layouts/app.html.heex delete mode 100644 example_project_sc/lib/example_project_web/components/layouts/root.html.heex delete mode 100644 example_project_sc/lib/example_project_web/endpoint.ex delete mode 100644 example_project_sc/lib/example_project_web/live_views/home_live.ex delete mode 100644 example_project_sc/lib/example_project_web/router.ex delete mode 100644 example_project_sc/mix.exs delete mode 100644 example_project_sc/mix.lock delete mode 100644 example_project_sc/priv/static/favicon.ico delete mode 100644 example_project_sc/priv/static/images/logo.svg delete mode 100644 example_project_sc/priv/static/robots.txt delete mode 100644 example_project_sc/test/example_project_web/controllers/error_html_test.exs delete mode 100644 example_project_sc/test/example_project_web/controllers/error_json_test.exs delete mode 100644 example_project_sc/test/example_project_web/controllers/page_controller_test.exs delete mode 100644 example_project_sc/test/support/conn_case.ex delete mode 100644 example_project_sc/test/test_helper.exs diff --git a/example_project_sc/.formatter.exs b/example_project_sc/.formatter.exs deleted file mode 100644 index e945e12..0000000 --- a/example_project_sc/.formatter.exs +++ /dev/null @@ -1,5 +0,0 @@ -[ - import_deps: [:phoenix], - plugins: [Phoenix.LiveView.HTMLFormatter], - inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] -] diff --git a/example_project_sc/.gitignore b/example_project_sc/.gitignore deleted file mode 100644 index d039d2b..0000000 --- a/example_project_sc/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -# The directory Mix will write compiled artifacts to. -/_build/ - -# If you run "mix test --cover", coverage assets end up here. -/cover/ - -# The directory Mix downloads your dependencies sources to. -/deps/ - -# Where 3rd-party dependencies like ExDoc output generated docs. -/doc/ - -# Ignore .fetch files in case you like to edit your project deps locally. -/.fetch - -# If the VM crashes, it generates a dump, let's ignore it too. -erl_crash.dump - -# Also ignore archive artifacts (built via "mix archive.build"). -*.ez - -# Temporary files, for example, from tests. -/tmp/ - -# Ignore package tarball (built via "mix hex.build"). -example_project-*.tar - -# Ignore assets that are produced by build tools. -/priv/static/assets/ - -# Ignore digested assets cache. -/priv/static/cache_manifest.json - -# In case you use Node.js/npm, you want to ignore these. -npm-debug.log -/assets/node_modules/ - diff --git a/example_project_sc/README.md b/example_project_sc/README.md deleted file mode 100644 index 9ff00e5..0000000 --- a/example_project_sc/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# ExampleProject - -To start your Phoenix server: - - * Run `mix setup` to install and setup dependencies - * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` - -Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. - -Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). - -## Learn more - - * Official website: https://www.phoenixframework.org/ - * Guides: https://hexdocs.pm/phoenix/overview.html - * Docs: https://hexdocs.pm/phoenix - * Forum: https://elixirforum.com/c/phoenix-forum - * Source: https://github.com/phoenixframework/phoenix diff --git a/example_project_sc/assets/css/app.css b/example_project_sc/assets/css/app.css deleted file mode 100644 index 378c8f9..0000000 --- a/example_project_sc/assets/css/app.css +++ /dev/null @@ -1,5 +0,0 @@ -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; - -/* This file is for your main application CSS */ diff --git a/example_project_sc/assets/js/app.js b/example_project_sc/assets/js/app.js deleted file mode 100644 index fa3be65..0000000 --- a/example_project_sc/assets/js/app.js +++ /dev/null @@ -1,52 +0,0 @@ -// If you want to use Phoenix channels, run `mix help phx.gen.channel` -// to get started and then uncomment the line below. -// import "./user_socket.js" - -// You can include dependencies in two ways. -// -// The simplest option is to put them in assets/vendor and -// import them using relative paths: -// -// import "../vendor/some-package.js" -// -// Alternatively, you can `npm install some-package --prefix assets` and import -// them using a path starting with the package name: -// -// import "some-package" -// - -// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. -import "phoenix_html"; -// Establish Phoenix Socket and LiveView configuration. -import { Socket } from "phoenix"; -import { LiveSocket } from "phoenix_live_view"; -import topbar from "../vendor/topbar"; - -import { createCaptureHook, createPlayerHook } from "boombox_live"; - -let Hooks = {}; -const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; -Hooks.Capture = createCaptureHook(iceServers); -Hooks.Player = createPlayerHook(iceServers); - -let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content"); -let liveSocket = new LiveSocket("/live", Socket, { - longPollFallbackMs: 2500, - params: { _csrf_token: csrfToken }, - hooks: Hooks, -}); - -// Show progress bar on live navigation and form submits -topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" }); -window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300)); -window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide()); - -// connect if there are any LiveViews on the page -liveSocket.connect(); -console.log("LiveView WebSocket connected"); - -// expose liveSocket on window for web console debug logs and latency simulation: -// liveSocket.enableDebug(); -// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session -// >> liveSocket.disableLatencySim() -window.liveSocket = liveSocket; diff --git a/example_project_sc/assets/tailwind.config.js b/example_project_sc/assets/tailwind.config.js deleted file mode 100644 index e766447..0000000 --- a/example_project_sc/assets/tailwind.config.js +++ /dev/null @@ -1,74 +0,0 @@ -// See the Tailwind configuration guide for advanced usage -// https://tailwindcss.com/docs/configuration - -const plugin = require("tailwindcss/plugin") -const fs = require("fs") -const path = require("path") - -module.exports = { - content: [ - "./js/**/*.js", - "../lib/example_project_web.ex", - "../lib/example_project_web/**/*.*ex" - ], - theme: { - extend: { - colors: { - brand: "#FD4F00", - } - }, - }, - plugins: [ - require("@tailwindcss/forms"), - // Allows prefixing tailwind classes with LiveView classes to add rules - // only when LiveView classes are applied, for example: - // - //
- // - plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])), - plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])), - plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])), - - // Embeds Heroicons (https://heroicons.com) into your app.css bundle - // See your `CoreComponents.icon/1` for more information. - // - plugin(function({matchComponents, theme}) { - let iconsDir = path.join(__dirname, "../deps/heroicons/optimized") - let values = {} - let icons = [ - ["", "/24/outline"], - ["-solid", "/24/solid"], - ["-mini", "/20/solid"], - ["-micro", "/16/solid"] - ] - icons.forEach(([suffix, dir]) => { - fs.readdirSync(path.join(iconsDir, dir)).forEach(file => { - let name = path.basename(file, ".svg") + suffix - values[name] = {name, fullPath: path.join(iconsDir, dir, file)} - }) - }) - matchComponents({ - "hero": ({name, fullPath}) => { - let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") - let size = theme("spacing.6") - if (name.endsWith("-mini")) { - size = theme("spacing.5") - } else if (name.endsWith("-micro")) { - size = theme("spacing.4") - } - return { - [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, - "-webkit-mask": `var(--hero-${name})`, - "mask": `var(--hero-${name})`, - "mask-repeat": "no-repeat", - "background-color": "currentColor", - "vertical-align": "middle", - "display": "inline-block", - "width": size, - "height": size - } - } - }, {values}) - }) - ] -} diff --git a/example_project_sc/assets/vendor/topbar.js b/example_project_sc/assets/vendor/topbar.js deleted file mode 100644 index 4195727..0000000 --- a/example_project_sc/assets/vendor/topbar.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * @license MIT - * topbar 2.0.0, 2023-02-04 - * https://buunguyen.github.io/topbar - * Copyright (c) 2021 Buu Nguyen - */ -(function (window, document) { - "use strict"; - - // https://gist.github.com/paulirish/1579671 - (function () { - var lastTime = 0; - var vendors = ["ms", "moz", "webkit", "o"]; - for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { - window.requestAnimationFrame = - window[vendors[x] + "RequestAnimationFrame"]; - window.cancelAnimationFrame = - window[vendors[x] + "CancelAnimationFrame"] || - window[vendors[x] + "CancelRequestAnimationFrame"]; - } - if (!window.requestAnimationFrame) - window.requestAnimationFrame = function (callback, element) { - var currTime = new Date().getTime(); - var timeToCall = Math.max(0, 16 - (currTime - lastTime)); - var id = window.setTimeout(function () { - callback(currTime + timeToCall); - }, timeToCall); - lastTime = currTime + timeToCall; - return id; - }; - if (!window.cancelAnimationFrame) - window.cancelAnimationFrame = function (id) { - clearTimeout(id); - }; - })(); - - var canvas, - currentProgress, - showing, - progressTimerId = null, - fadeTimerId = null, - delayTimerId = null, - addEvent = function (elem, type, handler) { - if (elem.addEventListener) elem.addEventListener(type, handler, false); - else if (elem.attachEvent) elem.attachEvent("on" + type, handler); - else elem["on" + type] = handler; - }, - options = { - autoRun: true, - barThickness: 3, - barColors: { - 0: "rgba(26, 188, 156, .9)", - ".25": "rgba(52, 152, 219, .9)", - ".50": "rgba(241, 196, 15, .9)", - ".75": "rgba(230, 126, 34, .9)", - "1.0": "rgba(211, 84, 0, .9)", - }, - shadowBlur: 10, - shadowColor: "rgba(0, 0, 0, .6)", - className: null, - }, - repaint = function () { - canvas.width = window.innerWidth; - canvas.height = options.barThickness * 5; // need space for shadow - - var ctx = canvas.getContext("2d"); - ctx.shadowBlur = options.shadowBlur; - ctx.shadowColor = options.shadowColor; - - var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); - for (var stop in options.barColors) - lineGradient.addColorStop(stop, options.barColors[stop]); - ctx.lineWidth = options.barThickness; - ctx.beginPath(); - ctx.moveTo(0, options.barThickness / 2); - ctx.lineTo( - Math.ceil(currentProgress * canvas.width), - options.barThickness / 2 - ); - ctx.strokeStyle = lineGradient; - ctx.stroke(); - }, - createCanvas = function () { - canvas = document.createElement("canvas"); - var style = canvas.style; - style.position = "fixed"; - style.top = style.left = style.right = style.margin = style.padding = 0; - style.zIndex = 100001; - style.display = "none"; - if (options.className) canvas.classList.add(options.className); - document.body.appendChild(canvas); - addEvent(window, "resize", repaint); - }, - topbar = { - config: function (opts) { - for (var key in opts) - if (options.hasOwnProperty(key)) options[key] = opts[key]; - }, - show: function (delay) { - if (showing) return; - if (delay) { - if (delayTimerId) return; - delayTimerId = setTimeout(() => topbar.show(), delay); - } else { - showing = true; - if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); - if (!canvas) createCanvas(); - canvas.style.opacity = 1; - canvas.style.display = "block"; - topbar.progress(0); - if (options.autoRun) { - (function loop() { - progressTimerId = window.requestAnimationFrame(loop); - topbar.progress( - "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) - ); - })(); - } - } - }, - progress: function (to) { - if (typeof to === "undefined") return currentProgress; - if (typeof to === "string") { - to = - (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 - ? currentProgress - : 0) + parseFloat(to); - } - currentProgress = to > 1 ? 1 : to; - repaint(); - return currentProgress; - }, - hide: function () { - clearTimeout(delayTimerId); - delayTimerId = null; - if (!showing) return; - showing = false; - if (progressTimerId != null) { - window.cancelAnimationFrame(progressTimerId); - progressTimerId = null; - } - (function loop() { - if (topbar.progress("+.1") >= 1) { - canvas.style.opacity -= 0.05; - if (canvas.style.opacity <= 0.05) { - canvas.style.display = "none"; - fadeTimerId = null; - return; - } - } - fadeTimerId = window.requestAnimationFrame(loop); - })(); - }, - }; - - if (typeof module === "object" && typeof module.exports === "object") { - module.exports = topbar; - } else if (typeof define === "function" && define.amd) { - define(function () { - return topbar; - }); - } else { - this.topbar = topbar; - } -}.call(this, window, document)); diff --git a/example_project_sc/config/config.exs b/example_project_sc/config/config.exs deleted file mode 100644 index 3b55888..0000000 --- a/example_project_sc/config/config.exs +++ /dev/null @@ -1,56 +0,0 @@ -# This file is responsible for configuring your application -# and its dependencies with the aid of the Config module. -# -# This configuration file is loaded before any dependency and -# is restricted to this project. - -# General application configuration -import Config - -config :example_project, - generators: [timestamp_type: :utc_datetime] - -# Configures the endpoint -config :example_project, ExampleProjectWeb.Endpoint, - url: [host: "localhost"], - adapter: Bandit.PhoenixAdapter, - render_errors: [ - formats: [html: ExampleProjectWeb.ErrorHTML, json: ExampleProjectWeb.ErrorJSON], - layout: false - ], - pubsub_server: ExampleProject.PubSub, - live_view: [signing_salt: "mjeuZD0H"] - -# Configure esbuild (the version is required) -config :esbuild, - version: "0.17.11", - example_project: [ - args: - ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), - cd: Path.expand("../assets", __DIR__), - env: %{"NODE_PATH" => Enum.map_join(["../deps", "../../.."], ":", &Path.expand(&1, __DIR__))} - ] - -# Configure tailwind (the version is required) -config :tailwind, - version: "3.4.3", - example_project: [ - args: ~w( - --config=tailwind.config.js - --input=css/app.css - --output=../priv/static/assets/app.css - ), - cd: Path.expand("../assets", __DIR__) - ] - -# Configures Elixir's Logger -config :logger, :console, - format: "$time $metadata[$level] $message\n", - metadata: [:request_id] - -# Use Jason for JSON parsing in Phoenix -config :phoenix, :json_library, Jason - -# Import environment specific config. This must remain at the bottom -# of this file so it overrides the configuration defined above. -import_config "#{config_env()}.exs" diff --git a/example_project_sc/config/dev.exs b/example_project_sc/config/dev.exs deleted file mode 100644 index 6893142..0000000 --- a/example_project_sc/config/dev.exs +++ /dev/null @@ -1,71 +0,0 @@ -import Config - -# For development, we disable any cache and enable -# debugging and code reloading. -# -# The watchers configuration can be used to run external -# watchers to your application. For example, we can use it -# to bundle .js and .css sources. -# Binding to loopback ipv4 address prevents access from other machines. -config :example_project, ExampleProjectWeb.Endpoint, - # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. - http: [ip: {127, 0, 0, 1}, port: 4000], - check_origin: false, - code_reloader: true, - debug_errors: true, - secret_key_base: "0PrhqMtCh4XT+kBypnvBe9sR3SiOUuhVtmxfyekYHusrqZmBO3PSWEJcr0jxj6WT", - watchers: [ - esbuild: {Esbuild, :install_and_run, [:example_project, ~w(--sourcemap=inline --watch)]}, - tailwind: {Tailwind, :install_and_run, [:example_project, ~w(--watch)]} - ] - -# ## SSL Support -# -# In order to use HTTPS in development, a self-signed -# certificate can be generated by running the following -# Mix task: -# -# mix phx.gen.cert -# -# Run `mix help phx.gen.cert` for more information. -# -# The `http:` config above can be replaced with: -# -# https: [ -# port: 4001, -# cipher_suite: :strong, -# keyfile: "priv/cert/selfsigned_key.pem", -# certfile: "priv/cert/selfsigned.pem" -# ], -# -# If desired, both `http:` and `https:` keys can be -# configured to run both http and https servers on -# different ports. - -# Watch static and templates for browser reloading. -config :example_project, ExampleProjectWeb.Endpoint, - live_reload: [ - patterns: [ - ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$", - ~r"lib/example_project_web/(controllers|live|components)/.*(ex|heex)$" - ] - ] - -# Enable dev routes for dashboard and mailbox -config :example_project, dev_routes: true - -# Do not include metadata nor timestamps in development logs -config :logger, :console, format: "[$level] $message\n" - -# Set a higher stacktrace during development. Avoid configuring such -# in production as building large stacktraces may be expensive. -config :phoenix, :stacktrace_depth, 20 - -# Initialize plugs at runtime for faster development compilation -config :phoenix, :plug_init_mode, :runtime - -config :phoenix_live_view, - # Include HEEx debug annotations as HTML comments in rendered markup - debug_heex_annotations: true, - # Enable helpful, but potentially expensive runtime checks - enable_expensive_runtime_checks: true diff --git a/example_project_sc/config/prod.exs b/example_project_sc/config/prod.exs deleted file mode 100644 index 8acfd85..0000000 --- a/example_project_sc/config/prod.exs +++ /dev/null @@ -1,15 +0,0 @@ -import Config - -# Note we also include the path to a cache manifest -# containing the digested version of static files. This -# manifest is generated by the `mix assets.deploy` task, -# which you should run after static files are built and -# before starting your production server. -config :example_project, ExampleProjectWeb.Endpoint, - cache_static_manifest: "priv/static/cache_manifest.json" - -# Do not print debug messages in production -config :logger, level: :info - -# Runtime production configuration, including reading -# of environment variables, is done on config/runtime.exs. diff --git a/example_project_sc/config/runtime.exs b/example_project_sc/config/runtime.exs deleted file mode 100644 index 07c04f3..0000000 --- a/example_project_sc/config/runtime.exs +++ /dev/null @@ -1,84 +0,0 @@ -import Config - -# config/runtime.exs is executed for all environments, including -# during releases. It is executed after compilation and before the -# system starts, so it is typically used to load production configuration -# and secrets from environment variables or elsewhere. Do not define -# any compile-time configuration in here, as it won't be applied. -# The block below contains prod specific runtime configuration. - -# ## Using releases -# -# If you use `mix release`, you need to explicitly enable the server -# by passing the PHX_SERVER=true when you start it: -# -# PHX_SERVER=true bin/example_project start -# -# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` -# script that automatically sets the env var above. -if System.get_env("PHX_SERVER") do - config :example_project, ExampleProjectWeb.Endpoint, server: true -end - -if config_env() == :prod do - # The secret key base is used to sign/encrypt cookies and other secrets. - # A default value is used in config/dev.exs and config/test.exs but you - # want to use a different value for prod and you most likely don't want - # to check this value into version control, so we use an environment - # variable instead. - secret_key_base = - System.get_env("SECRET_KEY_BASE") || - raise """ - environment variable SECRET_KEY_BASE is missing. - You can generate one by calling: mix phx.gen.secret - """ - - host = System.get_env("PHX_HOST") || "example.com" - port = String.to_integer(System.get_env("PORT") || "4000") - - config :example_project, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") - - config :example_project, ExampleProjectWeb.Endpoint, - url: [host: host, port: 443, scheme: "https"], - http: [ - # Enable IPv6 and bind on all interfaces. - # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. - # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0 - # for details about using IPv6 vs IPv4 and loopback vs public addresses. - ip: {0, 0, 0, 0, 0, 0, 0, 0}, - port: port - ], - secret_key_base: secret_key_base - - # ## SSL Support - # - # To get SSL working, you will need to add the `https` key - # to your endpoint configuration: - # - # config :example_project, ExampleProjectWeb.Endpoint, - # https: [ - # ..., - # port: 443, - # cipher_suite: :strong, - # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), - # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") - # ] - # - # The `cipher_suite` is set to `:strong` to support only the - # latest and more secure SSL ciphers. This means old browsers - # and clients may not be supported. You can set it to - # `:compatible` for wider support. - # - # `:keyfile` and `:certfile` expect an absolute path to the key - # and cert in disk or a relative path inside priv, for example - # "priv/ssl/server.key". For all supported SSL configuration - # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 - # - # We also recommend setting `force_ssl` in your config/prod.exs, - # ensuring no data is ever sent via http, always redirecting to https: - # - # config :example_project, ExampleProjectWeb.Endpoint, - # force_ssl: [hsts: true] - # - # Check `Plug.SSL` for all available options in `force_ssl`. -end diff --git a/example_project_sc/config/test.exs b/example_project_sc/config/test.exs deleted file mode 100644 index f43df7e..0000000 --- a/example_project_sc/config/test.exs +++ /dev/null @@ -1,18 +0,0 @@ -import Config - -# We don't run a server during test. If one is required, -# you can enable the server option below. -config :example_project, ExampleProjectWeb.Endpoint, - http: [ip: {127, 0, 0, 1}, port: 4002], - secret_key_base: "IWAvaHFOK0m2MvwpUQhGTPiKUPNIXs19LxW+Lts3kQISgoMMEWOW+haMYQnIK1by", - server: false - -# Print only warnings and errors during test -config :logger, level: :warning - -# Initialize plugs at runtime for faster test compilation -config :phoenix, :plug_init_mode, :runtime - -# Enable helpful, but potentially expensive runtime checks -config :phoenix_live_view, - enable_expensive_runtime_checks: true diff --git a/example_project_sc/lib/example_project.ex b/example_project_sc/lib/example_project.ex deleted file mode 100644 index ef1a176..0000000 --- a/example_project_sc/lib/example_project.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule ExampleProject do - @moduledoc """ - ExampleProject keeps the contexts that define your domain - and business logic. - - Contexts are also responsible for managing your data, regardless - if it comes from the database, an external API or others. - """ -end diff --git a/example_project_sc/lib/example_project/application.ex b/example_project_sc/lib/example_project/application.ex deleted file mode 100644 index 45343cb..0000000 --- a/example_project_sc/lib/example_project/application.ex +++ /dev/null @@ -1,32 +0,0 @@ -defmodule ExampleProject.Application do - # See https://hexdocs.pm/elixir/Application.html - # for more information on OTP Applications - @moduledoc false - - use Application - - @impl true - def start(_type, _args) do - children = [ - {DNSCluster, query: Application.get_env(:example_project, :dns_cluster_query) || :ignore}, - {Phoenix.PubSub, name: ExampleProject.PubSub}, - # Start a worker by calling: ExampleProject.Worker.start_link(arg) - # {ExampleProject.Worker, arg}, - # Start to serve requests, typically the last entry - ExampleProjectWeb.Endpoint - ] - - # See https://hexdocs.pm/elixir/Supervisor.html - # for other strategies and supported options - opts = [strategy: :one_for_one, name: ExampleProject.Supervisor] - Supervisor.start_link(children, opts) - end - - # Tell Phoenix to update the endpoint configuration - # whenever the application is updated. - @impl true - def config_change(changed, _new, removed) do - ExampleProjectWeb.Endpoint.config_change(changed, removed) - :ok - end -end diff --git a/example_project_sc/lib/example_project_web.ex b/example_project_sc/lib/example_project_web.ex deleted file mode 100644 index 6fa0969..0000000 --- a/example_project_sc/lib/example_project_web.ex +++ /dev/null @@ -1,111 +0,0 @@ -defmodule ExampleProjectWeb do - @moduledoc """ - The entrypoint for defining your web interface, such - as controllers, components, channels, and so on. - - This can be used in your application as: - - use ExampleProjectWeb, :controller - use ExampleProjectWeb, :html - - The definitions below will be executed for every controller, - component, etc, so keep them short and clean, focused - on imports, uses and aliases. - - Do NOT define functions inside the quoted expressions - below. Instead, define additional modules and import - those modules here. - """ - - def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) - - def router do - quote do - use Phoenix.Router, helpers: false - - # Import common connection and controller functions to use in pipelines - import Plug.Conn - import Phoenix.Controller - import Phoenix.LiveView.Router - end - end - - def channel do - quote do - use Phoenix.Channel - end - end - - def controller do - quote do - use Phoenix.Controller, - formats: [:html, :json], - layouts: [html: ExampleProjectWeb.Layouts] - - import Plug.Conn - - unquote(verified_routes()) - end - end - - def live_view do - quote do - use Phoenix.LiveView, - layout: {ExampleProjectWeb.Layouts, :app} - - unquote(html_helpers()) - end - end - - def live_component do - quote do - use Phoenix.LiveComponent - - unquote(html_helpers()) - end - end - - def html do - quote do - use Phoenix.Component - - # Import convenience functions from controllers - import Phoenix.Controller, - only: [get_csrf_token: 0, view_module: 1, view_template: 1] - - # Include general helpers for rendering HTML - unquote(html_helpers()) - end - end - - defp html_helpers do - quote do - # HTML escaping functionality - import Phoenix.HTML - # Core UI components - # import ExampleProjectWeb.CoreComponents - - # Shortcut for generating JS commands - alias Phoenix.LiveView.JS - - # Routes generation with the ~p sigil - unquote(verified_routes()) - end - end - - def verified_routes do - quote do - use Phoenix.VerifiedRoutes, - endpoint: ExampleProjectWeb.Endpoint, - router: ExampleProjectWeb.Router, - statics: ExampleProjectWeb.static_paths() - end - end - - @doc """ - When used, dispatch to the appropriate controller/live_view/etc. - """ - defmacro __using__(which) when is_atom(which) do - apply(__MODULE__, which, []) - end -end diff --git a/example_project_sc/lib/example_project_web/components/layouts.ex b/example_project_sc/lib/example_project_web/components/layouts.ex deleted file mode 100644 index ebdca43..0000000 --- a/example_project_sc/lib/example_project_web/components/layouts.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule ExampleProjectWeb.Layouts do - @moduledoc """ - This module holds different layouts used by your application. - - See the `layouts` directory for all templates available. - The "root" layout is a skeleton rendered as part of the - application router. The "app" layout is set as the default - layout on both `use ExampleProjectWeb, :controller` and - `use ExampleProjectWeb, :live_view`. - """ - use ExampleProjectWeb, :html - - embed_templates "layouts/*" -end diff --git a/example_project_sc/lib/example_project_web/components/layouts/app.html.heex b/example_project_sc/lib/example_project_web/components/layouts/app.html.heex deleted file mode 100644 index ef79d41..0000000 --- a/example_project_sc/lib/example_project_web/components/layouts/app.html.heex +++ /dev/null @@ -1,3 +0,0 @@ -
- {@inner_content} -
diff --git a/example_project_sc/lib/example_project_web/components/layouts/root.html.heex b/example_project_sc/lib/example_project_web/components/layouts/root.html.heex deleted file mode 100644 index 2f9ed93..0000000 --- a/example_project_sc/lib/example_project_web/components/layouts/root.html.heex +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - <.live_title default="ExampleProject" suffix=" · Phoenix Framework"> - {assigns[:page_title]} - - - - - - {@inner_content} - - diff --git a/example_project_sc/lib/example_project_web/endpoint.ex b/example_project_sc/lib/example_project_web/endpoint.ex deleted file mode 100644 index 3dbc810..0000000 --- a/example_project_sc/lib/example_project_web/endpoint.ex +++ /dev/null @@ -1,52 +0,0 @@ -defmodule ExampleProjectWeb.Endpoint do - use Phoenix.Endpoint, otp_app: :example_project - - # The session will be stored in the cookie and signed, - # this means its contents can be read but not tampered with. - # Set :encryption_salt if you would also like to encrypt it. - @session_options [ - store: :cookie, - key: "_example_project_key", - signing_salt: "qTrQh+Y1", - same_site: "Lax" - ] - - socket "/live", Phoenix.LiveView.Socket, - websocket: [connect_info: [session: @session_options]], - longpoll: [connect_info: [session: @session_options]] - - # Serve at "/" the static files from "priv/static" directory. - # - # You should set gzip to true if you are running phx.digest - # when deploying your static files in production. - plug Plug.Static, - at: "/", - from: :example_project, - gzip: false, - only: ExampleProjectWeb.static_paths() - - # Code reloading can be explicitly enabled under the - # :code_reloader configuration of your endpoint. - if code_reloading? do - socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket - plug Phoenix.LiveReloader - plug Phoenix.CodeReloader - end - - plug Phoenix.LiveDashboard.RequestLogger, - param_key: "request_logger", - cookie_key: "request_logger" - - plug Plug.RequestId - plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] - - plug Plug.Parsers, - parsers: [:urlencoded, :multipart, :json], - pass: ["*/*"], - json_decoder: Phoenix.json_library() - - plug Plug.MethodOverride - plug Plug.Head - plug Plug.Session, @session_options - plug ExampleProjectWeb.Router -end diff --git a/example_project_sc/lib/example_project_web/live_views/home_live.ex b/example_project_sc/lib/example_project_web/live_views/home_live.ex deleted file mode 100644 index fdb66af..0000000 --- a/example_project_sc/lib/example_project_web/live_views/home_live.ex +++ /dev/null @@ -1,65 +0,0 @@ -defmodule ExampleProjectWeb.HomeLive do - use ExampleProjectWeb, :live_view - - alias Boombox.Live.WebRTC.{Capture, Player} - - def mount(_params, _session, socket) do - IO.inspect(socket, label: "MOUNT") - - socket = - if connected?(socket) do - ingress_signaling = Membrane.WebRTC.SignalingChannel.new() - egress_signaling = Membrane.WebRTC.SignalingChannel.new() - - {:ok, _boombox_pid} = - Task.start_link(fn -> - Boombox.run( - input: {:webrtc, ingress_signaling}, - output: {:webrtc, egress_signaling} - ) - end) - - socket = - socket - |> Capture.attach( - id: "mediaCapture", - signaling_channel: ingress_signaling, - audio?: false, - video?: true - ) - |> Player.attach( - id: "videoPlayer", - signaling_channel: egress_signaling - ) - - socket - |> assign( - capture: Capture.get_attached(socket, "mediaCapture"), - player: Player.get_attached(socket, "videoPlayer") - ) - else - socket - end - - {:ok, socket} - end - - def render(%{capture: %Capture{}, player: %Player{}} = assigns) do - ~H""" - - - - - """ - end - - def render(assigns) do - ~H""" - - """ - end -end diff --git a/example_project_sc/lib/example_project_web/router.ex b/example_project_sc/lib/example_project_web/router.ex deleted file mode 100644 index e50c386..0000000 --- a/example_project_sc/lib/example_project_web/router.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule ExampleProjectWeb.Router do - use ExampleProjectWeb, :router - - pipeline :browser do - plug :accepts, ["html"] - plug :fetch_session - plug :fetch_live_flash - plug :put_root_layout, html: {ExampleProjectWeb.Layouts, :root} - plug :protect_from_forgery - plug :put_secure_browser_headers - end - - pipeline :api do - plug :accepts, ["json"] - end - - live "/", ExampleProjectWeb.HomeLive -end diff --git a/example_project_sc/mix.exs b/example_project_sc/mix.exs deleted file mode 100644 index 877803c..0000000 --- a/example_project_sc/mix.exs +++ /dev/null @@ -1,78 +0,0 @@ -defmodule ExampleProject.MixProject do - use Mix.Project - - def project do - [ - app: :example_project, - version: "0.1.0", - elixir: "~> 1.14", - elixirc_paths: elixirc_paths(Mix.env()), - start_permanent: Mix.env() == :prod, - aliases: aliases(), - deps: deps() - ] - end - - # Configuration for the OTP application. - # - # Type `mix help compile.app` for more information. - def application do - [ - mod: {ExampleProject.Application, []}, - extra_applications: [:logger, :runtime_tools] - ] - end - - # Specifies which paths to compile per environment. - defp elixirc_paths(:test), do: ["lib", "test/support"] - defp elixirc_paths(_), do: ["lib"] - - # Specifies your project dependencies. - # - # Type `mix help deps` for examples and options. - defp deps do - [ - {:boombox, path: "../../boombox"}, - {:boombox_live, path: ".."}, - {:phoenix, "~> 1.7.19"}, - {:phoenix_html, "~> 4.1"}, - {:phoenix_live_reload, "~> 1.2", only: :dev}, - {:phoenix_live_view, "~> 1.0.0"}, - {:floki, ">= 0.30.0", only: :test}, - {:phoenix_live_dashboard, "~> 0.8.3"}, - {:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, - {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, - {:heroicons, - github: "tailwindlabs/heroicons", - tag: "v2.1.1", - sparse: "optimized", - app: false, - compile: false, - depth: 1}, - {:telemetry_metrics, "~> 1.0"}, - {:telemetry_poller, "~> 1.0"}, - {:jason, "~> 1.2"}, - {:dns_cluster, "~> 0.1.1"}, - {:bandit, "~> 1.5"} - ] - end - - # Aliases are shortcuts or tasks specific to the current project. - # For example, to install project dependencies and perform other setup tasks, run: - # - # $ mix setup - # - # See the documentation for `Mix` for more info on aliases. - defp aliases do - [ - setup: ["deps.get", "assets.setup", "assets.build"], - "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"], - "assets.build": ["tailwind example_project", "esbuild example_project"], - "assets.deploy": [ - "tailwind example_project --minify", - "esbuild example_project --minify", - "phx.digest" - ] - ] - end -end diff --git a/example_project_sc/mix.lock b/example_project_sc/mix.lock deleted file mode 100644 index 4e7f4e8..0000000 --- a/example_project_sc/mix.lock +++ /dev/null @@ -1,123 +0,0 @@ -%{ - "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, - "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, - "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, - "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, - "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, - "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, - "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, - "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, - "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, - "corsica": {:hex, :corsica, "2.1.3", "dccd094ffce38178acead9ae743180cdaffa388f35f0461ba1e8151d32e190e6", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "616c08f61a345780c2cf662ff226816f04d8868e12054e68963e95285b5be8bc"}, - "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, - "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, - "cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"}, - "crc": {:hex, :crc, "0.10.5", "ee12a7c056ac498ef2ea985ecdc9fa53c1bfb4e53a484d9f17ff94803707dfd8", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3e673b6495a9525c5c641585af1accba59a1eb33de697bedf341e247012c2c7f"}, - "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, - "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, - "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, - "esbuild": {:hex, :esbuild, "0.9.0", "f043eeaca4932ca8e16e5429aebd90f7766f31ac160a25cbd9befe84f2bc068f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b415027f71d5ab57ef2be844b2a10d0c1b5a492d431727f43937adce22ba45ae"}, - "ex_dtls": {:hex, :ex_dtls, "0.16.0", "3ae38025ccc77f6db573e2e391602fa9bbc02253c137d8d2d59469a66cbe806b", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2a4e30d74c6ddf95cc5b796423293c06a0da295454c3823819808ff031b4b361"}, - "ex_ice": {:hex, :ex_ice, "0.9.3", "46700963acaba72737032500b6ee298a4effa7ad7189ab48887be5e9f4fe2107", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "4fd98d20a39ab70a62dd301c44e87437d479292c528ec7f21522ebfe0654b9cb"}, - "ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"}, - "ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"}, - "ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"}, - "ex_sdp": {:hex, :ex_sdp, "1.1.1", "1a7b049491e5ec02dad9251c53d960835dc5631321ae978ec331831f3e4f6d5f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "1b13a72ac9c5c695b8824dbdffc671be8cbb4c0d1ccb4ff76a04a6826759f233"}, - "ex_stun": {:hex, :ex_stun, "0.2.0", "feb1fc7db0356406655b2a617805e6c712b93308c8ea2bf0ba1197b1f0866deb", [:mix], [], "hexpm", "1e01ba8290082ccbf37acaa5190d1f69b51edd6de2026a8d6d51368b29d115d0"}, - "ex_turn": {:hex, :ex_turn, "0.2.0", "4e1f9b089e9a5ee44928d12370cc9ea7a89b84b2f6256832de65271212eb80de", [:mix], [{:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "08e884f0af2c4a147e3f8cd4ffe33e3452a256389f0956e55a8c4d75bf0e74cd"}, - "ex_webrtc": {:hex, :ex_webrtc, "0.8.1", "e507d1b3d89e9c8b74e3fef5f4070d57e20a3f77061b7439b1af1877d6577793", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.16.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:ex_ice, "~> 0.9.0", [hex: :ex_ice, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.7.1", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:ex_sctp, "0.1.2", [hex: :ex_sctp, repo: "hexpm", optional: true]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}], "hexpm", "2c5563cdaf998b5beed3c79b0feeaa1430f30118b21ab59dd18289d72177adf0"}, - "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, - "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, - "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, - "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, - "heap": {:hex, :heap, "2.0.2", "d98cb178286cfeb5edbcf17785e2d20af73ca57b5a2cf4af584118afbcf917eb", [:mix], [], "hexpm", "ba9ea2fe99eb4bcbd9a8a28eaf71cbcac449ca1d8e71731596aace9028c9d429"}, - "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]}, - "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "image": {:hex, :image, "0.54.4", "332cd64ca47938447dffee97b05a5e4203f2a45e8918537ab0fb971fa3c9debb", [:mix], [{:bumblebee, "~> 0.3", [hex: :bumblebee, repo: "hexpm", optional: true]}, {:evision, "~> 0.1.33 or ~> 0.2", [hex: :evision, repo: "hexpm", optional: true]}, {:exla, "~> 0.5", [hex: :exla, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:kino, "~> 0.13", [hex: :kino, repo: "hexpm", optional: true]}, {:nx, "~> 0.7", [hex: :nx, repo: "hexpm", optional: true]}, {:nx_image, "~> 0.1", [hex: :nx_image, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.1 or ~> 3.2 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:rustler, "> 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:scholar, "~> 0.3", [hex: :scholar, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}, {:vix, "~> 0.23", [hex: :vix, repo: "hexpm", optional: false]}], "hexpm", "4d66ee976c30ec181a54b99791354a4ae990521d64811cb2daed39c4cd95860b"}, - "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, - "logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"}, - "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.11", "6bdcde2eaeafcce136f21fb17effdcdaf9c73107771c8d2a661ca5d0b616ea12", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "dac3f0c839f33603dc2fdad8b9fbebd5ca578c9ca28ea65249ebb15c96c0fd31"}, - "membrane_aac_format": {:hex, :membrane_aac_format, "0.8.0", "515631eabd6e584e0e9af2cea80471fee6246484dbbefc4726c1d93ece8e0838", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}], "hexpm", "a30176a94491033ed32be45e51d509fc70a5ee6e751f12fd6c0d60bd637013f6"}, - "membrane_aac_plugin": {:hex, :membrane_aac_plugin, "0.19.0", "58a15efaaa4f2cc91b968464cfd269244e035efdd983aac2e3ddeb176fcf0585", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "eb7e786e650608ee205f4eebff4c1df3677e545acf09802458f77f64f9942fe9"}, - "membrane_cmaf_format": {:hex, :membrane_cmaf_format, "0.7.1", "9ea858faefdcb181cdfa8001be827c35c5f854e9809ad57d7062cff1f0f703fd", [:mix], [], "hexpm", "3c7b4ed2a986e27f6f336d2f19e9442cb31d93b3142fc024c019572faca54a73"}, - "membrane_common_c": {:hex, :membrane_common_c, "0.16.0", "caf3f29d2f5a1d32d8c2c122866110775866db2726e4272be58e66dfdf4bce40", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "a3c7e91de1ce1f8b23b9823188a5d13654d317235ea0ca781c05353ed3be9b1c"}, - "membrane_core": {:hex, :membrane_core, "1.1.2", "3ca206893e1d3739a24d5092d21c06fcb4db326733a1798f9788fc53abb74829", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a989fd7e0516a7e66f5fb63950b1027315b7f8c8d82d8d685e178b0fb780901b"}, - "membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.20.2", "2e669f0b25418d10b51a73bc52d2e12e4a3a26b416c5c1199d852c3f781a18b3", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:mockery, "~> 2.1", [hex: :mockery, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "6c8d3bcd61d568dd94cabb9b45f29e8926e0076e4432d8f419378e004e02147c"}, - "membrane_ffmpeg_swscale_plugin": {:hex, :membrane_ffmpeg_swscale_plugin, "0.16.2", "581909312d6d12ed560ee99caa1b1674a339760ab2ad6835d243326806c23da1", [:mix], [{:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "46c185dacff1e1b404d0ceb74d0d5224f0931fe1e8b951cc3776ebd099e39afc"}, - "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.2", "650e134c2345d946f930082fac8bac9f5aba785a7817d38a9a9da41ffc56fa92", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "df50c6040004cd7b901cf057bd7e99c875bbbd6ae574efc93b2c753c96f43b9d"}, - "membrane_flv_plugin": {:hex, :membrane_flv_plugin, "0.12.0", "d715ad405af86dcaf4b2f479e34088e1f6738c7280366828e1066b39d2aa493a", [:mix], [{:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}], "hexpm", "a317872d6d394e550c7bfd8979f12a3a1cc1e89b547d75360321025b403d3279"}, - "membrane_funnel_plugin": {:hex, :membrane_funnel_plugin, "0.9.2", "2b2e840dbb232ce29aaff2d55bd329d9978766518dbeb6e8dba7aba7115fadcc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "865ac9d84f86698e2cfeb7904d3b12ab74855a38ca651a880db1505965fa77cc"}, - "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.32.5", "30542fb5d6d36961a51906549b4338f4fc66a304bf92e7c7123e2b9971e3502d", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "8c80e11b9ec9ca23d44304ed7bb3daf665e98b91b2488608ee5718a88182e363"}, - "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, - "membrane_h265_ffmpeg_plugin": {:hex, :membrane_h265_ffmpeg_plugin, "0.4.2", "6dcd932fc2c65a851ab7a44f3996cc9613163bdf23b00568c87c9c0754a64edf", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.1", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "d7eb02110a47a9c11f1432f95b775ba7ce5e962dbe8fc07b421bdd2ce5283b2d"}, - "membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"}, - "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.2", "caf2790d8c107df35f8d456b45f4e09fb9c56ce6c7669a3a03f7d59972e6ed82", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}], "hexpm", "becf1ac4a589adecd850137ccd61a33058f686083a514a7e39fcd721bcf9fb2e"}, - "membrane_hackney_plugin": {:hex, :membrane_hackney_plugin, "0.11.0", "54b368333a23394e7cac2f4d6b701bf8c5ee6614670a31f4ebe009b5e691a5c1", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "2b28fd1be3c889d5824d7d985598386c7673828c88f49a91221df3626af8a998"}, - "membrane_http_adaptive_stream_plugin": {:hex, :membrane_http_adaptive_stream_plugin, "0.18.6", "d417f54da56f7a704200baf018cc0e6222e4c649672adac21e2b321b66c3a958", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_mp4_plugin, "~> 0.35.0", [hex: :membrane_mp4_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "7ca36002d0d74254537afb7aae31541d57a36b33c1abe114e6dd56cf7566bcb2"}, - "membrane_mp4_format": {:hex, :membrane_mp4_format, "0.8.0", "8c6e7d68829228117d333b4fbb030e7be829aab49dd8cb047fdc664db1812e6a", [:mix], [], "hexpm", "148dea678a1f82ccfd44dbde6f936d2f21255f496cb45a22cc6eec427f025522"}, - "membrane_mp4_plugin": {:hex, :membrane_mp4_plugin, "0.35.2", "cbedb5272ef1c8f7d9cd3c44f820a90306469b1dc84b8db30ff55bb6195b7cb2", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_cmaf_format, "~> 0.7.0", [hex: :membrane_cmaf_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_mp4_format, "~> 0.8.0", [hex: :membrane_mp4_format, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.1", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}], "hexpm", "8afd4e7779a742dd56c23f1f23053933d1b0b34d397ad368a2f56f995edb2fe0"}, - "membrane_opus_format": {:hex, :membrane_opus_format, "0.3.0", "3804d9916058b7cfa2baa0131a644d8186198d64f52d592ae09e0942513cb4c2", [:mix], [], "hexpm", "8fc89c97be50de23ded15f2050fe603dcce732566fe6fdd15a2de01cb6b81afe"}, - "membrane_opus_plugin": {:hex, :membrane_opus_plugin, "0.20.5", "aa344bb9931c8e22b2286778cce0658e0d4aa071a503c18c55e1b161e17ab337", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "94fd4447b6576780afc6144dbb0520b43bd399c86a10bf5df1fa878a91798cf6"}, - "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, - "membrane_raw_audio_format": {:hex, :membrane_raw_audio_format, "0.12.0", "b574cd90f69ce2a8b6201b0ccf0826ca28b0fbc8245b8078d9f11cef65f7d5d5", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "6e6c98e3622a2b9df19eab50ba65d7eb45949b1ba306fa8423df6cdb12fd0b44"}, - "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.1", "d7344499c2d80f236a7ef962b5490c651341a501052ee43dec56cf0319fa3936", [:mix], [], "hexpm", "9920b7d445b5357608a364fec5685acdfce85334c647f745045237a0d296c442"}, - "membrane_realtimer_plugin": {:hex, :membrane_realtimer_plugin, "0.9.0", "27210d5e32a5e8bfd101c41e4d8c1876e873a52cc129ebfbee4d0ccbea1cbd21", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "b2e96d62135ee57ef9a5fdea94b3a9ab1198e5ea8ee248391b89c671125d1b51"}, - "membrane_rtmp_plugin": {:hex, :membrane_rtmp_plugin, "0.27.3", "2ca6705668e207a2af1dfd459ec94cebf43bd40338b1b176af1b7ecfb7f5017e", [:mix], [{:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_flv_plugin, "~> 0.12.0", [hex: :membrane_flv_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "e3b3a858cee514aae1cd78b5c85a24b68edb15183e7aa909180c0ce7fb92ab10"}, - "membrane_rtp_aac_plugin": {:hex, :membrane_rtp_aac_plugin, "0.9.4", "355efe237151b304a479a8f0db12043aea2528718d045cb596cbfb85f64ff20a", [:mix], [{:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "f72d12b88b57a3c93eeea19c02c95c878c4b09883dbec703ae3d1557d3af44c0"}, - "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.10.0", "e7f62b1f522647a64d88b60f635476dcb71db61dccda32ffa21ecdb965314c91", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "493245bb45faf3a08343e29281e2aadac72b795d48d64bb805ea8daa31cfd414"}, - "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.20.2", "ab84db505d3102a9cdc300f137c78245ef3982a7ec545838f9544b6b0a2ca1ba", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "27f38c49544d1acf6f7c3f8770a7893f90813a31e8a26461e112a3d3142aff46"}, - "membrane_rtp_h265_plugin": {:hex, :membrane_rtp_h265_plugin, "0.5.2", "970155229e97311b13df8ffb705ee208df83de6d084b43bc77c35e692774616a", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "f8c34c0db3d13d84709fa0b41d4944fcbca50be1fbd292553bfd53512e9a0a26"}, - "membrane_rtp_opus_plugin": {:hex, :membrane_rtp_opus_plugin, "0.10.0", "5797c9b5f09a81c35edc13583eafdfa86b71ac5b2a9d399e95598e27c23ec1b6", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "988cf18ad1e5eb876ec93ae476f035bbec0aff2ca33127b1009217bd258e4845"}, - "membrane_rtp_plugin": {:hex, :membrane_rtp_plugin, "0.30.0", "f7dbc0c5e163edf4ec67b89b1028a7b99ea56e87f8cc01d01b01ec0470287128", [:mix], [{:bimap, "~> 1.2", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.6.0 or ~> 0.7.0", [hex: :ex_libsrtp, repo: "hexpm", optional: true]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:heap, "~> 2.0.2", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.1", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "9600e39c35428c3a86c469ee3cae5a60d259da3ee7e58f94b04866da6d719016"}, - "membrane_rtp_vp8_plugin": {:hex, :membrane_rtp_vp8_plugin, "0.9.4", "8eb7e90576e79ccfefe6cf54f982afd8109027b148e17e701e5fbabe485a7b53", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.4.0 or ~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}], "hexpm", "0fd14c81d3200fc83908f3d72ece3faed207e34c1f59a85b2b7625ec02fb30dd"}, - "membrane_rtsp": {:hex, :membrane_rtsp, "0.10.1", "c6cb8549daab155175896f677b049a0f65c3a650e93e603714ec94a836148722", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.17.0 or ~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.4.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ff382b38c09150ff43c7ec7a2a62df41b2bab42793633dcc40ef9b6e51ac70b9"}, - "membrane_rtsp_plugin": {:hex, :membrane_rtsp_plugin, "0.6.1", "56f11bbd74e47fe7240df44ffc2b17537bc295a4286e08ce3232a905347edfea", [:mix], [{:ex_sdp, "~> 1.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_aac_plugin, "~> 0.9.1", [hex: :membrane_rtp_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_h265_plugin, "~> 0.5.2", [hex: :membrane_rtp_h265_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.10.0", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:membrane_tcp_plugin, "~> 0.6.0", [hex: :membrane_tcp_plugin, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.14.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}], "hexpm", "80b6aedd27b4d0e2c99097b5b7d841855ff6fcaf7a11ef85d8e57f7b83a4d8c1"}, - "membrane_tcp_plugin": {:hex, :membrane_tcp_plugin, "0.6.0", "1f8dba5525504fb2d49070932f24113d1b26c7e5429c700671ed80433ac83f2f", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "820440f5a8181a96cff461ad2d5ed426d47eacfdd7764dd9596dad68ad892d3d"}, - "membrane_tee_plugin": {:hex, :membrane_tee_plugin, "0.12.0", "f94989b4080ef4b7937d74c1a14d3379577c7bd4c6d06e5a2bb41c351ad604d4", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "0d61c9ed5e68e5a75d54200e1c6df5739c0bcb52fee0974183ad72446a179887"}, - "membrane_telemetry_metrics": {:hex, :membrane_telemetry_metrics, "0.1.1", "57917e72012f9ebe124eab54f29ca74c9d9eb3ae2207f55c95618ee51280eb4f", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "69392966e6bd51937244758c2b3d835c5ff47d8953d25431a9d37059737afc11"}, - "membrane_timestamp_queue": {:hex, :membrane_timestamp_queue, "0.2.2", "1c831b2273d018a6548654aa9f7fa7c4b683f71d96ffe164934ef55f9d11f693", [:mix], [{:heap, "~> 2.0", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7c830e760baaced0988421671cd2c83c7cda8d1bd2b61fd05332711675d1204f"}, - "membrane_transcoder_plugin": {:hex, :membrane_transcoder_plugin, "0.1.2", "5d5546971289109423c1d02c4a45bfce7133293d76f5850824663a4613e5a728", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.0", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.20.0", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.1", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.32.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_ffmpeg_plugin, "~> 0.4.2", [hex: :membrane_h265_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.20.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.2", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vpx_plugin, "~> 0.3.0", [hex: :membrane_vpx_plugin, repo: "hexpm", optional: false]}], "hexpm", "50297b922fdd39520125120782bf679f26be8965b31565b470fb51b6ef908873"}, - "membrane_udp_plugin": {:hex, :membrane_udp_plugin, "0.14.0", "d533ee5f6fcdd0551ad690045cdb6c1a76307a155d9255cc4a4606f85774bc37", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "902d1a7aa228ec377482d53a605b100e20e0b6e59196f94f94147bb62b23c47e"}, - "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, - "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, - "membrane_vpx_plugin": {:hex, :membrane_vpx_plugin, "0.3.0", "60404d1b1511b4c62ba6bbf7b6212570f1732ba477015c4072e0aa33e18a8809", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vp9_format, "~> 0.5.0", [hex: :membrane_vp9_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "effa7762bbf73efd8d21d0978bce79538414719284194db97672afbce665b56a"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, - "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, - "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, - "mockery": {:hex, :mockery, "2.3.3", "3dba87bd0422a513e6af6e0d811383f38f82ac6be5d3d285a5fcca9c299bd0ac", [:mix], [], "hexpm", "17282be00613286254298117cd25e607a39f15ac03b41c631f60e52f5b5ec974"}, - "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, - "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, - "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "phoenix": {:hex, :phoenix, "1.7.19", "36617efe5afbd821099a8b994ff4618a340a5bfb25531a1802c4d4c634017a57", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "ba4dc14458278773f905f8ae6c2ec743d52c3a35b6b353733f64f02dfe096cd6"}, - "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, - "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"}, - "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.4", "327491b033e79db2f887b065c5a2993228449091883d74cfa1baa12f8c98d5eb", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9865316ddf8d78f382d63af278d20436b52d262b60239956817a61279514366"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, - "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, - "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, - "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, - "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, - "ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"}, - "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, - "shmex": {:hex, :shmex, "0.5.1", "81dd209093416bf6608e66882cb7e676089307448a1afd4fc906c1f7e5b94cf4", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "c29f8286891252f64c4e1dac40b217d960f7d58def597c4e606ff8fbe71ceb80"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"}, - "tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, - "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, - "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, - "thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, - "unifex": {:hex, :unifex, "1.2.1", "6841c170a6e16509fac30b19e4e0a19937c33155a59088b50c15fc2c36251b6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "8c9d2e3c48df031e9995dd16865bab3df402c0295ba3a31f38274bb5314c7d37"}, - "vix": {:hex, :vix, "0.33.0", "cd98084529fd8fe3d2336f157db6de03b297fb096508d820068117d58eadb6f1", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "9acde72b27bdfeadeb51f790f7a6cc0d06cf555718c05cf57e43c5cf93d8471b"}, - "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, - "zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"}, -} diff --git a/example_project_sc/priv/static/favicon.ico b/example_project_sc/priv/static/favicon.ico deleted file mode 100644 index 7f372bfc21cdd8cb47585339d5fa4d9dd424402f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=@t!V@Ar*{oFEH`~d50E!_s``s q?{G*w(7?#d#v@^nKnY_HKaYb01EZMZjMqTJ89ZJ6T-G@yGywoKK_h|y diff --git a/example_project_sc/priv/static/images/logo.svg b/example_project_sc/priv/static/images/logo.svg deleted file mode 100644 index 9f26bab..0000000 --- a/example_project_sc/priv/static/images/logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/example_project_sc/priv/static/robots.txt b/example_project_sc/priv/static/robots.txt deleted file mode 100644 index 26e06b5..0000000 --- a/example_project_sc/priv/static/robots.txt +++ /dev/null @@ -1,5 +0,0 @@ -# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file -# -# To ban all spiders from the entire site uncomment the next two lines: -# User-agent: * -# Disallow: / diff --git a/example_project_sc/test/example_project_web/controllers/error_html_test.exs b/example_project_sc/test/example_project_web/controllers/error_html_test.exs deleted file mode 100644 index 12099fd..0000000 --- a/example_project_sc/test/example_project_web/controllers/error_html_test.exs +++ /dev/null @@ -1,14 +0,0 @@ -defmodule ExampleProjectWeb.ErrorHTMLTest do - use ExampleProjectWeb.ConnCase, async: true - - # Bring render_to_string/4 for testing custom views - import Phoenix.Template - - test "renders 404.html" do - assert render_to_string(ExampleProjectWeb.ErrorHTML, "404", "html", []) == "Not Found" - end - - test "renders 500.html" do - assert render_to_string(ExampleProjectWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" - end -end diff --git a/example_project_sc/test/example_project_web/controllers/error_json_test.exs b/example_project_sc/test/example_project_web/controllers/error_json_test.exs deleted file mode 100644 index 27d1bd3..0000000 --- a/example_project_sc/test/example_project_web/controllers/error_json_test.exs +++ /dev/null @@ -1,12 +0,0 @@ -defmodule ExampleProjectWeb.ErrorJSONTest do - use ExampleProjectWeb.ConnCase, async: true - - test "renders 404" do - assert ExampleProjectWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} - end - - test "renders 500" do - assert ExampleProjectWeb.ErrorJSON.render("500.json", %{}) == - %{errors: %{detail: "Internal Server Error"}} - end -end diff --git a/example_project_sc/test/example_project_web/controllers/page_controller_test.exs b/example_project_sc/test/example_project_web/controllers/page_controller_test.exs deleted file mode 100644 index 54de386..0000000 --- a/example_project_sc/test/example_project_web/controllers/page_controller_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule ExampleProjectWeb.PageControllerTest do - use ExampleProjectWeb.ConnCase - - test "GET /", %{conn: conn} do - conn = get(conn, ~p"/") - assert html_response(conn, 200) =~ "Peace of mind from prototype to production" - end -end diff --git a/example_project_sc/test/support/conn_case.ex b/example_project_sc/test/support/conn_case.ex deleted file mode 100644 index 58e0e34..0000000 --- a/example_project_sc/test/support/conn_case.ex +++ /dev/null @@ -1,37 +0,0 @@ -defmodule ExampleProjectWeb.ConnCase do - @moduledoc """ - This module defines the test case to be used by - tests that require setting up a connection. - - Such tests rely on `Phoenix.ConnTest` and also - import other functionality to make it easier - to build common data structures and query the data layer. - - Finally, if the test case interacts with the database, - we enable the SQL sandbox, so changes done to the database - are reverted at the end of every test. If you are using - PostgreSQL, you can even run database tests asynchronously - by setting `use ExampleProjectWeb.ConnCase, async: true`, although - this option is not recommended for other databases. - """ - - use ExUnit.CaseTemplate - - using do - quote do - # The default endpoint for testing - @endpoint ExampleProjectWeb.Endpoint - - use ExampleProjectWeb, :verified_routes - - # Import conveniences for testing with connections - import Plug.Conn - import Phoenix.ConnTest - import ExampleProjectWeb.ConnCase - end - end - - setup _tags do - {:ok, conn: Phoenix.ConnTest.build_conn()} - end -end diff --git a/example_project_sc/test/test_helper.exs b/example_project_sc/test/test_helper.exs deleted file mode 100644 index 869559e..0000000 --- a/example_project_sc/test/test_helper.exs +++ /dev/null @@ -1 +0,0 @@ -ExUnit.start() From 123d12379f143faee401a8ceb9458516d264781d Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 19 Feb 2025 12:05:58 +0100 Subject: [PATCH 23/45] Delete single-file demos --- capture_and_play_demo.exs | 220 -------------------------------------- player_demo.exs | 185 -------------------------------- 2 files changed, 405 deletions(-) delete mode 100644 capture_and_play_demo.exs delete mode 100644 player_demo.exs diff --git a/capture_and_play_demo.exs b/capture_and_play_demo.exs deleted file mode 100644 index 39e130c..0000000 --- a/capture_and_play_demo.exs +++ /dev/null @@ -1,220 +0,0 @@ -Application.put_env(:sample, Example.Endpoint, - http: [ip: {127, 0, 0, 1}, port: 5001], - server: true, - live_view: [signing_salt: "aaaaaaaa"], - secret_key_base: String.duplicate("a", 64) -) - -# Mix.install([ -# {:plug_cowboy, "~> 2.5"}, -# {:jason, "~> 1.0"}, -# {:phoenix, "~> 1.7.0"}, -# {:phoenix_live_view, "~> 0.19.0"} -# ]) - -defmodule Example.ErrorView do - def render(template, _), do: Phoenix.Controller.status_message_from_template(template) -end - -defmodule Example.HomeLive do - use Phoenix.LiveView, layout: {__MODULE__, :live} - - alias Boombox.Live.WebRTC.{Capture, Player} - - def mount(_params, _session, socket) do - socket = - if connected?(socket) do - ingress_signaling = Membrane.WebRTC.SignalingChannel.new() - egress_signaling = Membrane.WebRTC.SignalingChannel.new() - - {:ok, _boombox_pid} = - Task.start_link(fn -> - Boombox.run( - input: {:webrtc, ingress_signaling}, - output: {:webrtc, egress_signaling} - ) - end) - - socket = - socket - |> Capture.attach( - id: "mediaCapture", - signaling_channel: ingress_signaling, - audio?: false, - video?: true - ) - |> Player.attach( - id: "videoPlayer", - signaling_channel: egress_signaling - ) - - socket - |> assign( - capture: Capture.get_attached(socket, "mediaCapture"), - player: Player.get_attached(socket, "videoPlayer") - ) - else - socket - end - - {:ok, socket} - end - - defp phx_vsn, do: Application.spec(:phoenix, :vsn) - defp lv_vsn, do: Application.spec(:phoenix_live_view, :vsn) - - def render("live.html", assigns) do - ~H""" - - - - - <%= @inner_content %> - """ - end - - def render(%{capture: %Capture{}, player: %Player{}} = assigns) do - ~H""" - - - """ - end - - def render(assigns) do - ~H""" - """ - end -end - -defmodule Example.Router do - use Phoenix.Router - import Phoenix.LiveView.Router - - pipeline :browser do - plug(:accepts, ["html"]) - end - - scope "/", Example do - pipe_through(:browser) - - live("/", HomeLive, :index) - end -end - -defmodule Example.Endpoint do - use Phoenix.Endpoint, otp_app: :sample - socket("/live", Phoenix.LiveView.Socket) - plug(Example.Router) -end - -{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one) -Process.sleep(:infinity) diff --git a/player_demo.exs b/player_demo.exs deleted file mode 100644 index 14fd331..0000000 --- a/player_demo.exs +++ /dev/null @@ -1,185 +0,0 @@ -Application.put_env(:sample, Example.Endpoint, - http: [ip: {127, 0, 0, 1}, port: 5001], - server: true, - live_view: [signing_salt: "aaaaaaaa"], - secret_key_base: String.duplicate("a", 64) -) - -# Mix.install([ -# {:plug_cowboy, "~> 2.5"}, -# {:jason, "~> 1.0"}, -# {:phoenix, "~> 1.7.0"}, -# {:phoenix_live_view, "~> 0.19.0"} -# ]) - -defmodule Example.ErrorView do - def render(template, _), do: Phoenix.Controller.status_message_from_template(template) -end - -defmodule Debugger do - def debug(pid) do - if Process.alive?(pid) do - IO.puts("ALIVE #{inspect(pid)} #{self() |> inspect()}") - else - IO.puts("NOT ALIVE #{inspect(pid)} #{self() |> inspect()}") - end - - Process.sleep(1000) - - debug(pid) - end -end - -defmodule Example.HomeLive do - use Phoenix.LiveView, layout: {__MODULE__, :live} - - alias Boombox.Live.WebRTC.Player - - def mount(_params, _session, socket) do - socket = - if connected?(socket) do - signaling_channel = Membrane.WebRTC.SignalingChannel.new() - - {:ok, boombox_pid} = - Task.start_link(fn -> - overlay = - Req.get!("https://avatars.githubusercontent.com/u/25247695?s=200&v=4").body - |> Vix.Vips.Image.new_from_buffer() - |> then(fn {:ok, img} -> img end) - |> Image.trim!() - |> Image.thumbnail!(100) - - bg = Image.new!(640, 480, color: :light_gray) - max_x = Image.width(bg) - Image.width(overlay) - max_y = Image.height(bg) - Image.height(overlay) - - Stream.iterate({_x = 300, _y = 0, _dx = 1, _dy = 2, _pts = 0}, fn {x, y, dx, dy, pts} -> - dx = if (x + dx) in 0..max_x, do: dx, else: -dx - dy = if (y + dy) in 0..max_y, do: dy, else: -dy - pts = pts + div(Membrane.Time.seconds(1), _fps = 60) - {x + dx, y + dy, dx, dy, pts} - end) - |> Stream.map(fn {x, y, _dx, _dy, pts} -> - img = Image.compose!(bg, overlay, x: x, y: y) - %Boombox.Packet{kind: :video, payload: img, pts: pts} - end) - |> Boombox.run( - input: {:stream, video: :image, audio: false}, - output: {:webrtc, signaling_channel} - ) - end) - - _debug_task = Task.start_link(fn -> Debugger.debug(boombox_pid) end) - - socket - |> Player.attach(id: "videoPlayer", signaling_channel: signaling_channel) - |> assign(signaling_channel: signaling_channel, boombox: boombox_pid) - else - socket - end - - {:ok, socket} - end - - defp phx_vsn, do: Application.spec(:phoenix, :vsn) - defp lv_vsn, do: Application.spec(:phoenix_live_view, :vsn) - - def render("live.html", assigns) do - ~H""" - - - - - <%= @inner_content %> - """ - end - - def render(%{player: %Player{}} = assigns) do - ~H""" - - """ - end - - def render(assigns) do - ~H""" - """ - end -end - -defmodule Example.Router do - use Phoenix.Router - import Phoenix.LiveView.Router - - pipeline :browser do - plug(:accepts, ["html"]) - end - - scope "/", Example do - pipe_through(:browser) - - live("/", HomeLive, :index) - end -end - -defmodule Example.Endpoint do - use Phoenix.Endpoint, otp_app: :sample - socket("/live", Phoenix.LiveView.Socket) - plug(Example.Router) -end - -{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one) -Process.sleep(:infinity) From 6c6972c1a0b3727150e23bc4f1be8743538395b9 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 19 Feb 2025 12:21:15 +0100 Subject: [PATCH 24/45] Rename boombox_live to membrane_webrtc_live --- .github/workflows/fetch_changes.yml | 54 ------------------- README.md | 14 ++--- example_project/assets/js/app.js | 2 +- .../example_project_web/live_views/echo.ex | 4 +- example_project/mix.exs | 8 ++- example_project/mix.lock | 2 + lib/boombox_live/webrtc/capture.ex | 2 +- lib/boombox_live/webrtc/player.ex | 4 +- lib/boombox_live/webrtc/utils.ex | 2 +- mix.exs | 12 ++--- mix.lock | 4 +- package.json | 8 +-- 12 files changed, 33 insertions(+), 83 deletions(-) delete mode 100644 .github/workflows/fetch_changes.yml diff --git a/.github/workflows/fetch_changes.yml b/.github/workflows/fetch_changes.yml deleted file mode 100644 index 7fb6d52..0000000 --- a/.github/workflows/fetch_changes.yml +++ /dev/null @@ -1,54 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: Fetch changes - -# Controls when the workflow will run -on: - # Trigger thrice a day - schedule: - - cron: '0 4,8,12 * * *' - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 - with: - fetch-depth: '0' - - - name: webfactory/ssh-agent - uses: webfactory/ssh-agent@v0.5.4 - with: - ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - - # Runs a set of commands using the runners shell - - name: Add remote - run: | - git remote add source git@github.com:membraneframework/boombox_live.git - git remote update - - echo "CURRENT_BRANCH=$(git branch --show-current)" >> $GITHUB_ENV - - - name: Check changes - run: | - echo ${{env.CURRENT_BRANCH}} - echo "LOG_SIZE=$(git log origin/${{ env.CURRENT_BRANCH }}..source/${{ env.CURRENT_BRANCH }} | wc -l)" - - echo "LOG_SIZE=$(git log origin/${{ env.CURRENT_BRANCH }}..source/${{ env.CURRENT_BRANCH }} | wc -l)" >> $GITHUB_ENV - - - if: ${{ env.LOG_SIZE != '0'}} - name: Merge changes - run: | - git config --global user.email "admin@membraneframework.com" - git config --global user.name "MembraneFramework" - - git merge source/master - git push origin master diff --git a/README.md b/README.md index aade3a1..08a2822 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Membrane Template Plugin -[![Hex.pm](https://img.shields.io/hexpm/v/boombox_live.svg)](https://hex.pm/packages/boombox_live) -[![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/boombox_live) -[![CircleCI](https://circleci.com/gh/membraneframework/boombox_live.svg?style=svg)](https://circleci.com/gh/membraneframework/boombox_live) +[![Hex.pm](https://img.shields.io/hexpm/v/membrane_webrtc_live.svg)](https://hex.pm/packages/membrane_webrtc_live) +[![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/membrane_webrtc_live) +[![CircleCI](https://circleci.com/gh/membraneframework/membrane_webrtc_live.svg?style=svg)](https://circleci.com/gh/membraneframework/membrane_webrtc_live) This repository contains a template for new plugins. @@ -12,12 +12,12 @@ It's a part of the [Membrane Framework](https://membrane.stream). ## Installation -The package can be installed by adding `boombox_live` to your list of dependencies in `mix.exs`: +The package can be installed by adding `membrane_webrtc_live` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:boombox_live, "~> 0.1.0"} + {:membrane_webrtc_live, "~> 0.1.0"} ] end ``` @@ -28,8 +28,8 @@ TODO ## Copyright and License -Copyright 2020, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=boombox_live) +Copyright 2020, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_webrtc_live) -[![Software Mansion](https://logo.swmansion.com/logo?color=white&variant=desktop&width=200&tag=membrane-github)](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=boombox_live) +[![Software Mansion](https://logo.swmansion.com/logo?color=white&variant=desktop&width=200&tag=membrane-github)](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_webrtc_live) Licensed under the [Apache License, Version 2.0](LICENSE) diff --git a/example_project/assets/js/app.js b/example_project/assets/js/app.js index ddfe6f3..22ed1ac 100644 --- a/example_project/assets/js/app.js +++ b/example_project/assets/js/app.js @@ -22,7 +22,7 @@ import { Socket } from "phoenix"; import { LiveSocket } from "phoenix_live_view"; import topbar from "../vendor/topbar"; -import { createCaptureHook, createPlayerHook } from "boombox_live"; +import { createCaptureHook, createPlayerHook } from "membrane_webrtc_live"; let Hooks = {}; const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; diff --git a/example_project/lib/example_project_web/live_views/echo.ex b/example_project/lib/example_project_web/live_views/echo.ex index c0c9dd6..5e70cae 100644 --- a/example_project/lib/example_project_web/live_views/echo.ex +++ b/example_project/lib/example_project_web/live_views/echo.ex @@ -1,10 +1,8 @@ defmodule ExampleProjectWeb.LiveViews.Echo do use ExampleProjectWeb, :live_view - alias Boombox.Live.WebRTC.{Capture, Player} + alias Membrane.WebRTC.Live.{Capture, Player} def mount(_params, _session, socket) do - IO.inspect(socket, label: "MOUNT") - socket = if connected?(socket) do ingress_signaling = Membrane.WebRTC.SignalingChannel.new() diff --git a/example_project/mix.exs b/example_project/mix.exs index bbb2f4a..e0c1929 100644 --- a/example_project/mix.exs +++ b/example_project/mix.exs @@ -32,7 +32,13 @@ defmodule ExampleProject.MixProject do # Type `mix help deps` for examples and options. defp deps do [ - {:boombox_live, path: ".."}, + {:boombox, + github: "membraneframework/boombox", ref: "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1"}, + {:membrane_webrtc_live, path: "../"}, + {:membrane_webrtc_plugin, + github: "membraneframework/membrane_webrtc_plugin", + ref: "c0a4c66cc3b7cd829579a325817ad1727b4474fe", + override: true}, {:phoenix, "~> 1.7.19"}, {:phoenix_html, "~> 4.1"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, diff --git a/example_project/mix.lock b/example_project/mix.lock index b6e8000..66a8ee2 100644 --- a/example_project/mix.lock +++ b/example_project/mix.lock @@ -1,6 +1,7 @@ %{ "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, + "boombox": {:git, "https://github.com/membraneframework/boombox.git", "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1", [ref: "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1"]}, "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, @@ -83,6 +84,7 @@ "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, "membrane_vpx_plugin": {:hex, :membrane_vpx_plugin, "0.3.0", "60404d1b1511b4c62ba6bbf7b6212570f1732ba477015c4072e0aa33e18a8809", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vp9_format, "~> 0.5.0", [hex: :membrane_vp9_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "effa7762bbf73efd8d21d0978bce79538414719284194db97672afbce665b56a"}, + "membrane_webrtc_plugin": {:git, "https://github.com/membraneframework/membrane_webrtc_plugin.git", "c0a4c66cc3b7cd829579a325817ad1727b4474fe", [ref: "c0a4c66cc3b7cd829579a325817ad1727b4474fe"]}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, diff --git a/lib/boombox_live/webrtc/capture.ex b/lib/boombox_live/webrtc/capture.ex index 6afc2de..1c74406 100644 --- a/lib/boombox_live/webrtc/capture.ex +++ b/lib/boombox_live/webrtc/capture.ex @@ -1,4 +1,4 @@ -defmodule Boombox.Live.WebRTC.Capture do +defmodule Membrane.WebRTC.Live.Capture do @moduledoc ~S''' Component for sending and playing audio and video via WebRTC from a Phoenix app to a browser (browser subscribes). diff --git a/lib/boombox_live/webrtc/player.ex b/lib/boombox_live/webrtc/player.ex index 0dab1a6..269ad50 100644 --- a/lib/boombox_live/webrtc/player.ex +++ b/lib/boombox_live/webrtc/player.ex @@ -1,4 +1,4 @@ -defmodule Boombox.Live.WebRTC.Player do +defmodule Membrane.WebRTC.Live.Player do @moduledoc ~S''' Component for sending and playing audio and video via WebRTC from a Phoenix app to a browser (browser subscribes). @@ -159,8 +159,6 @@ defmodule Boombox.Live.WebRTC.Player do @impl true def mount(_params, %{"class" => class, "id" => id}, socket) do - IO.inspect(self(), label: "DUPA") - socket = assign(socket, class: class, player: nil) if connected?(socket) do diff --git a/lib/boombox_live/webrtc/utils.ex b/lib/boombox_live/webrtc/utils.ex index ec0e7e1..e79208e 100644 --- a/lib/boombox_live/webrtc/utils.ex +++ b/lib/boombox_live/webrtc/utils.ex @@ -1,4 +1,4 @@ -defmodule Boombox.Live.WebRTC.Utils do +defmodule Membrane.WebRTC.Live.Utils do @moduledoc false defmacro log_prefix(id) do diff --git a/mix.exs b/mix.exs index 67a9cb7..41cb34c 100644 --- a/mix.exs +++ b/mix.exs @@ -1,12 +1,12 @@ -defmodule Boombox.Live.Mixfile do +defmodule Membrane.WebRTC.Live.Mixfile do use Mix.Project @version "0.1.0" - @github_url "https://github.com/membraneframework/boombox_live" + @github_url "https://github.com/membraneframework/membrane_webrtc_live" def project do [ - app: :boombox_live, + app: :membrane_webrtc_live, version: @version, elixir: "~> 1.13", elixirc_paths: elixirc_paths(Mix.env()), @@ -37,9 +37,9 @@ defmodule Boombox.Live.Mixfile do defp deps do [ - # {:boombox, "~> 0.1.0"}, - {:boombox, path: "../boombox"}, - # {:membrane_webrtc_plugin, "~> 0.23.2"}, + {:membrane_webrtc_plugin, + github: "membraneframework/membrane_webrtc_plugin", + ref: "c0a4c66cc3b7cd829579a325817ad1727b4474fe"}, {:plug_cowboy, "~> 2.5"}, {:phoenix_live_view, "~> 1.0"}, {:phoenix, "~> 1.7"}, diff --git a/mix.lock b/mix.lock index 7308552..9dc26f4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,7 @@ %{ "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, - "boombox": {:hex, :boombox, "0.1.0", "86433412c72c3fc6255cba223e968a857680b0d58225dffa3fa05c1eb10113b2", [:mix], [{:burrito, "~> 1.0", [hex: :burrito, repo: "hexpm", optional: true]}, {:image, "~> 0.54.0", [hex: :image, repo: "hexpm", optional: false]}, {:membrane_aac_fdk_plugin, "~> 0.18.0", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.20.0", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swscale_plugin, "~> 0.16.0", [hex: :membrane_ffmpeg_swscale_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.32.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_hackney_plugin, "~> 0.11.0", [hex: :membrane_hackney_plugin, repo: "hexpm", optional: false]}, {:membrane_http_adaptive_stream_plugin, "~> 0.18.5", [hex: :membrane_http_adaptive_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_mp4_plugin, "~> 0.35.2", [hex: :membrane_mp4_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.20.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_realtimer_plugin, "~> 0.9.0", [hex: :membrane_realtimer_plugin, repo: "hexpm", optional: false]}, {:membrane_rtmp_plugin, "~> 0.25.0", [hex: :membrane_rtmp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.29.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp_plugin, "~> 0.3.0", [hex: :membrane_rtsp_plugin, repo: "hexpm", optional: false]}, {:membrane_webrtc_plugin, "~> 0.22.0", [hex: :membrane_webrtc_plugin, repo: "hexpm", optional: false]}], "hexpm", "a1baaebbff25bd77a12261a29ef28e8e348bc3f1773c2665d781064575ffb7e8"}, + "boombox": {:git, "https://github.com/membraneframework/boombox.git", "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1", [ref: "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1"]}, "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, @@ -88,7 +88,7 @@ "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, "membrane_vpx_plugin": {:hex, :membrane_vpx_plugin, "0.3.0", "60404d1b1511b4c62ba6bbf7b6212570f1732ba477015c4072e0aa33e18a8809", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vp9_format, "~> 0.5.0", [hex: :membrane_vp9_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "effa7762bbf73efd8d21d0978bce79538414719284194db97672afbce665b56a"}, - "membrane_webrtc_plugin": {:git, "https://github.com/membraneframework/membrane_webrtc_plugin.git", "8c1567c212f6ce4dfb06b6d5ed183b37d435336f", []}, + "membrane_webrtc_plugin": {:git, "https://github.com/membraneframework/membrane_webrtc_plugin.git", "c0a4c66cc3b7cd829579a325817ad1727b4474fe", [ref: "c0a4c66cc3b7cd829579a325817ad1727b4474fe"]}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, diff --git a/package.json b/package.json index 7c9b6e6..fa204b9 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "boombox_live", + "name": "membrane_webrtc_live", "version": "0.1.0", - "description": "Phoenix Live Components for Boombox", + "description": "Phoenix Live Components for Membrane WebRTC Plugin", "main": "./assets/index.js", "repository": { "type": "git", - "url": "git://github.com/membraneframework/boombox_live.git" + "url": "git://github.com/membraneframework/membrane_webrtc_live.git" }, "license": "Apache-2.0", - "homepage": "https://github.com/membraneframework/boombox_live" + "homepage": "https://github.com/membraneframework/membrane_webrtc_live" } From e5e823b3388e57926511625efff3e61dd8aa8cde Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 19 Feb 2025 14:55:52 +0100 Subject: [PATCH 25/45] Refactor README.md --- README.md | 38 +++++++++++++++++++++++++------ example_project/mix.exs | 5 +--- example_project/mix.lock | 2 +- mix.exs | 4 +--- mix.lock | 49 +--------------------------------------- 5 files changed, 35 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 08a2822..f6c491d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ -# Membrane Template Plugin +# Membrane WebRTC Live [![Hex.pm](https://img.shields.io/hexpm/v/membrane_webrtc_live.svg)](https://hex.pm/packages/membrane_webrtc_live) [![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/membrane_webrtc_live) [![CircleCI](https://circleci.com/gh/membraneframework/membrane_webrtc_live.svg?style=svg)](https://circleci.com/gh/membraneframework/membrane_webrtc_live) -This repository contains a template for new plugins. - -Check out different branches for other flavors of this template. +Phoenix LiveViews that can be used with Membrane Components from [membrane_webrtc_plugin](https://github.com/membraneframework/membrane_webrtc_plugin). It's a part of the [Membrane Framework](https://membrane.stream). @@ -22,13 +20,39 @@ def deps do end ``` -## Usage +## Modules + +`Membrane.WebRTC.Live` comes with two `Phoenix.LiveView`s: + - `Membrane.WebRTC.Live.Capture` - exchanges WebRTC signaling messages between `Membrane.WebRTC.Source` and the browser. It expects the same `Membrane.WebRTC.SignalingChannel` that has been passed to the related `Membrane.WebRTC.Source`. As a result, `Membrane.Webrtc.Source` will return the media stream captured from the browser, where `Membrane.WebRTC.Live.Capture` has been rendered. + - `Membrane.WebRTC.Live.Player` - exchanges WebRTC signaling messages between `Membrane.WebRTC.Sink` and the browser. It expects the same `Membrane.WebRTC.SignalingChannel` that has been passed to the related `Membrane.WebRTC.Sink`. As a result, `Membrane.WebRTC.Live.Player` will play media streams passed to the related `Membrane.WebRTC.Sink`. Currently supports up to one video stream and up to one audio stream. + +## Usage + +To use `Phoenix.LiveView`s from this repository, you have to use related JS hooks. To do so, add following snippet to `assets/js/app.js` + +```js +import { createCaptureHook, createPlayerHook } from "membrane_webrtc_live"; + +let Hooks = {}; +const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; +Hooks.Capture = createCaptureHook(iceServers); +Hooks.Player = createPlayerHook(iceServers); +``` + +and add `Hooks` to the WebSocket constructor. It can be done in a following way: + +```js +new LiveSocket("/live", Socket, { + params: SomeParams, + hooks: Hooks, +}); +``` -TODO +To see full usage example, take a look at `example_project/` directory in this repository (take a look especially at `example_project/assets/js/app.js` and `example_project/lib/example_project_web/live_views/echo.ex`). ## Copyright and License -Copyright 2020, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_webrtc_live) +Copyright 2025, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_webrtc_live) [![Software Mansion](https://logo.swmansion.com/logo?color=white&variant=desktop&width=200&tag=membrane-github)](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_webrtc_live) diff --git a/example_project/mix.exs b/example_project/mix.exs index e0c1929..eba421c 100644 --- a/example_project/mix.exs +++ b/example_project/mix.exs @@ -35,10 +35,7 @@ defmodule ExampleProject.MixProject do {:boombox, github: "membraneframework/boombox", ref: "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1"}, {:membrane_webrtc_live, path: "../"}, - {:membrane_webrtc_plugin, - github: "membraneframework/membrane_webrtc_plugin", - ref: "c0a4c66cc3b7cd829579a325817ad1727b4474fe", - override: true}, + {:membrane_webrtc_plugin, "~> 0.23.3", override: true}, {:phoenix, "~> 1.7.19"}, {:phoenix_html, "~> 4.1"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, diff --git a/example_project/mix.lock b/example_project/mix.lock index 66a8ee2..c63b65f 100644 --- a/example_project/mix.lock +++ b/example_project/mix.lock @@ -84,7 +84,7 @@ "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, "membrane_vpx_plugin": {:hex, :membrane_vpx_plugin, "0.3.0", "60404d1b1511b4c62ba6bbf7b6212570f1732ba477015c4072e0aa33e18a8809", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vp9_format, "~> 0.5.0", [hex: :membrane_vp9_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "effa7762bbf73efd8d21d0978bce79538414719284194db97672afbce665b56a"}, - "membrane_webrtc_plugin": {:git, "https://github.com/membraneframework/membrane_webrtc_plugin.git", "c0a4c66cc3b7cd829579a325817ad1727b4474fe", [ref: "c0a4c66cc3b7cd829579a325817ad1727b4474fe"]}, + "membrane_webrtc_plugin": {:hex, :membrane_webrtc_plugin, "0.23.3", "77d8819729792c9b53a25b138407d4fc90757750909d05df3418bf73fa724579", [:mix], [{:bandit, "~> 1.2", [hex: :bandit, repo: "hexpm", optional: false]}, {:corsica, "~> 2.0", [hex: :corsica, repo: "hexpm", optional: false]}, {:ex_webrtc, "~> 0.8.0", [hex: :ex_webrtc, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.1", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.4", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.0", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.0", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8723d50d2a917b71598574d68382f70247b7a2876daefca7b457fdb10bc6ecdd"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, diff --git a/mix.exs b/mix.exs index 41cb34c..350df2a 100644 --- a/mix.exs +++ b/mix.exs @@ -37,9 +37,7 @@ defmodule Membrane.WebRTC.Live.Mixfile do defp deps do [ - {:membrane_webrtc_plugin, - github: "membraneframework/membrane_webrtc_plugin", - ref: "c0a4c66cc3b7cd829579a325817ad1727b4474fe"}, + {:membrane_webrtc_plugin, "~> 0.23.3"}, {:plug_cowboy, "~> 2.5"}, {:phoenix_live_view, "~> 1.0"}, {:phoenix, "~> 1.7"}, diff --git a/mix.lock b/mix.lock index 9dc26f4..b96f6e8 100644 --- a/mix.lock +++ b/mix.lock @@ -1,14 +1,11 @@ %{ "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, - "boombox": {:git, "https://github.com/membraneframework/boombox.git", "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1", [ref: "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1"]}, "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, - "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, - "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, "corsica": {:hex, :corsica, "2.1.3", "dccd094ffce38178acead9ae743180cdaffa388f35f0461ba1e8151d32e190e6", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "616c08f61a345780c2cf662ff226816f04d8868e12054e68963e95285b5be8bc"}, "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, @@ -33,72 +30,32 @@ "ex_webrtc": {:hex, :ex_webrtc, "0.8.0", "109f408e9a4e687018b365cbb94de554f184d31bb42623e2f7a93de06575fd30", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.16.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:ex_ice, "~> 0.9.0", [hex: :ex_ice, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.7.1", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:ex_sctp, "0.1.2", [hex: :ex_sctp, repo: "hexpm", optional: true]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}], "hexpm", "ce5c1b2792589975c7d59d56bad367dbe7d7fab35195e3edd85bbc72dd3bd621"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, - "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "heap": {:hex, :heap, "2.0.2", "d98cb178286cfeb5edbcf17785e2d20af73ca57b5a2cf4af584118afbcf917eb", [:mix], [], "hexpm", "ba9ea2fe99eb4bcbd9a8a28eaf71cbcac449ca1d8e71731596aace9028c9d429"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "image": {:hex, :image, "0.54.4", "332cd64ca47938447dffee97b05a5e4203f2a45e8918537ab0fb971fa3c9debb", [:mix], [{:bumblebee, "~> 0.3", [hex: :bumblebee, repo: "hexpm", optional: true]}, {:evision, "~> 0.1.33 or ~> 0.2", [hex: :evision, repo: "hexpm", optional: true]}, {:exla, "~> 0.5", [hex: :exla, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:kino, "~> 0.13", [hex: :kino, repo: "hexpm", optional: true]}, {:nx, "~> 0.7", [hex: :nx, repo: "hexpm", optional: true]}, {:nx_image, "~> 0.1", [hex: :nx_image, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.1 or ~> 3.2 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:rustler, "> 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:scholar, "~> 0.3", [hex: :scholar, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}, {:vix, "~> 0.23", [hex: :vix, repo: "hexpm", optional: false]}], "hexpm", "4d66ee976c30ec181a54b99791354a4ae990521d64811cb2daed39c4cd95860b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, - "logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, - "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.11", "6bdcde2eaeafcce136f21fb17effdcdaf9c73107771c8d2a661ca5d0b616ea12", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "dac3f0c839f33603dc2fdad8b9fbebd5ca578c9ca28ea65249ebb15c96c0fd31"}, - "membrane_aac_format": {:hex, :membrane_aac_format, "0.8.0", "515631eabd6e584e0e9af2cea80471fee6246484dbbefc4726c1d93ece8e0838", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}], "hexpm", "a30176a94491033ed32be45e51d509fc70a5ee6e751f12fd6c0d60bd637013f6"}, - "membrane_aac_plugin": {:hex, :membrane_aac_plugin, "0.19.0", "58a15efaaa4f2cc91b968464cfd269244e035efdd983aac2e3ddeb176fcf0585", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "eb7e786e650608ee205f4eebff4c1df3677e545acf09802458f77f64f9942fe9"}, - "membrane_cmaf_format": {:hex, :membrane_cmaf_format, "0.7.1", "9ea858faefdcb181cdfa8001be827c35c5f854e9809ad57d7062cff1f0f703fd", [:mix], [], "hexpm", "3c7b4ed2a986e27f6f336d2f19e9442cb31d93b3142fc024c019572faca54a73"}, - "membrane_common_c": {:hex, :membrane_common_c, "0.16.0", "caf3f29d2f5a1d32d8c2c122866110775866db2726e4272be58e66dfdf4bce40", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "a3c7e91de1ce1f8b23b9823188a5d13654d317235ea0ca781c05353ed3be9b1c"}, "membrane_core": {:hex, :membrane_core, "1.1.2", "3ca206893e1d3739a24d5092d21c06fcb4db326733a1798f9788fc53abb74829", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a989fd7e0516a7e66f5fb63950b1027315b7f8c8d82d8d685e178b0fb780901b"}, - "membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.20.2", "2e669f0b25418d10b51a73bc52d2e12e4a3a26b416c5c1199d852c3f781a18b3", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:mockery, "~> 2.1", [hex: :mockery, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "6c8d3bcd61d568dd94cabb9b45f29e8926e0076e4432d8f419378e004e02147c"}, - "membrane_ffmpeg_swscale_plugin": {:hex, :membrane_ffmpeg_swscale_plugin, "0.16.2", "581909312d6d12ed560ee99caa1b1674a339760ab2ad6835d243326806c23da1", [:mix], [{:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "46c185dacff1e1b404d0ceb74d0d5224f0931fe1e8b951cc3776ebd099e39afc"}, - "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.2", "650e134c2345d946f930082fac8bac9f5aba785a7817d38a9a9da41ffc56fa92", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "df50c6040004cd7b901cf057bd7e99c875bbbd6ae574efc93b2c753c96f43b9d"}, - "membrane_flv_plugin": {:hex, :membrane_flv_plugin, "0.12.0", "d715ad405af86dcaf4b2f479e34088e1f6738c7280366828e1066b39d2aa493a", [:mix], [{:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}], "hexpm", "a317872d6d394e550c7bfd8979f12a3a1cc1e89b547d75360321025b403d3279"}, "membrane_funnel_plugin": {:hex, :membrane_funnel_plugin, "0.9.2", "2b2e840dbb232ce29aaff2d55bd329d9978766518dbeb6e8dba7aba7115fadcc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "865ac9d84f86698e2cfeb7904d3b12ab74855a38ca651a880db1505965fa77cc"}, - "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.32.5", "30542fb5d6d36961a51906549b4338f4fc66a304bf92e7c7123e2b9971e3502d", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "8c80e11b9ec9ca23d44304ed7bb3daf665e98b91b2488608ee5718a88182e363"}, "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, - "membrane_h265_ffmpeg_plugin": {:hex, :membrane_h265_ffmpeg_plugin, "0.4.2", "6dcd932fc2c65a851ab7a44f3996cc9613163bdf23b00568c87c9c0754a64edf", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.1", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "d7eb02110a47a9c11f1432f95b775ba7ce5e962dbe8fc07b421bdd2ce5283b2d"}, - "membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"}, - "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.2", "caf2790d8c107df35f8d456b45f4e09fb9c56ce6c7669a3a03f7d59972e6ed82", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}], "hexpm", "becf1ac4a589adecd850137ccd61a33058f686083a514a7e39fcd721bcf9fb2e"}, - "membrane_hackney_plugin": {:hex, :membrane_hackney_plugin, "0.11.0", "54b368333a23394e7cac2f4d6b701bf8c5ee6614670a31f4ebe009b5e691a5c1", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "2b28fd1be3c889d5824d7d985598386c7673828c88f49a91221df3626af8a998"}, - "membrane_http_adaptive_stream_plugin": {:hex, :membrane_http_adaptive_stream_plugin, "0.18.6", "d417f54da56f7a704200baf018cc0e6222e4c649672adac21e2b321b66c3a958", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_mp4_plugin, "~> 0.35.0", [hex: :membrane_mp4_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "7ca36002d0d74254537afb7aae31541d57a36b33c1abe114e6dd56cf7566bcb2"}, - "membrane_mp4_format": {:hex, :membrane_mp4_format, "0.8.0", "8c6e7d68829228117d333b4fbb030e7be829aab49dd8cb047fdc664db1812e6a", [:mix], [], "hexpm", "148dea678a1f82ccfd44dbde6f936d2f21255f496cb45a22cc6eec427f025522"}, - "membrane_mp4_plugin": {:hex, :membrane_mp4_plugin, "0.35.2", "cbedb5272ef1c8f7d9cd3c44f820a90306469b1dc84b8db30ff55bb6195b7cb2", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_cmaf_format, "~> 0.7.0", [hex: :membrane_cmaf_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_mp4_format, "~> 0.8.0", [hex: :membrane_mp4_format, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.1", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}], "hexpm", "8afd4e7779a742dd56c23f1f23053933d1b0b34d397ad368a2f56f995edb2fe0"}, "membrane_opus_format": {:hex, :membrane_opus_format, "0.3.0", "3804d9916058b7cfa2baa0131a644d8186198d64f52d592ae09e0942513cb4c2", [:mix], [], "hexpm", "8fc89c97be50de23ded15f2050fe603dcce732566fe6fdd15a2de01cb6b81afe"}, - "membrane_opus_plugin": {:hex, :membrane_opus_plugin, "0.20.5", "aa344bb9931c8e22b2286778cce0658e0d4aa071a503c18c55e1b161e17ab337", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "94fd4447b6576780afc6144dbb0520b43bd399c86a10bf5df1fa878a91798cf6"}, "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, - "membrane_raw_audio_format": {:hex, :membrane_raw_audio_format, "0.12.0", "b574cd90f69ce2a8b6201b0ccf0826ca28b0fbc8245b8078d9f11cef65f7d5d5", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "6e6c98e3622a2b9df19eab50ba65d7eb45949b1ba306fa8423df6cdb12fd0b44"}, - "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.1", "d7344499c2d80f236a7ef962b5490c651341a501052ee43dec56cf0319fa3936", [:mix], [], "hexpm", "9920b7d445b5357608a364fec5685acdfce85334c647f745045237a0d296c442"}, - "membrane_realtimer_plugin": {:hex, :membrane_realtimer_plugin, "0.9.0", "27210d5e32a5e8bfd101c41e4d8c1876e873a52cc129ebfbee4d0ccbea1cbd21", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "b2e96d62135ee57ef9a5fdea94b3a9ab1198e5ea8ee248391b89c671125d1b51"}, - "membrane_rtmp_plugin": {:hex, :membrane_rtmp_plugin, "0.27.3", "2ca6705668e207a2af1dfd459ec94cebf43bd40338b1b176af1b7ecfb7f5017e", [:mix], [{:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_flv_plugin, "~> 0.12.0", [hex: :membrane_flv_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "e3b3a858cee514aae1cd78b5c85a24b68edb15183e7aa909180c0ce7fb92ab10"}, - "membrane_rtp_aac_plugin": {:hex, :membrane_rtp_aac_plugin, "0.9.4", "355efe237151b304a479a8f0db12043aea2528718d045cb596cbfb85f64ff20a", [:mix], [{:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "f72d12b88b57a3c93eeea19c02c95c878c4b09883dbec703ae3d1557d3af44c0"}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.10.0", "e7f62b1f522647a64d88b60f635476dcb71db61dccda32ffa21ecdb965314c91", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "493245bb45faf3a08343e29281e2aadac72b795d48d64bb805ea8daa31cfd414"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.20.2", "ab84db505d3102a9cdc300f137c78245ef3982a7ec545838f9544b6b0a2ca1ba", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "27f38c49544d1acf6f7c3f8770a7893f90813a31e8a26461e112a3d3142aff46"}, - "membrane_rtp_h265_plugin": {:hex, :membrane_rtp_h265_plugin, "0.5.2", "970155229e97311b13df8ffb705ee208df83de6d084b43bc77c35e692774616a", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "f8c34c0db3d13d84709fa0b41d4944fcbca50be1fbd292553bfd53512e9a0a26"}, "membrane_rtp_opus_plugin": {:hex, :membrane_rtp_opus_plugin, "0.10.0", "5797c9b5f09a81c35edc13583eafdfa86b71ac5b2a9d399e95598e27c23ec1b6", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "988cf18ad1e5eb876ec93ae476f035bbec0aff2ca33127b1009217bd258e4845"}, "membrane_rtp_plugin": {:hex, :membrane_rtp_plugin, "0.30.0", "f7dbc0c5e163edf4ec67b89b1028a7b99ea56e87f8cc01d01b01ec0470287128", [:mix], [{:bimap, "~> 1.2", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.6.0 or ~> 0.7.0", [hex: :ex_libsrtp, repo: "hexpm", optional: true]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:heap, "~> 2.0.2", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.1", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "9600e39c35428c3a86c469ee3cae5a60d259da3ee7e58f94b04866da6d719016"}, "membrane_rtp_vp8_plugin": {:hex, :membrane_rtp_vp8_plugin, "0.9.4", "8eb7e90576e79ccfefe6cf54f982afd8109027b148e17e701e5fbabe485a7b53", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.10.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.4.0 or ~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}], "hexpm", "0fd14c81d3200fc83908f3d72ece3faed207e34c1f59a85b2b7625ec02fb30dd"}, - "membrane_rtsp": {:hex, :membrane_rtsp, "0.10.1", "c6cb8549daab155175896f677b049a0f65c3a650e93e603714ec94a836148722", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.17.0 or ~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.4.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ff382b38c09150ff43c7ec7a2a62df41b2bab42793633dcc40ef9b6e51ac70b9"}, - "membrane_rtsp_plugin": {:hex, :membrane_rtsp_plugin, "0.6.1", "56f11bbd74e47fe7240df44ffc2b17537bc295a4286e08ce3232a905347edfea", [:mix], [{:ex_sdp, "~> 1.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_aac_plugin, "~> 0.9.1", [hex: :membrane_rtp_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_h265_plugin, "~> 0.5.2", [hex: :membrane_rtp_h265_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.10.0", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:membrane_tcp_plugin, "~> 0.6.0", [hex: :membrane_tcp_plugin, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.14.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}], "hexpm", "80b6aedd27b4d0e2c99097b5b7d841855ff6fcaf7a11ef85d8e57f7b83a4d8c1"}, - "membrane_tcp_plugin": {:hex, :membrane_tcp_plugin, "0.6.0", "1f8dba5525504fb2d49070932f24113d1b26c7e5429c700671ed80433ac83f2f", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "820440f5a8181a96cff461ad2d5ed426d47eacfdd7764dd9596dad68ad892d3d"}, - "membrane_tee_plugin": {:hex, :membrane_tee_plugin, "0.12.0", "f94989b4080ef4b7937d74c1a14d3379577c7bd4c6d06e5a2bb41c351ad604d4", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "0d61c9ed5e68e5a75d54200e1c6df5739c0bcb52fee0974183ad72446a179887"}, "membrane_telemetry_metrics": {:hex, :membrane_telemetry_metrics, "0.1.1", "57917e72012f9ebe124eab54f29ca74c9d9eb3ae2207f55c95618ee51280eb4f", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "69392966e6bd51937244758c2b3d835c5ff47d8953d25431a9d37059737afc11"}, "membrane_timestamp_queue": {:hex, :membrane_timestamp_queue, "0.2.2", "1c831b2273d018a6548654aa9f7fa7c4b683f71d96ffe164934ef55f9d11f693", [:mix], [{:heap, "~> 2.0", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7c830e760baaced0988421671cd2c83c7cda8d1bd2b61fd05332711675d1204f"}, - "membrane_transcoder_plugin": {:hex, :membrane_transcoder_plugin, "0.1.2", "5d5546971289109423c1d02c4a45bfce7133293d76f5850824663a4613e5a728", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.0", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.20.0", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.1", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.32.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_ffmpeg_plugin, "~> 0.4.2", [hex: :membrane_h265_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.20.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.2", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vpx_plugin, "~> 0.3.0", [hex: :membrane_vpx_plugin, repo: "hexpm", optional: false]}], "hexpm", "50297b922fdd39520125120782bf679f26be8965b31565b470fb51b6ef908873"}, - "membrane_udp_plugin": {:hex, :membrane_udp_plugin, "0.14.0", "d533ee5f6fcdd0551ad690045cdb6c1a76307a155d9255cc4a4606f85774bc37", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "902d1a7aa228ec377482d53a605b100e20e0b6e59196f94f94147bb62b23c47e"}, "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, - "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, - "membrane_vpx_plugin": {:hex, :membrane_vpx_plugin, "0.3.0", "60404d1b1511b4c62ba6bbf7b6212570f1732ba477015c4072e0aa33e18a8809", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vp9_format, "~> 0.5.0", [hex: :membrane_vp9_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "effa7762bbf73efd8d21d0978bce79538414719284194db97672afbce665b56a"}, - "membrane_webrtc_plugin": {:git, "https://github.com/membraneframework/membrane_webrtc_plugin.git", "c0a4c66cc3b7cd829579a325817ad1727b4474fe", [ref: "c0a4c66cc3b7cd829579a325817ad1727b4474fe"]}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "membrane_webrtc_plugin": {:hex, :membrane_webrtc_plugin, "0.23.3", "77d8819729792c9b53a25b138407d4fc90757750909d05df3418bf73fa724579", [:mix], [{:bandit, "~> 1.2", [hex: :bandit, repo: "hexpm", optional: false]}, {:corsica, "~> 2.0", [hex: :corsica, repo: "hexpm", optional: false]}, {:ex_webrtc, "~> 0.8.0", [hex: :ex_webrtc, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.1", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.4", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.0", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.0", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8723d50d2a917b71598574d68382f70247b7a2876daefca7b457fdb10bc6ecdd"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, - "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, - "mockery": {:hex, :mockery, "2.3.3", "3dba87bd0422a513e6af6e0d811383f38f82ac6be5d3d285a5fcca9c299bd0ac", [:mix], [], "hexpm", "17282be00613286254298117cd25e607a39f15ac03b41c631f60e52f5b5ec974"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, - "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"}, "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.2", "e7b1dd68c86326e2c45cc81da41e332cc8aa7228a7161e2c811dcd7f1dd14db1", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8a40265b0cd7d3a35f136dfa3cc048e3b198fc3718763411a78c323a44ebebee"}, @@ -112,14 +69,10 @@ "ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "shmex": {:hex, :shmex, "0.5.1", "81dd209093416bf6608e66882cb7e676089307448a1afd4fc906c1f7e5b94cf4", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "c29f8286891252f64c4e1dac40b217d960f7d58def597c4e606ff8fbe71ceb80"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, "thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "unifex": {:hex, :unifex, "1.2.1", "6841c170a6e16509fac30b19e4e0a19937c33155a59088b50c15fc2c36251b6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "8c9d2e3c48df031e9995dd16865bab3df402c0295ba3a31f38274bb5314c7d37"}, - "vix": {:hex, :vix, "0.33.0", "cd98084529fd8fe3d2336f157db6de03b297fb096508d820068117d58eadb6f1", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "9acde72b27bdfeadeb51f790f7a6cc0d06cf555718c05cf57e43c5cf93d8471b"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, "zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"}, From 7ef6aeef723b502780d49ad8848b2720fde0feb6 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 19 Feb 2025 14:58:19 +0100 Subject: [PATCH 26/45] Refactor README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6c491d..3e881c2 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ end ## Usage -To use `Phoenix.LiveView`s from this repository, you have to use related JS hooks. To do so, add following snippet to `assets/js/app.js` +To use `Phoenix.LiveView`s from this repository, you have to use related JS hooks. To do so, add the following code snippet to `assets/js/app.js` ```js import { createCaptureHook, createPlayerHook } from "membrane_webrtc_live"; From 745bdf84ddf55f4d676edc705a00abb529baef26 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 19 Feb 2025 15:11:16 +0100 Subject: [PATCH 27/45] Refactor liveview modules attrs --- README.md | 2 +- lib/boombox_live/webrtc/capture.ex | 8 ++++++-- lib/boombox_live/webrtc/player.ex | 8 ++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3e881c2..25bc7b5 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ new LiveSocket("/live", Socket, { }); ``` -To see full usage example, take a look at `example_project/` directory in this repository (take a look especially at `example_project/assets/js/app.js` and `example_project/lib/example_project_web/live_views/echo.ex`). +To see the full usage example, you can go to `example_project/` directory in this repository (take a look especially at `example_project/assets/js/app.js` and `example_project/lib/example_project_web/live_views/echo.ex`). ## Copyright and License diff --git a/lib/boombox_live/webrtc/capture.ex b/lib/boombox_live/webrtc/capture.ex index 1c74406..ab60cd7 100644 --- a/lib/boombox_live/webrtc/capture.ex +++ b/lib/boombox_live/webrtc/capture.ex @@ -69,9 +69,13 @@ defmodule Membrane.WebRTC.Live.Capture do attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") - attr(:capture, __MODULE__, required: true) + attr(:capture, __MODULE__, required: true, doc: """ + #{inspect(__MODULE__)} struct. It is used to pass player id to the newly created live view via live view session. + This data is then used to do a handshake between parent live view and child live view during which child live view + receives the whole #{inspect(__MODULE__)} struct. + """) - attr(:class, :string, default: nil, doc: "CSS/Tailwind classes for styling HTMLVideoElement") + attr(:class, :string, default: nil, doc: "CSS/Tailwind classes for styling") @doc """ Helper function for rendering Capture live view. diff --git a/lib/boombox_live/webrtc/player.ex b/lib/boombox_live/webrtc/player.ex index 269ad50..dd7c926 100644 --- a/lib/boombox_live/webrtc/player.ex +++ b/lib/boombox_live/webrtc/player.ex @@ -72,13 +72,13 @@ defmodule Membrane.WebRTC.Live.Player do attr(:player, __MODULE__, required: true, doc: """ - Player struct. It is used to pass player id and publisher id to the newly created live view via live view session. - This data is then used to do a handshake between parent live view and child live view during which child live view receives - the whole Player struct. + #{inspect(__MODULE__)} struct. It is used to pass player id to the newly created live view via live view session. + This data is then used to do a handshake between parent live view and child live view during which child live view + receives the whole #{inspect(__MODULE__)} struct. """ ) - attr(:class, :string, default: nil, doc: "CSS/Tailwind classes for styling HTMLVideoElement") + attr(:class, :string, default: nil, doc: "CSS/Tailwind classes for styling") @doc """ Helper function for rendering Player live view. From 63a8f3b1b9c5051a2b6c52a1823e148ded6f2e70 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 19 Feb 2025 16:05:03 +0100 Subject: [PATCH 28/45] Refactor moduledocs of player and capture --- lib/boombox_live/webrtc/capture.ex | 54 ++++++++++++++---------------- lib/boombox_live/webrtc/player.ex | 34 +++++++++---------- 2 files changed, 41 insertions(+), 47 deletions(-) diff --git a/lib/boombox_live/webrtc/capture.ex b/lib/boombox_live/webrtc/capture.ex index ab60cd7..2ff482a 100644 --- a/lib/boombox_live/webrtc/capture.ex +++ b/lib/boombox_live/webrtc/capture.ex @@ -1,33 +1,23 @@ defmodule Membrane.WebRTC.Live.Capture do @moduledoc ~S''' - Component for sending and playing audio and video via WebRTC from a Phoenix app to a browser (browser subscribes). + LiveView for capturing audio and video from a browser and sending it via WebRTC to `Membrane.WebRTC.Source`. It: - * renders a single HTMLVideoElement - * creates WebRTC PeerConnection both on the server and client side - * connects those two peer connections negotiating a single audio and a single video track - * attaches audio and video on the client side to the HTMLVideoElement - * subscribes to the configured PubSub where it expects audio and video packets and sends them to the client side. - - When `LiveExWebRTC.Publisher` is used, audio an video packets are delivered automatically, - assuming both components are configured with the same PubSub. - - If `LiveExWebRTC.Publisher` is not used, you should send packets to the - `streams:audio:#{publisher_id}` and `streams:video:#{publisher_id}` topics. - - Keyframe requests are sent under `publishers:#{publisher_id}` topic. + * creates WebRTC PeerConnection on the browser side. + * forwards signaling messages between the browser and `Membrane.WebRTC.Source` via `Membrane.WebRTC.SignalingChannel`. + * sends audio and video streams to the related `Membrane.WebRTC.Source`. ## JavaScript Hook - Player live view requires JavaScript hook to be registered under `Player` name. - The hook can be created using `createPlayerHook` function. + Player live view requires JavaScript hook to be registered under `Capture` name. + The hook can be created using `createCaptureHook` function. For example: ```javascript - import { createPlayerHook } from "live_ex_webrtc"; + import { createCaptureHook } from "membrane_webrtc_live"; let Hooks = {}; const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; - Hooks.Player = createPlayerHook(iceServers); + Hooks.Capture = createCaptureHook(iceServers); let liveSocket = new LiveSocket("/live", Socket, { // ... hooks: Hooks @@ -37,21 +27,26 @@ defmodule Membrane.WebRTC.Live.Capture do ## Examples ```elixir - defmodule LiveTwitchWeb.StreamViewerLive do - use LiveTwitchWeb, :live_view + defmodule StreamerWeb.StreamSenderLive do + use StreamerWeb, :live_view - alias LiveExWebRTC.Player + alias Membrane.WebRTC.Live.Capture @impl true def render(assigns) do ~H""" - + """ end @impl true def mount(_params, _session, socket) do - socket = Player.attach(socket, id: "player", publisher_id: "publisher", pubsub: LiveTwitch.PubSub) + signaling = Membrane.WebRTC.SignalingChannel.new() + {:ok, _supervisor, _pipelne} = Membrane.Pipeline.start_link(MyPipeline, signaling: signaling) + + socket = Capture.attach(socket, id: "capture", signaling: signaling) + socket = assign(socket, :capture, Capture.get_attached(socket, "capture")) + {:ok, socket} end end @@ -69,11 +64,14 @@ defmodule Membrane.WebRTC.Live.Capture do attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") - attr(:capture, __MODULE__, required: true, doc: """ - #{inspect(__MODULE__)} struct. It is used to pass player id to the newly created live view via live view session. - This data is then used to do a handshake between parent live view and child live view during which child live view - receives the whole #{inspect(__MODULE__)} struct. - """) + attr(:capture, __MODULE__, + required: true, + doc: """ + #{inspect(__MODULE__)} struct. It is used to pass player id to the newly created live view via live view session. + This data is then used to do a handshake between parent live view and child live view during which child live view + receives the whole #{inspect(__MODULE__)} struct. + """ + ) attr(:class, :string, default: nil, doc: "CSS/Tailwind classes for styling") diff --git a/lib/boombox_live/webrtc/player.ex b/lib/boombox_live/webrtc/player.ex index dd7c926..ada2bac 100644 --- a/lib/boombox_live/webrtc/player.ex +++ b/lib/boombox_live/webrtc/player.ex @@ -1,21 +1,12 @@ defmodule Membrane.WebRTC.Live.Player do @moduledoc ~S''' - Component for sending and playing audio and video via WebRTC from a Phoenix app to a browser (browser subscribes). + LiveView for playing audio and video get via WebRTC from `Membrane.WebRTC.Sink`. It: - * renders a single HTMLVideoElement - * creates WebRTC PeerConnection both on the server and client side - * connects those two peer connections negotiating a single audio and a single video track - * attaches audio and video on the client side to the HTMLVideoElement - * subscribes to the configured PubSub where it expects audio and video packets and sends them to the client side. - - When `LiveExWebRTC.Publisher` is used, audio an video packets are delivered automatically, - assuming both components are configured with the same PubSub. - - If `LiveExWebRTC.Publisher` is not used, you should send packets to the - `streams:audio:#{publisher_id}` and `streams:video:#{publisher_id}` topics. - - Keyframe requests are sent under `publishers:#{publisher_id}` topic. + * renders a single HTMLVideoElement. + * creates WebRTC PeerConnection on the browser side. + * forwards signaling messages between the browser and `Membrane.WebRTC.Sink` via `Membrane.WebRTC.SignalingChannel`. + * attaches audio and video from the Elixir to the HTMLVideoElement. ## JavaScript Hook @@ -24,7 +15,7 @@ defmodule Membrane.WebRTC.Live.Player do For example: ```javascript - import { createPlayerHook } from "live_ex_webrtc"; + import { createPlayerHook } from "membrane_webrtc_live"; let Hooks = {}; const iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; Hooks.Player = createPlayerHook(iceServers); @@ -37,10 +28,10 @@ defmodule Membrane.WebRTC.Live.Player do ## Examples ```elixir - defmodule LiveTwitchWeb.StreamViewerLive do - use LiveTwitchWeb, :live_view + defmodule StreamerWeb.StreamViewerLive do + use StreamerWeb, :live_view - alias LiveExWebRTC.Player + alias Membrane.WebRTC.Live.Player @impl true def render(assigns) do @@ -51,7 +42,12 @@ defmodule Membrane.WebRTC.Live.Player do @impl true def mount(_params, _session, socket) do - socket = Player.attach(socket, id: "player", publisher_id: "publisher", pubsub: LiveTwitch.PubSub) + signaling = Membrane.WebRTC.SignalingChannel.new() + {:ok, _supervisor, _pipelne} = Membrane.Pipeline.start_link(MyPipeline, signaling: signaling) + + socket = Player.attach(socket, id: "player", signaling: signaling) + socket = assign(socket, :player, Player.get_attached(socket, "player")) + {:ok, socket} end end From cbdc0a7a7e75c5734168b22e60fd6bd700d7a326 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 19 Feb 2025 16:33:12 +0100 Subject: [PATCH 29/45] Remove leftovers --- example_project/assets/js/app.js | 2 -- .../example_project_web/live_views/echo.ex | 7 ----- lib/boombox_live/webrtc/utils.ex | 30 ------------------- 3 files changed, 39 deletions(-) delete mode 100644 lib/boombox_live/webrtc/utils.ex diff --git a/example_project/assets/js/app.js b/example_project/assets/js/app.js index 22ed1ac..dc38d9a 100644 --- a/example_project/assets/js/app.js +++ b/example_project/assets/js/app.js @@ -49,5 +49,3 @@ liveSocket.connect(); // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() window.liveSocket = liveSocket; - -console.log("app.js executed succesfully"); diff --git a/example_project/lib/example_project_web/live_views/echo.ex b/example_project/lib/example_project_web/live_views/echo.ex index 5e70cae..7b5facc 100644 --- a/example_project/lib/example_project_web/live_views/echo.ex +++ b/example_project/lib/example_project_web/live_views/echo.ex @@ -43,10 +43,6 @@ defmodule ExampleProjectWeb.LiveViews.Echo do def render(%{capture: %Capture{}, player: %Player{}} = assigns) do ~H""" - - """ @@ -54,9 +50,6 @@ defmodule ExampleProjectWeb.LiveViews.Echo do def render(assigns) do ~H""" - """ end end diff --git a/lib/boombox_live/webrtc/utils.ex b/lib/boombox_live/webrtc/utils.ex deleted file mode 100644 index e79208e..0000000 --- a/lib/boombox_live/webrtc/utils.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Membrane.WebRTC.Live.Utils do - @moduledoc false - - defmacro log_prefix(id) do - quote do - [module: __MODULE__, id: unquote(id)] - |> inspect() - end - end - - defmacro assing_struct(socket, id, struct) do - quote do - map = - unquote(socket).assigns - |> Map.get(__MODULE__, %{}) - |> Map.put(unquote(id), unquote(struct)) - - unquote(socket) - |> Phoenix.Socket.assign(__MODULE__, map) - end - end - - defmacro get_struct(socket, id) do - quote do - unquote(socket).assigns - |> Map.get(__MODULE__) - |> Map.get(unquote(id)) - end - end -end From d672533d5ab41fb0587f1db29eedf76ecb722039 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 19 Feb 2025 17:28:07 +0100 Subject: [PATCH 30/45] Remove unnecesary modules --- .../lib/example_project/application.ex | 1 - example_project/lib/example_project_web.ex | 1 - .../components/core_components.ex | 676 ------------------ .../components/layouts/app.html.heex | 1 - .../controllers/error_html.ex | 24 - .../controllers/error_json.ex | 21 - .../controllers/page_controller.ex | 9 - .../controllers/page_html.ex | 10 - .../controllers/page_html/home.html.heex | 223 ------ .../lib/example_project_web/endpoint.ex | 1 - .../lib/example_project_web/gettext.ex | 25 - .../lib/example_project_web/telemetry.ex | 69 -- 12 files changed, 1061 deletions(-) delete mode 100644 example_project/lib/example_project_web/components/core_components.ex delete mode 100644 example_project/lib/example_project_web/controllers/error_html.ex delete mode 100644 example_project/lib/example_project_web/controllers/error_json.ex delete mode 100644 example_project/lib/example_project_web/controllers/page_controller.ex delete mode 100644 example_project/lib/example_project_web/controllers/page_html.ex delete mode 100644 example_project/lib/example_project_web/controllers/page_html/home.html.heex delete mode 100644 example_project/lib/example_project_web/gettext.ex delete mode 100644 example_project/lib/example_project_web/telemetry.ex diff --git a/example_project/lib/example_project/application.ex b/example_project/lib/example_project/application.ex index 0957c3f..45343cb 100644 --- a/example_project/lib/example_project/application.ex +++ b/example_project/lib/example_project/application.ex @@ -8,7 +8,6 @@ defmodule ExampleProject.Application do @impl true def start(_type, _args) do children = [ - ExampleProjectWeb.Telemetry, {DNSCluster, query: Application.get_env(:example_project, :dns_cluster_query) || :ignore}, {Phoenix.PubSub, name: ExampleProject.PubSub}, # Start a worker by calling: ExampleProject.Worker.start_link(arg) diff --git a/example_project/lib/example_project_web.ex b/example_project/lib/example_project_web.ex index 3c7d713..786bb1c 100644 --- a/example_project/lib/example_project_web.ex +++ b/example_project/lib/example_project_web.ex @@ -88,7 +88,6 @@ defmodule ExampleProjectWeb do # HTML escaping functionality import Phoenix.HTML # Core UI components - import ExampleProjectWeb.CoreComponents # Shortcut for generating JS commands alias Phoenix.LiveView.JS diff --git a/example_project/lib/example_project_web/components/core_components.ex b/example_project/lib/example_project_web/components/core_components.ex deleted file mode 100644 index 36f3d9b..0000000 --- a/example_project/lib/example_project_web/components/core_components.ex +++ /dev/null @@ -1,676 +0,0 @@ -defmodule ExampleProjectWeb.CoreComponents do - @moduledoc """ - Provides core UI components. - - At first glance, this module may seem daunting, but its goal is to provide - core building blocks for your application, such as modals, tables, and - forms. The components consist mostly of markup and are well-documented - with doc strings and declarative assigns. You may customize and style - them in any way you want, based on your application growth and needs. - - The default components use Tailwind CSS, a utility-first CSS framework. - See the [Tailwind CSS documentation](https://tailwindcss.com) to learn - how to customize them or feel free to swap in another framework altogether. - - Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage. - """ - use Phoenix.Component - use Gettext, backend: ExampleProjectWeb.Gettext - - alias Phoenix.LiveView.JS - - @doc """ - Renders a modal. - - ## Examples - - <.modal id="confirm-modal"> - This is a modal. - - - JS commands may be passed to the `:on_cancel` to configure - the closing/cancel event, for example: - - <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}> - This is another modal. - - - """ - attr :id, :string, required: true - attr :show, :boolean, default: false - attr :on_cancel, JS, default: %JS{} - slot :inner_block, required: true - - def modal(assigns) do - ~H""" - - """ - end - - def input(%{type: "select"} = assigns) do - ~H""" -
- <.label for={@id}>{@label} - - <.error :for={msg <- @errors}>{msg} -
- """ - end - - def input(%{type: "textarea"} = assigns) do - ~H""" -
- <.label for={@id}>{@label} - - <.error :for={msg <- @errors}>{msg} -
- """ - end - - # All other inputs text, datetime-local, url, password, etc. are handled here... - def input(assigns) do - ~H""" -
- <.label for={@id}>{@label} - - <.error :for={msg <- @errors}>{msg} -
- """ - end - - @doc """ - Renders a label. - """ - attr :for, :string, default: nil - slot :inner_block, required: true - - def label(assigns) do - ~H""" - - """ - end - - @doc """ - Generates a generic error message. - """ - slot :inner_block, required: true - - def error(assigns) do - ~H""" -

- <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> - {render_slot(@inner_block)} -

- """ - end - - @doc """ - Renders a header with title. - """ - attr :class, :string, default: nil - - slot :inner_block, required: true - slot :subtitle - slot :actions - - def header(assigns) do - ~H""" -
-
-

- {render_slot(@inner_block)} -

-

- {render_slot(@subtitle)} -

-
-
{render_slot(@actions)}
-
- """ - end - - @doc ~S""" - Renders a table with generic styling. - - ## Examples - - <.table id="users" rows={@users}> - <:col :let={user} label="id">{user.id} - <:col :let={user} label="username">{user.username} - - """ - attr :id, :string, required: true - attr :rows, :list, required: true - attr :row_id, :any, default: nil, doc: "the function for generating the row id" - attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" - - attr :row_item, :any, - default: &Function.identity/1, - doc: "the function for mapping each row before calling the :col and :action slots" - - slot :col, required: true do - attr :label, :string - end - - slot :action, doc: "the slot for showing user actions in the last table column" - - def table(assigns) do - assigns = - with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do - assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) - end - - ~H""" -
- - - - - - - - - - - - - -
{col[:label]} - {gettext("Actions")} -
-
- - - {render_slot(col, @row_item.(row))} - -
-
-
- - - {render_slot(action, @row_item.(row))} - -
-
-
- """ - end - - @doc """ - Renders a data list. - - ## Examples - - <.list> - <:item title="Title">{@post.title} - <:item title="Views">{@post.views} - - """ - slot :item, required: true do - attr :title, :string, required: true - end - - def list(assigns) do - ~H""" -
-
-
-
{item.title}
-
{render_slot(item)}
-
-
-
- """ - end - - @doc """ - Renders a back navigation link. - - ## Examples - - <.back navigate={~p"/posts"}>Back to posts - """ - attr :navigate, :any, required: true - slot :inner_block, required: true - - def back(assigns) do - ~H""" -
- <.link - navigate={@navigate} - class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700" - > - <.icon name="hero-arrow-left-solid" class="h-3 w-3" /> - {render_slot(@inner_block)} - -
- """ - end - - @doc """ - Renders a [Heroicon](https://heroicons.com). - - Heroicons come in three styles – outline, solid, and mini. - By default, the outline style is used, but solid and mini may - be applied by using the `-solid` and `-mini` suffix. - - You can customize the size and colors of the icons by setting - width, height, and background color classes. - - Icons are extracted from the `deps/heroicons` directory and bundled within - your compiled app.css by the plugin in your `assets/tailwind.config.js`. - - ## Examples - - <.icon name="hero-x-mark-solid" /> - <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" /> - """ - attr :name, :string, required: true - attr :class, :string, default: nil - - def icon(%{name: "hero-" <> _} = assigns) do - ~H""" - - """ - end - - ## JS Commands - - def show(js \\ %JS{}, selector) do - JS.show(js, - to: selector, - time: 300, - transition: - {"transition-all transform ease-out duration-300", - "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", - "opacity-100 translate-y-0 sm:scale-100"} - ) - end - - def hide(js \\ %JS{}, selector) do - JS.hide(js, - to: selector, - time: 200, - transition: - {"transition-all transform ease-in duration-200", - "opacity-100 translate-y-0 sm:scale-100", - "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} - ) - end - - def show_modal(js \\ %JS{}, id) when is_binary(id) do - js - |> JS.show(to: "##{id}") - |> JS.show( - to: "##{id}-bg", - time: 300, - transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"} - ) - |> show("##{id}-container") - |> JS.add_class("overflow-hidden", to: "body") - |> JS.focus_first(to: "##{id}-content") - end - - def hide_modal(js \\ %JS{}, id) do - js - |> JS.hide( - to: "##{id}-bg", - transition: {"transition-all transform ease-in duration-200", "opacity-100", "opacity-0"} - ) - |> hide("##{id}-container") - |> JS.hide(to: "##{id}", transition: {"block", "block", "hidden"}) - |> JS.remove_class("overflow-hidden", to: "body") - |> JS.pop_focus() - end - - @doc """ - Translates an error message using gettext. - """ - def translate_error({msg, opts}) do - # When using gettext, we typically pass the strings we want - # to translate as a static argument: - # - # # Translate the number of files with plural rules - # dngettext("errors", "1 file", "%{count} files", count) - # - # However the error messages in our forms and APIs are generated - # dynamically, so we need to translate them by calling Gettext - # with our gettext backend as first argument. Translations are - # available in the errors.po file (as we use the "errors" domain). - if count = opts[:count] do - Gettext.dngettext(ExampleProjectWeb.Gettext, "errors", msg, msg, count, opts) - else - Gettext.dgettext(ExampleProjectWeb.Gettext, "errors", msg, opts) - end - end - - @doc """ - Translates the errors for a field from a keyword list of errors. - """ - def translate_errors(errors, field) when is_list(errors) do - for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) - end -end diff --git a/example_project/lib/example_project_web/components/layouts/app.html.heex b/example_project/lib/example_project_web/components/layouts/app.html.heex index 59a702e..8fbfe45 100644 --- a/example_project/lib/example_project_web/components/layouts/app.html.heex +++ b/example_project/lib/example_project_web/components/layouts/app.html.heex @@ -26,7 +26,6 @@
- <.flash_group flash={@flash} /> {@inner_content}
diff --git a/example_project/lib/example_project_web/controllers/error_html.ex b/example_project/lib/example_project_web/controllers/error_html.ex deleted file mode 100644 index 0823974..0000000 --- a/example_project/lib/example_project_web/controllers/error_html.ex +++ /dev/null @@ -1,24 +0,0 @@ -defmodule ExampleProjectWeb.ErrorHTML do - @moduledoc """ - This module is invoked by your endpoint in case of errors on HTML requests. - - See config/config.exs. - """ - use ExampleProjectWeb, :html - - # If you want to customize your error pages, - # uncomment the embed_templates/1 call below - # and add pages to the error directory: - # - # * lib/example_project_web/controllers/error_html/404.html.heex - # * lib/example_project_web/controllers/error_html/500.html.heex - # - # embed_templates "error_html/*" - - # The default is to render a plain text page based on - # the template name. For example, "404.html" becomes - # "Not Found". - def render(template, _assigns) do - Phoenix.Controller.status_message_from_template(template) - end -end diff --git a/example_project/lib/example_project_web/controllers/error_json.ex b/example_project/lib/example_project_web/controllers/error_json.ex deleted file mode 100644 index b4174b1..0000000 --- a/example_project/lib/example_project_web/controllers/error_json.ex +++ /dev/null @@ -1,21 +0,0 @@ -defmodule ExampleProjectWeb.ErrorJSON do - @moduledoc """ - This module is invoked by your endpoint in case of errors on JSON requests. - - See config/config.exs. - """ - - # If you want to customize a particular status code, - # you may add your own clauses, such as: - # - # def render("500.json", _assigns) do - # %{errors: %{detail: "Internal Server Error"}} - # end - - # By default, Phoenix returns the status message from - # the template name. For example, "404.json" becomes - # "Not Found". - def render(template, _assigns) do - %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} - end -end diff --git a/example_project/lib/example_project_web/controllers/page_controller.ex b/example_project/lib/example_project_web/controllers/page_controller.ex deleted file mode 100644 index 54ad28d..0000000 --- a/example_project/lib/example_project_web/controllers/page_controller.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule ExampleProjectWeb.PageController do - use ExampleProjectWeb, :controller - - def home(conn, _params) do - # The home page is often custom made, - # so skip the default app layout. - render(conn, :home, layout: false) - end -end diff --git a/example_project/lib/example_project_web/controllers/page_html.ex b/example_project/lib/example_project_web/controllers/page_html.ex deleted file mode 100644 index 0eb606b..0000000 --- a/example_project/lib/example_project_web/controllers/page_html.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule ExampleProjectWeb.PageHTML do - @moduledoc """ - This module contains pages rendered by PageController. - - See the `page_html` directory for all templates available. - """ - use ExampleProjectWeb, :html - - embed_templates "page_html/*" -end diff --git a/example_project/lib/example_project_web/controllers/page_html/home.html.heex b/example_project/lib/example_project_web/controllers/page_html/home.html.heex deleted file mode 100644 index 798d8f7..0000000 --- a/example_project/lib/example_project_web/controllers/page_html/home.html.heex +++ /dev/null @@ -1,223 +0,0 @@ - -<.flash_group flash={@flash} /> - -
-
- -

- Phoenix Framework - - v{Application.spec(:phoenix, :vsn)} - -

-

- Peace of mind from prototype to production. -

-

- Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale. -

- -
-
diff --git a/example_project/lib/example_project_web/endpoint.ex b/example_project/lib/example_project_web/endpoint.ex index 8eb9395..0dfc3b0 100644 --- a/example_project/lib/example_project_web/endpoint.ex +++ b/example_project/lib/example_project_web/endpoint.ex @@ -34,7 +34,6 @@ defmodule ExampleProjectWeb.Endpoint do end plug Plug.RequestId - plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json], diff --git a/example_project/lib/example_project_web/gettext.ex b/example_project/lib/example_project_web/gettext.ex deleted file mode 100644 index c90f163..0000000 --- a/example_project/lib/example_project_web/gettext.ex +++ /dev/null @@ -1,25 +0,0 @@ -defmodule ExampleProjectWeb.Gettext do - @moduledoc """ - A module providing Internationalization with a gettext-based API. - - By using [Gettext](https://hexdocs.pm/gettext), your module compiles translations - that you can use in your application. To use this Gettext backend module, - call `use Gettext` and pass it as an option: - - use Gettext, backend: ExampleProjectWeb.Gettext - - # Simple translation - gettext("Here is the string to translate") - - # Plural translation - ngettext("Here is the string to translate", - "Here are the strings to translate", - 3) - - # Domain-based translation - dgettext("errors", "Here is the error message to translate") - - See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. - """ - use Gettext.Backend, otp_app: :example_project -end diff --git a/example_project/lib/example_project_web/telemetry.ex b/example_project/lib/example_project_web/telemetry.ex deleted file mode 100644 index fe93a7e..0000000 --- a/example_project/lib/example_project_web/telemetry.ex +++ /dev/null @@ -1,69 +0,0 @@ -defmodule ExampleProjectWeb.Telemetry do - use Supervisor - import Telemetry.Metrics - - def start_link(arg) do - Supervisor.start_link(__MODULE__, arg, name: __MODULE__) - end - - @impl true - def init(_arg) do - children = [ - # Telemetry poller will execute the given period measurements - # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics - {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} - # Add reporters as children of your supervision tree. - # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} - ] - - Supervisor.init(children, strategy: :one_for_one) - end - - def metrics do - [ - # Phoenix Metrics - summary("phoenix.endpoint.start.system_time", - unit: {:native, :millisecond} - ), - summary("phoenix.endpoint.stop.duration", - unit: {:native, :millisecond} - ), - summary("phoenix.router_dispatch.start.system_time", - tags: [:route], - unit: {:native, :millisecond} - ), - summary("phoenix.router_dispatch.exception.duration", - tags: [:route], - unit: {:native, :millisecond} - ), - summary("phoenix.router_dispatch.stop.duration", - tags: [:route], - unit: {:native, :millisecond} - ), - summary("phoenix.socket_connected.duration", - unit: {:native, :millisecond} - ), - summary("phoenix.channel_joined.duration", - unit: {:native, :millisecond} - ), - summary("phoenix.channel_handled_in.duration", - tags: [:event], - unit: {:native, :millisecond} - ), - - # VM Metrics - summary("vm.memory.total", unit: {:byte, :kilobyte}), - summary("vm.total_run_queue_lengths.total"), - summary("vm.total_run_queue_lengths.cpu"), - summary("vm.total_run_queue_lengths.io") - ] - end - - defp periodic_measurements do - [ - # A module, function and arguments to be invoked periodically. - # This function must call :telemetry.execute/3 and a metric must be added above. - # {ExampleProjectWeb, :count_users, []} - ] - end -end From c1648507fd3222fd524b0160dcaa96115c8dad6a Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 21 Feb 2025 15:38:03 +0100 Subject: [PATCH 31/45] Bump webrtc plugin 0.24.0 --- README.md | 4 ++-- .../example_project_web/live_views/echo.ex | 8 +++---- example_project/mix.exs | 4 ++-- example_project/mix.lock | 8 +++---- lib/boombox_live/webrtc/capture.ex | 24 +++++++++---------- lib/boombox_live/webrtc/player.ex | 20 ++++++++-------- mix.exs | 2 +- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 25bc7b5..c3bb5c4 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ end ## Modules `Membrane.WebRTC.Live` comes with two `Phoenix.LiveView`s: - - `Membrane.WebRTC.Live.Capture` - exchanges WebRTC signaling messages between `Membrane.WebRTC.Source` and the browser. It expects the same `Membrane.WebRTC.SignalingChannel` that has been passed to the related `Membrane.WebRTC.Source`. As a result, `Membrane.Webrtc.Source` will return the media stream captured from the browser, where `Membrane.WebRTC.Live.Capture` has been rendered. - - `Membrane.WebRTC.Live.Player` - exchanges WebRTC signaling messages between `Membrane.WebRTC.Sink` and the browser. It expects the same `Membrane.WebRTC.SignalingChannel` that has been passed to the related `Membrane.WebRTC.Sink`. As a result, `Membrane.WebRTC.Live.Player` will play media streams passed to the related `Membrane.WebRTC.Sink`. Currently supports up to one video stream and up to one audio stream. + - `Membrane.WebRTC.Live.Capture` - exchanges WebRTC signaling messages between `Membrane.WebRTC.Source` and the browser. It expects the same `Membrane.WebRTC.Signaling` that has been passed to the related `Membrane.WebRTC.Source`. As a result, `Membrane.Webrtc.Source` will return the media stream captured from the browser, where `Membrane.WebRTC.Live.Capture` has been rendered. + - `Membrane.WebRTC.Live.Player` - exchanges WebRTC signaling messages between `Membrane.WebRTC.Sink` and the browser. It expects the same `Membrane.WebRTC.Signaling` that has been passed to the related `Membrane.WebRTC.Sink`. As a result, `Membrane.WebRTC.Live.Player` will play media streams passed to the related `Membrane.WebRTC.Sink`. Currently supports up to one video stream and up to one audio stream. ## Usage diff --git a/example_project/lib/example_project_web/live_views/echo.ex b/example_project/lib/example_project_web/live_views/echo.ex index 7b5facc..d208e95 100644 --- a/example_project/lib/example_project_web/live_views/echo.ex +++ b/example_project/lib/example_project_web/live_views/echo.ex @@ -5,8 +5,8 @@ defmodule ExampleProjectWeb.LiveViews.Echo do def mount(_params, _session, socket) do socket = if connected?(socket) do - ingress_signaling = Membrane.WebRTC.SignalingChannel.new() - egress_signaling = Membrane.WebRTC.SignalingChannel.new() + ingress_signaling = Membrane.WebRTC.Signaling.new() + egress_signaling = Membrane.WebRTC.Signaling.new() {:ok, _boombox_pid} = Task.start_link(fn -> @@ -20,13 +20,13 @@ defmodule ExampleProjectWeb.LiveViews.Echo do socket |> Capture.attach( id: "mediaCapture", - signaling_channel: ingress_signaling, + signaling: ingress_signaling, audio?: false, video?: true ) |> Player.attach( id: "videoPlayer", - signaling_channel: egress_signaling + signaling: egress_signaling ) socket diff --git a/example_project/mix.exs b/example_project/mix.exs index eba421c..06f2202 100644 --- a/example_project/mix.exs +++ b/example_project/mix.exs @@ -33,9 +33,9 @@ defmodule ExampleProject.MixProject do defp deps do [ {:boombox, - github: "membraneframework/boombox", ref: "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1"}, + github: "membraneframework/boombox", ref: "f4ccbfcf4a71d14764fd269b3491d4c862c4d4c2"}, {:membrane_webrtc_live, path: "../"}, - {:membrane_webrtc_plugin, "~> 0.23.3", override: true}, + {:membrane_webrtc_plugin, "~> 0.24.0", override: true}, {:phoenix, "~> 1.7.19"}, {:phoenix_html, "~> 4.1"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, diff --git a/example_project/mix.lock b/example_project/mix.lock index c63b65f..dacef03 100644 --- a/example_project/mix.lock +++ b/example_project/mix.lock @@ -1,7 +1,7 @@ %{ "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, - "boombox": {:git, "https://github.com/membraneframework/boombox.git", "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1", [ref: "0dae52a0d3023f32c3039ae1eeb353e1433aa2a1"]}, + "boombox": {:git, "https://github.com/membraneframework/boombox.git", "f4ccbfcf4a71d14764fd269b3491d4c862c4d4c2", [ref: "f4ccbfcf4a71d14764fd269b3491d4c862c4d4c2"]}, "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, @@ -19,7 +19,7 @@ "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "esbuild": {:hex, :esbuild, "0.9.0", "f043eeaca4932ca8e16e5429aebd90f7766f31ac160a25cbd9befe84f2bc068f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b415027f71d5ab57ef2be844b2a10d0c1b5a492d431727f43937adce22ba45ae"}, "ex_dtls": {:hex, :ex_dtls, "0.16.0", "3ae38025ccc77f6db573e2e391602fa9bbc02253c137d8d2d59469a66cbe806b", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2a4e30d74c6ddf95cc5b796423293c06a0da295454c3823819808ff031b4b361"}, - "ex_ice": {:hex, :ex_ice, "0.9.3", "46700963acaba72737032500b6ee298a4effa7ad7189ab48887be5e9f4fe2107", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "4fd98d20a39ab70a62dd301c44e87437d479292c528ec7f21522ebfe0654b9cb"}, + "ex_ice": {:hex, :ex_ice, "0.9.4", "793121989164e49d8dc64b82bcb7842a4c2e0d224a2f00379ab415293a78c8e7", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "fc328ed721c558440266def81a2cd5138d163164218ebe449fa9a10fcda72574"}, "ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"}, "ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"}, "ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"}, @@ -79,12 +79,12 @@ "membrane_tee_plugin": {:hex, :membrane_tee_plugin, "0.12.0", "f94989b4080ef4b7937d74c1a14d3379577c7bd4c6d06e5a2bb41c351ad604d4", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "0d61c9ed5e68e5a75d54200e1c6df5739c0bcb52fee0974183ad72446a179887"}, "membrane_telemetry_metrics": {:hex, :membrane_telemetry_metrics, "0.1.1", "57917e72012f9ebe124eab54f29ca74c9d9eb3ae2207f55c95618ee51280eb4f", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "69392966e6bd51937244758c2b3d835c5ff47d8953d25431a9d37059737afc11"}, "membrane_timestamp_queue": {:hex, :membrane_timestamp_queue, "0.2.2", "1c831b2273d018a6548654aa9f7fa7c4b683f71d96ffe164934ef55f9d11f693", [:mix], [{:heap, "~> 2.0", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7c830e760baaced0988421671cd2c83c7cda8d1bd2b61fd05332711675d1204f"}, - "membrane_transcoder_plugin": {:hex, :membrane_transcoder_plugin, "0.1.2", "5d5546971289109423c1d02c4a45bfce7133293d76f5850824663a4613e5a728", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.0", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.20.0", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.1", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.32.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_ffmpeg_plugin, "~> 0.4.2", [hex: :membrane_h265_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.20.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.2", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vpx_plugin, "~> 0.3.0", [hex: :membrane_vpx_plugin, repo: "hexpm", optional: false]}], "hexpm", "50297b922fdd39520125120782bf679f26be8965b31565b470fb51b6ef908873"}, + "membrane_transcoder_plugin": {:git, "https://github.com/membraneframework/membrane_transcoder_plugin.git", "9a6fbbb2318b5ac4c2ecbd77c5845d3e0ca62531", [ref: "9a6fbbb2318b5ac4c2ecbd77c5845d3e0ca62531"]}, "membrane_udp_plugin": {:hex, :membrane_udp_plugin, "0.14.0", "d533ee5f6fcdd0551ad690045cdb6c1a76307a155d9255cc4a4606f85774bc37", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "902d1a7aa228ec377482d53a605b100e20e0b6e59196f94f94147bb62b23c47e"}, "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, "membrane_vpx_plugin": {:hex, :membrane_vpx_plugin, "0.3.0", "60404d1b1511b4c62ba6bbf7b6212570f1732ba477015c4072e0aa33e18a8809", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vp9_format, "~> 0.5.0", [hex: :membrane_vp9_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.2", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "effa7762bbf73efd8d21d0978bce79538414719284194db97672afbce665b56a"}, - "membrane_webrtc_plugin": {:hex, :membrane_webrtc_plugin, "0.23.3", "77d8819729792c9b53a25b138407d4fc90757750909d05df3418bf73fa724579", [:mix], [{:bandit, "~> 1.2", [hex: :bandit, repo: "hexpm", optional: false]}, {:corsica, "~> 2.0", [hex: :corsica, repo: "hexpm", optional: false]}, {:ex_webrtc, "~> 0.8.0", [hex: :ex_webrtc, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.1", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.4", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.0", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.0", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8723d50d2a917b71598574d68382f70247b7a2876daefca7b457fdb10bc6ecdd"}, + "membrane_webrtc_plugin": {:hex, :membrane_webrtc_plugin, "0.24.0", "20606f4f7082690598f2f30e0097f7708f46098b2d84cc8f98977e4341e510c1", [:mix], [{:bandit, "~> 1.2", [hex: :bandit, repo: "hexpm", optional: false]}, {:corsica, "~> 2.0", [hex: :corsica, repo: "hexpm", optional: false]}, {:ex_webrtc, "~> 0.8.0", [hex: :ex_webrtc, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.1", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.4", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.0", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.0", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "25e88a0d7ce8298dcf8cd1155d77efc55fffb20d0ae844cd80b20300d1875d04"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, diff --git a/lib/boombox_live/webrtc/capture.ex b/lib/boombox_live/webrtc/capture.ex index 2ff482a..6851538 100644 --- a/lib/boombox_live/webrtc/capture.ex +++ b/lib/boombox_live/webrtc/capture.ex @@ -4,12 +4,12 @@ defmodule Membrane.WebRTC.Live.Capture do It: * creates WebRTC PeerConnection on the browser side. - * forwards signaling messages between the browser and `Membrane.WebRTC.Source` via `Membrane.WebRTC.SignalingChannel`. + * forwards signaling messages between the browser and `Membrane.WebRTC.Source` via `Membrane.WebRTC.Signaling`. * sends audio and video streams to the related `Membrane.WebRTC.Source`. ## JavaScript Hook - Player live view requires JavaScript hook to be registered under `Capture` name. + Capture live view requires JavaScript hook to be registered under `Capture` name. The hook can be created using `createCaptureHook` function. For example: @@ -41,7 +41,7 @@ defmodule Membrane.WebRTC.Live.Capture do @impl true def mount(_params, _session, socket) do - signaling = Membrane.WebRTC.SignalingChannel.new() + signaling = Membrane.WebRTC.Signaling.new() {:ok, _supervisor, _pipelne} = Membrane.Pipeline.start_link(MyPipeline, signaling: signaling) socket = Capture.attach(socket, id: "capture", signaling: signaling) @@ -54,20 +54,20 @@ defmodule Membrane.WebRTC.Live.Capture do ''' use Phoenix.LiveView - alias Membrane.WebRTC.SignalingChannel + alias Membrane.WebRTC.Signaling require Logger @type t() :: struct() - defstruct [:ice_servers, id: nil, signaling_channel: nil, video?: true, audio?: true] + defstruct [:ice_servers, id: nil, signaling: nil, video?: true, audio?: true] attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") attr(:capture, __MODULE__, required: true, doc: """ - #{inspect(__MODULE__)} struct. It is used to pass player id to the newly created live view via live view session. + #{inspect(__MODULE__)} struct. It is used to pass capture id to the newly created live view via live view session. This data is then used to do a handshake between parent live view and child live view during which child live view receives the whole #{inspect(__MODULE__)} struct. """ @@ -100,7 +100,7 @@ defmodule Membrane.WebRTC.Live.Capture do opts |> Keyword.validate!([ :id, - :signaling_channel, + :signaling, video?: true, audio?: true, ice_servers: [%{urls: "stun:stun.l.google.com:19302"}] @@ -162,8 +162,8 @@ defmodule Membrane.WebRTC.Live.Capture do socket = receive do %__MODULE__{} = capture -> - capture.signaling_channel - |> SignalingChannel.register_peer(message_format: :json_data) + capture.signaling + |> Signaling.register_peer(message_format: :json_data) media_constraints = %{ "audio" => inspect(capture.audio?), @@ -184,7 +184,7 @@ defmodule Membrane.WebRTC.Live.Capture do end @impl true - def handle_info({SignalingChannel, _pid, message, _metadata}, socket) do + def handle_info({Signaling, _pid, message, _metadata}, socket) do Logger.info(""" #{log_prefix(socket.assigns.capture.id)} Sent WebRTC signaling message: #{inspect(message, pretty: true)} """) @@ -203,8 +203,8 @@ defmodule Membrane.WebRTC.Live.Capture do """) if message["data"] do - SignalingChannel.signal( - socket.assigns.capture.signaling_channel, + Signaling.signal( + socket.assigns.capture.signaling, message ) end diff --git a/lib/boombox_live/webrtc/player.ex b/lib/boombox_live/webrtc/player.ex index ada2bac..2a98a1a 100644 --- a/lib/boombox_live/webrtc/player.ex +++ b/lib/boombox_live/webrtc/player.ex @@ -5,7 +5,7 @@ defmodule Membrane.WebRTC.Live.Player do It: * renders a single HTMLVideoElement. * creates WebRTC PeerConnection on the browser side. - * forwards signaling messages between the browser and `Membrane.WebRTC.Sink` via `Membrane.WebRTC.SignalingChannel`. + * forwards signaling messages between the browser and `Membrane.WebRTC.Sink` via `Membrane.WebRTC.Signaling`. * attaches audio and video from the Elixir to the HTMLVideoElement. ## JavaScript Hook @@ -42,7 +42,7 @@ defmodule Membrane.WebRTC.Live.Player do @impl true def mount(_params, _session, socket) do - signaling = Membrane.WebRTC.SignalingChannel.new() + signaling = Membrane.WebRTC.Signaling.new() {:ok, _supervisor, _pipelne} = Membrane.Pipeline.start_link(MyPipeline, signaling: signaling) socket = Player.attach(socket, id: "player", signaling: signaling) @@ -55,13 +55,13 @@ defmodule Membrane.WebRTC.Live.Player do ''' use Phoenix.LiveView - alias Membrane.WebRTC.SignalingChannel + alias Membrane.WebRTC.Signaling require Logger @type t() :: struct() - defstruct [:video?, :audio?, :ice_servers, id: nil, signaling_channel: nil] + defstruct [:video?, :audio?, :ice_servers, id: nil, signaling: nil] attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") @@ -101,7 +101,7 @@ defmodule Membrane.WebRTC.Live.Player do opts |> Keyword.validate!([ :id, - :signaling_channel, + :signaling, video?: true, audio?: true, ice_servers: [%{urls: "stun:stun.l.google.com:19302"}] @@ -163,8 +163,8 @@ defmodule Membrane.WebRTC.Live.Player do socket = receive do %__MODULE__{} = player -> - player.signaling_channel - |> SignalingChannel.register_peer(message_format: :json_data) + player.signaling + |> Signaling.register_peer(message_format: :json_data) socket |> assign(player: player) after @@ -178,7 +178,7 @@ defmodule Membrane.WebRTC.Live.Player do end @impl true - def handle_info({SignalingChannel, _pid, message, _metadata}, socket) do + def handle_info({Signaling, _pid, message, _metadata}, socket) do Logger.info(""" #{log_prefix(socket.assigns.player.id)} Sent WebRTC signaling message: #{inspect(message, pretty: true)} """) @@ -197,8 +197,8 @@ defmodule Membrane.WebRTC.Live.Player do """) if message["data"] do - SignalingChannel.signal( - socket.assigns.player.signaling_channel, + Signaling.signal( + socket.assigns.player.signaling, message ) end diff --git a/mix.exs b/mix.exs index 350df2a..c715edc 100644 --- a/mix.exs +++ b/mix.exs @@ -37,7 +37,7 @@ defmodule Membrane.WebRTC.Live.Mixfile do defp deps do [ - {:membrane_webrtc_plugin, "~> 0.23.3"}, + {:membrane_webrtc_plugin, "~> 0.24.0"}, {:plug_cowboy, "~> 2.5"}, {:phoenix_live_view, "~> 1.0"}, {:phoenix, "~> 1.7"}, From 30d3ca57797c0ec6e7ad718da7534c596836d632 Mon Sep 17 00:00:00 2001 From: Mateusz Front Date: Mon, 24 Feb 2025 11:56:29 +0100 Subject: [PATCH 32/45] simplify liveviews APIs --- .../example_project_web/live_views/echo.ex | 34 ++++++------------- lib/boombox_live/webrtc/capture.ex | 4 +-- lib/boombox_live/webrtc/player.ex | 4 +-- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/example_project/lib/example_project_web/live_views/echo.ex b/example_project/lib/example_project_web/live_views/echo.ex index d208e95..957bbb7 100644 --- a/example_project/lib/example_project_web/live_views/echo.ex +++ b/example_project/lib/example_project_web/live_views/echo.ex @@ -16,23 +16,16 @@ defmodule ExampleProjectWeb.LiveViews.Echo do ) end) - socket = - socket - |> Capture.attach( - id: "mediaCapture", - signaling: ingress_signaling, - audio?: false, - video?: true - ) - |> Player.attach( - id: "videoPlayer", - signaling: egress_signaling - ) - socket - |> assign( - capture: Capture.get_attached(socket, "mediaCapture"), - player: Player.get_attached(socket, "videoPlayer") + |> Capture.attach( + id: "mediaCapture", + signaling: ingress_signaling, + audio?: false, + video?: true + ) + |> Player.attach( + id: "videoPlayer", + signaling: egress_signaling ) else socket @@ -41,15 +34,10 @@ defmodule ExampleProjectWeb.LiveViews.Echo do {:ok, socket} end - def render(%{capture: %Capture{}, player: %Player{}} = assigns) do - ~H""" - - - """ - end - def render(assigns) do ~H""" + + """ end end diff --git a/lib/boombox_live/webrtc/capture.ex b/lib/boombox_live/webrtc/capture.ex index 6851538..9ef5afa 100644 --- a/lib/boombox_live/webrtc/capture.ex +++ b/lib/boombox_live/webrtc/capture.ex @@ -64,7 +64,7 @@ defmodule Membrane.WebRTC.Live.Capture do attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") - attr(:capture, __MODULE__, + attr(:capture, :string, required: true, doc: """ #{inspect(__MODULE__)} struct. It is used to pass capture id to the newly created live view via live view session. @@ -80,7 +80,7 @@ defmodule Membrane.WebRTC.Live.Capture do """ def live_render(assigns) do ~H""" - <%= live_render(@socket, __MODULE__, id: "#{@capture.id}-lv", session: %{"class" => @class, "id" => @capture.id}) %> + <%= live_render(@socket, __MODULE__, id: "#{@capture}-lv", session: %{"class" => @class, "id" => @capture}) %> """ end diff --git a/lib/boombox_live/webrtc/player.ex b/lib/boombox_live/webrtc/player.ex index 2a98a1a..5b3a1d3 100644 --- a/lib/boombox_live/webrtc/player.ex +++ b/lib/boombox_live/webrtc/player.ex @@ -65,7 +65,7 @@ defmodule Membrane.WebRTC.Live.Player do attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") - attr(:player, __MODULE__, + attr(:player, :string, required: true, doc: """ #{inspect(__MODULE__)} struct. It is used to pass player id to the newly created live view via live view session. @@ -81,7 +81,7 @@ defmodule Membrane.WebRTC.Live.Player do """ def live_render(assigns) do ~H""" - <%= live_render(@socket, __MODULE__, id: "#{@player.id}-lv", session: %{"class" => @class, "id" => @player.id}) %> + <%= live_render(@socket, __MODULE__, id: "#{@player}-lv", session: %{"class" => @class, "id" => @player}) %> """ end From 10e41947b89ae51f63f849fdcdf7b98ab8e61d43 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 25 Feb 2025 16:13:02 +0100 Subject: [PATCH 33/45] Partially implement CR --- example_project/lib/example_project_web/live_views/echo.ex | 2 +- lib/{boombox_live/webrtc => membrane_webrtc_live}/capture.ex | 0 lib/{boombox_live/webrtc => membrane_webrtc_live}/player.ex | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename lib/{boombox_live/webrtc => membrane_webrtc_live}/capture.ex (100%) rename lib/{boombox_live/webrtc => membrane_webrtc_live}/player.ex (100%) diff --git a/example_project/lib/example_project_web/live_views/echo.ex b/example_project/lib/example_project_web/live_views/echo.ex index 957bbb7..75da083 100644 --- a/example_project/lib/example_project_web/live_views/echo.ex +++ b/example_project/lib/example_project_web/live_views/echo.ex @@ -8,7 +8,7 @@ defmodule ExampleProjectWeb.LiveViews.Echo do ingress_signaling = Membrane.WebRTC.Signaling.new() egress_signaling = Membrane.WebRTC.Signaling.new() - {:ok, _boombox_pid} = + {:ok, _task_pid} = Task.start_link(fn -> Boombox.run( input: {:webrtc, ingress_signaling}, diff --git a/lib/boombox_live/webrtc/capture.ex b/lib/membrane_webrtc_live/capture.ex similarity index 100% rename from lib/boombox_live/webrtc/capture.ex rename to lib/membrane_webrtc_live/capture.ex diff --git a/lib/boombox_live/webrtc/player.ex b/lib/membrane_webrtc_live/player.ex similarity index 100% rename from lib/boombox_live/webrtc/player.ex rename to lib/membrane_webrtc_live/player.ex From 14f9209a0ce072fae63b4a8e847102d8504d0c65 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 25 Feb 2025 17:29:51 +0100 Subject: [PATCH 34/45] Add captured video preview --- assets/capture.js | 5 +++++ example_project/config/config.exs | 3 ++- lib/membrane_webrtc_live/capture.ex | 25 +++++++++++++++++++------ lib/membrane_webrtc_live/player.ex | 19 +++++-------------- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/assets/capture.js b/assets/capture.js index 0e6714e..46f7950 100644 --- a/assets/capture.js +++ b/assets/capture.js @@ -22,10 +22,15 @@ export function createCaptureHook(iceServers = [{ urls: "stun:stun.l.google.com: ); }; + this.el.srcObject = new MediaStream(); + for (const track of localStream.getTracks()) { this.pc.addTrack(track, localStream); + this.el.srcObject.addTrack(track); } + this.el.play(); + this.handleEvent("webrtc_signaling-" + this.el.id, async (event) => { const { type, data } = event; diff --git a/example_project/config/config.exs b/example_project/config/config.exs index 0eeb1d8..4e81c3c 100644 --- a/example_project/config/config.exs +++ b/example_project/config/config.exs @@ -34,7 +34,8 @@ config :esbuild, # Configures Elixir's Logger config :logger, :console, format: "$time $metadata[$level] $message\n", - metadata: [:request_id] + metadata: [:request_id], + level: :info # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason diff --git a/lib/membrane_webrtc_live/capture.ex b/lib/membrane_webrtc_live/capture.ex index 9ef5afa..016debc 100644 --- a/lib/membrane_webrtc_live/capture.ex +++ b/lib/membrane_webrtc_live/capture.ex @@ -60,7 +60,7 @@ defmodule Membrane.WebRTC.Live.Capture do @type t() :: struct() - defstruct [:ice_servers, id: nil, signaling: nil, video?: true, audio?: true] + defstruct [id: nil, signaling: nil, video?: true, audio?: true, preview?: true] attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") @@ -103,7 +103,7 @@ defmodule Membrane.WebRTC.Live.Capture do :signaling, video?: true, audio?: true, - ice_servers: [%{urls: "stun:stun.l.google.com:19302"}] + preview?: true ]) capture = struct!(__MODULE__, opts) @@ -146,9 +146,22 @@ defmodule Membrane.WebRTC.Live.Capture do end @impl true - def render(assigns) do + def render(%{capture: %__MODULE__{preview?: true}} = assigns) do ~H""" - + + """ + end + + @impl true + def render(%{capture: %__MODULE__{preview?: false}} = assigns) do + ~H""" + """ end @@ -185,7 +198,7 @@ defmodule Membrane.WebRTC.Live.Capture do @impl true def handle_info({Signaling, _pid, message, _metadata}, socket) do - Logger.info(""" + Logger.debug(""" #{log_prefix(socket.assigns.capture.id)} Sent WebRTC signaling message: #{inspect(message, pretty: true)} """) @@ -198,7 +211,7 @@ defmodule Membrane.WebRTC.Live.Capture do def handle_event("webrtc_signaling", message, socket) do message = Jason.decode!(message) - Logger.info(""" + Logger.debug(""" #{log_prefix(socket.assigns.capture.id)} Received WebRTC signaling message: #{inspect(message, pretty: true)} """) diff --git a/lib/membrane_webrtc_live/player.ex b/lib/membrane_webrtc_live/player.ex index 5b3a1d3..3ea32fb 100644 --- a/lib/membrane_webrtc_live/player.ex +++ b/lib/membrane_webrtc_live/player.ex @@ -61,7 +61,7 @@ defmodule Membrane.WebRTC.Live.Player do @type t() :: struct() - defstruct [:video?, :audio?, :ice_servers, id: nil, signaling: nil] + defstruct id: nil, signaling: nil attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") @@ -97,16 +97,7 @@ defmodule Membrane.WebRTC.Live.Player do """ @spec attach(Phoenix.LiveView.Socket.t(), Keyword.t()) :: Phoenix.LiveView.Socket.t() def attach(socket, opts) do - opts = - opts - |> Keyword.validate!([ - :id, - :signaling, - video?: true, - audio?: true, - ice_servers: [%{urls: "stun:stun.l.google.com:19302"}] - ]) - + opts = opts |> Keyword.validate!([:id, :signaling]) player = struct!(__MODULE__, opts) all_players = @@ -149,7 +140,7 @@ defmodule Membrane.WebRTC.Live.Player do @impl true def render(assigns) do ~H""" - + """ end @@ -179,7 +170,7 @@ defmodule Membrane.WebRTC.Live.Player do @impl true def handle_info({Signaling, _pid, message, _metadata}, socket) do - Logger.info(""" + Logger.debug(""" #{log_prefix(socket.assigns.player.id)} Sent WebRTC signaling message: #{inspect(message, pretty: true)} """) @@ -192,7 +183,7 @@ defmodule Membrane.WebRTC.Live.Player do def handle_event("webrtc_signaling", message, socket) do message = Jason.decode!(message) - Logger.info(""" + Logger.debug(""" #{log_prefix(socket.assigns.player.id)} Received WebRTC signaling message: #{inspect(message, pretty: true)} """) From 88dd3377a260c0ee93afa3f26726ba94a490071b Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 26 Feb 2025 12:35:03 +0100 Subject: [PATCH 35/45] Refactor player and capture --- example_project/assets/vendor/topbar.js | 26 ++---- lib/membrane_webrtc_live/capture.ex | 107 ++++++++++++------------ lib/membrane_webrtc_live/player.ex | 93 ++++++++++---------- 3 files changed, 109 insertions(+), 117 deletions(-) diff --git a/example_project/assets/vendor/topbar.js b/example_project/assets/vendor/topbar.js index 4195727..4e2445f 100644 --- a/example_project/assets/vendor/topbar.js +++ b/example_project/assets/vendor/topbar.js @@ -12,8 +12,7 @@ var lastTime = 0; var vendors = ["ms", "moz", "webkit", "o"]; for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { - window.requestAnimationFrame = - window[vendors[x] + "RequestAnimationFrame"]; + window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"]; window.cancelAnimationFrame = window[vendors[x] + "CancelAnimationFrame"] || window[vendors[x] + "CancelRequestAnimationFrame"]; @@ -68,15 +67,11 @@ ctx.shadowColor = options.shadowColor; var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); - for (var stop in options.barColors) - lineGradient.addColorStop(stop, options.barColors[stop]); + for (var stop in options.barColors) lineGradient.addColorStop(stop, options.barColors[stop]); ctx.lineWidth = options.barThickness; ctx.beginPath(); ctx.moveTo(0, options.barThickness / 2); - ctx.lineTo( - Math.ceil(currentProgress * canvas.width), - options.barThickness / 2 - ); + ctx.lineTo(Math.ceil(currentProgress * canvas.width), options.barThickness / 2); ctx.strokeStyle = lineGradient; ctx.stroke(); }, @@ -93,15 +88,14 @@ }, topbar = { config: function (opts) { - for (var key in opts) - if (options.hasOwnProperty(key)) options[key] = opts[key]; + for (var key in opts) if (options.hasOwnProperty(key)) options[key] = opts[key]; }, show: function (delay) { if (showing) return; if (delay) { if (delayTimerId) return; delayTimerId = setTimeout(() => topbar.show(), delay); - } else { + } else { showing = true; if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); if (!canvas) createCanvas(); @@ -111,9 +105,7 @@ if (options.autoRun) { (function loop() { progressTimerId = window.requestAnimationFrame(loop); - topbar.progress( - "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) - ); + topbar.progress("+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)); })(); } } @@ -122,9 +114,7 @@ if (typeof to === "undefined") return currentProgress; if (typeof to === "string") { to = - (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 - ? currentProgress - : 0) + parseFloat(to); + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 ? currentProgress : 0) + parseFloat(to); } currentProgress = to > 1 ? 1 : to; repaint(); @@ -162,4 +152,4 @@ } else { this.topbar = topbar; } -}.call(this, window, document)); +}).call(this, window, document); diff --git a/lib/membrane_webrtc_live/capture.ex b/lib/membrane_webrtc_live/capture.ex index 016debc..804ee2a 100644 --- a/lib/membrane_webrtc_live/capture.ex +++ b/lib/membrane_webrtc_live/capture.ex @@ -35,7 +35,7 @@ defmodule Membrane.WebRTC.Live.Capture do @impl true def render(assigns) do ~H""" - + """ end @@ -44,9 +44,7 @@ defmodule Membrane.WebRTC.Live.Capture do signaling = Membrane.WebRTC.Signaling.new() {:ok, _supervisor, _pipelne} = Membrane.Pipeline.start_link(MyPipeline, signaling: signaling) - socket = Capture.attach(socket, id: "capture", signaling: signaling) - socket = assign(socket, :capture, Capture.get_attached(socket, "capture")) - + socket = socket |> Capture.attach(id: "capture", signaling: signaling) {:ok, socket} end end @@ -58,9 +56,9 @@ defmodule Membrane.WebRTC.Live.Capture do require Logger - @type t() :: struct() + @type t() :: %__MODULE__{} - defstruct [id: nil, signaling: nil, video?: true, audio?: true, preview?: true] + defstruct id: nil, signaling: nil, video?: true, audio?: true, preview?: true attr(:socket, Phoenix.LiveView.Socket, required: true, doc: "Parent live view socket") @@ -87,12 +85,17 @@ defmodule Membrane.WebRTC.Live.Capture do @doc """ Attaches required hooks and creates `t:t/0` struct. - Created struct is saved in socket's assigns and has to be passed to `LiveExWebRTC.Player.live_render/1`. + Created struct is saved in socket's assigns (in `socket.assigns[#{inspect(__MODULE__)}][id]`) and then + it is sent by an attached hook to a child live view process. Options: - * `id` - capture id. This is typically your user id (if there is users database). - It is used to identify live view and generated HTML video player. - * `class` - a list of CSS/Tailwind classes that will be applied to the HTMLVideoPlayer. Defaults to "". + * `id` - capture id. It is used to identify live view and generated HTML video player. It must be unique + withing single page. + * `signaling` - `Membrane.WebRTC.Signaling.t()`, that has been passed to `Membrane.Webrtc.Source` as well. + * `video?` - if `true`, the video stream from the computer camera will be captured. Defaults to `true`. + * `audio?` - if `true`, the audio stream from the computer mic will be captured. Defaults to `true`. + * `preview?` - if `true`, the function `#{inspect(__MODULE__)}.live_render/1` return a video HTML tag + with attached captured video stream. Defaults to `true`. """ @spec attach(Phoenix.LiveView.Socket.t(), Keyword.t()) :: Phoenix.LiveView.Socket.t() def attach(socket, opts) do @@ -116,27 +119,12 @@ defmodule Membrane.WebRTC.Live.Capture do socket |> assign(__MODULE__, all_captures) |> detach_hook(:capture_handshake, :handle_info) - |> attach_hook(:capture_handshake, :handle_info, &handshake/2) + |> attach_hook(:capture_handshake, :handle_info, &parent_handshake/2) end + @spec get_attached(Phoenix.LiveView.Socket.t(), String.t()) :: t() def get_attached(socket, id), do: socket.assigns[__MODULE__][id] - defp handshake({__MODULE__, {:connected, capture_id, child_pid, _meta}}, socket) do - # child live view is connected, send it capture struct - capture = - socket.assigns - |> Map.fetch!(__MODULE__) - |> Map.fetch!(capture_id) - - send(child_pid, capture) - - {:halt, socket} - end - - defp handshake(_msg, socket) do - {:cont, socket} - end - ## CALLBACKS @impl true @@ -167,32 +155,49 @@ defmodule Membrane.WebRTC.Live.Capture do @impl true def mount(_params, %{"class" => class, "id" => id}, socket) do - socket = assign(socket, class: class, capture: nil) + socket = socket |> assign(class: class, capture: nil) - if connected?(socket) do - send(socket.parent_pid, {__MODULE__, {:connected, id, self(), %{}}}) + socket = + if connected?(socket), + do: socket |> client_handshake(id), + else: socket - socket = - receive do - %__MODULE__{} = capture -> - capture.signaling - |> Signaling.register_peer(message_format: :json_data) + {:ok, socket} + end - media_constraints = %{ - "audio" => inspect(capture.audio?), - "video" => inspect(capture.video?) - } + defp parent_handshake({__MODULE__, {:connected, id, capture_pid}}, socket) do + capture_struct = + socket.assigns + |> Map.fetch!(__MODULE__) + |> Map.fetch!(id) - socket - |> assign(capture: capture) - |> push_event("media_constraints-#{capture.id}", media_constraints) - after - 5000 -> exit(:timeout) - end + send(capture_pid, capture_struct) - {:ok, socket} - else - {:ok, socket} + {:halt, socket} + end + + defp parent_handshake(_msg, socket) do + {:cont, socket} + end + + defp client_handshake(socket, id) do + send(socket.parent_pid, {__MODULE__, {:connected, id, self()}}) + + receive do + %__MODULE__{} = capture -> + capture.signaling + |> Signaling.register_peer(message_format: :json_data) + + media_constraints = %{ + "audio" => inspect(capture.audio?), + "video" => inspect(capture.video?) + } + + socket + |> assign(capture: capture) + |> push_event("media_constraints-#{capture.id}", media_constraints) + after + 5000 -> exit(:timeout) end end @@ -216,10 +221,8 @@ defmodule Membrane.WebRTC.Live.Capture do """) if message["data"] do - Signaling.signal( - socket.assigns.capture.signaling, - message - ) + socket.assigns.capture.signaling + |> Signaling.signal(message) end {:noreply, socket} diff --git a/lib/membrane_webrtc_live/player.ex b/lib/membrane_webrtc_live/player.ex index 3ea32fb..2f43485 100644 --- a/lib/membrane_webrtc_live/player.ex +++ b/lib/membrane_webrtc_live/player.ex @@ -36,7 +36,7 @@ defmodule Membrane.WebRTC.Live.Player do @impl true def render(assigns) do ~H""" - + """ end @@ -45,9 +45,7 @@ defmodule Membrane.WebRTC.Live.Player do signaling = Membrane.WebRTC.Signaling.new() {:ok, _supervisor, _pipelne} = Membrane.Pipeline.start_link(MyPipeline, signaling: signaling) - socket = Player.attach(socket, id: "player", signaling: signaling) - socket = assign(socket, :player, Player.get_attached(socket, "player")) - + socket = socket |> Player.attach(id: "player", signaling: signaling) {:ok, socket} end end @@ -59,7 +57,7 @@ defmodule Membrane.WebRTC.Live.Player do require Logger - @type t() :: struct() + @type t() :: %__MODULE__{} defstruct id: nil, signaling: nil @@ -86,14 +84,15 @@ defmodule Membrane.WebRTC.Live.Player do end @doc """ - Attaches required hooks and creates `t:t/0` struct. + Attaches required hooks and creates `t:t/0` struct. - Created struct is saved in socket's assigns and has to be passed to `LiveExWebRTC.Player.live_render/1`. + Created struct is saved in socket's assigns (in `socket.assigns[#{inspect(__MODULE__)}][id]`) and then + it is sent by an attached hook to a child live view process. Options: - * `id` - player id. This is typically your user id (if there is users database). - It is used to identify live view and generated HTML video player. - * `class` - a list of CSS/Tailwind classes that will be applied to the HTMLVideoPlayer. Defaults to "". + * `id` - player id. It is used to identify live view and generated HTML video player. It must be unique + withing single page. + * `signaling` - `Membrane.WebRTC.Signaling.t()`, that has been passed to `Membrane.Webrtc.Sink` as well. """ @spec attach(Phoenix.LiveView.Socket.t(), Keyword.t()) :: Phoenix.LiveView.Socket.t() def attach(socket, opts) do @@ -108,27 +107,12 @@ defmodule Membrane.WebRTC.Live.Player do socket |> assign(__MODULE__, all_players) |> detach_hook(:player_handshake, :handle_info) - |> attach_hook(:player_handshake, :handle_info, &handshake/2) + |> attach_hook(:player_handshake, :handle_info, &parent_handshake/2) end + @spec get_attached(Phoenix.LiveView.Socket.t(), String.t()) :: t() def get_attached(socket, id), do: socket.assigns[__MODULE__][id] - defp handshake({__MODULE__, {:connected, id, child_pid, _meta}}, socket) do - # child live view is connected, send it player struct - player = - socket.assigns - |> Map.fetch!(__MODULE__) - |> Map.fetch!(id) - - send(child_pid, player) - - {:halt, socket} - end - - defp handshake(_msg, socket) do - {:cont, socket} - end - ## CALLBACKS @impl true @@ -140,31 +124,48 @@ defmodule Membrane.WebRTC.Live.Player do @impl true def render(assigns) do ~H""" - + """ end @impl true def mount(_params, %{"class" => class, "id" => id}, socket) do - socket = assign(socket, class: class, player: nil) + socket = socket |> assign(class: class, player: nil) - if connected?(socket) do - send(socket.parent_pid, {__MODULE__, {:connected, id, self(), %{}}}) + socket = + if connected?(socket), + do: socket |> client_handshake(id), + else: socket - socket = - receive do - %__MODULE__{} = player -> - player.signaling - |> Signaling.register_peer(message_format: :json_data) + {:ok, socket} + end - socket |> assign(player: player) - after - 5000 -> exit(:timeout) - end + defp parent_handshake({__MODULE__, {:connected, id, player_pid}}, socket) do + player_struct = + socket.assigns + |> Map.fetch!(__MODULE__) + |> Map.fetch!(id) - {:ok, socket} - else - {:ok, socket} + send(player_pid, player_struct) + + {:halt, socket} + end + + defp parent_handshake(_msg, socket) do + {:cont, socket} + end + + defp client_handshake(socket, id) do + send(socket.parent_pid, {__MODULE__, {:connected, id, self()}}) + + receive do + %__MODULE__{} = player -> + player.signaling + |> Signaling.register_peer(message_format: :json_data) + + socket |> assign(player: player) + after + 5000 -> exit(:timeout) end end @@ -188,10 +189,8 @@ defmodule Membrane.WebRTC.Live.Player do """) if message["data"] do - Signaling.signal( - socket.assigns.player.signaling, - message - ) + socket.assigns.player.signaling + |> Signaling.signal(message) end {:noreply, socket} From aad34f2bb9816be9895a9fac276a70ef78efe040 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 26 Feb 2025 14:14:35 +0100 Subject: [PATCH 36/45] Fix README.md --- example_project/README.md | 29 +++++++++++-------- .../example_project_web/live_views/echo.ex | 7 +++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/example_project/README.md b/example_project/README.md index 9ff00e5..e8d516a 100644 --- a/example_project/README.md +++ b/example_project/README.md @@ -1,18 +1,23 @@ -# ExampleProject +# Example Project -To start your Phoenix server: +Example project showing how `Membrane.WebRTC.Live.Capture` and `Membrane.WebRTC.Live.Player` can be used. - * Run `mix setup` to install and setup dependencies - * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` +It contains a simple demo, where: + - the video stream is get from the browser and sent via WebRTC to Elixir server using `Membrane.WebRTC.Live.Capture` + - then, this same video stream is re-sent again to the browser and displayed using `Membrane.WebRTC.Live.Player`. -Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. +This demo uses also [Boombox](https://hex.pm/packages/boombox). + +The most important file in the project is `example_project/lib/example_project_web/live_views/echo.ex`, that +contains the usage of `Boombox` and LiveViews defined in `membrane_webrtc_live`. + +You can also take a look at `example_project/assets/js/app.js` to see how you can use `membrane_webrtc_live` JS hooks. -Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). +## Run server -## Learn more +To start Phoenix server: - * Official website: https://www.phoenixframework.org/ - * Guides: https://hexdocs.pm/phoenix/overview.html - * Docs: https://hexdocs.pm/phoenix - * Forum: https://elixirforum.com/c/phoenix-forum - * Source: https://github.com/phoenixframework/phoenix + * Run `mix setup` to install and setup dependencies + * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. diff --git a/example_project/lib/example_project_web/live_views/echo.ex b/example_project/lib/example_project_web/live_views/echo.ex index 75da083..eceb9d1 100644 --- a/example_project/lib/example_project_web/live_views/echo.ex +++ b/example_project/lib/example_project_web/live_views/echo.ex @@ -36,8 +36,11 @@ defmodule ExampleProjectWeb.LiveViews.Echo do def render(assigns) do ~H""" - - +

Captured stream preview

+ + +

Stream sent by the server

+ """ end end From 5cf38ed4bbcc3c937964224e0c60fc3bdcbde6b5 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 26 Feb 2025 15:28:58 +0100 Subject: [PATCH 37/45] Fix dialyzer --- lib/membrane_webrtc_live/capture.ex | 9 ++++++++- lib/membrane_webrtc_live/player.ex | 9 ++++++++- mix.lock | 18 +++++++++--------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/membrane_webrtc_live/capture.ex b/lib/membrane_webrtc_live/capture.ex index 804ee2a..b8bb89d 100644 --- a/lib/membrane_webrtc_live/capture.ex +++ b/lib/membrane_webrtc_live/capture.ex @@ -214,7 +214,11 @@ defmodule Membrane.WebRTC.Live.Capture do @impl true def handle_event("webrtc_signaling", message, socket) do - message = Jason.decode!(message) + message = + # this is a hack to supress dialyzer, that for some reason thinks that `message` + # is a map, while it is a binary + apply(__MODULE__, :identity, [message]) + |> Jason.decode!() Logger.debug(""" #{log_prefix(socket.assigns.capture.id)} Received WebRTC signaling message: #{inspect(message, pretty: true)} @@ -229,4 +233,7 @@ defmodule Membrane.WebRTC.Live.Capture do end defp log_prefix(id), do: [module: __MODULE__, id: id] |> inspect() + + @spec identity(term()) :: term() + def identity(sth), do: sth end diff --git a/lib/membrane_webrtc_live/player.ex b/lib/membrane_webrtc_live/player.ex index 2f43485..27da471 100644 --- a/lib/membrane_webrtc_live/player.ex +++ b/lib/membrane_webrtc_live/player.ex @@ -182,7 +182,11 @@ defmodule Membrane.WebRTC.Live.Player do @impl true def handle_event("webrtc_signaling", message, socket) do - message = Jason.decode!(message) + message = + # this is a hack to supress dialyzer, that for some reason thinks that `message` + # is a map, while it is a binary + apply(__MODULE__, :identity, [message]) + |> Jason.decode!() Logger.debug(""" #{log_prefix(socket.assigns.player.id)} Received WebRTC signaling message: #{inspect(message, pretty: true)} @@ -197,4 +201,7 @@ defmodule Membrane.WebRTC.Live.Player do end defp log_prefix(id), do: [module: __MODULE__, id: id] |> inspect() + + @spec identity(term()) :: term() + def identity(sth), do: sth end diff --git a/mix.lock b/mix.lock index b96f6e8..6daa061 100644 --- a/mix.lock +++ b/mix.lock @@ -5,12 +5,12 @@ "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, + "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, "corsica": {:hex, :corsica, "2.1.3", "dccd094ffce38178acead9ae743180cdaffa388f35f0461ba1e8151d32e190e6", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "616c08f61a345780c2cf662ff226816f04d8868e12054e68963e95285b5be8bc"}, - "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, + "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, - "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"}, "crc": {:hex, :crc, "0.10.5", "ee12a7c056ac498ef2ea985ecdc9fa53c1bfb4e53a484d9f17ff94803707dfd8", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3e673b6495a9525c5c641585af1accba59a1eb33de697bedf341e247012c2c7f"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, @@ -20,14 +20,14 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, "ex_dtls": {:hex, :ex_dtls, "0.16.0", "3ae38025ccc77f6db573e2e391602fa9bbc02253c137d8d2d59469a66cbe806b", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2a4e30d74c6ddf95cc5b796423293c06a0da295454c3823819808ff031b4b361"}, - "ex_ice": {:hex, :ex_ice, "0.9.3", "46700963acaba72737032500b6ee298a4effa7ad7189ab48887be5e9f4fe2107", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "4fd98d20a39ab70a62dd301c44e87437d479292c528ec7f21522ebfe0654b9cb"}, + "ex_ice": {:hex, :ex_ice, "0.9.4", "793121989164e49d8dc64b82bcb7842a4c2e0d224a2f00379ab415293a78c8e7", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "fc328ed721c558440266def81a2cd5138d163164218ebe449fa9a10fcda72574"}, "ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"}, "ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"}, "ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"}, "ex_sdp": {:hex, :ex_sdp, "1.1.1", "1a7b049491e5ec02dad9251c53d960835dc5631321ae978ec331831f3e4f6d5f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "1b13a72ac9c5c695b8824dbdffc671be8cbb4c0d1ccb4ff76a04a6826759f233"}, "ex_stun": {:hex, :ex_stun, "0.2.0", "feb1fc7db0356406655b2a617805e6c712b93308c8ea2bf0ba1197b1f0866deb", [:mix], [], "hexpm", "1e01ba8290082ccbf37acaa5190d1f69b51edd6de2026a8d6d51368b29d115d0"}, "ex_turn": {:hex, :ex_turn, "0.2.0", "4e1f9b089e9a5ee44928d12370cc9ea7a89b84b2f6256832de65271212eb80de", [:mix], [{:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "08e884f0af2c4a147e3f8cd4ffe33e3452a256389f0956e55a8c4d75bf0e74cd"}, - "ex_webrtc": {:hex, :ex_webrtc, "0.8.0", "109f408e9a4e687018b365cbb94de554f184d31bb42623e2f7a93de06575fd30", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.16.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:ex_ice, "~> 0.9.0", [hex: :ex_ice, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.7.1", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:ex_sctp, "0.1.2", [hex: :ex_sctp, repo: "hexpm", optional: true]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}], "hexpm", "ce5c1b2792589975c7d59d56bad367dbe7d7fab35195e3edd85bbc72dd3bd621"}, + "ex_webrtc": {:hex, :ex_webrtc, "0.8.1", "e507d1b3d89e9c8b74e3fef5f4070d57e20a3f77061b7439b1af1877d6577793", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.16.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:ex_ice, "~> 0.9.0", [hex: :ex_ice, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.7.1", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:ex_sctp, "0.1.2", [hex: :ex_sctp, repo: "hexpm", optional: true]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}], "hexpm", "2c5563cdaf998b5beed3c79b0feeaa1430f30118b21ab59dd18289d72177adf0"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "heap": {:hex, :heap, "2.0.2", "d98cb178286cfeb5edbcf17785e2d20af73ca57b5a2cf4af584118afbcf917eb", [:mix], [], "hexpm", "ba9ea2fe99eb4bcbd9a8a28eaf71cbcac449ca1d8e71731596aace9028c9d429"}, @@ -49,7 +49,7 @@ "membrane_telemetry_metrics": {:hex, :membrane_telemetry_metrics, "0.1.1", "57917e72012f9ebe124eab54f29ca74c9d9eb3ae2207f55c95618ee51280eb4f", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "69392966e6bd51937244758c2b3d835c5ff47d8953d25431a9d37059737afc11"}, "membrane_timestamp_queue": {:hex, :membrane_timestamp_queue, "0.2.2", "1c831b2273d018a6548654aa9f7fa7c4b683f71d96ffe164934ef55f9d11f693", [:mix], [{:heap, "~> 2.0", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7c830e760baaced0988421671cd2c83c7cda8d1bd2b61fd05332711675d1204f"}, "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, - "membrane_webrtc_plugin": {:hex, :membrane_webrtc_plugin, "0.23.3", "77d8819729792c9b53a25b138407d4fc90757750909d05df3418bf73fa724579", [:mix], [{:bandit, "~> 1.2", [hex: :bandit, repo: "hexpm", optional: false]}, {:corsica, "~> 2.0", [hex: :corsica, repo: "hexpm", optional: false]}, {:ex_webrtc, "~> 0.8.0", [hex: :ex_webrtc, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.1", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.4", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.0", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.0", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8723d50d2a917b71598574d68382f70247b7a2876daefca7b457fdb10bc6ecdd"}, + "membrane_webrtc_plugin": {:hex, :membrane_webrtc_plugin, "0.24.0", "20606f4f7082690598f2f30e0097f7708f46098b2d84cc8f98977e4341e510c1", [:mix], [{:bandit, "~> 1.2", [hex: :bandit, repo: "hexpm", optional: false]}, {:corsica, "~> 2.0", [hex: :corsica, repo: "hexpm", optional: false]}, {:ex_webrtc, "~> 0.8.0", [hex: :ex_webrtc, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.20.1", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, "~> 0.10.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.30.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.4", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.0", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.0", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "25e88a0d7ce8298dcf8cd1155d77efc55fffb20d0ae844cd80b20300d1875d04"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, @@ -62,16 +62,16 @@ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.3", "1304d36752e8bdde213cea59ef424ca932910a91a07ef9f3874be709c4ddb94b", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "77c95524b2aa5364b247fa17089029e73b951ebc1adeef429361eab0bb55819d"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, - "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, "ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "shmex": {:hex, :shmex, "0.5.1", "81dd209093416bf6608e66882cb7e676089307448a1afd4fc906c1f7e5b94cf4", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "c29f8286891252f64c4e1dac40b217d960f7d58def597c4e606ff8fbe71ceb80"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, - "thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"}, + "thousand_island": {:hex, :thousand_island, "1.3.11", "b68f3e91f74d564ae20b70d981bbf7097dde084343c14ae8a33e5b5fbb3d6f37", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "555c18c62027f45d9c80df389c3d01d86ba11014652c00be26e33b1b64e98d29"}, "unifex": {:hex, :unifex, "1.2.1", "6841c170a6e16509fac30b19e4e0a19937c33155a59088b50c15fc2c36251b6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "8c9d2e3c48df031e9995dd16865bab3df402c0295ba3a31f38274bb5314c7d37"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, From e6d833c63b5ca0bf53a5b7e81efe47d52709a6ab Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 27 Feb 2025 12:07:26 +0100 Subject: [PATCH 38/45] Fix lint --- lib/membrane_webrtc_live/capture.ex | 4 ++-- lib/membrane_webrtc_live/player.ex | 4 ++-- mix.lock | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/membrane_webrtc_live/capture.ex b/lib/membrane_webrtc_live/capture.ex index b8bb89d..c9f9d84 100644 --- a/lib/membrane_webrtc_live/capture.ex +++ b/lib/membrane_webrtc_live/capture.ex @@ -51,11 +51,10 @@ defmodule Membrane.WebRTC.Live.Capture do ``` ''' use Phoenix.LiveView + require Logger alias Membrane.WebRTC.Signaling - require Logger - @type t() :: %__MODULE__{} defstruct id: nil, signaling: nil, video?: true, audio?: true, preview?: true @@ -76,6 +75,7 @@ defmodule Membrane.WebRTC.Live.Capture do @doc """ Helper function for rendering Capture live view. """ + @spec live_render(map()) :: live_view when live_view: term() def live_render(assigns) do ~H""" <%= live_render(@socket, __MODULE__, id: "#{@capture}-lv", session: %{"class" => @class, "id" => @capture}) %> diff --git a/lib/membrane_webrtc_live/player.ex b/lib/membrane_webrtc_live/player.ex index 27da471..8785880 100644 --- a/lib/membrane_webrtc_live/player.ex +++ b/lib/membrane_webrtc_live/player.ex @@ -52,11 +52,10 @@ defmodule Membrane.WebRTC.Live.Player do ``` ''' use Phoenix.LiveView + require Logger alias Membrane.WebRTC.Signaling - require Logger - @type t() :: %__MODULE__{} defstruct id: nil, signaling: nil @@ -77,6 +76,7 @@ defmodule Membrane.WebRTC.Live.Player do @doc """ Helper function for rendering Player live view. """ + @spec live_render(map()) :: live_view when live_view: term() def live_render(assigns) do ~H""" <%= live_render(@socket, __MODULE__, id: "#{@player}-lv", session: %{"class" => @class, "id" => @player}) %> diff --git a/mix.lock b/mix.lock index 6daa061..ef2ca6b 100644 --- a/mix.lock +++ b/mix.lock @@ -4,7 +4,7 @@ "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, - "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, "corsica": {:hex, :corsica, "2.1.3", "dccd094ffce38178acead9ae743180cdaffa388f35f0461ba1e8151d32e190e6", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "616c08f61a345780c2cf662ff226816f04d8868e12054e68963e95285b5be8bc"}, @@ -12,13 +12,13 @@ "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"}, "crc": {:hex, :crc, "0.10.5", "ee12a7c056ac498ef2ea985ecdc9fa53c1bfb4e53a484d9f17ff94803707dfd8", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3e673b6495a9525c5c641585af1accba59a1eb33de697bedf341e247012c2c7f"}, - "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, - "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, + "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, - "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, + "ex_doc": {:hex, :ex_doc, "0.37.2", "2a3aa7014094f0e4e286a82aa5194a34dd17057160988b8509b15aa6c292720c", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "4dfa56075ce4887e4e8b1dcc121cd5fcb0f02b00391fd367ff5336d98fa49049"}, "ex_dtls": {:hex, :ex_dtls, "0.16.0", "3ae38025ccc77f6db573e2e391602fa9bbc02253c137d8d2d59469a66cbe806b", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2a4e30d74c6ddf95cc5b796423293c06a0da295454c3823819808ff031b4b361"}, "ex_ice": {:hex, :ex_ice, "0.9.4", "793121989164e49d8dc64b82bcb7842a4c2e0d224a2f00379ab415293a78c8e7", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "fc328ed721c558440266def81a2cd5138d163164218ebe449fa9a10fcda72574"}, "ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"}, @@ -28,14 +28,14 @@ "ex_stun": {:hex, :ex_stun, "0.2.0", "feb1fc7db0356406655b2a617805e6c712b93308c8ea2bf0ba1197b1f0866deb", [:mix], [], "hexpm", "1e01ba8290082ccbf37acaa5190d1f69b51edd6de2026a8d6d51368b29d115d0"}, "ex_turn": {:hex, :ex_turn, "0.2.0", "4e1f9b089e9a5ee44928d12370cc9ea7a89b84b2f6256832de65271212eb80de", [:mix], [{:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "08e884f0af2c4a147e3f8cd4ffe33e3452a256389f0956e55a8c4d75bf0e74cd"}, "ex_webrtc": {:hex, :ex_webrtc, "0.8.1", "e507d1b3d89e9c8b74e3fef5f4070d57e20a3f77061b7439b1af1877d6577793", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.16.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:ex_ice, "~> 0.9.0", [hex: :ex_ice, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.7.1", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:ex_sctp, "0.1.2", [hex: :ex_sctp, repo: "hexpm", optional: true]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}], "hexpm", "2c5563cdaf998b5beed3c79b0feeaa1430f30118b21ab59dd18289d72177adf0"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "heap": {:hex, :heap, "2.0.2", "d98cb178286cfeb5edbcf17785e2d20af73ca57b5a2cf4af584118afbcf917eb", [:mix], [], "hexpm", "ba9ea2fe99eb4bcbd9a8a28eaf71cbcac449ca1d8e71731596aace9028c9d429"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, "membrane_core": {:hex, :membrane_core, "1.1.2", "3ca206893e1d3739a24d5092d21c06fcb4db326733a1798f9788fc53abb74829", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a989fd7e0516a7e66f5fb63950b1027315b7f8c8d82d8d685e178b0fb780901b"}, "membrane_funnel_plugin": {:hex, :membrane_funnel_plugin, "0.9.2", "2b2e840dbb232ce29aaff2d55bd329d9978766518dbeb6e8dba7aba7115fadcc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "865ac9d84f86698e2cfeb7904d3b12ab74855a38ca651a880db1505965fa77cc"}, "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, @@ -56,9 +56,9 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, - "phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"}, - "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.2", "e7b1dd68c86326e2c45cc81da41e332cc8aa7228a7161e2c811dcd7f1dd14db1", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8a40265b0cd7d3a35f136dfa3cc048e3b198fc3718763411a78c323a44ebebee"}, + "phoenix": {:hex, :phoenix, "1.7.20", "6bababaf27d59f5628f9b608de902a021be2cecefb8231e1dbdc0a2e2e480e9b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "6be2ab98302e8784a31829e0d50d8bdfa81a23cd912c395bafd8b8bfb5a086c2"}, + "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.4", "327491b033e79db2f887b065c5a2993228449091883d74cfa1baa12f8c98d5eb", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9865316ddf8d78f382d63af278d20436b52d262b60239956817a61279514366"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, From 2abbd7ca1f3bd7fc03a38692ee55d14ce74f1af7 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 27 Feb 2025 12:07:41 +0100 Subject: [PATCH 39/45] Fix lint --- lib/membrane_webrtc_live/capture.ex | 4 ++-- lib/membrane_webrtc_live/player.ex | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/membrane_webrtc_live/capture.ex b/lib/membrane_webrtc_live/capture.ex index c9f9d84..ee4164f 100644 --- a/lib/membrane_webrtc_live/capture.ex +++ b/lib/membrane_webrtc_live/capture.ex @@ -214,9 +214,9 @@ defmodule Membrane.WebRTC.Live.Capture do @impl true def handle_event("webrtc_signaling", message, socket) do + # this is a hack to supress dialyzer, that for some reason thinks that `message` + # is a map, while it is a binary message = - # this is a hack to supress dialyzer, that for some reason thinks that `message` - # is a map, while it is a binary apply(__MODULE__, :identity, [message]) |> Jason.decode!() diff --git a/lib/membrane_webrtc_live/player.ex b/lib/membrane_webrtc_live/player.ex index 8785880..7ca9026 100644 --- a/lib/membrane_webrtc_live/player.ex +++ b/lib/membrane_webrtc_live/player.ex @@ -182,9 +182,9 @@ defmodule Membrane.WebRTC.Live.Player do @impl true def handle_event("webrtc_signaling", message, socket) do + # this is a hack to supress dialyzer, that for some reason thinks that `message` + # is a map, while it is a binary message = - # this is a hack to supress dialyzer, that for some reason thinks that `message` - # is a map, while it is a binary apply(__MODULE__, :identity, [message]) |> Jason.decode!() From e6ce995b0bb55467c5ccf65744fa9ef1adb55d93 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 27 Feb 2025 17:27:24 +0100 Subject: [PATCH 40/45] Refactor example project filenames --- .../lib/example_project_web/{live_views => live}/echo.ex | 2 +- example_project/lib/example_project_web/router.ex | 2 +- lib/membrane_webrtc_live/capture.ex | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename example_project/lib/example_project_web/{live_views => live}/echo.ex (95%) diff --git a/example_project/lib/example_project_web/live_views/echo.ex b/example_project/lib/example_project_web/live/echo.ex similarity index 95% rename from example_project/lib/example_project_web/live_views/echo.ex rename to example_project/lib/example_project_web/live/echo.ex index eceb9d1..a622cc6 100644 --- a/example_project/lib/example_project_web/live_views/echo.ex +++ b/example_project/lib/example_project_web/live/echo.ex @@ -1,4 +1,4 @@ -defmodule ExampleProjectWeb.LiveViews.Echo do +defmodule ExampleProjectWeb.Live.EchoLive do use ExampleProjectWeb, :live_view alias Membrane.WebRTC.Live.{Capture, Player} diff --git a/example_project/lib/example_project_web/router.ex b/example_project/lib/example_project_web/router.ex index fad2b5f..5b5b548 100644 --- a/example_project/lib/example_project_web/router.ex +++ b/example_project/lib/example_project_web/router.ex @@ -17,7 +17,7 @@ defmodule ExampleProjectWeb.Router do scope "/", ExampleProjectWeb do pipe_through(:browser) - live("/", LiveViews.Echo, :index) + live("/", Live.EchoLive, :index) end # Other scopes may use custom stacks. diff --git a/lib/membrane_webrtc_live/capture.ex b/lib/membrane_webrtc_live/capture.ex index ee4164f..ba01430 100644 --- a/lib/membrane_webrtc_live/capture.ex +++ b/lib/membrane_webrtc_live/capture.ex @@ -70,7 +70,7 @@ defmodule Membrane.WebRTC.Live.Capture do """ ) - attr(:class, :string, default: nil, doc: "CSS/Tailwind classes for styling") + attr(:class, :string, default: "", doc: "CSS/Tailwind classes for styling") @doc """ Helper function for rendering Capture live view. @@ -136,7 +136,7 @@ defmodule Membrane.WebRTC.Live.Capture do @impl true def render(%{capture: %__MODULE__{preview?: true}} = assigns) do ~H""" -