diff --git a/lib/plug/router.ex b/lib/plug/router.ex
index a902cef..dee6c3c 100644
--- a/lib/plug/router.ex
+++ b/lib/plug/router.ex
@@ -66,6 +66,8 @@ if Code.ensure_loaded?(Plug) do
end
end
+ forward "/webrtc", to: Plug.Shinkai.Router.WebRTC
+
match _ do
send_resp(conn, 404, "Not Found")
end
diff --git a/lib/plug/router/webrtc.ex b/lib/plug/router/webrtc.ex
new file mode 100644
index 0000000..9f94665
--- /dev/null
+++ b/lib/plug/router/webrtc.ex
@@ -0,0 +1,62 @@
+if Code.ensure_loaded?(Plug) do
+ defmodule Plug.Shinkai.Router.WebRTC do
+ @moduledoc false
+ require Logger
+
+ require EEx
+
+ use Plug.Router
+ use Plug.ErrorHandler
+
+ plug :match
+ plug :dispatch
+
+ EEx.function_from_file(:defp, :webrtc_index, "lib/plug/templates/webrtc.html.eex", [:assigns])
+
+ get "/:source_id" do
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_resp(200, webrtc_index(source_id: source_id))
+ end
+
+ post "/:source_id/whep" do
+ case Shinkai.Sources.add_webrtc_peer(source_id) do
+ {:ok, sdp_offer, session_id} ->
+ conn
+ |> put_resp_content_type("application/sdp")
+ |> put_resp_header("location", "/webrtc/#{source_id}/whep/#{session_id}")
+ |> send_resp(416, sdp_offer)
+
+ {:error, reason} ->
+ Logger.error("Failed to create WebRTC peer: #{inspect(reason)}")
+ send_resp(conn, 400, "Bad Request")
+ end
+ end
+
+ patch "/:source_id/whep/:session_id" do
+ case get_req_header(conn, "content-type") do
+ ["application/sdp"] ->
+ {:ok, body, conn} = Plug.Conn.read_body(conn)
+
+ case Shinkai.Sources.handle_webrtc_peer_answer(source_id, session_id, body) do
+ :ok ->
+ send_resp(conn, 204, "")
+
+ {:error, reason} ->
+ Logger.error("Failed to handle WebRTC peer answer: #{inspect(reason)}")
+ send_resp(conn, 400, "Bad Request")
+ end
+
+ send_resp(conn, 204, "")
+
+ _ ->
+ send_resp(conn, 415, "Unsupported Media Type")
+ end
+ end
+
+ delete "/:source_id/whep/:session_id" do
+ Shinkai.Sources.remove_webrtc_peer(source_id, session_id)
+ send_resp(conn, 204, "")
+ end
+ end
+end
diff --git a/lib/plug/templates/webrtc.html.eex b/lib/plug/templates/webrtc.html.eex
new file mode 100644
index 0000000..bb0a190
--- /dev/null
+++ b/lib/plug/templates/webrtc.html.eex
@@ -0,0 +1,60 @@
+
+
+
+
+ Shinkai WebRTC
+
+
+
+
+
+
+
diff --git a/lib/shinkai/pipeline.ex b/lib/shinkai/pipeline.ex
index 0788231..fce5e49 100644
--- a/lib/shinkai/pipeline.ex
+++ b/lib/shinkai/pipeline.ex
@@ -15,7 +15,19 @@ defmodule Shinkai.Pipeline do
@spec add_rtmp_client(String.t()) :: :ok
def add_rtmp_client(source_id) do
- Sink.RTMP.add_client({:via, Registry, {Source.Registry, {:rtmp_sink, source_id}}}, self())
+ Sink.RTMP.add_client(rtmp_name(source_id), self())
+ end
+
+ def add_webrtc_peer(source_id) do
+ Sink.WebRTC.add_new_peer(webrtc_name(source_id))
+ end
+
+ def handle_webrtc_peer_answer(source_id, session_id, sdp_answer) do
+ Sink.WebRTC.handle_peer_answer(webrtc_name(source_id), session_id, sdp_answer)
+ end
+
+ def remove_webrtc_peer(source_id, session_id) do
+ Sink.WebRTC.remove_peer(webrtc_name(source_id), session_id)
end
def stop(source_id) do
@@ -29,7 +41,8 @@ defmodule Shinkai.Pipeline do
children =
[
- {Sink.Hls, [id: id] ++ hls_config}
+ {Sink.Hls, [id: id] ++ hls_config},
+ {Sink.WebRTC, id: id}
] ++ rtmp_sink(rtmp_config[:enabled], id) ++ source(source)
Supervisor.init(children, strategy: :one_for_all)
@@ -41,4 +54,7 @@ defmodule Shinkai.Pipeline do
defp rtmp_sink(false, _id), do: []
defp rtmp_sink(true, id), do: [{Sink.RTMP, [id: id]}]
+
+ defp webrtc_name(id), do: {:via, Registry, {Source.Registry, {:webrtc_sink, id}}}
+ defp rtmp_name(id), do: {:via, Registry, {Source.Registry, {:rtmp_sink, id}}}
end
diff --git a/lib/shinkai/sink/webrtc.ex b/lib/shinkai/sink/webrtc.ex
new file mode 100644
index 0000000..98670a9
--- /dev/null
+++ b/lib/shinkai/sink/webrtc.ex
@@ -0,0 +1,218 @@
+defmodule Shinkai.Sink.WebRTC do
+ @moduledoc false
+
+ use GenServer
+
+ require Logger
+
+ import Shinkai.Utils
+
+ alias __MODULE__.PeerManager
+ alias ExWebRTC.RTPCodecParameters
+ alias Phoenix.PubSub
+ alias RTSP.RTP.Encoder, as: RTPEncoder
+
+ @supported_codecs [:h264, :h265, :av1, :pcma]
+ @video_clock_rate 90_000
+
+ def start_link(opts) do
+ id = {:via, Registry, {Source.Registry, {:webrtc_sink, opts[:id]}}}
+ GenServer.start_link(__MODULE__, opts, name: id)
+ end
+
+ @spec add_new_peer(GenServer.server()) :: {:ok, String.t(), String.t()} | {:error, any()}
+ def add_new_peer(server) do
+ GenServer.call(server, :add_new_peer)
+ end
+
+ @spec handle_peer_answer(
+ GenServer.server(),
+ session_id :: String.t(),
+ sdp :: String.t()
+ ) :: :ok | {:error, any()}
+ def handle_peer_answer(server, session_id, sdp) do
+ GenServer.call(server, {:handle_peer_answer, session_id, sdp})
+ end
+
+ @spec remove_peer(GenServer.server(), session_id :: String.t()) :: :ok
+ def remove_peer(server, session_id) do
+ GenServer.cast(server, {:remove_peer, session_id})
+ end
+
+ @impl true
+ def init(opts) do
+ source_id = opts[:id]
+ {:ok, peer_manager} = PeerManager.start_link(source_id: source_id)
+
+ PubSub.subscribe(Shinkai.PubSub, tracks_topic(source_id))
+
+ {:ok,
+ %{
+ peer_manager: peer_manager,
+ source_id: source_id,
+ packets_topic: packets_topic(source_id),
+ tracks: %{}
+ }}
+ end
+
+ @impl true
+ def handle_call(:add_new_peer, _from, %{video_tracks: [], audio_tracks: []} = state) do
+ {:reply, {:error, :no_tracks}, state}
+ end
+
+ def handle_call(:add_new_peer, from, state) do
+ :ok = PeerManager.add_peer(state.peer_manager, from)
+ {:noreply, state}
+ end
+
+ def handle_call({:handle_peer_answer, session_id, sdp}, from, state) do
+ :ok = PeerManager.handle_peer_answer(state.peer_manager, from, session_id, sdp)
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_cast({:remove_peer, session_id}, state) do
+ :ok = PeerManager.remove_peer(state.peer_manager, session_id)
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:tracks, tracks}, state) do
+ {tracks, unsupported_tracks} = Enum.split_with(tracks, &(&1.codec in @supported_codecs))
+
+ if unsupported_tracks != [] do
+ Logger.warning(
+ "Unsupported codecs received in WebRTC sink: #{join_codecs(unsupported_tracks)}"
+ )
+ end
+
+ video_track = Enum.find(tracks, fn t -> t.type == :video end)
+ audio_track = Enum.find(tracks, fn t -> t.type == :audio end)
+
+ stream_id = ExWebRTC.MediaStreamTrack.generate_stream_id()
+
+ state =
+ [video_track, audio_track]
+ |> Enum.reject(&is_nil/1)
+ |> Enum.reduce(state, fn track, state ->
+ media_stream = ExWebRTC.MediaStreamTrack.new(track.type, [stream_id])
+ webrtc_track = webrtc_track(track)
+
+ payloader_mod = payloader_mod(track.codec)
+
+ track_ctx = %{
+ id: media_stream.id,
+ timescale: track.timescale,
+ target_timescale: webrtc_track.clock_rate,
+ payloader_mod: payloader_mod,
+ payloader_state: payloader_mod.init([])
+ }
+
+ if track.type == :video,
+ do: PeerManager.add_video_track(state.peer_manager, {media_stream, webrtc_track}),
+ else: PeerManager.add_audio_track(state.peer_manager, {media_stream, webrtc_track})
+
+ %{state | tracks: Map.put(state.tracks, track.id, track_ctx)}
+ end)
+
+ :ok = PubSub.subscribe(Shinkai.PubSub, state.packets_topic)
+
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:packet, packets}, state) when is_list(packets) do
+ track_id = hd(packets).track_id
+
+ case(Map.fetch(state, track_id)) do
+ :error ->
+ {:noreply, state}
+
+ {:ok, track_ctx} ->
+ track_ctx =
+ Enum.reduce(packets, track_ctx, fn packet, track_ctx ->
+ do_handle_packet(packet, state.source_id, track_ctx)
+ end)
+
+ {:noreply, %{state | tracks: Map.put(state.tracks, track_id, track_ctx)}}
+ end
+ end
+
+ def handle_info({:packet, packet}, state) do
+ case Map.fetch(state.tracks, packet.track_id) do
+ :error ->
+ {:noreply, state}
+
+ {:ok, track_ctx} ->
+ track_ctx = do_handle_packet(packet, state.source_id, track_ctx)
+ {:noreply, %{state | tracks: Map.put(state.tracks, packet.track_id, track_ctx)}}
+ end
+ end
+
+ def handle_info(_msg, state) do
+ {:noreply, state}
+ end
+
+ defp do_handle_packet(packet, source_id, track_ctx) do
+ rtp_timestamp =
+ ExMP4.Helper.timescalify(packet.pts, track_ctx.timescale, track_ctx.target_timescale)
+
+ {packets, payloader_state} =
+ track_ctx.payloader_mod.handle_sample(
+ packet.data,
+ rtp_timestamp,
+ track_ctx.payloader_state
+ )
+
+ track_id = track_ctx.id
+
+ Registry.dispatch(Sink.Registry, {:webrtc, source_id}, fn peers ->
+ for {_pid, {pc, _session_id}} <- peers do
+ Enum.each(packets, fn rtp_packet ->
+ :ok = ExWebRTC.PeerConnection.send_rtp(pc, track_id, rtp_packet)
+ end)
+ end
+ end)
+
+ %{track_ctx | payloader_state: payloader_state}
+ end
+
+ defp webrtc_track(track) do
+ pt = payload_type(track.codec)
+
+ %RTPCodecParameters{
+ payload_type: pt,
+ mime_type: mime_type(track.codec),
+ clock_rate: clock_rate(track),
+ channels: if(track.type == :audio, do: 1, else: nil),
+ sdp_fmtp_line: sdp_fmtp_line(track.codec, pt)
+ }
+ end
+
+ defp clock_rate(%{type: :video}), do: @video_clock_rate
+ defp clock_rate(%{timescale: timescale}), do: timescale
+
+ defp payload_type(:pcma), do: 8
+ defp payload_type(_codec), do: 96
+
+ defp mime_type(:h264), do: "video/H264"
+ defp mime_type(:h265), do: "video/H265"
+ defp mime_type(:av1), do: "video/AV1"
+ defp mime_type(:pcma), do: "audio/PCMA"
+
+ defp sdp_fmtp_line(:h264, pt) do
+ %ExSDP.Attribute.FMTP{
+ pt: pt,
+ level_asymmetry_allowed: true,
+ packetization_mode: 1,
+ profile_level_id: 0x42E01F
+ }
+ end
+
+ defp sdp_fmtp_line(_codec, _pt), do: nil
+
+ defp payloader_mod(:h264), do: RTPEncoder.H264
+ defp payloader_mod(:h265), do: RTPEncoder.H265
+ defp payloader_mod(:av1), do: RTPEncoder.AV1
+ defp payloader_mod(:pcma), do: RTPEncoder.G711
+end
diff --git a/lib/shinkai/sink/webrtc/peer_manager.ex b/lib/shinkai/sink/webrtc/peer_manager.ex
new file mode 100644
index 0000000..72a1eaf
--- /dev/null
+++ b/lib/shinkai/sink/webrtc/peer_manager.ex
@@ -0,0 +1,183 @@
+defmodule Shinkai.Sink.WebRTC.PeerManager do
+ @moduledoc false
+
+ use GenServer
+
+ require Logger
+
+ alias ExWebRTC.PeerConnection
+
+ def start_link(opts) do
+ GenServer.start_link(__MODULE__, opts, name: opts[:name])
+ end
+
+ @spec add_video_track(server :: GenServer.name() | pid(), tuple()) :: :ok
+ def add_video_track(manager, track) do
+ GenServer.call(manager, {:add_video_track, track})
+ end
+
+ @spec add_audio_track(server :: GenServer.name() | pid(), tuple()) :: :ok
+ def add_audio_track(manager, track) do
+ GenServer.call(manager, {:add_audio_track, track})
+ end
+
+ @spec add_peer(server :: GenServer.name() | pid(), from :: GenServer.from()) :: :ok
+ def add_peer(manager, from) do
+ GenServer.cast(manager, {:add_peer, from})
+ end
+
+ @spec handle_peer_answer(
+ server :: pid() | atom(),
+ from :: GenServer.from(),
+ session_id :: String.t(),
+ sdp_answer :: String.t()
+ ) :: :ok
+ def handle_peer_answer(manager, from, session_id, sdp_answer) do
+ GenServer.cast(manager, {:handle_peer_answer, from, session_id, sdp_answer})
+ end
+
+ @spec remove_peer(server :: pid() | atom(), session_id :: String.t()) :: :ok
+ def remove_peer(manager, session_id) do
+ GenServer.call(manager, {:remove_peer, session_id})
+ end
+
+ @impl true
+ def init(opts) do
+ {:ok,
+ %{
+ source_id: opts[:source_id],
+ sessions: %{},
+ peers: %{},
+ video_track: nil,
+ audio_track: nil
+ }}
+ end
+
+ @impl true
+ def handle_call({:add_video_track, track}, _from, state) do
+ {:reply, :ok, %{state | video_track: track}}
+ end
+
+ def handle_call({:add_audio_track, track}, _from, state) do
+ {:reply, :ok, %{state | audio_track: track}}
+ end
+
+ def handle_call({:remove_peer, session_id}, _from, state) do
+ Logger.info("Removing WebRTC peer with session ID: #{session_id}")
+
+ case Registry.match(Sink.Registry, {:webrtc, state.source_id}, {:_, session_id}) do
+ [{_pid, {pc, _session_id}}] -> unregister(state.source_id, pc)
+ [] -> :ok
+ end
+
+ {pc, sessions} = Map.pop(state.sessions, session_id)
+ if pc, do: PeerConnection.stop(pc)
+
+ {:reply, :ok, %{state | sessions: sessions}}
+ end
+
+ @impl true
+ def handle_cast({:add_peer, from}, state) do
+ video_tracks = if state.video_track, do: [elem(state.video_track, 1)], else: []
+ audio_tracks = if state.audio_track, do: [elem(state.audio_track, 1)], else: []
+
+ tracks =
+ Enum.reject([state.video_track, state.audio_track], &is_nil/1) |> Enum.map(&elem(&1, 0))
+
+ with {:ok, pc} <-
+ PeerConnection.start(video_codecs: video_tracks, audio_codecs: audio_tracks),
+ :ok <- add_tracks(pc, tracks),
+ {:ok, offer} <- PeerConnection.create_offer(pc),
+ :ok <- PeerConnection.set_local_description(pc, offer) do
+ {:noreply, %{state | peers: Map.put(state.peers, pc, from)}}
+ else
+ {:error, reason} ->
+ GenServer.reply(from, {:error, reason})
+ {:noreply, state}
+ end
+ end
+
+ def handle_cast({:handle_peer_answer, from, session_id, sdp}, state) do
+ case Map.fetch(state.sessions, session_id) do
+ {:ok, pid} ->
+ desc = %ExWebRTC.SessionDescription{
+ type: :answer,
+ sdp: sdp
+ }
+
+ :ok = PeerConnection.set_remote_description(pid, desc)
+ GenServer.reply(from, :ok)
+ {:noreply, state}
+
+ :error ->
+ GenServer.reply(from, {:error, :invalid_session_id})
+ {:noreply, state}
+ end
+ end
+
+ @impl true
+ def handle_info({:ex_webrtc, pid, {:ice_gathering_state_change, :complete}}, state) do
+ state =
+ case Map.pop(state.peers, pid) do
+ {nil, peers} ->
+ %{state | peers: peers}
+
+ {from, peers} ->
+ session_id = UUID.uuid4()
+ offer = PeerConnection.get_local_description(pid)
+ GenServer.reply(from, {:ok, offer.sdp, session_id})
+
+ %{
+ state
+ | peers: peers,
+ sessions: Map.put(state.sessions, session_id, pid)
+ }
+ end
+
+ {:noreply, state}
+ end
+
+ def handle_info({:ex_webrtc, pid, {:connection_state_change, :connected}}, state) do
+ Logger.info("New WebRTC peer connected")
+ {session_id, pc} = Enum.find(state.sessions, fn {_session_id, p} -> p == pid end)
+ Registry.register(Sink.Registry, {:webrtc, state.source_id}, {pc, session_id})
+ {:noreply, %{state | sessions: Map.delete(state.sessions, session_id)}}
+ end
+
+ def handle_info({:ex_webrtc, pid, {:connection_state_change, connection_state}}, state)
+ when connection_state in [:failed, :closed] do
+ Logger.debug(
+ "WebRTC PeerConnection #{inspect(pid)} connection state changed to: #{connection_state}"
+ )
+
+ unregister(state.source_id, pid)
+ {:noreply, state}
+ end
+
+ def handle_info({:ex_webrtc, _pid, {:rtcp, _}}, state) do
+ {:noreply, state}
+ end
+
+ def handle_info({:ex_webrtc, _pid, _msg}, state) do
+ # Logger.info("Unhandled ExWebRTC message: #{inspect(msg)}")
+ {:noreply, state}
+ end
+
+ def handle_info(_msg, state) do
+ {:noreply, state}
+ end
+
+ defp add_tracks(pc, tracks) do
+ Enum.reduce_while(tracks, :ok, fn track, :ok ->
+ case PeerConnection.add_track(pc, track) do
+ {:ok, _} -> {:cont, :ok}
+ {:error, reason} -> {:halt, {:error, reason}}
+ end
+ end)
+ end
+
+ defp unregister(source_id, pc) do
+ PeerConnection.stop(pc)
+ Registry.unregister_match(Sink.Registry, {:webrtc, source_id}, {pc, :_})
+ end
+end
diff --git a/lib/shinkai/sources.ex b/lib/shinkai/sources.ex
index 6f0aae6..c2f919e 100644
--- a/lib/shinkai/sources.ex
+++ b/lib/shinkai/sources.ex
@@ -91,6 +91,10 @@ defmodule Shinkai.Sources do
end
end
+ defdelegate add_webrtc_peer(source_id), to: Shinkai.Pipeline
+ defdelegate handle_webrtc_peer_answer(source_id, session_id, sdp_answer), to: Shinkai.Pipeline
+ defdelegate remove_webrtc_peer(source_id, session_id), to: Shinkai.Pipeline
+
defp storage_impl do
Application.get_env(:shinkai, :storage_impl, Shinkai.Sources.Storage.File)
end
diff --git a/lib/shinkai/utils.ex b/lib/shinkai/utils.ex
index e73222c..cdae759 100644
--- a/lib/shinkai/utils.ex
+++ b/lib/shinkai/utils.ex
@@ -12,4 +12,7 @@ defmodule Shinkai.Utils do
@spec sink_topic(String.t()) :: String.t()
def sink_topic(id), do: "source:sink:#{id}"
+
+ @spec join_codecs([Shinkai.Track.t()]) :: String.t()
+ def join_codecs(tracks), do: Enum.map_join(tracks, ", ", & &1.codec)
end
diff --git a/mix.exs b/mix.exs
index 8ebb00f..7a077a5 100644
--- a/mix.exs
+++ b/mix.exs
@@ -35,6 +35,7 @@ defmodule Shinkai.MixProject do
{:rtsp, "~> 0.8.0"},
{:hlx, "~> 0.5.0"},
{:ex_rtmp, "~> 0.4.1"},
+ {:ex_webrtc, "~> 0.15.0"},
{:yaml_elixir, "~> 2.12"},
{:plug, "~> 1.19", optional: true},
{:bandit, "~> 1.8", optional: true},
diff --git a/mix.lock b/mix.lock
index 3e1d075..30bdb61 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,20 +1,31 @@
%{
"bandit": {:hex, :bandit, "1.10.2", "d15ea32eb853b5b42b965b24221eb045462b2ba9aff9a0bda71157c06338cbff", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [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", "27b2a61b647914b1726c2ced3601473be5f7aa6bb468564a688646a689b3ee45"},
"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, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"coerce": {:hex, :coerce, "1.0.2", "5ef791040c92baaa5dd344887563faaeac6e6742573a167493294f8af3672bbe", [:mix], [], "hexpm", "0b3451c729571234fdac478636c298e71d1f2ce1243abed5fa43fa3181b980eb"},
"credo": {:hex, :credo, "1.7.15", "283da72eeb2fd3ccf7248f4941a0527efb97afa224bcdef30b4b580bc8258e1c", [: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", "291e8645ea3fea7481829f1e1eb0881b8395db212821338e577a90bf225c5607"},
+ "crc": {:hex, :crc, "0.10.6", "a52243715da06265399ade929b12e6807a82ddbd04231d8bd3069480aa890f01", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9e832833d48a5fff03cb7488f8aa5c08adda0a5fa8188bbe124cb17c4e39a00d"},
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
+ "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
"ex_doc": {:hex, :ex_doc, "0.40.0", "2635974389b80fd3ca61b0f993d459dad05b4a8f9b069dcfbbc5f6a8a6aef60e", [:mix], [{:earmark_parser, "~> 1.4.44", [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", "c040735250e2752b6e1102eeb4aa3f1dca74c316db873ae09f955d42136e7e5b"},
"ex_flv": {:hex, :ex_flv, "0.4.0", "9e43c833b5cbe3c6e21bb2651ae7650f3ec939eac8079f34efed8f813bf9133d", [:mix], [], "hexpm", "484f6990791e0c8862a88e4150f004deb067c7342e40b779c82d5c9a9c057969"},
+ "ex_dtls": {:hex, :ex_dtls, "0.18.0", "0815e3384bb0c1e6c06559012479cf9a94a501ddf46c3df54dc2d1b169e29d5c", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "562eda1815eeaed8360b2b5c34d4db5b453794bc096404a4c64f193fa7b18bf2"},
+ "ex_ice": {:hex, :ex_ice, "0.13.0", "13a6ae106b26bb5f2957a586bf20d4031299e5b968533828e637bb4ac7645d31", [: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", "0d65afa15e36b5610d0f51e72e4c25b22346caa9a6d7d2f6f1cfd8db94bd494e"},
+ "ex_libsrtp": {:hex, :ex_libsrtp, "0.7.3", "f0a0dcb6c6518986c61a01ff47e99d71ff6eeef8108a207d92e3ab8a3687b435", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.2.1", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "0964a9ad35f4aa871a472fa827cfef8dcd3cbf22c912a32bc7b19a8769fbc744"},
"ex_m3u8": {:hex, :ex_m3u8, "0.15.4", "66f6ec7e4fb7372c48032db1c2d4a3e6c2bbbde2d1d9a1098986e3caa0ab7a55", [:mix], [{:nimble_parsec, "~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "ec03aa516919e0c8ec202da55f609b763bd7960195a3388900090fcad270c873"},
"ex_mp4": {:hex, :ex_mp4, "0.14.2", "c362b27c50fa8d5a16e4f5652963fcc47d5a61215eb729a0d6f8ec521575ed6d", [:mix], [{:media_codecs, "~> 0.10.0", [hex: :media_codecs, repo: "hexpm", optional: true]}, {:ratio, "~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:table_rex, "~> 4.0", [hex: :table_rex, repo: "hexpm", optional: true]}], "hexpm", "3712c62a93ddde83419bb22e382a145c6527c8b002d8a22348202828022e1041"},
"ex_rtcp": {:hex, :ex_rtcp, "0.4.1", "e0f0c0baf329de92059e2afdc34d66d61f8b983f0801daa10f1a712360919e45", [:mix], [], "hexpm", "83ab3dffcffc6149eb404f1e1b1b62f755efd54c818e27c2c88e6ba3341c5d41"},
"ex_rtmp": {:hex, :ex_rtmp, "0.4.1", "1be8a1f75f2940d59ae07939218d1cdddac85de118370f0f001f816b8bac4576", [:mix], [{:ex_flv, "~> 0.4.0", [hex: :ex_flv, repo: "hexpm", optional: false]}], "hexpm", "58e1f993c575b6604e8256ed89887fcb7f7b80d0627e0bd71e9eea98bbe79766"},
"ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"},
"ex_sdp": {:hex, :ex_sdp, "1.1.2", "7e7465cb13b557cc76ef3e854bad7626b73cc1d1f480d38b5fbcf539c7d8a45d", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "50a27c2d745924679acca32b3d5499d0b35d135a180b83422df82c289afce564"},
+ "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.15.0", "c5849edcf7d035fcecf01db5be6d33a9d111999640bfc9d13a8c24e8eab7cced", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.18.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:ex_ice, "~> 0.13.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", "79c21017b45a464c513f87e64ae9a20c8085d937fb5e0d639c50a8c41018172d"},
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
+ "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [: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", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"},
"hlx": {:hex, :hlx, "0.5.0", "22542ba77c4fd9a50d4566e546ff49b19d9b22d5110bf3b920d3d1f5f61ca9c1", [:mix], [{:ex_m3u8, "~> 0.15.0", [hex: :ex_m3u8, repo: "hexpm", optional: false]}, {:ex_mp4, "~> 0.14.0", [hex: :ex_mp4, repo: "hexpm", optional: false]}, {:media_codecs, "~> 0.10.0", [hex: :media_codecs, repo: "hexpm", optional: false]}, {:mpeg_ts, "~> 3.3.5", [hex: :mpeg_ts, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.1", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "74bcb44fda7a2407b37c125385b2389b7922438aba05b13bbc6ab01f8ba42a70"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
@@ -22,12 +33,15 @@
"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.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"},
"media_codecs": {:hex, :media_codecs, "0.10.0", "dcc64779c3b287202fd8083fe49bf11b37f7b6bbd8edf3a9bd756370ee4417c5", [:mix], [], "hexpm", "8ea233ae378acfae3ab95a90f6f5c99711d55f15d0c5fac244d46b42f6a9ca04"},
+ "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.2.2", "0fbff1eb651619ce95abd7f9d19dd636ce460adc01bea36a440c48d1a6572a95", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "60296232d613856d22494303b64487bfa141666544f2e83a97f1d2dd28c34453"},
"membrane_rtsp": {:hex, :membrane_rtsp, "0.11.0", "887b1c0cd4f40f6ce93880bfa1a1e8c9e250aabb24810a8fe2a7556bb54c29c4", [: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", "69252d77ad3df48e6cb21fc16b0c5730607709714ad7849b7635813f9741ee2f"},
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
+ "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.5.0", "a87acd74fd733aa3b9cb5663d6f690178b056608f2652f18e4ec423ddd5496ed", [:mix], [], "hexpm", "52492b2eba61055df1c626e894663b624b5e6fdfaaaba1d9a8596236fbf4da69"},
"mpeg_ts": {:hex, :mpeg_ts, "3.3.11", "77d69c5599fcd6eadef926b03cf6fe990dd76301ec41ce77de71bc84ad53412c", [:mix], [], "hexpm", "e1554e7b2ffe5692effca19173200fdee0959bd40d201ec920a950054c27cb76"},
"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"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"},
"plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [: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", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
@@ -35,10 +49,14 @@
"qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"},
"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"},
"rtsp": {:hex, :rtsp, "0.8.1", "4bffebfcb0e1354283567178c040bbf40a85c4fbbde6d23addbbc7672cb3c700", [:mix], [{:ex_mp4, "~> 0.14.0", [hex: :ex_mp4, 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]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:media_codecs, "~> 0.10.0", [hex: :media_codecs, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.11.0", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}], "hexpm", "b4af3c30b8f79dd642940452c6ad6727bfd1df492e5ddbc1eb705f25df6f4053"},
+ "req": {:hex, :req, "0.5.16", "99ba6a36b014458e52a8b9a0543bfa752cb0344b2a9d756651db1281d4ba4450", [: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", "974a7a27982b9b791df84e8f6687d21483795882a7840e8309abdbe08bb06f09"},
+ "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"},
"thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"},
"typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"},
+ "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"},
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
"yaml_elixir": {:hex, :yaml_elixir, "2.12.0", "30343ff5018637a64b1b7de1ed2a3ca03bc641410c1f311a4dbdc1ffbbf449c7", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "ca6bacae7bac917a7155dca0ab6149088aa7bc800c94d0fe18c5238f53b313c6"},
+ "zarex": {:hex, :zarex, "1.0.6", "f657ed1187e6e90472e24c92b1fd5bf3f846e74bd240bd77276c13f336a8d168", [:mix], [], "hexpm", "b628a9b0bc312f278af2c288078c31fd4757224b82d768e91bcf3bedbe3a50e7"},
}