From 6de98af036437dcfa871105ce76522a2beea1971 Mon Sep 17 00:00:00 2001 From: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:59:55 +0100 Subject: [PATCH 01/51] API spec fixes (#159) * Don't start the app when generating API spec * lint * Fix api spec not being versioned * fix --- lib/jellyfish_web/api_spec.ex | 4 +++- mix.exs | 8 +++++++- openapi.yaml | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/jellyfish_web/api_spec.ex b/lib/jellyfish_web/api_spec.ex index a54cd914..7a7186fe 100644 --- a/lib/jellyfish_web/api_spec.ex +++ b/lib/jellyfish_web/api_spec.ex @@ -4,6 +4,8 @@ defmodule JellyfishWeb.ApiSpec do alias OpenApiSpex.{Components, Info, License, Paths, Schema, SecurityScheme} + @version Mix.Project.config()[:version] + # OpenAPISpex master specification @impl OpenApiSpex.OpenApi @@ -11,7 +13,7 @@ defmodule JellyfishWeb.ApiSpec do %OpenApiSpex.OpenApi{ info: %Info{ title: "Jellyfish Media Server", - version: "0.2.0", + version: @version, license: %License{ name: "Apache 2.0", url: "https://www.apache.org/licenses/LICENSE-2.0" diff --git a/mix.exs b/mix.exs index a862f3e6..94feed30 100644 --- a/mix.exs +++ b/mix.exs @@ -150,7 +150,13 @@ defmodule Jellyfish.MixProject do {_io_stream, exit_status} = System.cmd( "mix", - ["openapi.spec.yaml", "--spec", "JellyfishWeb.ApiSpec", generated_filename], + [ + "openapi.spec.yaml", + "--start-app=false", + "--spec", + "JellyfishWeb.ApiSpec", + generated_filename + ], into: IO.stream() ) diff --git a/openapi.yaml b/openapi.yaml index e1ac7fe5..f2248c6c 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -747,7 +747,7 @@ info: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 title: Jellyfish Media Server - version: 0.2.0 + version: 0.4.0 openapi: 3.0.0 paths: /health: From f9ff4130f4b0a46ff69ba447503fc9c6071d45cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= <48837433+roznawsk@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:28:09 +0100 Subject: [PATCH 02/51] Fix peer being removed when it disconnects (#162) * Fix peer removal * Purge when peers disconnected * Add comment --- lib/jellyfish/room.ex | 41 +++-- test/jellyfish/room_test.exs | 74 -------- .../controllers/room_controller_test.exs | 162 ++++++++++++++++++ .../integration/server_notification_test.exs | 3 + 4 files changed, 190 insertions(+), 90 deletions(-) delete mode 100644 test/jellyfish/room_test.exs diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index 37310716..ca43f1c4 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -82,7 +82,8 @@ defmodule Jellyfish.Room do try do GenServer.call(registry_room_id, :get_state) catch - :exit, {:noproc, {GenServer, :call, [^registry_room_id, :get_state, _timeout]}} -> + :exit, {reason, {GenServer, :call, [^registry_room_id, :get_state, _timeout]}} + when reason in [:noproc, :normal] -> Logger.warning( "Cannot get state of #{inspect(room_id)}, the room's process doesn't exist anymore" ) @@ -187,7 +188,7 @@ defmodule Jellyfish.Room do ) with {:ok, peer} <- Peer.new(peer_type, options) do - state = put_in(state, [:peers, peer.id], peer) + state = put_in(state, [:peers, peer.id], peer) |> maybe_schedule_peerless_purge() Logger.info("Added peer #{inspect(peer.id)}") @@ -433,7 +434,9 @@ defmodule Jellyfish.Room do :ok = Engine.remove_endpoint(state.engine_pid, peer_id) Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) peer = %{peer | status: :disconnected, socket_pid: nil} + put_in(state, [:peers, peer_id], peer) + |> maybe_schedule_peerless_purge() end {:noreply, state} @@ -475,9 +478,8 @@ defmodule Jellyfish.Room do @impl true def handle_info(%EndpointRemoved{endpoint_id: endpoint_id}, state) when is_map_key(state.peers, endpoint_id) do - {_endpoint, state} = pop_in(state, [:peers, endpoint_id]) - Logger.info("Peer #{endpoint_id} removed") - state = maybe_schedule_peerless_purge(state) + # The peer has been either removed, crashed or disconnected + # The changes in state are applied in appropriate callbacks {:noreply, state} end @@ -674,22 +676,29 @@ defmodule Jellyfish.Room do defp maybe_schedule_peerless_purge(%{config: %{peerless_purge_timeout: nil}} = state), do: state - defp maybe_schedule_peerless_purge(%{config: config, peers: peers} = state) - when map_size(peers) == 0 do - last_peer_left = Klotho.monotonic_time(:millisecond) - Klotho.send_after(config.peerless_purge_timeout * 1000, self(), :peerless_purge) + defp maybe_schedule_peerless_purge(%{config: config, peers: peers} = state) do + if all_peers_disconnected?(peers) do + last_peer_left = Klotho.monotonic_time(:millisecond) - %{state | last_peer_left: last_peer_left} - end + Klotho.send_after(config.peerless_purge_timeout * 1000, self(), :peerless_purge) - defp maybe_schedule_peerless_purge(state), do: state + %{state | last_peer_left: last_peer_left} + else + state + end + end - defp peerless_long_enough?(%{config: config, peers: peers, last_peer_left: last_peer_left}) - when map_size(peers) == 0 do - Klotho.monotonic_time(:millisecond) >= last_peer_left + config.peerless_purge_timeout * 1000 + defp peerless_long_enough?(%{config: config, peers: peers, last_peer_left: last_peer_left}) do + if all_peers_disconnected?(peers) do + Klotho.monotonic_time(:millisecond) >= last_peer_left + config.peerless_purge_timeout * 1000 + else + false + end end - defp peerless_long_enough?(_state), do: false + defp all_peers_disconnected?(peers) do + peers |> Map.values() |> Enum.all?(&(&1.status == :disconnected)) + end defp handle_remove_component(component_id, state, reason) do {component, state} = pop_in(state, [:components, component_id]) diff --git a/test/jellyfish/room_test.exs b/test/jellyfish/room_test.exs deleted file mode 100644 index 357ec9b4..00000000 --- a/test/jellyfish/room_test.exs +++ /dev/null @@ -1,74 +0,0 @@ -defmodule Jellyfish.RoomTest do - use ExUnit.Case, async: true - - alias Jellyfish.{Peer, Room} - - @purge_timeout_s 60 - @purge_timeout_ms @purge_timeout_s * 1000 - @message_timeout_ms 20 - - setup do - Klotho.Mock.reset() - Klotho.Mock.freeze() - end - - describe "peerless purge" do - test "happens if peers never joined" do - {:ok, config} = Room.Config.from_params(%{"peerlessPurgeTimeout" => @purge_timeout_s}) - {:ok, pid, _id} = Room.start(config) - Process.monitor(pid) - - Klotho.Mock.warp_by(@purge_timeout_ms + 10) - - assert_receive {:DOWN, _ref, :process, ^pid, :normal}, @message_timeout_ms - end - - test "happens if peers joined, then left" do - {:ok, config} = Room.Config.from_params(%{"peerlessPurgeTimeout" => @purge_timeout_s}) - {:ok, pid, id} = Room.start(config) - Process.monitor(pid) - - {:ok, peer} = Room.add_peer(id, Peer.WebRTC) - - Klotho.Mock.warp_by(@purge_timeout_ms + 10) - refute_receive {:DOWN, _ref, :process, ^pid, :normal}, @message_timeout_ms - - :ok = Room.remove_peer(id, peer.id) - - Klotho.Mock.warp_by(@purge_timeout_ms + 10) - assert_receive {:DOWN, _ref, :process, ^pid, :normal}, @message_timeout_ms - end - - test "does not happen if peers rejoined quickly" do - {:ok, config} = Room.Config.from_params(%{"peerlessPurgeTimeout" => @purge_timeout_s}) - {:ok, pid, id} = Room.start(config) - Process.monitor(pid) - - {:ok, peer} = Room.add_peer(id, Peer.WebRTC) - - Klotho.Mock.warp_by(@purge_timeout_ms + 10) - refute_receive {:DOWN, _ref, :process, ^pid, :normal}, @message_timeout_ms - - :ok = Room.remove_peer(id, peer.id) - - Klotho.Mock.warp_by(@purge_timeout_ms |> div(2)) - refute_receive {:DOWN, _ref, :process, ^pid, :normal}, @message_timeout_ms - - {:ok, _peer} = Room.add_peer(id, Peer.WebRTC) - Klotho.Mock.warp_by(@purge_timeout_ms + 10) - refute_receive {:DOWN, _ref, :process, ^pid, :normal}, @message_timeout_ms - - :ok = GenServer.stop(pid) - end - - test "does not happen when not configured" do - {:ok, config} = Room.Config.from_params(%{}) - {:ok, pid, _id} = Room.start(config) - - Klotho.Mock.warp_by(@purge_timeout_ms + 10) - refute_receive {:DOWN, _ref, :process, ^pid, :normal}, @message_timeout_ms - - :ok = GenServer.stop(pid) - end - end -end diff --git a/test/jellyfish_web/controllers/room_controller_test.exs b/test/jellyfish_web/controllers/room_controller_test.exs index 7c7a9e68..5902f9b0 100644 --- a/test/jellyfish_web/controllers/room_controller_test.exs +++ b/test/jellyfish_web/controllers/room_controller_test.exs @@ -2,18 +2,53 @@ defmodule JellyfishWeb.RoomControllerTest do use JellyfishWeb.ConnCase, async: false import OpenApiSpex.TestAssertions + + alias __MODULE__.Endpoint + + alias Jellyfish.PeerMessage.Authenticated alias Jellyfish.RoomService + alias JellyfishWeb.{PeerSocket, WS} @schema JellyfishWeb.ApiSpec.spec() + @purge_timeout_s 3 + @purge_timeout_ms @purge_timeout_s * 1000 + + @port 5910 + @path "ws://127.0.0.1:#{@port}/socket/peer/websocket" + @auth_response %Authenticated{} + + Application.put_env( + :jellyfish, + Endpoint, + https: false, + http: [port: @port], + server: true + ) + + defmodule Endpoint do + use Phoenix.Endpoint, otp_app: :jellyfish + + alias JellyfishWeb.PeerSocket + + socket "/socket/peer", PeerSocket, + websocket: true, + longpoll: false + end + setup_all do delete_all_rooms() + assert {:ok, _pid} = Endpoint.start_link() + :ok end setup %{conn: conn} do server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) + Klotho.Mock.reset() + Klotho.Mock.freeze() + on_exit(fn -> delete_all_rooms() end) [conn: conn] @@ -142,6 +177,125 @@ defmodule JellyfishWeb.RoomControllerTest do end end + describe "peerless purge" do + setup %{conn: conn} do + conn = post(conn, ~p"/room", peerlessPurgeTimeout: @purge_timeout_s) + assert %{"id" => id} = json_response(conn, :created)["data"]["room"] + %{conn: conn, id: id} + end + + test "happens if peers never joined", %{conn: conn, id: id} do + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + assert response(conn, :created) + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + + conn = get(conn, ~p"/room") + assert Enum.empty?(json_response(conn, :ok)["data"]) + end + + test "happens if peer joined, then removed", %{conn: conn, id: id} do + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + + assert %{"token" => token, "peer" => %{"id" => peer_id}} = + json_response(conn, :created)["data"] + + connect_peer(token) + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + + conn = delete(conn, ~p"/room/#{id}/peer/#{peer_id}") + assert response(conn, :no_content) + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + + conn = get(conn, ~p"/room") + assert Enum.empty?(json_response(conn, :ok)["data"]) + end + + test "happens if peer joined, then disconnected", %{conn: conn, id: id} do + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + assert %{"token" => token} = json_response(conn, :created)["data"] + + ws = connect_peer(token) + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + + GenServer.stop(ws) + Process.sleep(10) + + conn = get(conn, ~p"/room/#{id}") + + assert %{"status" => "disconnected"} = + json_response(conn, :ok)["data"]["peers"] |> List.first() + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + + conn = get(conn, ~p"/room") + assert Enum.empty?(json_response(conn, :ok)["data"]) + end + + test "does not happen if peers rejoined quickly", %{conn: conn, id: id} do + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + assert %{"token" => token} = json_response(conn, :created)["data"] + + ws = connect_peer(token) + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + + GenServer.stop(ws) + Process.sleep(10) + conn = get(conn, ~p"/room/#{id}") + + assert %{"status" => "disconnected"} = + json_response(conn, :ok)["data"]["peers"] |> List.first() + + Klotho.Mock.warp_by(@purge_timeout_ms |> div(2)) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + + connect_peer(token) + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + end + + test "timeout is reset if another peer is created and removed", %{conn: conn, id: id} do + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + assert %{"peer" => %{"id" => peer_id}} = json_response(conn, :created)["data"] + + conn = delete(conn, ~p"/room/#{id}/peer/#{peer_id}") + assert response(conn, :no_content) + + Klotho.Mock.warp_by(@purge_timeout_ms |> div(2)) + + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + assert response(conn, :created) + + Klotho.Mock.warp_by(@purge_timeout_ms |> div(2) |> Kernel.+(10)) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + + Klotho.Mock.warp_by(@purge_timeout_ms |> div(2)) + conn = get(conn, ~p"/room") + assert Enum.empty?(json_response(conn, :ok)["data"]) + end + + test "does not happen when not configured", %{conn: conn} do + conn = post(conn, ~p"/room") + assert %{"id" => id} = json_response(conn, :created)["data"]["room"] + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + end + end + describe "delete room" do setup [:create_room] @@ -237,4 +391,12 @@ defmodule JellyfishWeb.RoomControllerTest do HTTPoison.delete("http://127.0.0.1:4002/room/#{room["id"]}", headers) end) end + + def connect_peer(token) do + {:ok, ws} = WS.start_link(@path, :peer) + WS.send_auth_request(ws, token) + assert_receive @auth_response + + ws + end end diff --git a/test/jellyfish_web/integration/server_notification_test.exs b/test/jellyfish_web/integration/server_notification_test.exs index 36461cf3..cd56d84a 100644 --- a/test/jellyfish_web/integration/server_notification_test.exs +++ b/test/jellyfish_web/integration/server_notification_test.exs @@ -279,6 +279,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}}, 2_000 + state = :sys.get_state(room_pid) + assert Map.has_key?(state.peers, peer_id) + delete(conn, ~p"/room/#{room_id}") end From 0eb6625bca0baa31915fd5699a5ee691e649c7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Wed, 6 Mar 2024 15:09:58 +0100 Subject: [PATCH 03/51] Update OpenApi spec --- mix.exs | 2 +- openapi.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 94feed30..56894504 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Jellyfish.MixProject do def project do [ app: :jellyfish, - version: "0.4.0", + version: "0.4.1", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, diff --git a/openapi.yaml b/openapi.yaml index f2248c6c..88c5c724 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -747,7 +747,7 @@ info: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 title: Jellyfish Media Server - version: 0.4.0 + version: 0.4.1 openapi: 3.0.0 paths: /health: From 29c27ca552da4a14a4f84a4182cad5ceb00c95d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= <48837433+roznawsk@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:52:07 +0100 Subject: [PATCH 04/51] [RTC-478] Propagate peer crash reason (#161) * Send crash reason when closing peer ws * Fix tests * Update protos * Update engine dep * Fix ci * Fix protos --- lib/jellyfish/event.ex | 4 ++-- lib/jellyfish/room.ex | 18 +++++++++++++----- lib/jellyfish_web/peer_socket.ex | 8 +++++++- .../jellyfish/server_notifications.pb.ex | 1 + mix.exs | 3 ++- mix.lock | 4 ++-- protos | 2 +- 7 files changed, 28 insertions(+), 12 deletions(-) diff --git a/lib/jellyfish/event.ex b/lib/jellyfish/event.ex index f8a73f36..ffed71f6 100644 --- a/lib/jellyfish/event.ex +++ b/lib/jellyfish/event.ex @@ -60,8 +60,8 @@ defmodule Jellyfish.Event do defp to_proto_server_notification({:peer_disconnected, room_id, peer_id}), do: {:peer_disconnected, %PeerDisconnected{room_id: room_id, peer_id: peer_id}} - defp to_proto_server_notification({:peer_crashed, room_id, peer_id}), - do: {:peer_crashed, %PeerCrashed{room_id: room_id, peer_id: peer_id}} + defp to_proto_server_notification({:peer_crashed, room_id, peer_id, reason}), + do: {:peer_crashed, %PeerCrashed{room_id: room_id, peer_id: peer_id, reason: reason}} defp to_proto_server_notification({:component_crashed, room_id, component_id}), do: {:component_crashed, %ComponentCrashed{room_id: room_id, component_id: component_id}} diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index ca43f1c4..0434716b 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -410,12 +410,12 @@ defmodule Jellyfish.Room do end @impl true - def handle_info(%EndpointCrashed{endpoint_id: endpoint_id}, state) do - Logger.error("RTC Engine endpoint #{inspect(endpoint_id)} crashed") + def handle_info(%EndpointCrashed{endpoint_id: endpoint_id, reason: reason}, state) do + Logger.error("RTC Engine endpoint #{inspect(endpoint_id)} crashed: #{inspect(reason)}") state = if Map.has_key?(state.peers, endpoint_id) do - handle_remove_peer(endpoint_id, state, :peer_crashed) + handle_remove_peer(endpoint_id, state, {:peer_crashed, parse_crash_reason(reason)}) else handle_remove_component(endpoint_id, state, :component_crashed) end @@ -740,8 +740,9 @@ defmodule Jellyfish.Room do if peer.status == :connected and reason == :peer_removed, do: Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) - if reason == :peer_crashed, - do: Event.broadcast_server_notification({:peer_crashed, state.id, peer_id}) + with {:peer_crashed, crash_reason} <- reason do + Event.broadcast_server_notification({:peer_crashed, state.id, peer_id, crash_reason}) + end state end @@ -800,4 +801,11 @@ defmodule Jellyfish.Room do :components -> :component_id end end + + defp parse_crash_reason( + {:membrane_child_crash, _child, {%RuntimeError{message: reason}, _stack}} + ), + do: reason + + defp parse_crash_reason(_reason), do: nil end diff --git a/lib/jellyfish_web/peer_socket.ex b/lib/jellyfish_web/peer_socket.ex index ec2a61f9..54f0e9ee 100644 --- a/lib/jellyfish_web/peer_socket.ex +++ b/lib/jellyfish_web/peer_socket.ex @@ -124,7 +124,13 @@ defmodule JellyfishWeb.PeerSocket do end @impl true - def handle_info({:stop_connection, _reason}, state) do + def handle_info({:stop_connection, {:peer_crashed, crash_reason}}, state) + when crash_reason != nil do + {:stop, :closed, {1011, crash_reason}, state} + end + + @impl true + def handle_info({:stop_connection, {:peer_crashed, _reason}}, state) do {:stop, :closed, {1011, "Internal server error"}, state} end diff --git a/lib/protos/jellyfish/server_notifications.pb.ex b/lib/protos/jellyfish/server_notifications.pb.ex index 5348b6c5..d00fc40b 100644 --- a/lib/protos/jellyfish/server_notifications.pb.ex +++ b/lib/protos/jellyfish/server_notifications.pb.ex @@ -51,6 +51,7 @@ defmodule Jellyfish.ServerMessage.PeerCrashed do field :room_id, 1, type: :string, json_name: "roomId" field :peer_id, 2, type: :string, json_name: "peerId" + field :reason, 3, type: :string end defmodule Jellyfish.ServerMessage.ComponentCrashed do diff --git a/mix.exs b/mix.exs index 56894504..eb807cf2 100644 --- a/mix.exs +++ b/mix.exs @@ -69,7 +69,8 @@ defmodule Jellyfish.MixProject do {:protobuf, "~> 0.12.0"}, # Membrane deps - {:membrane_rtc_engine, "~> 0.21.0"}, + {:membrane_rtc_engine, + github: "jellyfish-dev/membrane_rtc_engine", sparse: "engine", override: true}, {:membrane_rtc_engine_webrtc, "~> 0.7.0"}, {:membrane_rtc_engine_hls, "~> 0.6.0"}, {:membrane_rtc_engine_rtsp, "~> 0.6.0"}, diff --git a/mix.lock b/mix.lock index e384b55d..3ab830f9 100644 --- a/mix.lock +++ b/mix.lock @@ -63,12 +63,12 @@ "membrane_ogg_plugin": {:hex, :membrane_ogg_plugin, "0.3.0", "6e98b8932a2b88174dc3922989a475e02ee327589222b1c8422ff4fa630325c3", [:mix], [{:crc, "~> 0.10", [hex: :crc, 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]}], "hexpm", "82557299b3d72aab0fafa07a05dcc3a5a842eeaa3de3f2f3959677517b66b713"}, "membrane_opus_format": {:hex, :membrane_opus_format, "0.3.0", "3804d9916058b7cfa2baa0131a644d8186198d64f52d592ae09e0942513cb4c2", [:mix], [], "hexpm", "8fc89c97be50de23ded15f2050fe603dcce732566fe6fdd15a2de01cb6b81afe"}, "membrane_opus_plugin": {:hex, :membrane_opus_plugin, "0.19.3", "af398a10c84d27e49b9a68ec78a54f123f2637441dd380857a3da4bb492eca5c", [: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", "172d5637233e4e7cb2c464be34ea85b487188887381ef5ff98d5c110fdf44f5b"}, - "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.1", "a0d5b7942f8be452c30744207f78284f6a0e0c84c968aba7d76e206fbf75bc5d", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "87ad44752e2cf0fa3b31c5aac15b863343c2f6e0f0fd201f5ec4c0bcda8c6fa3"}, + "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_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:hex, :membrane_rtc_engine, "0.21.0", "eb97284e75b08ae6f63395e7eef192c8859a64a18859c2a37a1f56a1852c799c", [:mix], [{:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.1", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:statistics, "~> 0.6.0", [hex: :statistics, repo: "hexpm", optional: false]}], "hexpm", "1e779824acdcc8f0d1d81ba0d51ebc634b2716839e161886ee59e4fc7ee1e0b1"}, + "membrane_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "4a8f08b7430d5faaee899a1c4c8e72debcb44cf4", [sparse: "engine"]}, "membrane_rtc_engine_file": {:hex, :membrane_rtc_engine_file, "0.4.0", "85f873478e73253bebd27045b3ce0ef5fd7713f09bb559953221727f1c0b501b", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_plugin, "~> 0.9.0", [hex: :membrane_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_ogg_plugin, "~> 0.3.0", [hex: :membrane_ogg_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_realtimer_plugin, "~> 0.9.0", [hex: :membrane_realtimer_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.21.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.7.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}], "hexpm", "5e6939c6d52033734b4eaa214e3d6b7dc1e44db5419a18917111f29101ca9f06"}, "membrane_rtc_engine_hls": {:hex, :membrane_rtc_engine_hls, "0.6.0", "104756d9cfad5bb9642db477ecd16e5bffd6c2fd0204432c28514ff2aaf69da0", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.5", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_audio_mix_plugin, "~> 0.16.0", [hex: :membrane_audio_mix_plugin, repo: "hexpm", optional: true]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.31.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_plugin, "~> 0.9.0", [hex: :membrane_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_http_adaptive_stream_plugin, "~> 0.18.0", [hex: :membrane_http_adaptive_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.21.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.7.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_video_compositor_plugin, "~> 0.7.0", [hex: :membrane_video_compositor_plugin, repo: "hexpm", optional: true]}], "hexpm", "d310c4b6a05965b544cd64765553522e500203c5a20b3d86930f4e916054951c"}, "membrane_rtc_engine_rtsp": {:hex, :membrane_rtc_engine_rtsp, "0.6.0", "6fd7db85ada0beddb3681bd5b3c3230ff21c839cae6072d84df66763374f24fe", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_plugin, "~> 0.9.0", [hex: :membrane_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.21.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.7.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.19.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.1", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.5.1", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.13.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}], "hexpm", "e17e21a9274f5edb3f234cb23a7cf0315a9b0b93abf936cd9fc30588eae7c98a"}, diff --git a/protos b/protos index cb67f49c..b9683a3f 160000 --- a/protos +++ b/protos @@ -1 +1 @@ -Subproject commit cb67f49c47250daf9a97f1296ef7be8965ef4acf +Subproject commit b9683a3faec93b90b5f39e68cae387390fceba05 From 16996d351c4ccf3455ee40cdd9ec1d934e1f568b Mon Sep 17 00:00:00 2001 From: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:15:17 +0100 Subject: [PATCH 05/51] [RTC-474] Expand room metrics (#160) --- lib/jellyfish/room.ex | 9 +++++-- lib/jellyfish/room_service.ex | 12 +++------ lib/jellyfish_web/telemetry.ex | 47 ++++++++++++++++++++++------------ 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index 0434716b..dd3abd87 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -216,6 +216,7 @@ defmodule Jellyfish.Room do :ok = Engine.add_endpoint(state.engine_pid, peer.engine_endpoint, id: peer_id) Logger.info("Peer #{inspect(peer_id)} connected") + :telemetry.execute([:jellyfish, :room], %{peer_connects: 1}, %{room_id: state.id}) {:ok, state} @@ -433,6 +434,7 @@ defmodule Jellyfish.Room do {peer_id, peer} -> :ok = Engine.remove_endpoint(state.engine_pid, peer_id) Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) + :telemetry.execute([:jellyfish, :room], %{peer_disconnects: 1}, %{room_id: state.id}) peer = %{peer | status: :disconnected, socket_pid: nil} put_in(state, [:peers, peer_id], peer) @@ -737,11 +739,14 @@ defmodule Jellyfish.Room do Logger.info("Removed peer #{inspect(peer_id)} from room #{inspect(state.id)}") - if peer.status == :connected and reason == :peer_removed, - do: Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) + if peer.status == :connected and reason == :peer_removed do + Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) + :telemetry.execute([:jellyfish, :room], %{peer_disconnects: 1}, %{room_id: state.id}) + end with {:peer_crashed, crash_reason} <- reason do Event.broadcast_server_notification({:peer_crashed, state.id, peer_id, crash_reason}) + :telemetry.execute([:jellyfish, :room], %{peer_crashes: 1}, %{room_id: state.id}) end state diff --git a/lib/jellyfish/room_service.ex b/lib/jellyfish/room_service.ex index a7723274..350e1c35 100644 --- a/lib/jellyfish/room_service.ex +++ b/lib/jellyfish/room_service.ex @@ -186,7 +186,8 @@ defmodule Jellyfish.RoomService do [:jellyfish, :room], %{ peers: peer_count, - peer_time_total: peer_count * @metric_interval_in_seconds + peer_time: peer_count * @metric_interval_in_seconds, + duration: @metric_interval_in_seconds }, %{room_id: room.id} ) @@ -223,14 +224,7 @@ defmodule Jellyfish.RoomService do end defp clear_room_metrics(room_id) do - :telemetry.execute( - [:jellyfish, :room], - %{ - peers: 0, - peer_time_total: 0 - }, - %{room_id: room_id} - ) + :telemetry.execute([:jellyfish, :room], %{peers: 0}, %{room_id: room_id}) end defp find_best_node(node_resources) do diff --git a/lib/jellyfish_web/telemetry.ex b/lib/jellyfish_web/telemetry.ex index 5b31485d..8cdef662 100644 --- a/lib/jellyfish_web/telemetry.ex +++ b/lib/jellyfish_web/telemetry.ex @@ -125,37 +125,52 @@ defmodule JellyfishWeb.Telemetry do description: "Total HTTP traffic sent (bytes)" ), last_value("jellyfish.rooms", - description: "Amount of rooms currently present in Jellyfish" + description: "Number of rooms currently present in Jellyfish" ), # FIXME: Prometheus warns about using labels to store dimensions with high cardinality, # such as UUIDs. For more information refer here: https://prometheus.io/docs/practices/naming/#labels last_value("jellyfish.room.peers", tags: [:room_id], - description: "Amount of peers currently present in a given room" + description: "Number of peers currently present in a given room" ), sum("jellyfish.room.peer_time.total.seconds", event_name: [:jellyfish, :room], - measurement: :peer_time_total, + measurement: :peer_time, tags: [:room_id], description: "Total peer time accumulated for a given room (seconds)" + ), + sum("jellyfish.room.duration.seconds", + event_name: [:jellyfish, :room], + measurement: :duration, + tags: [:room_id], + description: "Duration of a given room (seconds)" + ), + sum("jellyfish.room.peer_connects.total", + event_name: [:jellyfish, :room], + measurement: :peer_connects, + tags: [:room_id], + description: + "Number of PeerConnected events emitted during the lifetime of a given room" + ), + sum("jellyfish.room.peer_disconnects.total", + event_name: [:jellyfish, :room], + measurement: :peer_disconnects, + tags: [:room_id], + description: + "Number of PeerDisconnected events emitted during the lifetime of a given room" + ), + sum("jellyfish.room.peer_crashes.total", + event_name: [:jellyfish, :room], + measurement: :peer_crashes, + tags: [:room_id], + description: "Number of PeerCrashed events emitted during the lifetime of a given room" ) ] end def default_webrtc_metrics() do - :telemetry.execute( - [Membrane.ICE, :ice, :payload, :sent], - %{ - bytes: 0 - } - ) - - :telemetry.execute( - [Membrane.ICE, :ice, :payload, :received], - %{ - bytes: 0 - } - ) + :telemetry.execute(@ice_sent_event, %{bytes: 0}) + :telemetry.execute(@ice_received_event, %{bytes: 0}) end end From 95e0bfa65cf85e78928e43f62c709bdb36de8fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= <48837433+roznawsk@users.noreply.github.com> Date: Fri, 8 Mar 2024 10:38:38 +0100 Subject: [PATCH 06/51] [RTC-498] Fix tracks present after peer disconnect (#164) * Refactor server_notification_test.exs * Refactor v2 * Remove tracks when peer disconnects * Remove comment * Revert unnecessary change --- lib/jellyfish/room.ex | 11 +- .../integration/server_notification_test.exs | 349 +++++++++++------- 2 files changed, 223 insertions(+), 137 deletions(-) diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index dd3abd87..5c802aee 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -435,7 +435,16 @@ defmodule Jellyfish.Room do :ok = Engine.remove_endpoint(state.engine_pid, peer_id) Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) :telemetry.execute([:jellyfish, :room], %{peer_disconnects: 1}, %{room_id: state.id}) - peer = %{peer | status: :disconnected, socket_pid: nil} + + peer.tracks + |> Map.values() + |> Enum.each( + &Event.broadcast_server_notification( + {:track_removed, state.id, {:peer_id, peer_id}, &1} + ) + ) + + peer = %{peer | status: :disconnected, socket_pid: nil, tracks: %{}} put_in(state, [:peers, peer_id], peer) |> maybe_schedule_peerless_purge() diff --git a/test/jellyfish_web/integration/server_notification_test.exs b/test/jellyfish_web/integration/server_notification_test.exs index cd56d84a..f83810ed 100644 --- a/test/jellyfish_web/integration/server_notification_test.exs +++ b/test/jellyfish_web/integration/server_notification_test.exs @@ -7,11 +7,11 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do alias __MODULE__.Endpoint + alias Membrane.RTC.Engine + alias Jellyfish.Component.HLS alias Jellyfish.Component.HLS.Manager - alias Jellyfish.PeerMessage - alias Jellyfish.RoomService - alias Jellyfish.ServerMessage + alias Jellyfish.{PeerMessage, Room, RoomService, ServerMessage} alias Jellyfish.ServerMessage.{ Authenticated, @@ -106,10 +106,10 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do setup(%{conn: conn}) do :ok = PubSub.subscribe(@pubsub, "webhook") - on_exit(fn -> - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) - conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) + server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) + on_exit(fn -> conn = get(conn, ~p"/room") rooms = json_response(conn, :ok)["data"] @@ -121,73 +121,55 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do :ok = PubSub.unsubscribe(@pubsub, "webhook") end) - end - test "invalid token" do - {:ok, ws} = WS.start_link(@path, :server) - server_api_token = "invalid" <> Application.fetch_env!(:jellyfish, :server_api_token) - WS.send_auth_request(ws, server_api_token) - - assert_receive {:disconnected, {:remote, 1000, "invalid token"}}, 1000 - end - - test "invalid first message" do - {:ok, ws} = WS.start_link(@path, :server) - msg = ServerMessage.encode(%ServerMessage{content: {:authenticated, %Authenticated{}}}) - - :ok = WS.send_binary_frame(ws, msg) - assert_receive {:disconnected, {:remote, 1000, "invalid auth request"}}, 1000 + %{conn: conn} end - test "correct token" do - create_and_authenticate() - end + describe "establishing connection" do + test "invalid token" do + {:ok, ws} = WS.start_link(@path, :server) + server_api_token = "invalid" <> Application.fetch_env!(:jellyfish, :server_api_token) + WS.send_auth_request(ws, server_api_token) - test "closes on receiving an invalid message from a client" do - ws = create_and_authenticate() + assert_receive {:disconnected, {:remote, 1000, "invalid token"}}, 1000 + end - Process.flag(:trap_exit, true) + test "invalid first message" do + {:ok, ws} = WS.start_link(@path, :server) + msg = ServerMessage.encode(%ServerMessage{content: {:authenticated, %Authenticated{}}}) - :ok = - WS.send_binary_frame( - ws, - ServerMessage.encode(%ServerMessage{content: {:authenticated, %Authenticated{}}}) - ) + :ok = WS.send_binary_frame(ws, msg) + assert_receive {:disconnected, {:remote, 1000, "invalid auth request"}}, 1000 + end - assert_receive {:disconnected, {:remote, 1003, "operation not allowed"}}, 1000 - assert_receive {:EXIT, ^ws, {:remote, 1003, "operation not allowed"}} + test "correct token" do + create_and_authenticate() + end - Process.flag(:trap_exit, false) - end + test "closes on receiving an invalid message from a client" do + ws = create_and_authenticate() - test "sends HlsPlayable notification", %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) - ws = create_and_authenticate() - subscribe(ws, :server_notification) - {room_id, _peer_id, _token, conn} = add_room_and_peer(conn, server_api_token) + Process.flag(:trap_exit, true) - {conn, hls_id} = add_hls_component(conn, room_id) - {conn, _rtsp_id} = add_rtsp_component(conn, room_id) + :ok = + WS.send_binary_frame( + ws, + ServerMessage.encode(%ServerMessage{content: {:authenticated, %Authenticated{}}}) + ) - {:ok, room_pid} = RoomService.find_room(room_id) + assert_receive {:disconnected, {:remote, 1003, "operation not allowed"}}, 1000 + assert_receive {:EXIT, ^ws, {:remote, 1003, "operation not allowed"}} - send(room_pid, {:playlist_playable, :video, "hls_output/#{room_id}"}) - assert_receive %HlsPlayable{room_id: ^room_id, component_id: ^hls_id} - - assert_receive {:webhook_notification, - %HlsPlayable{room_id: ^room_id, component_id: ^hls_id}}, - 1_000 - - conn = delete(conn, ~p"/room/#{room_id}/") - assert response(conn, :no_content) - end + Process.flag(:trap_exit, false) + end - test "doesn't send messages if not subscribed", %{conn: conn} do - create_and_authenticate() + test "doesn't send messages if not subscribed", %{conn: conn} do + create_and_authenticate() - trigger_notification(conn) + trigger_notification(conn) - refute_receive %PeerConnected{}, 200 + refute_receive %PeerConnected{}, 200 + end end test "sends a message when room gets created and deleted", %{conn: conn} do @@ -211,81 +193,175 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do assert_receive {:webhook_notification, %RoomDeleted{room_id: ^room_id}}, 1_000 end - test "sends a message when peer connects and room is deleted", %{conn: conn} do - {room_id, peer_id, conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) + describe "WebRTC Peer" do + test "sends a message when peer connects and room is deleted", %{conn: conn} do + {room_id, peer_id, conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) - _conn = delete(conn, ~p"/room/#{room_id}") - assert_receive %RoomDeleted{room_id: ^room_id} + _conn = delete(conn, ~p"/room/#{room_id}") + assert_receive %RoomDeleted{room_id: ^room_id} - assert_receive {:webhook_notification, %RoomDeleted{room_id: ^room_id}}, - 1_000 + assert_receive {:webhook_notification, %RoomDeleted{room_id: ^room_id}}, + 1_000 - refute_received %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id} + refute_received %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id} - refute_received {:webhook_notification, - %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}} - end + refute_received {:webhook_notification, + %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}} + end - test "sends a message when peer connects and peer is removed", %{conn: conn} do - {room_id, peer_id, conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) + test "sends a message when peer connects and peer is removed", %{conn: conn} do + {room_id, peer_id, conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) - conn = delete(conn, ~p"/room/#{room_id}/peer/#{peer_id}") - assert response(conn, :no_content) + conn = delete(conn, ~p"/room/#{room_id}/peer/#{peer_id}") + assert response(conn, :no_content) - assert_receive %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id} + assert_receive %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id} - assert_receive {:webhook_notification, - %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}}, - 2_500 + assert_receive {:webhook_notification, + %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}}, + 1_000 - _conn = delete(conn, ~p"/room/#{room_id}") - assert_receive %RoomDeleted{room_id: ^room_id} + _conn = delete(conn, ~p"/room/#{room_id}") + assert_receive %RoomDeleted{room_id: ^room_id} - assert_receive {:webhook_notification, %RoomDeleted{room_id: ^room_id}}, - 1_000 - end + assert_receive {:webhook_notification, %RoomDeleted{room_id: ^room_id}}, + 1_000 + end - test "sends a message when peer connects and room crashes", %{conn: conn} do - {room_id, peer_id, _conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) - {:ok, room_pid} = Jellyfish.RoomService.find_room(room_id) + test "sends a message when peer connects and room crashes", %{conn: conn} do + {room_id, peer_id, _conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) + {:ok, room_pid} = Jellyfish.RoomService.find_room(room_id) - Process.exit(room_pid, :kill) + Process.exit(room_pid, :kill) - assert_receive %RoomCrashed{room_id: ^room_id} + assert_receive %RoomCrashed{room_id: ^room_id} - assert_receive {:webhook_notification, %RoomCrashed{room_id: ^room_id}}, - 1_000 + assert_receive {:webhook_notification, %RoomCrashed{room_id: ^room_id}}, + 1_000 - refute_received %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id} + refute_received %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id} - refute_received {:webhook_notification, - %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}} - end + refute_received {:webhook_notification, + %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}} + end - test "sends a message when peer connects and it crashes", %{conn: conn} do - {room_id, peer_id, conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) + test "sends a message when peer connects and it crashes", %{conn: conn} do + {room_id, peer_id, conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) - {:ok, room_pid} = Jellyfish.RoomService.find_room(room_id) + {:ok, room_pid} = Jellyfish.RoomService.find_room(room_id) - state = :sys.get_state(room_pid) + state = :sys.get_state(room_pid) - peer_socket_pid = state.peers[peer_id].socket_pid + peer_socket_pid = state.peers[peer_id].socket_pid - Process.exit(peer_socket_pid, :kill) + Process.exit(peer_socket_pid, :kill) - assert_receive %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id} + assert_receive %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id} - assert_receive {:webhook_notification, - %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}}, - 2_000 + assert_receive {:webhook_notification, + %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}}, + 2_000 + + state = :sys.get_state(room_pid) + assert Map.has_key?(state.peers, peer_id) + + delete(conn, ~p"/room/#{room_id}") + end + + test "sends message when peer metadata is updated", %{conn: conn} do + {room_id, peer_id, _conn, peer_ws} = subscribe_on_notifications_and_connect_peer(conn) + + metadata = %{name: "Jellyuser"} + metadata_encoded = Jason.encode!(metadata) + + media_event = %PeerMessage.MediaEvent{ + data: %{"type" => "connect", "data" => %{"metadata" => metadata}} |> Jason.encode!() + } + + :ok = + WS.send_binary_frame( + peer_ws, + PeerMessage.encode(%PeerMessage{content: {:media_event, media_event}}) + ) + + assert_receive %PeerMetadataUpdated{ + room_id: ^room_id, + peer_id: ^peer_id, + metadata: ^metadata_encoded + } = peer_metadata_updated + + assert_receive {:webhook_notification, ^peer_metadata_updated}, 1_000 + end + + test "sends notifications when peer adds track and then disconnects", %{conn: conn} do + ws = create_and_authenticate() + subscribe(ws, :server_notification) + + {:ok, config} = Room.Config.from_params(%{"webhookUrl" => @webhook_url}) + + {:ok, room_pid, room_id} = Room.start(config) + Jellyfish.WebhookNotifier.add_webhook(room_id, config.webhook_url) + + {peer_id, token, _conn} = add_peer(conn, room_id) + {:ok, peer_ws} = WS.start("ws://127.0.0.1:#{@port}/socket/peer/websocket", :peer) + WS.send_auth_request(peer_ws, token) + assert_receive %PeerConnected{peer_id: ^peer_id, room_id: ^room_id} + + msg = %Engine.Message.TrackAdded{ + endpoint_id: peer_id, + endpoint_type: nil, + track_id: "funny-cat-must-see", + track_type: :video, + track_encoding: :H264, + track_metadata: "myvideo" + } + + send(room_pid, msg) + + assert_receive %TrackAdded{ + room_id: ^room_id, + endpoint_info: {:peer_id, ^peer_id}, + track: + %Track{ + id: _track_id, + type: :TRACK_TYPE_VIDEO, + metadata: "\"myvideo\"" + } = track_info + } = track_added + + assert_receive {:webhook_notification, ^track_added}, 1000 + + GenServer.stop(peer_ws) + + assert_receive %TrackRemoved{ + room_id: ^room_id, + endpoint_info: {:peer_id, ^peer_id}, + track: ^track_info + } = track_removed + + assert_receive {:webhook_notification, ^track_removed}, 1000 + + assert_receive %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id} + + assert_receive {:webhook_notification, + %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}}, + 1000 + + conn = get(conn, ~p"/room/#{room_id}") + peer = json_response(conn, :ok)["data"]["peers"] |> List.first() - state = :sys.get_state(room_pid) - assert Map.has_key?(state.peers, peer_id) + assert %{ + "id" => ^peer_id, + "type" => "webrtc", + "status" => "disconnected", + "tracks" => tracks + } = peer - delete(conn, ~p"/room/#{room_id}") + assert Enum.empty?(tracks) + end end - test "sends message when tracks are added or removed", %{conn: conn} do + test "sends message when File adds or removes tracks", %{conn: conn} do media_sources_directory = Application.fetch_env!(:jellyfish, :media_files_path) |> Path.join(@file_component_directory) @@ -324,29 +400,25 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do :file.del_dir_r(media_sources_directory) end - test "sends message when peer metadata is updated", %{conn: conn} do - {room_id, peer_id, _conn, peer_ws} = subscribe_on_notifications_and_connect_peer(conn) + test "sends HlsPlayable notification", %{conn: conn} do + ws = create_and_authenticate() + subscribe(ws, :server_notification) + {room_id, _peer_id, _token, conn} = add_room_and_peer(conn) - metadata = %{name: "Jellyuser"} - metadata_encoded = Jason.encode!(metadata) + {conn, hls_id} = add_hls_component(conn, room_id) + {conn, _rtsp_id} = add_rtsp_component(conn, room_id) - media_event = %PeerMessage.MediaEvent{ - data: %{"type" => "connect", "data" => %{"metadata" => metadata}} |> Jason.encode!() - } + {:ok, room_pid} = RoomService.find_room(room_id) - :ok = - WS.send_binary_frame( - peer_ws, - PeerMessage.encode(%PeerMessage{content: {:media_event, media_event}}) - ) + send(room_pid, {:playlist_playable, :video, "hls_output/#{room_id}"}) + assert_receive %HlsPlayable{room_id: ^room_id, component_id: ^hls_id} - assert_receive %PeerMetadataUpdated{ - room_id: ^room_id, - peer_id: ^peer_id, - metadata: ^metadata_encoded - } = peer_metadata_updated + assert_receive {:webhook_notification, + %HlsPlayable{room_id: ^room_id, component_id: ^hls_id}}, + 1_000 - assert_receive {:webhook_notification, ^peer_metadata_updated}, 1_000 + conn = delete(conn, ~p"/room/#{room_id}/") + assert response(conn, :no_content) end describe "hls upload" do @@ -381,7 +453,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do assert response(conn, :created) == "Successfully schedule calling phone_number: 1230" - refute_receive %ComponentCrashed{component_id: ^component_id}, 2_500 + refute_receive %ComponentCrashed{component_id: ^component_id}, 1_000 refute_received {:webhook_notification, %ComponentCrashed{component_id: ^component_id}} conn = delete(conn, ~p"/sip/#{room_id}/#{component_id}/call") @@ -390,12 +462,11 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do end test "sends metrics", %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) ws = create_and_authenticate() subscribe(ws, :server_notification) - {room_id, peer_id, peer_token, _conn} = add_room_and_peer(conn, server_api_token) + {room_id, peer_id, peer_token, _conn} = add_room_and_peer(conn) {:ok, peer_ws} = WS.start_link("ws://127.0.0.1:#{@port}/socket/peer/websocket", :peer) WS.send_auth_request(peer_ws, peer_token) @@ -412,13 +483,11 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do assert String.contains?(endpoint_id, "endpoint_id") end - def subscribe_on_notifications_and_connect_peer(conn) do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + defp subscribe_on_notifications_and_connect_peer(conn) do ws = create_and_authenticate() - subscribe(ws, :server_notification) - {room_id, peer_id, peer_token, conn} = add_room_and_peer(conn, server_api_token) + {room_id, peer_id, peer_token, conn} = add_room_and_peer(conn) {:ok, peer_ws} = WS.start("ws://127.0.0.1:#{@port}/socket/peer/websocket", :peer) WS.send_auth_request(peer_ws, peer_token) @@ -441,9 +510,14 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do ws end - defp add_room_and_peer(conn, server_api_token) do - conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) + defp add_room_and_peer(conn) do + {room_id, conn} = add_room(conn) + {peer_id, token, conn} = add_peer(conn, room_id) + + {room_id, peer_id, token, conn} + end + defp add_room(conn) do conn = post(conn, ~p"/room", maxPeers: @max_peers, @@ -453,12 +527,16 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do assert %{"id" => room_id} = json_response(conn, :created)["data"]["room"] + {room_id, conn} + end + + defp add_peer(conn, room_id) do conn = post(conn, ~p"/room/#{room_id}/peer", type: "webrtc") assert %{"token" => peer_token, "peer" => %{"id" => peer_id}} = json_response(conn, :created)["data"] - {room_id, peer_id, peer_token, conn} + {peer_id, peer_token, conn} end defp add_hls_component(conn, room_id) do @@ -511,8 +589,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do end defp trigger_notification(conn) do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) - {_room_id, _peer_id, peer_token, _conn} = add_room_and_peer(conn, server_api_token) + {_room_id, _peer_id, peer_token, _conn} = add_room_and_peer(conn) {:ok, peer_ws} = WS.start_link("ws://127.0.0.1:#{@port}/socket/peer/websocket", :peer) WS.send_auth_request(peer_ws, peer_token) From d47cfa97d436cbd4a163878cb3c56d28259e89e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= <48837433+roznawsk@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:34:44 +0100 Subject: [PATCH 07/51] Add jf version and commit to healthcheck (#166) * Add jf version and commit to healthcheck * Short sha --- .github/workflows/deploy-image.yml | 5 +++++ Dockerfile | 3 +++ config/runtime.exs | 3 ++- lib/jellyfish.ex | 4 ++++ lib/jellyfish/application.ex | 5 ++--- lib/jellyfish/config_reader.ex | 6 +++++- lib/jellyfish_web/api_spec.ex | 4 +--- lib/jellyfish_web/api_spec/health_report.ex | 6 ++++-- .../controllers/healthcheck_controller.ex | 4 +++- lib/jellyfish_web/controllers/healthcheck_json.ex | 9 +++++---- mix.exs | 2 +- openapi.yaml | 10 +++++++++- .../controllers/healthcheck_controller_test.exs | 10 +++++++++- 13 files changed, 53 insertions(+), 18 deletions(-) diff --git a/.github/workflows/deploy-image.yml b/.github/workflows/deploy-image.yml index 7c4b74f3..c224a5e1 100644 --- a/.github/workflows/deploy-image.yml +++ b/.github/workflows/deploy-image.yml @@ -37,6 +37,10 @@ jobs: tags: | type=semver,pattern={{version}} type=edge,branch=main + - name: Set short sha + shell: bash + run: | + echo "sha_short=$(git rev-parse --short "$GITHUB_SHA")" >> "$GITHUB_ENV" - name: Build and push Docker image uses: docker/build-push-action@v4 @@ -47,3 +51,4 @@ jobs: tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max + build-args: JF_GIT_COMMIT=${{ env.sha_short }} diff --git a/Dockerfile b/Dockerfile index c86006a6..969d8363 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,6 +51,9 @@ RUN mix release FROM alpine:3.17 AS app +ARG JF_GIT_COMMIT +ENV JF_GIT_COMMIT=$JF_GIT_COMMIT + RUN addgroup -S jellyfish && adduser -S jellyfish -G jellyfish # We run the whole image as root, fix permissions in diff --git a/config/runtime.exs b/config/runtime.exs index 6044a8a6..804b59fb 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -34,7 +34,8 @@ config :jellyfish, metrics_port: ConfigReader.read_port("JF_METRICS_PORT") || 9568, dist_config: ConfigReader.read_dist_config(), webrtc_config: ConfigReader.read_webrtc_config(), - sip_config: ConfigReader.read_sip_config() + sip_config: ConfigReader.read_sip_config(), + git_commit: ConfigReader.read_git_commit() case System.get_env("JF_SERVER_API_TOKEN") do nil when prod? == true -> diff --git a/lib/jellyfish.ex b/lib/jellyfish.ex index 341ef641..52a1505a 100644 --- a/lib/jellyfish.ex +++ b/lib/jellyfish.ex @@ -6,4 +6,8 @@ defmodule Jellyfish do Contexts are also responsible for managing your data, regardless if it comes from the database, an external API or others. """ + + @version Mix.Project.config()[:version] + + def version(), do: @version end diff --git a/lib/jellyfish/application.ex b/lib/jellyfish/application.ex index 596c31cb..c55fc00d 100644 --- a/lib/jellyfish/application.ex +++ b/lib/jellyfish/application.ex @@ -7,15 +7,14 @@ defmodule Jellyfish.Application do require Logger - @version Mix.Project.config()[:version] - @impl true def start(_type, _args) do scrape_interval = Application.fetch_env!(:jellyfish, :webrtc_metrics_scrape_interval) dist_config = Application.fetch_env!(:jellyfish, :dist_config) webrtc_config = Application.fetch_env!(:jellyfish, :webrtc_config) + git_commit = Application.get_env(:jellyfish, :git_commit) - Logger.info("Starting Jellyfish v#{@version}") + Logger.info("Starting Jellyfish v#{Jellyfish.version()} (#{git_commit})") Logger.info("Distribution config: #{inspect(Keyword.delete(dist_config, :cookie))}") Logger.info("WebRTC config: #{inspect(webrtc_config)}") diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex index 245dfeaf..7b69cc96 100644 --- a/lib/jellyfish/config_reader.ex +++ b/lib/jellyfish/config_reader.ex @@ -180,6 +180,10 @@ defmodule Jellyfish.ConfigReader do end end + def read_git_commit() do + System.get_env("JF_GIT_COMMIT", "dev") + end + defp do_read_nodes_list_config(node_name_value, cookie, mode) do nodes_value = System.get_env("JF_DIST_NODES", "") @@ -300,7 +304,7 @@ defmodule Jellyfish.ConfigReader do {:error, reason} -> raise """ - Couldn't resolve #{hostname}, reason: #{reason}. + Couldn't resolve #{hostname}, reason: #{reason}. """ end end diff --git a/lib/jellyfish_web/api_spec.ex b/lib/jellyfish_web/api_spec.ex index 7a7186fe..194925b2 100644 --- a/lib/jellyfish_web/api_spec.ex +++ b/lib/jellyfish_web/api_spec.ex @@ -4,8 +4,6 @@ defmodule JellyfishWeb.ApiSpec do alias OpenApiSpex.{Components, Info, License, Paths, Schema, SecurityScheme} - @version Mix.Project.config()[:version] - # OpenAPISpex master specification @impl OpenApiSpex.OpenApi @@ -13,7 +11,7 @@ defmodule JellyfishWeb.ApiSpec do %OpenApiSpex.OpenApi{ info: %Info{ title: "Jellyfish Media Server", - version: @version, + version: Jellyfish.version(), license: %License{ name: "Apache 2.0", url: "https://www.apache.org/licenses/LICENSE-2.0" diff --git a/lib/jellyfish_web/api_spec/health_report.ex b/lib/jellyfish_web/api_spec/health_report.ex index c4aeea72..682c12dd 100644 --- a/lib/jellyfish_web/api_spec/health_report.ex +++ b/lib/jellyfish_web/api_spec/health_report.ex @@ -50,8 +50,10 @@ defmodule JellyfishWeb.ApiSpec.HealthReport do properties: %{ status: Status, uptime: %Schema{type: :integer, description: "Uptime of Jellyfish (in seconds)"}, - distribution: Distribution + distribution: Distribution, + version: %Schema{type: :string, description: "Version of Jellyfish"}, + gitCommit: %Schema{type: :string, description: "Commit hash of the build"} }, - required: [:status, :uptime, :distribution] + required: [:status, :uptime, :distribution, :version, :gitCommit] }) end diff --git a/lib/jellyfish_web/controllers/healthcheck_controller.ex b/lib/jellyfish_web/controllers/healthcheck_controller.ex index c5407ebc..de2ab21b 100644 --- a/lib/jellyfish_web/controllers/healthcheck_controller.ex +++ b/lib/jellyfish_web/controllers/healthcheck_controller.ex @@ -30,7 +30,9 @@ defmodule JellyfishWeb.HealthcheckController do %{ status: :up, uptime: get_uptime(), - distribution: get_distribution_report() + distribution: get_distribution_report(), + version: Jellyfish.version(), + gitCommit: Application.get_env(:jellyfish, :git_commit) } end diff --git a/lib/jellyfish_web/controllers/healthcheck_json.ex b/lib/jellyfish_web/controllers/healthcheck_json.ex index 46620075..22d2dc31 100644 --- a/lib/jellyfish_web/controllers/healthcheck_json.ex +++ b/lib/jellyfish_web/controllers/healthcheck_json.ex @@ -5,16 +5,17 @@ defmodule JellyfishWeb.HealthcheckJSON do %{data: data(report)} end - def data(%{status: status, uptime: uptime, distribution: distribution}) do - %{ + def data(%{status: status, distribution: distribution} = report) do + report + |> Map.take([:uptime, :version, :gitCommit]) + |> Map.merge(%{ status: status_str(status), - uptime: uptime, distribution: %{ enabled: distribution.enabled, nodeStatus: status_str(distribution.node_status), nodesInCluster: distribution.nodes_in_cluster } - } + }) end defp status_str(:up), do: "UP" diff --git a/mix.exs b/mix.exs index eb807cf2..10e2deaa 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Jellyfish.MixProject do def project do [ app: :jellyfish, - version: "0.4.1", + version: "0.4.2", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, diff --git a/openapi.yaml b/openapi.yaml index 88c5c724..0e9ec4d2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -535,15 +535,23 @@ components: properties: distribution: $ref: '#/components/schemas/HealthReportDistribution' + gitCommit: + description: Commit hash of the build + type: string status: $ref: '#/components/schemas/HealthReportStatus' uptime: description: Uptime of Jellyfish (in seconds) type: integer + version: + description: Version of Jellyfish + type: string required: - status - uptime - distribution + - version + - gitCommit title: HealthReport type: object x-struct: Elixir.JellyfishWeb.ApiSpec.HealthReport @@ -747,7 +755,7 @@ info: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 title: Jellyfish Media Server - version: 0.4.1 + version: 0.4.2 openapi: 3.0.0 paths: /health: diff --git a/test/jellyfish_web/controllers/healthcheck_controller_test.exs b/test/jellyfish_web/controllers/healthcheck_controller_test.exs index 31d3b8f2..8ab06b8f 100644 --- a/test/jellyfish_web/controllers/healthcheck_controller_test.exs +++ b/test/jellyfish_web/controllers/healthcheck_controller_test.exs @@ -5,6 +5,8 @@ defmodule JellyfishWeb.HealthcheckControllerTest do @schema JellyfishWeb.ApiSpec.spec() + @commit_hash_length 7 + setup %{conn: conn} do server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) @@ -17,6 +19,8 @@ defmodule JellyfishWeb.HealthcheckControllerTest do response = json_response(conn, :ok) assert_response_schema(response, "HealthcheckResponse", @schema) + version = Jellyfish.version() + assert %{ "status" => "UP", "uptime" => _uptime, @@ -24,7 +28,11 @@ defmodule JellyfishWeb.HealthcheckControllerTest do "enabled" => false, "nodeStatus" => "DOWN", "nodesInCluster" => 0 - } + }, + "version" => ^version, + "gitCommit" => commit } = response["data"] + + assert commit == "dev" || String.length(commit) == @commit_hash_length end end From 9d522fbaee31455e2221afb13255d63952b6028f Mon Sep 17 00:00:00 2001 From: Karol Konkol <56369082+Karolk99@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:14:45 +0100 Subject: [PATCH 08/51] Add recording component (#165) * Add recording component * Fix recording tests * Requested changes * Requested changes --- config/runtime.exs | 1 + lib/jellyfish/component.ex | 16 ++-- lib/jellyfish/component/recording.ex | 51 +++++++++++ lib/jellyfish/config_reader.ex | 28 ++++++ lib/jellyfish/room.ex | 4 + lib/jellyfish_web/api_spec/component.ex | 11 ++- .../api_spec/component/recording.ex | 72 +++++++++++++++ .../controllers/component_controller.ex | 10 ++- .../controllers/component_json.ex | 3 +- mix.exs | 13 +-- mix.lock | 34 +++---- openapi.yaml | 56 ++++++++++++ test/jellyfish/config_reader_test.exs | 20 +++++ .../component/recording_component_test.exs | 88 +++++++++++++++++++ 14 files changed, 372 insertions(+), 35 deletions(-) create mode 100644 lib/jellyfish/component/recording.ex create mode 100644 lib/jellyfish_web/api_spec/component/recording.ex create mode 100644 test/jellyfish_web/controllers/component/recording_component_test.exs diff --git a/config/runtime.exs b/config/runtime.exs index 804b59fb..4f1045dc 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -35,6 +35,7 @@ config :jellyfish, dist_config: ConfigReader.read_dist_config(), webrtc_config: ConfigReader.read_webrtc_config(), sip_config: ConfigReader.read_sip_config(), + s3_credentials: ConfigReader.read_s3_credentials(), git_commit: ConfigReader.read_git_commit() case System.get_env("JF_SERVER_API_TOKEN") do diff --git a/lib/jellyfish/component.ex b/lib/jellyfish/component.ex index b257accd..6f0e75d4 100644 --- a/lib/jellyfish/component.ex +++ b/lib/jellyfish/component.ex @@ -11,7 +11,7 @@ defmodule Jellyfish.Component do use Bunch.Access alias Jellyfish.Room - alias Jellyfish.Component.{File, HLS, RTSP, SIP} + alias Jellyfish.Component.{File, HLS, Recording, RTSP, SIP} alias Jellyfish.Track @enforce_keys [ @@ -23,8 +23,13 @@ defmodule Jellyfish.Component do defstruct @enforce_keys ++ [tracks: %{}] @type id :: String.t() - @type component :: HLS | RTSP | File | SIP - @type properties :: HLS.properties() | RTSP.properties() | File.properties() | SIP.properties() + @type component :: HLS | RTSP | File | SIP | Recording + @type properties :: + HLS.properties() + | RTSP.properties() + | File.properties() + | SIP.properties() + | Recording.properties() @typedoc """ This module contains: @@ -42,7 +47,7 @@ defmodule Jellyfish.Component do } @doc """ - This callback is run after initialization of the component. + This callback is run after initialization of the component. In it some additional work can be done, which can't be run inside Engine endpoint. """ @callback after_init( @@ -52,7 +57,7 @@ defmodule Jellyfish.Component do ) :: :ok @doc """ - This callback is run after scheduling removing of component. + This callback is run after scheduling removing of component. In it some additional cleanup can be done. """ @callback on_remove( @@ -99,6 +104,7 @@ defmodule Jellyfish.Component do "rtsp" -> {:ok, RTSP} "file" -> {:ok, File} "sip" -> {:ok, SIP} + "recording" -> {:ok, Recording} _other -> {:error, :invalid_type} end end diff --git a/lib/jellyfish/component/recording.ex b/lib/jellyfish/component/recording.ex new file mode 100644 index 00000000..033608b0 --- /dev/null +++ b/lib/jellyfish/component/recording.ex @@ -0,0 +1,51 @@ +defmodule Jellyfish.Component.Recording do + @moduledoc """ + Module representing the Recording component. + """ + + @behaviour Jellyfish.Endpoint.Config + use Jellyfish.Component + + alias JellyfishWeb.ApiSpec.Component.Recording.Options + alias Membrane.RTC.Engine.Endpoint.Recording + + @type properties :: %{path_prefix: Path.t()} + + @impl true + def config(%{engine_pid: engine} = options) do + with {:ok, serialized_opts} <- serialize_options(options, Options.schema()), + {:ok, credentials} <- get_credentials(serialized_opts) do + path_prefix = serialized_opts.path_prefix + output_dir = Path.join(get_base_path(), path_prefix) + + File.mkdir_p!(output_dir) + + file_storage = {Recording.Storage.File, %{output_dir: output_dir}} + s3_storage = {Recording.Storage.S3, %{credentials: credentials, path_prefix: path_prefix}} + + endpoint = %Recording{ + rtc_engine: engine, + recording_id: options.room_id, + stores: [file_storage, s3_storage] + } + + {:ok, %{endpoint: endpoint, properties: %{path_prefix: path_prefix}}} + else + {:error, [%OpenApiSpex.Cast.Error{reason: :missing_field, name: name} | _rest]} -> + {:error, {:missing_parameter, name}} + + {:error, _reason} = error -> + error + end + end + + defp get_credentials(%{credentials: nil}) do + case Application.fetch_env!(:jellyfish, :s3_credentials) do + nil -> {:error, :missing_s3_credentials} + credentials -> {:ok, Enum.into(credentials, %{})} + end + end + + defp get_credentials(%{credentials: credentials}), do: {:ok, credentials} + defp get_base_path(), do: Application.fetch_env!(:jellyfish, :media_files_path) +end diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex index 7b69cc96..f90670a6 100644 --- a/lib/jellyfish/config_reader.ex +++ b/lib/jellyfish/config_reader.ex @@ -143,6 +143,34 @@ defmodule Jellyfish.ConfigReader do end end + def read_s3_credentials() do + credentials = [ + bucket: System.get_env("JF_S3_BUCKET"), + region: System.get_env("JF_S3_REGION"), + access_key_id: System.get_env("JF_S3_ACCESS_KEY_ID"), + secret_access_key: System.get_env("JF_S3_SECRET_ACCESS_KEY") + ] + + cond do + Enum.all?(credentials, fn {_key, val} -> not is_nil(val) end) -> + credentials + + Enum.all?(credentials, fn {_key, val} -> is_nil(val) end) -> + nil + + true -> + missing_envs = + credentials + |> Enum.filter(fn {_key, val} -> val == nil end) + |> Enum.map(fn {key, _val} -> "JF_" <> (key |> Atom.to_string() |> String.upcase()) end) + + raise """ + Either all S3 credentials have to be set: `JF_S3_BUCKET`, `JF_S3_REGION`, `JF_S3_ACCESS_KEY_ID`, `JF_S3_SECRET_ACCESS_KEY`, or none must be set. + Currently, the following required credentials are missing: #{inspect(missing_envs)}. + """ + end + end + def read_dist_config() do dist_enabled? = read_boolean("JF_DIST_ENABLED") dist_strategy = System.get_env("JF_DIST_STRATEGY_NAME") diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index 5c802aee..4ec416ad 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -313,6 +313,10 @@ defmodule Jellyfish.Room do Logger.warning("Unable to add component: missing parameter #{inspect(name)}") {:reply, {:error, {:missing_parameter, name}}, state} + {:error, :missing_s3_credentials} -> + Logger.warning("Unable to add component: missing s3 credentials") + {:reply, {:error, :missing_s3_credentials}, state} + {:error, reason} -> Logger.warning("Unable to add component: #{inspect(reason)}") {:reply, :error, state} diff --git a/lib/jellyfish_web/api_spec/component.ex b/lib/jellyfish_web/api_spec/component.ex index cf65afda..119d2328 100644 --- a/lib/jellyfish_web/api_spec/component.ex +++ b/lib/jellyfish_web/api_spec/component.ex @@ -3,7 +3,7 @@ defmodule JellyfishWeb.ApiSpec.Component do require OpenApiSpex - alias JellyfishWeb.ApiSpec.Component.{File, HLS, RTSP, SIP} + alias JellyfishWeb.ApiSpec.Component.{File, HLS, Recording, RTSP, SIP} defmodule Type do @moduledoc false @@ -31,7 +31,8 @@ defmodule JellyfishWeb.ApiSpec.Component do HLS.Options, RTSP.Options, File.Options, - SIP.Options + SIP.Options, + Recording.Options ] }) end @@ -44,7 +45,8 @@ defmodule JellyfishWeb.ApiSpec.Component do HLS, RTSP, File, - SIP + SIP, + Recording ], discriminator: %OpenApiSpex.Discriminator{ propertyName: "type", @@ -52,7 +54,8 @@ defmodule JellyfishWeb.ApiSpec.Component do "hls" => HLS, "rtsp" => RTSP, "file" => File, - "sip" => SIP + "sip" => SIP, + "recording" => Recording } } }) diff --git a/lib/jellyfish_web/api_spec/component/recording.ex b/lib/jellyfish_web/api_spec/component/recording.ex new file mode 100644 index 00000000..2d38ea64 --- /dev/null +++ b/lib/jellyfish_web/api_spec/component/recording.ex @@ -0,0 +1,72 @@ +defmodule JellyfishWeb.ApiSpec.Component.Recording do + @moduledoc false + + require OpenApiSpex + + alias JellyfishWeb.ApiSpec.Component.HLS.S3 + alias OpenApiSpex.Schema + + defmodule Properties do + @moduledoc false + + require OpenApiSpex + alias OpenApiSpex.Schema + + OpenApiSpex.schema(%{ + title: "ComponentPropertiesRecording", + description: "Properties specific to the Recording component", + type: :object, + properties: %{ + pathPrefix: %Schema{ + type: :string, + description: "Path prefix under which all recording are stored" + } + }, + required: [:pathPrefix] + }) + end + + defmodule Options do + @moduledoc false + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "ComponentOptionsRecording", + description: "Options specific to the Recording component", + type: :object, + properties: %{ + pathPrefix: %Schema{ + type: :string, + description: "Path prefix under which all recording are stored", + default: "" + }, + credentials: %Schema{ + type: :object, + description: "Credentials to AWS S3 bucket.", + oneOf: [S3], + nullable: true + } + }, + required: [] + }) + end + + OpenApiSpex.schema(%{ + title: "ComponentRecording", + description: "Describes the Recording component", + type: :object, + properties: %{ + id: %Schema{type: :string, description: "Assigned component ID", example: "component-1"}, + # FIXME: due to cyclic imports, we can't use ApiSpec.Component.Type here + type: %Schema{type: :string, description: "Component type", example: "recording"}, + properties: Properties, + tracks: %Schema{ + type: :array, + items: JellyfishWeb.ApiSpec.Track, + description: "List of all component's tracks" + } + }, + required: [:id, :type, :properties, :tracks] + }) +end diff --git a/lib/jellyfish_web/controllers/component_controller.ex b/lib/jellyfish_web/controllers/component_controller.ex index 97b83517..60fc6ceb 100644 --- a/lib/jellyfish_web/controllers/component_controller.ex +++ b/lib/jellyfish_web/controllers/component_controller.ex @@ -65,11 +65,9 @@ defmodule JellyfishWeb.ComponentController do def create(conn, %{"room_id" => room_id} = params) do with component_options <- Map.get(params, "options", %{}), {:ok, component_type_string} <- Map.fetch(params, "type"), - {:ok, component_type} <- - Component.parse_type(component_type_string), + {:ok, component_type} <- Component.parse_type(component_type_string), {:ok, _room_pid} <- RoomService.find_room(room_id), - {:ok, component} <- - Room.add_component(room_id, component_type, component_options) do + {:ok, component} <- Room.add_component(room_id, component_type, component_options) do conn |> put_resp_content_type("application/json") |> put_status(:created) @@ -81,6 +79,10 @@ defmodule JellyfishWeb.ComponentController do {:error, {:missing_parameter, name}} -> {:error, :bad_request, "Required field \"#{Atom.to_string(name)}\" missing"} + {:error, :missing_s3_credentials} -> + {:error, :bad_request, + "S3 credentials has to be passed either by request or at application startup as envs"} + {:error, :invalid_type} -> {:error, :bad_request, "Invalid component type"} diff --git a/lib/jellyfish_web/controllers/component_json.ex b/lib/jellyfish_web/controllers/component_json.ex index a89e7f61..c7987d67 100644 --- a/lib/jellyfish_web/controllers/component_json.ex +++ b/lib/jellyfish_web/controllers/component_json.ex @@ -1,7 +1,7 @@ defmodule JellyfishWeb.ComponentJSON do @moduledoc false - alias Jellyfish.Component.{File, HLS, RTSP, SIP} + alias Jellyfish.Component.{File, HLS, Recording, RTSP, SIP} alias Jellyfish.Utils.ParserJSON def show(%{component: component}) do @@ -15,6 +15,7 @@ defmodule JellyfishWeb.ComponentJSON do RTSP -> "rtsp" File -> "file" SIP -> "sip" + Recording -> "recording" end %{ diff --git a/mix.exs b/mix.exs index 10e2deaa..b8de3fc2 100644 --- a/mix.exs +++ b/mix.exs @@ -71,11 +71,14 @@ defmodule Jellyfish.MixProject do # Membrane deps {:membrane_rtc_engine, github: "jellyfish-dev/membrane_rtc_engine", sparse: "engine", override: true}, - {:membrane_rtc_engine_webrtc, "~> 0.7.0"}, - {:membrane_rtc_engine_hls, "~> 0.6.0"}, - {:membrane_rtc_engine_rtsp, "~> 0.6.0"}, - {:membrane_rtc_engine_file, "~> 0.4.0"}, - {:membrane_rtc_engine_sip, "~> 0.2.0"}, + {:membrane_rtc_engine_webrtc, + github: "jellyfish-dev/membrane_rtc_engine", sparse: "webrtc", override: true}, + {:membrane_rtc_engine_hls, github: "jellyfish-dev/membrane_rtc_engine", sparse: "hls"}, + {:membrane_rtc_engine_recording, + github: "jellyfish-dev/membrane_rtc_engine", sparse: "recording"}, + {:membrane_rtc_engine_rtsp, github: "jellyfish-dev/membrane_rtc_engine", sparse: "rtsp"}, + {:membrane_rtc_engine_file, github: "jellyfish-dev/membrane_rtc_engine", sparse: "file"}, + {:membrane_rtc_engine_sip, github: "jellyfish-dev/membrane_rtc_engine", sparse: "sip"}, {:membrane_telemetry_metrics, "~> 0.1.0"}, # HLS endpoints deps diff --git a/mix.lock b/mix.lock index 3ab830f9..15cde6bd 100644 --- a/mix.lock +++ b/mix.lock @@ -16,7 +16,7 @@ "credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [: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", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "divo": {:hex, :divo, "1.3.2", "3a5ce880a1fe930ea804361d1b57b5144129e79e1c856623d923a6fab6d539a1", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:patiently, "~> 0.2", [hex: :patiently, repo: "hexpm", optional: false]}], "hexpm", "4bd035510838959709db2cacd28edd2eda7948d0e7f1b0dfa810a134c913a88a"}, - "elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"}, + "elixir_make": {:hex, :elixir_make, "0.8.2", "cd4a5a75891362e9207adaac7e66223fd256ec2518ae013af7f10c9c85b50b5c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "9d9607d640c372a7291e5a56ce655aa2351897929be20bd211648fdb79e725dc"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_aws": {:hex, :ex_aws, "2.5.1", "7418917974ea42e9e84b25e88b9f3d21a861d5f953ad453e212f48e593d8d39f", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1b95431f70c446fa1871f0eb9b183043c5a625f75f9948a42d25f43ae2eff12b"}, @@ -25,7 +25,7 @@ "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_sdp": {:hex, :ex_sdp, "0.13.1", "8f8ea458694660fae5e687444b484f34ff2981401c5d11a64a8a3e004c4d53f1", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "a3d66221e2aed4ea4efff98a9a6b52613b4d2eb10937d46bd2d1cae99a8f183b"}, "excoveralls": {:hex, :excoveralls, "0.15.3", "54bb54043e1cf5fe431eb3db36b25e8fd62cf3976666bafe491e3fa5e29eba47", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f8eb5d8134d84c327685f7bb8f1db4147f1363c3c9533928234e496e3070114e"}, - "fake_turn": {:hex, :fake_turn, "0.4.1", "8ca4677c8ca242055aa37048e6e2983bd369d591cad1450963e640edf89c1ab7", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "dcba3f798c5c7dc25462982a9107f32b51aad028607345a46c0975dd9b531421"}, + "fake_turn": {:hex, :fake_turn, "0.4.2", "8cd6c29d7ef8d2b42078ab2347c781ff90bd30e62d2e36f8a493ebe026947476", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d260a3b487c732f44ef7989c685e586dc51974076eb524504639c7f0080c14ac"}, "fast_tls": {:hex, :fast_tls, "1.1.13", "828cdc75e1e8fce8158846d2b971d8b4fe2b2ddcc75b759e88d751079bf78afd", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d1f422af40c7777fe534496f508ee86515cb929ad10f7d1d56aa94ce899b44a0"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, @@ -38,7 +38,7 @@ "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "klotho": {:hex, :klotho, "0.1.2", "3b1f1a569703e0cdce1ba964f41711351a7b06846c38fcbd601faa407e712bf2", [:mix], [], "hexpm", "a6a387982753582e30a5246fe9561721c6b9a4dd27678296cf2cd44faa6f3733"}, "libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"}, - "logger_json": {:hex, :logger_json, "5.1.3", "fe931b54826e7ba3b1233ede5c13d87cd670a23563f8d146d0ee22985549dbb5", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "ecc67e24f9ccf1688c5e48c3d6b7889a0ab5d398fe32a7fec69c461303a9b89c"}, + "logger_json": {:hex, :logger_json, "5.1.4", "9e30a4f2e31a8b9e402bdc20bd37cf9b67d3a31f19d0b33082a19a06b4c50f6d", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "3f20eea58e406a33d3eb7814c7dff5accb503bab2ee8601e84da02976fa3934c"}, "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.5", "74a0fd9b121a9f18e038573931fa2952b95a977a4e982a844734129e977e0fb9", [: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", "560ea01c1fc707770bcdfb30d47be5f77be3e4d86a872bc1e34261a134bf6f98"}, "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.18.1", "30433bffd4d5d773f79448dd9afd55d77338721688f09a89b20d742a68cc2c3d", [: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", "8fd048c47d5d2949eb557e19f43f62d534d3af5096187f1a1a3a1694d14b772c"}, @@ -46,7 +46,7 @@ "membrane_cmaf_format": {:hex, :membrane_cmaf_format, "0.7.0", "573bfff6acf2371c5046b9174569f6316f4205e3d6e13e814bf7e613e5653a54", [:mix], [], "hexpm", "4ac6a24a33f61347a2714c982a5f84aa6207641f4de2ad5afde68a8b800da8de"}, "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.0.1", "08aa546c0d131c66f8b906b3dfb2b8f2749b56859f6fc52bd3ac846b944b3baa", [: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", "a35ed68561bdf0a2dbb2f994333be78cf4e1c4d734e4cd927d77d92049bb1273"}, - "membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.19.1", "46104f915c5dd93495bc1aec4d574746a80203f9f82e62fa81f2e222eed28afb", [: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", "5a188a13c524390d9995029c140afa2cdb15ed838db86c5641031386e59d58d1"}, + "membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.19.2", "c5e23f124f0b06283c1119525bf5d1fe595808fd6aeddf3b751c337499204910", [: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", "3c66264ee1d94c4d076f4e431014553a886730a909ad96a07714167656e0b8f2"}, "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.16.0", "7917f6682c22b9bcfc2ca20ed960eee0f7d03ad31fd5f59ed850f1fe3ddd545a", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "b0727998f75a9b4dab8a2baefdfc13c3eac00a04e061ab1b0e61dc5566927acc"}, "membrane_framerate_converter_plugin": {:hex, :membrane_framerate_converter_plugin, "0.8.0", "a6f89dc89bc41e23c70f6af96e447c0577d54c0f0457a8383b652650088cb864", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}], "hexpm", "15faf8267f4d0ea3f511085bec14b90e93b6140ea4047f419e5cfbdef1543c3c"}, "membrane_funnel_plugin": {:hex, :membrane_funnel_plugin, "0.9.0", "9cfe09e44d65751f7d9d8d3c42e14797f7be69e793ac112ea63cd224af70a7bf", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "988790aca59d453a6115109f050699f7f45a2eb6a7f8dc5c96392760cddead54"}, @@ -54,12 +54,12 @@ "membrane_g711_plugin": {:hex, :membrane_g711_plugin, "0.1.0", "b813a506069f4a72fe96a1ff183ce466dd62e6651581798ff7937427403fad77", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "32ecbe8951f016137b3bf4123aa475b8429c78ca7ad8c730729ccc6efc29fbc9"}, "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.31.4", "a037365fb23ad4dea73c9176a90f777fb2f8537516530a583a1e2617da7f1b71", [: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.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "168275f146fbd3ef0490f012921a6b6e14b820cf06990d250c0909d4efc2e46d"}, "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, - "membrane_h264_plugin": {:hex, :membrane_h264_plugin, "0.9.2", "65d4ab0f5b8276af232f22aefdb89fbbc20bae08082cac84d769a6cfa88fc4e5", [: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]}], "hexpm", "eceda387d96d3e6c773f83950edeecbc053da5c5b2e57f462035637d3cac8789"}, "membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"}, - "membrane_http_adaptive_stream_plugin": {:hex, :membrane_http_adaptive_stream_plugin, "0.18.2", "420519e956540d00bfe97594bcda893f0616c4251297500c855290fccc5f899a", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_plugin, "~> 0.9.0", [hex: :membrane_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_mp4_plugin, "~> 0.31.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", "540cf54a85410aa2f4dd40c420a4a7da7493c0c14c5d935e84857c02ff1096fb"}, + "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.0", "0a7c6b9a7678e8c111b22b5417465ac31cf6e598cff6a53ab53a9c379bdfa1ef", [: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", "e9cde8c8995ace9fc26355037cbcc780f1727a3f63d36c21b52232fd29d0ad40"}, + "membrane_http_adaptive_stream_plugin": {:hex, :membrane_http_adaptive_stream_plugin, "0.18.3", "202d409bda5ff9e611d482b61c5fcc893c59905d92f3b8dd5d0288561449535c", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.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.33.1", [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", "adb36192eba89e81d458c54eeccfee4da8d11ca78ee3488c57da4893203ed80d"}, "membrane_ice_plugin": {:hex, :membrane_ice_plugin, "0.18.0", "beecb741b641b0c8b4efea0569fa68a3564051294e3ed10a10a1e29028e1d474", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.12.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:fake_turn, "~> 0.4.0", [hex: :fake_turn, 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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "fff74d447d42902bb014bc8cdc78d6da3f797b59ed8fd622bfc7c6854323a287"}, "membrane_mp4_format": {:hex, :membrane_mp4_format, "0.8.0", "8c6e7d68829228117d333b4fbb030e7be829aab49dd8cb047fdc664db1812e6a", [:mix], [], "hexpm", "148dea678a1f82ccfd44dbde6f936d2f21255f496cb45a22cc6eec427f025522"}, - "membrane_mp4_plugin": {:hex, :membrane_mp4_plugin, "0.31.0", "1932c86e2f4a24aca1b99ee531a131fd0da1128db8975ba8f8738e3b1bbcfabd", [: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.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h264_plugin, "~> 0.9.0", [hex: :membrane_h264_plugin, 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]}], "hexpm", "9968e56e02085228974bf6a59c8858f3c0d9800a4e767c1b3b2f2890050c72f4"}, + "membrane_mp4_plugin": {:hex, :membrane_mp4_plugin, "0.33.1", "190ca3768333728e85dd831a9607c2b600a091463f2affdfe18123ed10c927e4", [: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.16.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_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, 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]}], "hexpm", "6c783ab882099461755a9cb3debc6c2b82137b1f9f5a7292be216f36dec187a3"}, "membrane_ogg_plugin": {:hex, :membrane_ogg_plugin, "0.3.0", "6e98b8932a2b88174dc3922989a475e02ee327589222b1c8422ff4fa630325c3", [:mix], [{:crc, "~> 0.10", [hex: :crc, 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]}], "hexpm", "82557299b3d72aab0fafa07a05dcc3a5a842eeaa3de3f2f3959677517b66b713"}, "membrane_opus_format": {:hex, :membrane_opus_format, "0.3.0", "3804d9916058b7cfa2baa0131a644d8186198d64f52d592ae09e0942513cb4c2", [:mix], [], "hexpm", "8fc89c97be50de23ded15f2050fe603dcce732566fe6fdd15a2de01cb6b81afe"}, "membrane_opus_plugin": {:hex, :membrane_opus_plugin, "0.19.3", "af398a10c84d27e49b9a68ec78a54f123f2637441dd380857a3da4bb492eca5c", [: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", "172d5637233e4e7cb2c464be34ea85b487188887381ef5ff98d5c110fdf44f5b"}, @@ -68,12 +68,13 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "4a8f08b7430d5faaee899a1c4c8e72debcb44cf4", [sparse: "engine"]}, - "membrane_rtc_engine_file": {:hex, :membrane_rtc_engine_file, "0.4.0", "85f873478e73253bebd27045b3ce0ef5fd7713f09bb559953221727f1c0b501b", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_plugin, "~> 0.9.0", [hex: :membrane_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_ogg_plugin, "~> 0.3.0", [hex: :membrane_ogg_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_realtimer_plugin, "~> 0.9.0", [hex: :membrane_realtimer_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.21.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.7.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}], "hexpm", "5e6939c6d52033734b4eaa214e3d6b7dc1e44db5419a18917111f29101ca9f06"}, - "membrane_rtc_engine_hls": {:hex, :membrane_rtc_engine_hls, "0.6.0", "104756d9cfad5bb9642db477ecd16e5bffd6c2fd0204432c28514ff2aaf69da0", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.5", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_audio_mix_plugin, "~> 0.16.0", [hex: :membrane_audio_mix_plugin, repo: "hexpm", optional: true]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.31.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_plugin, "~> 0.9.0", [hex: :membrane_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_http_adaptive_stream_plugin, "~> 0.18.0", [hex: :membrane_http_adaptive_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.21.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.7.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_video_compositor_plugin, "~> 0.7.0", [hex: :membrane_video_compositor_plugin, repo: "hexpm", optional: true]}], "hexpm", "d310c4b6a05965b544cd64765553522e500203c5a20b3d86930f4e916054951c"}, - "membrane_rtc_engine_rtsp": {:hex, :membrane_rtc_engine_rtsp, "0.6.0", "6fd7db85ada0beddb3681bd5b3c3230ff21c839cae6072d84df66763374f24fe", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_plugin, "~> 0.9.0", [hex: :membrane_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.21.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.7.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.19.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.1", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.5.1", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.13.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}], "hexpm", "e17e21a9274f5edb3f234cb23a7cf0315a9b0b93abf936cd9fc30588eae7c98a"}, - "membrane_rtc_engine_sip": {:hex, :membrane_rtc_engine_sip, "0.2.0", "bba91600313d92092f5946ab4dc63d755648a313b1ffc2b0e28583195ab8e0ab", [:mix], [{:ex_sdp, "~> 0.11", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_audio_mix_plugin, "~> 0.16.0", [hex: :membrane_audio_mix_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.19.1", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_g711_plugin, "~> 0.1.0", [hex: :membrane_g711_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:membrane_raw_audio_parser_plugin, "~> 0.4.0", [hex: :membrane_raw_audio_parser_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.21.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.7.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_g711_plugin, "~> 0.2.0", [hex: :membrane_rtp_g711_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.13.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}, {:sippet, "~> 1.0.11", [hex: :sippet, repo: "hexpm", optional: false]}], "hexpm", "f9c2dd61346a9b5cb843968123b5b867060ae99aedcd30be73f964f88169155c"}, - "membrane_rtc_engine_webrtc": {:hex, :membrane_rtc_engine_webrtc, "0.7.0", "2cd7db8cd540ff3d065b3eacb71be90162dea6df909895e8cdfa86e08d567c9e", [:mix], [{:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_ice_plugin, "~> 0.18.0", [hex: :membrane_ice_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.21.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.19.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.1", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.0", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}, {:membrane_webrtc_plugin, "~> 0.18.0", [hex: :membrane_webrtc_plugin, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "d5895ebd2df5360c1f6777ac8339d47cf9bf2ffea40807be97b0a7c47ca4657e"}, + "membrane_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "engine"]}, + "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "file"]}, + "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "hls"]}, + "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "recording"]}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "rtsp"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "sip"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "webrtc"]}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.0", "112bfedc14fb83bdb549ef1a03da23908feedeb165fd3e4512a549f1af532ae7", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "76fd159e7406cadbef15124cba30eca3fffcf71a7420964f26e23d4cffd9b29d"}, @@ -81,12 +82,13 @@ "membrane_rtp_plugin": {:hex, :membrane_rtp_plugin, "0.24.1", "56238f31b28e66da8b22cacb1aa54aa607f0522d4ad4709aa0718512f10dd808", [: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]}, {: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.8.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", "04827ab3d16ebe980b88e2c40ddfbfb828d5d029365867669fd28862d8c3a559"}, "membrane_rtp_vp8_plugin": {:hex, :membrane_rtp_vp8_plugin, "0.9.0", "b45d597d3612f62faf734e9bc96c8fb4f4dc480ae76efb86ef099fa81036d7b5", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.4.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}], "hexpm", "2e8a48170d296678426e2a3a0bd45a27308aa4608ffc374f3968495187e8666b"}, "membrane_rtsp": {:hex, :membrane_rtsp, "0.5.1", "3176333ce321081087579b9d9c087262980a368de51be38b7af26fb2a0c0ac74", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "ef7d90446fe036a46afdca3076d00c47093e46c9e2a49957d9734c247b03c1a3"}, + "membrane_stream_plugin": {:hex, :membrane_stream_plugin, "0.4.0", "0c4ab72a4e13bf0faa0f1166fbaf68d2e34167dbec345aedb74ce1eb7497bdda", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "5a9a9c17783e18ad740e6ddfed364581bdb7ebdab8e61ba2c19a1830356f7eb8"}, "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.0", "cb93d28356b436b0597736c3e4153738d82d2a14ff547f831df7e9051e54fc06", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "aba28dc8311f70ced95d984509be930fac55857d2d18bffcf768815e627be3f0"}, "membrane_udp_plugin": {:hex, :membrane_udp_plugin, "0.13.0", "c4d10b4cb152a95779e36fac4338e11ef0b0cb545c78ca337d7676f6df5d5709", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "47a1661038ef65025fe36cfcae8ce23c022f9dc0867b8340c46dd4963f5a1bcb"}, "membrane_video_compositor_plugin": {:hex, :membrane_video_compositor_plugin, "0.7.0", "2273743fd0a47660880276e0bbb8a9f8848e09b05b425101d1cc0c5d245ff8ea", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_framerate_converter_plugin, "~> 0.8.0", [hex: :membrane_framerate_converter_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.1", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:rustler, "~> 0.26.0", [hex: :rustler, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "68ea6a5770cf053464d6fa009a20f85ca559267f5a454370506c6d40965985de"}, "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.4.0", "6c29ec67479edfbab27b11266dc92f18f3baf4421262c5c31af348c33e5b92c7", [:mix], [], "hexpm", "8bb005ede61db8fcb3535a883f32168b251c2dfd1109197c8c3b39ce28ed08e2"}, - "membrane_webrtc_plugin": {:hex, :membrane_webrtc_plugin, "0.18.1", "af5988cdfddc95174f365ce18b16694d862ab1d95bd2671297a8ab5fe65837fb", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_libsrtp, ">= 0.0.0", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, 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_h264_plugin, "~> 0.9.0", [hex: :membrane_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_ice_plugin, "~> 0.18.0", [hex: :membrane_ice_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.19.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, ">= 0.9.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.0", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.0", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "08de22bdd3a6c9e7d79bad45b1a0eb87f335e33804a9b5afd2c84ee36428b683"}, + "membrane_webrtc_plugin": {:hex, :membrane_webrtc_plugin, "0.18.2", "b8b3528b45a7a61c987435bec741c6204df3584b044ab165776bb4d7a01b8455", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_libsrtp, ">= 0.0.0", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, 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_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_ice_plugin, "~> 0.18.0", [hex: :membrane_ice_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.19.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, ">= 0.9.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.0", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.0", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "7dc127f3e0a199529374797256756e67f0beea89ba8992f9ec84e5ebc3037fee"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, @@ -114,10 +116,10 @@ "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "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"}, - "req": {:hex, :req, "0.4.11", "cb19f87d5251e7de30cfc67d1899696b290711092207c6b2e8fc2294f237fcdc", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {: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, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbf4f2393c649fa4146a3b8470e2a7e8c9b23e4100a16c75f5e7d1d3d33144f3"}, + "req": {:hex, :req, "0.4.13", "6fde45b78e606e2a46fc3a7e4a74828c220cd0b16951f4321c1214f955402d72", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {: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, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "e01a596b74272de799bc57191883e5d4d3875be63f0480223edc5a0086bfe31b"}, "rustler": {:hex, :rustler, "0.26.0", "06a2773d453ee3e9109efda643cf2ae633dedea709e2455ac42b83637c9249bf", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "42961e9d2083d004d5a53e111ad1f0c347efd9a05cb2eb2ffa1d037cdc74db91"}, "shmex": {:hex, :shmex, "0.5.0", "7dc4fb1a8bd851085a652605d690bdd070628717864b442f53d3447326bcd3e8", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "b67bb1e22734758397c84458dbb746519e28eac210423c267c7248e59fc97bdc"}, - "sippet": {:hex, :sippet, "1.0.11", "9e696caf738e5a2deef401fdc49f79a0a5bc95b7442b023ddad430c7ab406111", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:gen_state_machine, ">= 3.0.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:sippet_uri, "~> 0.1", [hex: :sippet_uri, repo: "hexpm", optional: false]}], "hexpm", "339aaa10b5e8e8a127d6a8fda7d5f97558ae3da8d9c8df4c86008bd79d33e09b"}, + "sippet": {:hex, :sippet, "1.0.16", "614d472d4d7e2b7f0828c554aaf9a0238e0c6e903dbb17334f5cb2baa58da1c2", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:gen_state_machine, ">= 3.0.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:sippet_uri, "~> 0.1", [hex: :sippet_uri, repo: "hexpm", optional: false]}], "hexpm", "1a23af1ce85adeae89863daaded233637075a7dd031e589b4f343ae08901667c"}, "sippet_uri": {:hex, :sippet_uri, "0.1.0", "ec8bfe6df12e17c0ca32b18b2177585426448bf3e3b0434f5ddfb93dc075add2", [:mix], [], "hexpm", "e8b1ad3fdd12670b4e9851a273624a7ed4b12cf5e705dec7906204c294d9790b"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistics": {:hex, :statistics, "0.6.3", "7fb182e7c1cab2980e392c7efef7ce326539f081f9defda4099550e9c2c7cb0f", [:mix], [], "hexpm", "a43d87726d240205e9ef47f29650a6e3132b4e4061e05512f32fa8120784a8e0"}, diff --git a/openapi.yaml b/openapi.yaml index 0e9ec4d2..4b0694f7 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -151,12 +151,29 @@ components: title: Room type: object x-struct: Elixir.JellyfishWeb.ApiSpec.Room + ComponentOptionsRecording: + description: Options specific to the Recording component + properties: + credentials: + description: Credentials to AWS S3 bucket. + nullable: true + oneOf: + - $ref: '#/components/schemas/S3Credentials' + type: object + pathPrefix: + default: '' + description: Path prefix under which all recording are stored + type: string + title: ComponentOptionsRecording + type: object + x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Recording.Options Component: description: Describes component discriminator: mapping: file: '#/components/schemas/ComponentFile' hls: '#/components/schemas/ComponentHLS' + recording: '#/components/schemas/ComponentRecording' rtsp: '#/components/schemas/ComponentRTSP' sip: '#/components/schemas/ComponentSIP' propertyName: type @@ -165,6 +182,7 @@ components: - $ref: '#/components/schemas/ComponentRTSP' - $ref: '#/components/schemas/ComponentFile' - $ref: '#/components/schemas/ComponentSIP' + - $ref: '#/components/schemas/ComponentRecording' title: Component type: object x-struct: Elixir.JellyfishWeb.ApiSpec.Component @@ -315,6 +333,17 @@ components: title: HlsMsn type: integer x-struct: Elixir.JellyfishWeb.ApiSpec.HLS.Params.HlsMsn + ComponentPropertiesRecording: + description: Properties specific to the Recording component + properties: + pathPrefix: + description: Path prefix under which all recording are stored + type: string + required: + - pathPrefix + title: ComponentPropertiesRecording + type: object + x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Recording.Properties DialConfig: description: Dial config properties: @@ -461,6 +490,7 @@ components: - $ref: '#/components/schemas/ComponentOptionsRTSP' - $ref: '#/components/schemas/ComponentOptionsFile' - $ref: '#/components/schemas/ComponentOptionsSIP' + - $ref: '#/components/schemas/ComponentOptionsRecording' title: ComponentOptions type: object x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Options @@ -657,6 +687,32 @@ components: title: HlsResponse type: string x-struct: Elixir.JellyfishWeb.ApiSpec.HLS.Response + ComponentRecording: + description: Describes the Recording component + properties: + id: + description: Assigned component ID + example: component-1 + type: string + properties: + $ref: '#/components/schemas/ComponentPropertiesRecording' + tracks: + description: List of all component's tracks + items: + $ref: '#/components/schemas/Track' + type: array + type: + description: Component type + example: recording + type: string + required: + - id + - type + - properties + - tracks + title: ComponentRecording + type: object + x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Recording PeerType: description: Peer type example: webrtc diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs index 747e1ab0..ed2fda3c 100644 --- a/test/jellyfish/config_reader_test.exs +++ b/test/jellyfish/config_reader_test.exs @@ -143,6 +143,26 @@ defmodule Jellyfish.ConfigReaderTest do end end + test "read_s3_credentials/0" do + with_env ["JF_S3_BUCKET", "JF_S3_ACCESS_KEY_ID", "JF_S3_SECRET_ACCESS_KEY", "JF_S3_REGION"] do + assert ConfigReader.read_s3_credentials() == nil + + System.put_env("JF_S3_BUCKET", "bucket") + assert_raise RuntimeError, fn -> ConfigReader.read_s3_credentials() end + + System.put_env("JF_S3_ACCESS_KEY_ID", "id") + System.put_env("JF_S3_SECRET_ACCESS_KEY", "key") + System.put_env("JF_S3_REGION", "region") + + assert ConfigReader.read_s3_credentials() == [ + bucket: "bucket", + region: "region", + access_key_id: "id", + secret_access_key: "key" + ] + end + end + test "read_dist_config/0 NODES_LIST" do with_env [ "JF_DIST_ENABLED", diff --git a/test/jellyfish_web/controllers/component/recording_component_test.exs b/test/jellyfish_web/controllers/component/recording_component_test.exs new file mode 100644 index 00000000..5998ed54 --- /dev/null +++ b/test/jellyfish_web/controllers/component/recording_component_test.exs @@ -0,0 +1,88 @@ +defmodule JellyfishWeb.Component.RecordingComponentTest do + use JellyfishWeb.ConnCase + use JellyfishWeb.ComponentCase + + import Mox + + @s3_credentials %{ + accessKeyId: "access_key_id", + secretAccessKey: "secret_access_key", + region: "region", + bucket: "bucket" + } + + @path_prefix "path_prefix" + + describe "create recording component" do + setup :set_mox_from_context + + test "renders component with required options", %{conn: conn, room_id: room_id} do + mock_http_request() + + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "recording", + options: %{credentials: Enum.into(@s3_credentials, %{}), pathPrefix: @path_prefix} + ) + + assert %{ + "data" => %{ + "id" => id, + "type" => "recording", + "properties" => %{"pathPrefix" => @path_prefix} + } + } = model_response(conn, :created, "ComponentDetailsResponse") + + assert_component_created(conn, room_id, id, "recording") + end + + setup :set_mox_from_context + + test "renders component when credentials are in passed in config", %{ + conn: conn, + room_id: room_id + } do + mock_http_request() + Application.put_env(:jellyfish, :s3_credentials, @s3_credentials) + + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "recording", + options: %{pathPrefix: @path_prefix} + ) + + assert %{ + "data" => %{ + "id" => id, + "type" => "recording", + "properties" => %{"pathPrefix" => @path_prefix} + } + } = model_response(conn, :created, "ComponentDetailsResponse") + + assert_component_created(conn, room_id, id, "recording") + + Application.put_env(:jellyfish, :s3_credentials, nil) + end + + test "renders errors when required options are missing", %{conn: conn, room_id: room_id} do + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "recording", + options: %{pathPrefix: @path_prefix} + ) + + assert model_response(conn, :bad_request, "Error")["errors"] == + "S3 credentials has to be passed either by request or at application startup as envs" + end + end + + defp mock_http_request() do + expect(ExAws.Request.HttpMock, :request, 4, fn _method, + _url, + _req_body, + _headers, + _http_opts -> + {:ok, %{status_code: 200, headers: %{}}} + end) + end +end From 6a7ac13457b31ae12c1bfb7018e568592fa97bfb Mon Sep 17 00:00:00 2001 From: Karol Konkol <56369082+Karolk99@users.noreply.github.com> Date: Fri, 15 Mar 2024 15:46:36 +0100 Subject: [PATCH 09/51] Fix httppoison for aws (#167) --- lib/jellyfish/component/hls/httpoison.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/jellyfish/component/hls/httpoison.ex b/lib/jellyfish/component/hls/httpoison.ex index 352ff817..0db6ec9a 100644 --- a/lib/jellyfish/component/hls/httpoison.ex +++ b/lib/jellyfish/component/hls/httpoison.ex @@ -6,12 +6,12 @@ defmodule Jellyfish.Component.HLS.HTTPoison do @impl true def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do case HTTPoison.request(method, url, body, headers, http_opts) do - {:ok, %HTTPoison.Response{status_code: status, headers: headers}} -> - {:ok, %{status_code: status, headers: headers}} - {:ok, %HTTPoison.Response{status_code: status, headers: headers, body: body}} -> {:ok, %{status_code: status, headers: headers, body: body}} + {:ok, %HTTPoison.Response{status_code: status, headers: headers}} -> + {:ok, %{status_code: status, headers: headers}} + {:error, %HTTPoison.Error{reason: reason}} -> {:error, reason} end From 3891613ab1a6bfd570fdcaa4c4f11428f625620f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:04:27 +0100 Subject: [PATCH 10/51] Update deps (#168) --- mix.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/mix.lock b/mix.lock index 15cde6bd..4736189c 100644 --- a/mix.lock +++ b/mix.lock @@ -4,7 +4,7 @@ "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.4.6", "6d93c4ca3bfb2282445e1e76ea263cedae49ba8524bf4cce94eb8124421c81fc", [: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", "549115f64f55e29b32f566ae054caa5557b334aceab279e0b820055ad0bfc8b6"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, + "castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, @@ -39,11 +39,11 @@ "klotho": {:hex, :klotho, "0.1.2", "3b1f1a569703e0cdce1ba964f41711351a7b06846c38fcbd601faa407e712bf2", [:mix], [], "hexpm", "a6a387982753582e30a5246fe9561721c6b9a4dd27678296cf2cd44faa6f3733"}, "libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"}, "logger_json": {:hex, :logger_json, "5.1.4", "9e30a4f2e31a8b9e402bdc20bd37cf9b67d3a31f19d0b33082a19a06b4c50f6d", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "3f20eea58e406a33d3eb7814c7dff5accb503bab2ee8601e84da02976fa3934c"}, - "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.5", "74a0fd9b121a9f18e038573931fa2952b95a977a4e982a844734129e977e0fb9", [: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", "560ea01c1fc707770bcdfb30d47be5f77be3e4d86a872bc1e34261a134bf6f98"}, + "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.6", "82c5064a11781adc212ba7facebd63015473b99702e39fbaac9922e638436966", [: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", "956c18eacbfaa908535f306462c47de6dbd0892db5c6cdcce5daaa67c853268a"}, "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.18.1", "30433bffd4d5d773f79448dd9afd55d77338721688f09a89b20d742a68cc2c3d", [: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", "8fd048c47d5d2949eb557e19f43f62d534d3af5096187f1a1a3a1694d14b772c"}, "membrane_audio_mix_plugin": {:hex, :membrane_audio_mix_plugin, "0.16.0", "34997707ee186683c6d7bd87572944e5e37c0249235cc44915d181d653c5c40e", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, 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_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", "a4a8c723f0da8d9cf9ac11bf657a732770ea0b8db4eff2efc16caa3a1819f435"}, - "membrane_cmaf_format": {:hex, :membrane_cmaf_format, "0.7.0", "573bfff6acf2371c5046b9174569f6316f4205e3d6e13e814bf7e613e5653a54", [:mix], [], "hexpm", "4ac6a24a33f61347a2714c982a5f84aa6207641f4de2ad5afde68a8b800da8de"}, + "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.0.1", "08aa546c0d131c66f8b906b3dfb2b8f2749b56859f6fc52bd3ac846b944b3baa", [: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", "a35ed68561bdf0a2dbb2f994333be78cf4e1c4d734e4cd927d77d92049bb1273"}, "membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.19.2", "c5e23f124f0b06283c1119525bf5d1fe595808fd6aeddf3b751c337499204910", [: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", "3c66264ee1d94c4d076f4e431014553a886730a909ad96a07714167656e0b8f2"}, @@ -52,7 +52,7 @@ "membrane_funnel_plugin": {:hex, :membrane_funnel_plugin, "0.9.0", "9cfe09e44d65751f7d9d8d3c42e14797f7be69e793ac112ea63cd224af70a7bf", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "988790aca59d453a6115109f050699f7f45a2eb6a7f8dc5c96392760cddead54"}, "membrane_g711_format": {:hex, :membrane_g711_format, "0.1.0", "a570a9832a6bf23074210816560e5116935f0face32605968135bf3451ad8a12", [:mix], [], "hexpm", "cbf2c0482b4ca2145c440cd1dcfa865967bcaeaa3813f4f4d2c28a66b59659ef"}, "membrane_g711_plugin": {:hex, :membrane_g711_plugin, "0.1.0", "b813a506069f4a72fe96a1ff183ce466dd62e6651581798ff7937427403fad77", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "32ecbe8951f016137b3bf4123aa475b8429c78ca7ad8c730729ccc6efc29fbc9"}, - "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.31.4", "a037365fb23ad4dea73c9176a90f777fb2f8537516530a583a1e2617da7f1b71", [: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.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "168275f146fbd3ef0490f012921a6b6e14b820cf06990d250c0909d4efc2e46d"}, + "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.31.5", "7db887780516164c68200e8e39c339e6077a82d6fe78c402e2e0576d2261c76c", [: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.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "472618e76565cea20d26b5659246a517e891e2ba8462d016eea90e9bfb49bd77"}, "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, "membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"}, "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.0", "0a7c6b9a7678e8c111b22b5417465ac31cf6e598cff6a53ab53a9c379bdfa1ef", [: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", "e9cde8c8995ace9fc26355037cbcc780f1727a3f63d36c21b52232fd29d0ad40"}, @@ -68,13 +68,13 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "engine"]}, - "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "file"]}, - "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "hls"]}, - "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "recording"]}, - "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "rtsp"]}, - "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "sip"]}, - "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "e1d5c4d09f8dd1755017283d104ec84167ed2ca1", [sparse: "webrtc"]}, + "membrane_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "engine"]}, + "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "file"]}, + "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "hls"]}, + "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "recording"]}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "rtsp"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "sip"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "webrtc"]}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.0", "112bfedc14fb83bdb549ef1a03da23908feedeb165fd3e4512a549f1af532ae7", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "76fd159e7406cadbef15124cba30eca3fffcf71a7420964f26e23d4cffd9b29d"}, @@ -96,10 +96,10 @@ "mockery": {:hex, :mockery, "2.3.1", "a02fd60b10ac9ed37a7a2ecf6786c1f1dd5c75d2b079a60594b089fba32dc087", [:mix], [], "hexpm", "1d0971d88ebf084e962da3f2cfee16f0ea8e04ff73a7710428500d4500b947fa"}, "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, - "nimble_ownership": {:hex, :nimble_ownership, "0.2.1", "3e44c72ebe8dd213db4e13aff4090aaa331d158e72ce1891d02e0ffb05a1eb2d", [:mix], [], "hexpm", "bf38d2ef4fb990521a4ecf112843063c1f58a5c602484af4c7977324042badee"}, + "nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, "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"}, - "open_api_spex": {:hex, :open_api_spex, "3.18.2", "8c855e83bfe8bf81603d919d6e892541eafece3720f34d1700b58024dadde247", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "aa3e6dcfc0ad6a02596b2172662da21c9dd848dac145ea9e603f54e3d81b8d2b"}, + "open_api_spex": {:hex, :open_api_spex, "3.18.3", "fefb84fe323cacfc92afdd0ecb9e89bc0261ae00b7e3167ffc2028ce3944de42", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "c0cfc31570199ce7e7520b494a591027da609af45f6bf9adce51e2469b1609fb"}, "p1_utils": {:hex, :p1_utils, "1.0.23", "7f94466ada69bd982ea7bb80fbca18e7053e7d0b82c9d9e37621fa508587069b", [:rebar3], [], "hexpm", "47f21618694eeee5006af1c88731ad86b757161e7823c29b6f73921b571c8502"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "patiently": {:hex, :patiently, "0.2.0", "67eb139591e10c4b363ae0198e832552f191c58894731efd3bf124ec4722267a", [:mix], [], "hexpm", "c08cc5edc27def565647a9b55a0bea8025a5f81a4472e57692f28f2292c44c94"}, @@ -116,7 +116,7 @@ "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "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"}, - "req": {:hex, :req, "0.4.13", "6fde45b78e606e2a46fc3a7e4a74828c220cd0b16951f4321c1214f955402d72", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {: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, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "e01a596b74272de799bc57191883e5d4d3875be63f0480223edc5a0086bfe31b"}, + "req": {:hex, :req, "0.4.14", "103de133a076a31044e5458e0f850d5681eef23dfabf3ea34af63212e3b902e2", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {: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, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0 or ~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2ddd3d33f9ab714ced8d3c15fd03db40c14dbf129003c4a3eb80fac2cc0b1b08"}, "rustler": {:hex, :rustler, "0.26.0", "06a2773d453ee3e9109efda643cf2ae633dedea709e2455ac42b83637c9249bf", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "42961e9d2083d004d5a53e111ad1f0c347efd9a05cb2eb2ffa1d037cdc74db91"}, "shmex": {:hex, :shmex, "0.5.0", "7dc4fb1a8bd851085a652605d690bdd070628717864b442f53d3447326bcd3e8", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "b67bb1e22734758397c84458dbb746519e28eac210423c267c7248e59fc97bdc"}, "sippet": {:hex, :sippet, "1.0.16", "614d472d4d7e2b7f0828c554aaf9a0238e0c6e903dbb17334f5cb2baa58da1c2", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:gen_state_machine, ">= 3.0.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:sippet_uri, "~> 0.1", [hex: :sippet_uri, repo: "hexpm", optional: false]}], "hexpm", "1a23af1ce85adeae89863daaded233637075a7dd031e589b4f343ae08901667c"}, @@ -131,7 +131,7 @@ "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, - "unifex": {:hex, :unifex, "1.1.1", "e8445ff780ea07c10657428051e4cf84359f2770e27d24e9d8636430662691ff", [: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", "3e2867237a5582a40cb7c88d9ed0955071ebb1c4d525345513544adc0abd3b4b"}, + "unifex": {:hex, :unifex, "1.2.0", "90d1ec5e6d788350e07e474f7bd8b0ee866d6606beb9ca4e20dbb26328712a84", [: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", "7a8395aabc3ba6cff04bbe5b995de7f899a38eb57f189e49927d6b8b6ccb6883"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [: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", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"}, "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"}, From 6d3b5a3ef93156f30cbfe479cc41e417710b46c1 Mon Sep 17 00:00:00 2001 From: Karol Konkol <56369082+Karolk99@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:31:02 +0200 Subject: [PATCH 11/51] Restrict available codecs in room for recording component (#169) * Restrict available codecs in room for recording component * Restrict recording component to one per room * Requested changes * Requested changes --- lib/jellyfish/component.ex | 12 +++++++++ lib/jellyfish/room.ex | 22 ++++++++-------- .../controllers/component_controller.ex | 5 ++-- .../component/hls_component_test.exs | 15 +---------- .../component/recording_component_test.exs | 25 +++++++++++++++++++ .../controllers/component_controller_test.exs | 1 + test/support/component_case.ex | 16 ++++++++++++ 7 files changed, 70 insertions(+), 26 deletions(-) diff --git a/lib/jellyfish/component.ex b/lib/jellyfish/component.ex index 6f0e75d4..a5a683cb 100644 --- a/lib/jellyfish/component.ex +++ b/lib/jellyfish/component.ex @@ -109,6 +109,18 @@ defmodule Jellyfish.Component do end end + @spec to_string!(module()) :: String.t() + def to_string!(component) do + case component do + HLS -> "hls" + RTSP -> "rtsp" + File -> "file" + SIP -> "sip" + Recording -> "recording" + _other -> raise "Invalid component" + end + end + @spec new(component(), map()) :: {:ok, t()} | {:error, term()} def new(type, options) do with {:ok, %{endpoint: endpoint, properties: properties}} <- type.config(options) do diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index 4ec416ad..39245b47 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -9,7 +9,7 @@ defmodule Jellyfish.Room do require Logger alias Jellyfish.Component - alias Jellyfish.Component.{HLS, RTSP, SIP} + alias Jellyfish.Component.{HLS, Recording, RTSP, SIP} alias Jellyfish.Event alias Jellyfish.Peer alias Jellyfish.Room.Config @@ -281,9 +281,10 @@ defmodule Jellyfish.Room do Logger.warning("Unable to add component: incompatible codec") {:reply, {:error, :incompatible_codec}, state} - {:error, :reached_components_limit_hls} -> - Logger.warning("Unable to add component: reached components limit") - {:reply, {:error, :reached_components_limit_hls}, state} + {:error, :reached_components_limit} -> + type = Component.to_string!(component_type) + Logger.warning("Unable to add component: reached components limit #{type}") + {:reply, {:error, {:reached_components_limit, type}}, state} {:error, :file_does_not_exist} -> Logger.warning("Unable to add component: file does not exist") @@ -771,16 +772,17 @@ defmodule Jellyfish.Room do if component.type == HLS, do: component end) - defp check_component_allowed(HLS, %{ + defp check_component_allowed(type, %{ config: %{video_codec: video_codec}, components: components - }) do + }) + when type in [HLS, Recording] do cond do video_codec != :h264 -> {:error, :incompatible_codec} - hls_component_already_present?(components) -> - {:error, :reached_components_limit_hls} + component_already_present?(type, components) -> + {:error, :reached_components_limit} true -> :ok @@ -797,8 +799,8 @@ defmodule Jellyfish.Room do defp check_component_allowed(_component_type, _state), do: :ok - defp hls_component_already_present?(components), - do: components |> Map.values() |> Enum.any?(&(&1.type == HLS)) + defp component_already_present?(type, components), + do: components |> Map.values() |> Enum.any?(&(&1.type == type)) defp validate_hls_subscription(nil), do: {:error, :hls_component_not_exists} diff --git a/lib/jellyfish_web/controllers/component_controller.ex b/lib/jellyfish_web/controllers/component_controller.ex index 60fc6ceb..57ee752f 100644 --- a/lib/jellyfish_web/controllers/component_controller.ex +++ b/lib/jellyfish_web/controllers/component_controller.ex @@ -108,8 +108,9 @@ defmodule JellyfishWeb.ComponentController do {:error, :unsupported_file_type} -> {:error, :bad_request, "Unsupported file type"} - {:error, :reached_components_limit_hls} -> - {:error, :bad_request, "Reached components limit for component HLS in room #{room_id}"} + {:error, {:reached_components_limit, type}} -> + {:error, :bad_request, + "Reached #{type} components limit for component in room #{room_id}"} end end diff --git a/test/jellyfish_web/controllers/component/hls_component_test.exs b/test/jellyfish_web/controllers/component/hls_component_test.exs index 3c0e2357..3998fe74 100644 --- a/test/jellyfish_web/controllers/component/hls_component_test.exs +++ b/test/jellyfish_web/controllers/component/hls_component_test.exs @@ -46,7 +46,7 @@ defmodule JellyfishWeb.Component.HlsComponentTest do conn = post(conn, ~p"/room/#{room_id}/component", type: "hls") assert model_response(conn, :bad_request, "Error")["errors"] == - "Reached components limit for component HLS in room #{room_id}" + "Reached hls components limit for component in room #{room_id}" conn = delete(conn, ~p"/room/#{room_id}") assert response(conn, :no_content) @@ -208,19 +208,6 @@ defmodule JellyfishWeb.Component.HlsComponentTest do end end - defp create_h264_room(%{conn: conn}) do - conn = post(conn, ~p"/room", videoCodec: "h264") - - assert %{"id" => room_id} = - model_response(conn, :created, "RoomCreateDetailsResponse")["data"]["room"] - - on_exit(fn -> - RoomService.delete_room(room_id) - end) - - %{room_id: room_id} - end - defp assert_hls_path(room_id, persistent: persistent) do hls_path = HLS.output_dir(room_id, persistent: persistent) assert {:ok, ^hls_path} = HLS.EtsHelper.get_hls_folder_path(room_id) diff --git a/test/jellyfish_web/controllers/component/recording_component_test.exs b/test/jellyfish_web/controllers/component/recording_component_test.exs index 5998ed54..18553b3c 100644 --- a/test/jellyfish_web/controllers/component/recording_component_test.exs +++ b/test/jellyfish_web/controllers/component/recording_component_test.exs @@ -4,6 +4,8 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do import Mox + alias Jellyfish.RoomService + @s3_credentials %{ accessKeyId: "access_key_id", secretAccessKey: "secret_access_key", @@ -14,6 +16,7 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do @path_prefix "path_prefix" describe "create recording component" do + setup [:create_h264_room] setup :set_mox_from_context test "renders component with required options", %{conn: conn, room_id: room_id} do @@ -34,6 +37,12 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do } = model_response(conn, :created, "ComponentDetailsResponse") assert_component_created(conn, room_id, id, "recording") + + # Try to add another recording component + conn = post(conn, ~p"/room/#{room_id}/component", type: "recording") + + assert model_response(conn, :bad_request, "Error")["errors"] == + "Reached recording components limit for component in room #{room_id}" end setup :set_mox_from_context @@ -74,6 +83,22 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do assert model_response(conn, :bad_request, "Error")["errors"] == "S3 credentials has to be passed either by request or at application startup as envs" end + + test "renders errors when video codec is different than h264 - vp8", %{conn: conn} do + Application.put_env(:jellyfish, :s3_credentials, @s3_credentials) + conn = post(conn, ~p"/room", videoCodec: "vp8") + + assert %{"id" => room_id} = + model_response(conn, :created, "RoomCreateDetailsResponse")["data"]["room"] + + conn = post(conn, ~p"/room/#{room_id}/component", type: "recording") + + assert model_response(conn, :bad_request, "Error")["errors"] == + "Incompatible video codec enforced in room #{room_id}" + + RoomService.delete_room(room_id) + Application.put_env(:jellyfish, :s3_credentials, nil) + end end defp mock_http_request() do diff --git a/test/jellyfish_web/controllers/component_controller_test.exs b/test/jellyfish_web/controllers/component_controller_test.exs index a5a0863f..3d5c3b75 100644 --- a/test/jellyfish_web/controllers/component_controller_test.exs +++ b/test/jellyfish_web/controllers/component_controller_test.exs @@ -22,6 +22,7 @@ defmodule JellyfishWeb.ComponentControllerTest do end describe "delete component" do + setup [:create_h264_room] setup [:create_rtsp_component] test "deletes chosen component", %{conn: conn, room_id: room_id, component_id: component_id} do diff --git a/test/support/component_case.ex b/test/support/component_case.ex index 081df736..bbf72e6f 100644 --- a/test/support/component_case.ex +++ b/test/support/component_case.ex @@ -7,6 +7,8 @@ defmodule JellyfishWeb.ComponentCase do use ExUnit.CaseTemplate use JellyfishWeb.ConnCase + alias Jellyfish.RoomService + using do quote do import JellyfishWeb.ComponentCase @@ -71,4 +73,18 @@ defmodule JellyfishWeb.ComponentCase do JellyfishWeb.ApiSpec.spec() ) end + + @spec create_h264_room(context :: term()) :: map() + def create_h264_room(%{conn: conn}) do + conn = post(conn, ~p"/room", videoCodec: "h264") + + assert %{"id" => room_id} = + model_response(conn, :created, "RoomCreateDetailsResponse")["data"]["room"] + + on_exit(fn -> + RoomService.delete_room(room_id) + end) + + %{room_id: room_id} + end end From 8bc4145e8303469c54e0a1d6a6ba71c31567b3bd Mon Sep 17 00:00:00 2001 From: Karol Konkol <56369082+Karolk99@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:43:52 +0200 Subject: [PATCH 12/51] =?UTF-8?q?Add=20JF=5FS3=5FPATH=5FPREFIX=20to=20conf?= =?UTF-8?q?ig=20and=20restrict=20s3=5Fconfig=20to=20be=20provided=E2=80=A6?= =?UTF-8?q?=20(#170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Restrict available codecs in room for recording component * Restrict recording component to one per room * Add JF_S3_PATH_PREFIX to config and restrict s3_config to be provided through only one method * Requested changes --- config/runtime.exs | 3 +- lib/jellyfish/component/recording.ex | 39 +++++++++---- lib/jellyfish/config_reader.ex | 48 ++++++++++------ lib/jellyfish/room.ex | 12 +++- .../controllers/component_controller.ex | 8 +++ test/jellyfish/config_reader_test.exs | 30 +++++++--- .../component/recording_component_test.exs | 56 +++++++++++++++++-- .../component/rtsp_component_test.exs | 2 + 8 files changed, 156 insertions(+), 42 deletions(-) diff --git a/config/runtime.exs b/config/runtime.exs index 4f1045dc..ca8b3fbf 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -35,7 +35,8 @@ config :jellyfish, dist_config: ConfigReader.read_dist_config(), webrtc_config: ConfigReader.read_webrtc_config(), sip_config: ConfigReader.read_sip_config(), - s3_credentials: ConfigReader.read_s3_credentials(), + recording_config: ConfigReader.read_recording_config(), + s3_config: ConfigReader.read_s3_config(), git_commit: ConfigReader.read_git_commit() case System.get_env("JF_SERVER_API_TOKEN") do diff --git a/lib/jellyfish/component/recording.ex b/lib/jellyfish/component/recording.ex index 033608b0..882d1d59 100644 --- a/lib/jellyfish/component/recording.ex +++ b/lib/jellyfish/component/recording.ex @@ -13,11 +13,19 @@ defmodule Jellyfish.Component.Recording do @impl true def config(%{engine_pid: engine} = options) do - with {:ok, serialized_opts} <- serialize_options(options, Options.schema()), - {:ok, credentials} <- get_credentials(serialized_opts) do - path_prefix = serialized_opts.path_prefix - output_dir = Path.join(get_base_path(), path_prefix) + recording_config = Application.fetch_env!(:jellyfish, :recording_config) + sink_config = Application.fetch_env!(:jellyfish, :s3_config) + + unless recording_config[:recording_used?], + do: + raise(""" + Recording components can only be used if JF_RECORDING_USED environmental variable is set to \"true\" + """) + with {:ok, serialized_opts} <- serialize_options(options, Options.schema()), + {:ok, credentials} <- get_credentials(serialized_opts, sink_config), + {:ok, path_prefix} <- get_path_prefix(serialized_opts, sink_config) do + output_dir = get_base_path() File.mkdir_p!(output_dir) file_storage = {Recording.Storage.File, %{output_dir: output_dir}} @@ -39,13 +47,24 @@ defmodule Jellyfish.Component.Recording do end end - defp get_credentials(%{credentials: nil}) do - case Application.fetch_env!(:jellyfish, :s3_credentials) do - nil -> {:error, :missing_s3_credentials} - credentials -> {:ok, Enum.into(credentials, %{})} + defp get_credentials(%{credentials: credentials}, s3_config) do + case {credentials, s3_config[:credentials]} do + {nil, nil} -> {:error, :missing_s3_credentials} + {nil, credentials} -> {:ok, Enum.into(credentials, %{})} + {credentials, nil} -> {:ok, credentials} + _else -> {:error, :overridding_credentials} + end + end + + defp get_path_prefix(%{path_prefix: path_prefix}, s3_config) do + case {path_prefix, s3_config[:path_prefix]} do + {nil, nil} -> {:ok, ""} + {nil, path_prefix} -> {:ok, path_prefix} + {path_prefix, nil} -> {:ok, path_prefix} + _else -> {:error, :overridding_path_prefix} end end - defp get_credentials(%{credentials: credentials}), do: {:ok, credentials} - defp get_base_path(), do: Application.fetch_env!(:jellyfish, :media_files_path) + defp get_base_path(), + do: :jellyfish |> Application.fetch_env!(:media_files_path) |> Path.join("raw_recordings") end diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex index f90670a6..7fdf2a54 100644 --- a/lib/jellyfish/config_reader.ex +++ b/lib/jellyfish/config_reader.ex @@ -143,7 +143,13 @@ defmodule Jellyfish.ConfigReader do end end - def read_s3_credentials() do + def read_recording_config() do + [ + recording_used?: read_boolean("JF_RECORDING_USED") != false + ] + end + + def read_s3_config() do credentials = [ bucket: System.get_env("JF_S3_BUCKET"), region: System.get_env("JF_S3_REGION"), @@ -151,24 +157,34 @@ defmodule Jellyfish.ConfigReader do secret_access_key: System.get_env("JF_S3_SECRET_ACCESS_KEY") ] - cond do - Enum.all?(credentials, fn {_key, val} -> not is_nil(val) end) -> - credentials - - Enum.all?(credentials, fn {_key, val} -> is_nil(val) end) -> - nil + path_prefix = System.get_env("JF_S3_PATH_PREFIX") - true -> - missing_envs = + credentials = + cond do + Enum.all?(credentials, fn {_key, val} -> not is_nil(val) end) -> credentials - |> Enum.filter(fn {_key, val} -> val == nil end) - |> Enum.map(fn {key, _val} -> "JF_" <> (key |> Atom.to_string() |> String.upcase()) end) - raise """ - Either all S3 credentials have to be set: `JF_S3_BUCKET`, `JF_S3_REGION`, `JF_S3_ACCESS_KEY_ID`, `JF_S3_SECRET_ACCESS_KEY`, or none must be set. - Currently, the following required credentials are missing: #{inspect(missing_envs)}. - """ - end + Enum.all?(credentials, fn {_key, val} -> is_nil(val) end) -> + nil + + true -> + missing_envs = + credentials + |> Enum.filter(fn {_key, val} -> val == nil end) + |> Enum.map(fn {key, _val} -> + "JF_" <> (key |> Atom.to_string() |> String.upcase()) + end) + + raise """ + Either all S3 credentials have to be set: `JF_S3_BUCKET`, `JF_S3_REGION`, `JF_S3_ACCESS_KEY_ID`, `JF_S3_SECRET_ACCESS_KEY`, or none must be set. + Currently, the following required credentials are missing: #{inspect(missing_envs)}. + """ + end + + [ + path_prefix: path_prefix, + credentials: credentials + ] end def read_dist_config() do diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index 39245b47..0da55a99 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -318,6 +318,14 @@ defmodule Jellyfish.Room do Logger.warning("Unable to add component: missing s3 credentials") {:reply, {:error, :missing_s3_credentials}, state} + {:error, :overridding_credentials} -> + Logger.warning("Unable to add component: tried to override s3 credentials") + {:reply, {:error, :overridding_credentials}, state} + + {:error, :overridding_path_prefix} -> + Logger.warning("Unable to add component: tried to override s3 path_prefix") + {:reply, {:error, :overridding_path_prefix}, state} + {:error, reason} -> Logger.warning("Unable to add component: #{inspect(reason)}") {:reply, :error, state} @@ -791,8 +799,8 @@ defmodule Jellyfish.Room do defp check_component_allowed(RTSP, %{config: %{video_codec: video_codec}}) do # Right now, RTSP component can only publish H264, so there's no point adding it - # to a room which enforces another video codec, e.g. VP8 - if video_codec in [:h264, nil], + # to a room which allows another video codec, e.g. VP8 + if video_codec == :h264, do: :ok, else: {:error, :incompatible_codec} end diff --git a/lib/jellyfish_web/controllers/component_controller.ex b/lib/jellyfish_web/controllers/component_controller.ex index 57ee752f..efc9fe92 100644 --- a/lib/jellyfish_web/controllers/component_controller.ex +++ b/lib/jellyfish_web/controllers/component_controller.ex @@ -83,6 +83,14 @@ defmodule JellyfishWeb.ComponentController do {:error, :bad_request, "S3 credentials has to be passed either by request or at application startup as envs"} + {:error, :overridding_credentials} -> + {:error, :bad_request, + "Conflicting S3 credentials supplied via environment variables and the REST API. Overrides on existing values are disallowed"} + + {:error, :overridding_path_prefix} -> + {:error, :bad_request, + "Conflicting S3 path prefix supplied via environment variables and the REST API. Overrides on existing values are disallowed"} + {:error, :invalid_type} -> {:error, :bad_request, "Invalid component type"} diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs index ed2fda3c..2e2093ef 100644 --- a/test/jellyfish/config_reader_test.exs +++ b/test/jellyfish/config_reader_test.exs @@ -143,22 +143,34 @@ defmodule Jellyfish.ConfigReaderTest do end end - test "read_s3_credentials/0" do - with_env ["JF_S3_BUCKET", "JF_S3_ACCESS_KEY_ID", "JF_S3_SECRET_ACCESS_KEY", "JF_S3_REGION"] do - assert ConfigReader.read_s3_credentials() == nil + test "read_s3_config/0" do + with_env [ + "JF_S3_BUCKET", + "JF_S3_ACCESS_KEY_ID", + "JF_S3_SECRET_ACCESS_KEY", + "JF_S3_REGION", + "JF_S3_PATH_PREFIX" + ] do + assert ConfigReader.read_s3_config() == [path_prefix: nil, credentials: nil] + + System.put_env("JF_S3_PATH_PREFIX", "path_prefix") + assert ConfigReader.read_s3_config() == [path_prefix: "path_prefix", credentials: nil] System.put_env("JF_S3_BUCKET", "bucket") - assert_raise RuntimeError, fn -> ConfigReader.read_s3_credentials() end + assert_raise RuntimeError, fn -> ConfigReader.read_s3_config() end System.put_env("JF_S3_ACCESS_KEY_ID", "id") System.put_env("JF_S3_SECRET_ACCESS_KEY", "key") System.put_env("JF_S3_REGION", "region") - assert ConfigReader.read_s3_credentials() == [ - bucket: "bucket", - region: "region", - access_key_id: "id", - secret_access_key: "key" + assert ConfigReader.read_s3_config() == [ + path_prefix: "path_prefix", + credentials: [ + bucket: "bucket", + region: "region", + access_key_id: "id", + secret_access_key: "key" + ] ] end end diff --git a/test/jellyfish_web/controllers/component/recording_component_test.exs b/test/jellyfish_web/controllers/component/recording_component_test.exs index 18553b3c..822561e1 100644 --- a/test/jellyfish_web/controllers/component/recording_component_test.exs +++ b/test/jellyfish_web/controllers/component/recording_component_test.exs @@ -52,7 +52,7 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do room_id: room_id } do mock_http_request() - Application.put_env(:jellyfish, :s3_credentials, @s3_credentials) + put_s3_envs(path_prefix: nil, credentials: @s3_credentials) conn = post(conn, ~p"/room/#{room_id}/component", @@ -70,7 +70,43 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do assert_component_created(conn, room_id, id, "recording") - Application.put_env(:jellyfish, :s3_credentials, nil) + clean_s3_envs() + end + + test "renders error when credentials are passed both in config and request", %{ + conn: conn, + room_id: room_id + } do + put_s3_envs(path_prefix: nil, credentials: @s3_credentials) + + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "recording", + options: %{credentials: @s3_credentials} + ) + + assert model_response(conn, :bad_request, "Error")["errors"] == + "Conflicting S3 credentials supplied via environment variables and the REST API. Overrides on existing values are disallowed" + + clean_s3_envs() + end + + test "renders error when path prefix is passed both in config and request", %{ + conn: conn, + room_id: room_id + } do + put_s3_envs(path_prefix: @path_prefix, credentials: @s3_credentials) + + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "recording", + options: %{path_prefix: @path_prefix} + ) + + assert model_response(conn, :bad_request, "Error")["errors"] == + "Conflicting S3 path prefix supplied via environment variables and the REST API. Overrides on existing values are disallowed" + + clean_s3_envs() end test "renders errors when required options are missing", %{conn: conn, room_id: room_id} do @@ -85,7 +121,8 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do end test "renders errors when video codec is different than h264 - vp8", %{conn: conn} do - Application.put_env(:jellyfish, :s3_credentials, @s3_credentials) + put_s3_envs(path_prefix: nil, credentials: @s3_credentials) + conn = post(conn, ~p"/room", videoCodec: "vp8") assert %{"id" => room_id} = @@ -97,7 +134,7 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do "Incompatible video codec enforced in room #{room_id}" RoomService.delete_room(room_id) - Application.put_env(:jellyfish, :s3_credentials, nil) + clean_s3_envs() end end @@ -110,4 +147,15 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do {:ok, %{status_code: 200, headers: %{}}} end) end + + defp put_s3_envs(path_prefix: path_prefix, credentials: credentials) do + Application.put_env(:jellyfish, :s3_config, + path_prefix: path_prefix, + credentials: credentials + ) + end + + defp clean_s3_envs() do + Application.put_env(:jellyfish, :s3_config, path_prefix: nil, credentials: nil) + end end diff --git a/test/jellyfish_web/controllers/component/rtsp_component_test.exs b/test/jellyfish_web/controllers/component/rtsp_component_test.exs index ba2739a8..cc61b56f 100644 --- a/test/jellyfish_web/controllers/component/rtsp_component_test.exs +++ b/test/jellyfish_web/controllers/component/rtsp_component_test.exs @@ -23,6 +23,8 @@ defmodule JellyfishWeb.Component.RTSPComponentTest do @rtsp_custom_properties @rtsp_custom_options |> map_keys_to_string() describe "create rtsp component" do + setup [:create_h264_room] + test "renders component with required options", %{conn: conn, room_id: room_id} do conn = post(conn, ~p"/room/#{room_id}/component", From 4eb36e6cdd5b7bf53d8d28146bdac1898869a448 Mon Sep 17 00:00:00 2001 From: Karol Konkol <56369082+Karolk99@users.noreply.github.com> Date: Wed, 3 Apr 2024 12:48:27 +0200 Subject: [PATCH 13/51] Fix recording component request (#172) --- .../api_spec/component/recording.ex | 2 +- openapi.yaml | 1 - .../component/recording_component_test.exs | 30 +++++++++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/jellyfish_web/api_spec/component/recording.ex b/lib/jellyfish_web/api_spec/component/recording.ex index 2d38ea64..3f47234e 100644 --- a/lib/jellyfish_web/api_spec/component/recording.ex +++ b/lib/jellyfish_web/api_spec/component/recording.ex @@ -39,7 +39,7 @@ defmodule JellyfishWeb.ApiSpec.Component.Recording do pathPrefix: %Schema{ type: :string, description: "Path prefix under which all recording are stored", - default: "" + default: nil }, credentials: %Schema{ type: :object, diff --git a/openapi.yaml b/openapi.yaml index 4b0694f7..70170a0c 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -161,7 +161,6 @@ components: - $ref: '#/components/schemas/S3Credentials' type: object pathPrefix: - default: '' description: Path prefix under which all recording are stored type: string title: ComponentOptionsRecording diff --git a/test/jellyfish_web/controllers/component/recording_component_test.exs b/test/jellyfish_web/controllers/component/recording_component_test.exs index 822561e1..5910e981 100644 --- a/test/jellyfish_web/controllers/component/recording_component_test.exs +++ b/test/jellyfish_web/controllers/component/recording_component_test.exs @@ -47,7 +47,7 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do setup :set_mox_from_context - test "renders component when credentials are in passed in config", %{ + test "renders component when credentials are passed in config", %{ conn: conn, room_id: room_id } do @@ -73,6 +73,32 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do clean_s3_envs() end + test "renders component when path prefix is passed in config", %{ + conn: conn, + room_id: room_id + } do + mock_http_request() + put_s3_envs(path_prefix: @path_prefix, credentials: nil) + + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "recording", + options: %{credentials: Enum.into(@s3_credentials, %{})} + ) + + assert %{ + "data" => %{ + "id" => id, + "type" => "recording", + "properties" => %{"pathPrefix" => @path_prefix} + } + } = model_response(conn, :created, "ComponentDetailsResponse") + + assert_component_created(conn, room_id, id, "recording") + + clean_s3_envs() + end + test "renders error when credentials are passed both in config and request", %{ conn: conn, room_id: room_id @@ -100,7 +126,7 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do conn = post(conn, ~p"/room/#{room_id}/component", type: "recording", - options: %{path_prefix: @path_prefix} + options: %{pathPrefix: @path_prefix} ) assert model_response(conn, :bad_request, "Error")["errors"] == From 87bb5a38f348cefa5bbbde915fe1ae42737479c1 Mon Sep 17 00:00:00 2001 From: Kamil Stasiak Date: Fri, 5 Apr 2024 14:18:51 +0200 Subject: [PATCH 14/51] Fix OpenAPI spec (#173) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix OpenAPI spec * Update openapi.yaml --------- Co-authored-by: Radosław Szuma --- .../api_spec/component/recording.ex | 3 +- mix.lock | 40 +++++++++---------- openapi.yaml | 1 + 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/jellyfish_web/api_spec/component/recording.ex b/lib/jellyfish_web/api_spec/component/recording.ex index 3f47234e..e4e44f9f 100644 --- a/lib/jellyfish_web/api_spec/component/recording.ex +++ b/lib/jellyfish_web/api_spec/component/recording.ex @@ -39,7 +39,8 @@ defmodule JellyfishWeb.ApiSpec.Component.Recording do pathPrefix: %Schema{ type: :string, description: "Path prefix under which all recording are stored", - default: nil + default: nil, + nullable: true }, credentials: %Schema{ type: :object, diff --git a/mix.lock b/mix.lock index 4736189c..c88e0805 100644 --- a/mix.lock +++ b/mix.lock @@ -2,24 +2,24 @@ "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.4.6", "6d93c4ca3bfb2282445e1e76ea263cedae49ba8524bf4cce94eb8124421c81fc", [: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", "549115f64f55e29b32f566ae054caa5557b334aceab279e0b820055ad0bfc8b6"}, + "bundlex": {:hex, :bundlex, "1.5.0", "295472109d6c8a2fb59712523f20f9502105e3306edc3b490aa1cc4df3f9c5cf", [: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", "e5841de5380b9e99eef347656a71f3d6fab7ff34dee41da3e70c256d39cfdf77"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"}, - "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, + "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.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, + "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.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [: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", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "divo": {:hex, :divo, "1.3.2", "3a5ce880a1fe930ea804361d1b57b5144129e79e1c856623d923a6fab6d539a1", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:patiently, "~> 0.2", [hex: :patiently, repo: "hexpm", optional: false]}], "hexpm", "4bd035510838959709db2cacd28edd2eda7948d0e7f1b0dfa810a134c913a88a"}, - "elixir_make": {:hex, :elixir_make, "0.8.2", "cd4a5a75891362e9207adaac7e66223fd256ec2518ae013af7f10c9c85b50b5c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "9d9607d640c372a7291e5a56ce655aa2351897929be20bd211648fdb79e725dc"}, + "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_aws": {:hex, :ex_aws, "2.5.1", "7418917974ea42e9e84b25e88b9f3d21a861d5f953ad453e212f48e593d8d39f", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1b95431f70c446fa1871f0eb9b183043c5a625f75f9948a42d25f43ae2eff12b"}, + "ex_aws": {:hex, :ex_aws, "2.5.3", "9c2d05ba0c057395b12c7b5ca6267d14cdaec1d8e65bdf6481fe1fd245accfb4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67115f1d399d7ec4d191812ee565c6106cb4b1bbf19a9d4db06f265fd87da97e"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"}, "ex_dtls": {:hex, :ex_dtls, "0.12.0", "648522f53340b42301eae57627bb8276555be508ec1010561e606b1621d9d2e9", [:mix], [{:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "0bc2d0de146e7cf9d85eb8d2c0a6a518479a66a2ded8a79c0960eced23fe73a9"}, "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"}, @@ -39,7 +39,7 @@ "klotho": {:hex, :klotho, "0.1.2", "3b1f1a569703e0cdce1ba964f41711351a7b06846c38fcbd601faa407e712bf2", [:mix], [], "hexpm", "a6a387982753582e30a5246fe9561721c6b9a4dd27678296cf2cd44faa6f3733"}, "libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"}, "logger_json": {:hex, :logger_json, "5.1.4", "9e30a4f2e31a8b9e402bdc20bd37cf9b67d3a31f19d0b33082a19a06b4c50f6d", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "3f20eea58e406a33d3eb7814c7dff5accb503bab2ee8601e84da02976fa3934c"}, - "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.6", "82c5064a11781adc212ba7facebd63015473b99702e39fbaac9922e638436966", [: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", "956c18eacbfaa908535f306462c47de6dbd0892db5c6cdcce5daaa67c853268a"}, + "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.7", "4d9af018c22d9291b72d6025941452dd53c7921bcdbc826da8866bb6ecefa8cb", [: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", "79904c3b78882bd0cec15b02928e6b53780602e64a359941acbc9a2408e7b74b"}, "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.18.1", "30433bffd4d5d773f79448dd9afd55d77338721688f09a89b20d742a68cc2c3d", [: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", "8fd048c47d5d2949eb557e19f43f62d534d3af5096187f1a1a3a1694d14b772c"}, "membrane_audio_mix_plugin": {:hex, :membrane_audio_mix_plugin, "0.16.0", "34997707ee186683c6d7bd87572944e5e37c0249235cc44915d181d653c5c40e", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, 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_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", "a4a8c723f0da8d9cf9ac11bf657a732770ea0b8db4eff2efc16caa3a1819f435"}, @@ -52,10 +52,10 @@ "membrane_funnel_plugin": {:hex, :membrane_funnel_plugin, "0.9.0", "9cfe09e44d65751f7d9d8d3c42e14797f7be69e793ac112ea63cd224af70a7bf", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "988790aca59d453a6115109f050699f7f45a2eb6a7f8dc5c96392760cddead54"}, "membrane_g711_format": {:hex, :membrane_g711_format, "0.1.0", "a570a9832a6bf23074210816560e5116935f0face32605968135bf3451ad8a12", [:mix], [], "hexpm", "cbf2c0482b4ca2145c440cd1dcfa865967bcaeaa3813f4f4d2c28a66b59659ef"}, "membrane_g711_plugin": {:hex, :membrane_g711_plugin, "0.1.0", "b813a506069f4a72fe96a1ff183ce466dd62e6651581798ff7937427403fad77", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "32ecbe8951f016137b3bf4123aa475b8429c78ca7ad8c730729ccc6efc29fbc9"}, - "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.31.5", "7db887780516164c68200e8e39c339e6077a82d6fe78c402e2e0576d2261c76c", [: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.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "472618e76565cea20d26b5659246a517e891e2ba8462d016eea90e9bfb49bd77"}, + "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.31.6", "95574b1e2fea79f17c57db4295364ed82e2d57ab4ce229734421fde37e9bc632", [: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.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "30a45d79317c27c1402ab871e3e42b0244e6ecd72cbf1145aea337726cff7ac2"}, "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, "membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"}, - "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.0", "0a7c6b9a7678e8c111b22b5417465ac31cf6e598cff6a53ab53a9c379bdfa1ef", [: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", "e9cde8c8995ace9fc26355037cbcc780f1727a3f63d36c21b52232fd29d0ad40"}, + "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.1", "d7aeb166da55c6573b2178e18caeea290b09fd6f3cca428454085223e81476a0", [: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", "9cd63a67ffed0654a932efff34395ded04a05e48d08ea996c93daebf889dac08"}, "membrane_http_adaptive_stream_plugin": {:hex, :membrane_http_adaptive_stream_plugin, "0.18.3", "202d409bda5ff9e611d482b61c5fcc893c59905d92f3b8dd5d0288561449535c", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.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.33.1", [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", "adb36192eba89e81d458c54eeccfee4da8d11ca78ee3488c57da4893203ed80d"}, "membrane_ice_plugin": {:hex, :membrane_ice_plugin, "0.18.0", "beecb741b641b0c8b4efea0569fa68a3564051294e3ed10a10a1e29028e1d474", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.12.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:fake_turn, "~> 0.4.0", [hex: :fake_turn, 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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "fff74d447d42902bb014bc8cdc78d6da3f797b59ed8fd622bfc7c6854323a287"}, "membrane_mp4_format": {:hex, :membrane_mp4_format, "0.8.0", "8c6e7d68829228117d333b4fbb030e7be829aab49dd8cb047fdc664db1812e6a", [:mix], [], "hexpm", "148dea678a1f82ccfd44dbde6f936d2f21255f496cb45a22cc6eec427f025522"}, @@ -68,13 +68,13 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "engine"]}, - "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "file"]}, - "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "hls"]}, - "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "recording"]}, - "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "rtsp"]}, - "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "sip"]}, - "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "17bb6fcdc3c8ad5ceba52054efbc8ca81cb78185", [sparse: "webrtc"]}, + "membrane_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "engine"]}, + "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "file"]}, + "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "hls"]}, + "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "recording"]}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "rtsp"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "sip"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "webrtc"]}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.0", "112bfedc14fb83bdb549ef1a03da23908feedeb165fd3e4512a549f1af532ae7", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "76fd159e7406cadbef15124cba30eca3fffcf71a7420964f26e23d4cffd9b29d"}, @@ -97,7 +97,7 @@ "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"}, - "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, + "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"}, "open_api_spex": {:hex, :open_api_spex, "3.18.3", "fefb84fe323cacfc92afdd0ecb9e89bc0261ae00b7e3167ffc2028ce3944de42", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "c0cfc31570199ce7e7520b494a591027da609af45f6bf9adce51e2469b1609fb"}, "p1_utils": {:hex, :p1_utils, "1.0.23", "7f94466ada69bd982ea7bb80fbca18e7053e7d0b82c9d9e37621fa508587069b", [:rebar3], [], "hexpm", "47f21618694eeee5006af1c88731ad86b757161e7823c29b6f73921b571c8502"}, @@ -110,7 +110,7 @@ "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.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [: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", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.0", "3ae9369c60641084363b08fe90267cbdd316df57e3557ea522114b30b63256ea", [:mix], [{:cowboy, "~> 2.7.0 or ~> 2.8.0 or ~> 2.9.0 or ~> 2.10.0", [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", "d85444fb8aa1f2fc62eabe83bbe387d81510d773886774ebdcb429b3da3c1a4a"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [: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", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "protobuf": {:hex, :protobuf, "0.12.0", "58c0dfea5f929b96b5aa54ec02b7130688f09d2de5ddc521d696eec2a015b223", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "75fa6cbf262062073dd51be44dd0ab940500e18386a6c4e87d5819a58964dc45"}, "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, @@ -118,7 +118,7 @@ "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"}, "req": {:hex, :req, "0.4.14", "103de133a076a31044e5458e0f850d5681eef23dfabf3ea34af63212e3b902e2", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {: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, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0 or ~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2ddd3d33f9ab714ced8d3c15fd03db40c14dbf129003c4a3eb80fac2cc0b1b08"}, "rustler": {:hex, :rustler, "0.26.0", "06a2773d453ee3e9109efda643cf2ae633dedea709e2455ac42b83637c9249bf", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "42961e9d2083d004d5a53e111ad1f0c347efd9a05cb2eb2ffa1d037cdc74db91"}, - "shmex": {:hex, :shmex, "0.5.0", "7dc4fb1a8bd851085a652605d690bdd070628717864b442f53d3447326bcd3e8", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "b67bb1e22734758397c84458dbb746519e28eac210423c267c7248e59fc97bdc"}, + "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"}, "sippet": {:hex, :sippet, "1.0.16", "614d472d4d7e2b7f0828c554aaf9a0238e0c6e903dbb17334f5cb2baa58da1c2", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:gen_state_machine, ">= 3.0.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:sippet_uri, "~> 0.1", [hex: :sippet_uri, repo: "hexpm", optional: false]}], "hexpm", "1a23af1ce85adeae89863daaded233637075a7dd031e589b4f343ae08901667c"}, "sippet_uri": {:hex, :sippet_uri, "0.1.0", "ec8bfe6df12e17c0ca32b18b2177585426448bf3e3b0434f5ddfb93dc075add2", [:mix], [], "hexpm", "e8b1ad3fdd12670b4e9851a273624a7ed4b12cf5e705dec7906204c294d9790b"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, @@ -127,13 +127,13 @@ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, "telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"}, - "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.0", "b583c3f18508f5c5561b674d16cf5d9afd2ea3c04505b7d92baaeac93c1b8260", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "9cba950e1c4733468efbe3f821841f34ac05d28e7af7798622f88ecdbbe63ea3"}, + "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "unifex": {:hex, :unifex, "1.2.0", "90d1ec5e6d788350e07e474f7bd8b0ee866d6606beb9ca4e20dbb26328712a84", [: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", "7a8395aabc3ba6cff04bbe5b995de7f899a38eb57f189e49927d6b8b6ccb6883"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [: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", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [: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", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"}, "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"}, "ymlr": {:hex, :ymlr, "3.0.1", "c7b459262aa52cbfee5d324995ac1bff8c765983460b1a4bb792f0f4db392232", [:mix], [], "hexpm", "759ce05102f7cb741cc65148044443d330cc75c9abd0769d5735bbf522219309"}, "zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"}, diff --git a/openapi.yaml b/openapi.yaml index 70170a0c..fb564dee 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -162,6 +162,7 @@ components: type: object pathPrefix: description: Path prefix under which all recording are stored + nullable: true type: string title: ComponentOptionsRecording type: object From ec13dd3a4cd5fa8a8998b6b2a1455f26d334dfea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Mon, 8 Apr 2024 08:47:16 +0200 Subject: [PATCH 15/51] RTC-506 Restrict user assigned room's id to alphanumericals (#171) * Allow only alphanumeric characters in room_id * Add path_suffix to path_prefix * Update lib/jellyfish_web/controllers/room_controller.ex Co-authored-by: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> * Don't allow empty strings as room_id * Change deps * Update deps --------- Co-authored-by: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> --- lib/jellyfish/component.ex | 3 +- lib/jellyfish/component/recording.ex | 7 +- lib/jellyfish/room/config.ex | 10 ++- .../controllers/room_controller.ex | 6 ++ .../component/recording_component_test.exs | 67 ++++++++++++++++++- .../controllers/room_controller_test.exs | 10 +++ 6 files changed, 98 insertions(+), 5 deletions(-) diff --git a/lib/jellyfish/component.ex b/lib/jellyfish/component.ex index a5a683cb..a8bbc219 100644 --- a/lib/jellyfish/component.ex +++ b/lib/jellyfish/component.ex @@ -123,7 +123,8 @@ defmodule Jellyfish.Component do @spec new(component(), map()) :: {:ok, t()} | {:error, term()} def new(type, options) do - with {:ok, %{endpoint: endpoint, properties: properties}} <- type.config(options) do + with {:ok, %{endpoint: endpoint, properties: properties}} <- + type.config(options) do {:ok, %__MODULE__{ id: UUID.uuid4(), diff --git a/lib/jellyfish/component/recording.ex b/lib/jellyfish/component/recording.ex index 882d1d59..70665807 100644 --- a/lib/jellyfish/component/recording.ex +++ b/lib/jellyfish/component/recording.ex @@ -25,7 +25,12 @@ defmodule Jellyfish.Component.Recording do with {:ok, serialized_opts} <- serialize_options(options, Options.schema()), {:ok, credentials} <- get_credentials(serialized_opts, sink_config), {:ok, path_prefix} <- get_path_prefix(serialized_opts, sink_config) do - output_dir = get_base_path() + datetime = DateTime.utc_now() |> to_string() + path_suffix = Path.join(options.room_id, "part_#{datetime}") + + path_prefix = Path.join(path_prefix, path_suffix) + output_dir = Path.join(get_base_path(), path_suffix) + File.mkdir_p!(output_dir) file_storage = {Recording.Storage.File, %{output_dir: output_dir}} diff --git a/lib/jellyfish/room/config.ex b/lib/jellyfish/room/config.ex index 5252c985..d833163c 100644 --- a/lib/jellyfish/room/config.ex +++ b/lib/jellyfish/room/config.ex @@ -45,7 +45,15 @@ defmodule Jellyfish.Room.Config do end defp parse_room_id(nil), do: {:ok, UUID.uuid4()} - defp parse_room_id(room_id) when is_binary(room_id), do: {:ok, room_id} + + defp parse_room_id(room_id) when is_binary(room_id) do + if Regex.match?(~r/^[a-zA-Z0-9-]+$/, room_id) do + {:ok, room_id} + else + {:error, :invalid_room_id} + end + end + defp parse_room_id(_room_id), do: {:error, :invalid_room_id} defp validate_max_peers(nil), do: :ok diff --git a/lib/jellyfish_web/controllers/room_controller.ex b/lib/jellyfish_web/controllers/room_controller.ex index 7d4750f3..9fe170e4 100644 --- a/lib/jellyfish_web/controllers/room_controller.ex +++ b/lib/jellyfish_web/controllers/room_controller.ex @@ -104,6 +104,12 @@ defmodule JellyfishWeb.RoomController do {:error, :room_already_exists} -> room_id = Map.get(params, "roomId") {:error, :bad_request, "Cannot add room with id \"#{room_id}\" - room already exists"} + + {:error, :invalid_room_id} -> + room_id = Map.get(params, "roomId") + + {:error, :bad_request, + "Cannot add room with id \"#{room_id}\" - roomId may contain only alphanumeric characters and hyphens"} end end diff --git a/test/jellyfish_web/controllers/component/recording_component_test.exs b/test/jellyfish_web/controllers/component/recording_component_test.exs index 5910e981..ed4492f9 100644 --- a/test/jellyfish_web/controllers/component/recording_component_test.exs +++ b/test/jellyfish_web/controllers/component/recording_component_test.exs @@ -28,14 +28,18 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do options: %{credentials: Enum.into(@s3_credentials, %{}), pathPrefix: @path_prefix} ) + prefix = "#{@path_prefix}/#{room_id}/part_" + assert %{ "data" => %{ "id" => id, "type" => "recording", - "properties" => %{"pathPrefix" => @path_prefix} + "properties" => %{"pathPrefix" => path_prefix} } } = model_response(conn, :created, "ComponentDetailsResponse") + assert String.starts_with?(path_prefix, prefix) + assert_component_created(conn, room_id, id, "recording") # Try to add another recording component @@ -60,19 +64,78 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do options: %{pathPrefix: @path_prefix} ) + prefix = "#{@path_prefix}/#{room_id}/part_" + assert %{ "data" => %{ "id" => id, "type" => "recording", - "properties" => %{"pathPrefix" => @path_prefix} + "properties" => %{"pathPrefix" => path_prefix} } } = model_response(conn, :created, "ComponentDetailsResponse") + assert String.starts_with?(path_prefix, prefix) + assert_component_created(conn, room_id, id, "recording") clean_s3_envs() end + test "path prefix modify when recording is created second time", + %{ + conn: conn, + room_id: room_id + } do + mock_http_request() + put_s3_envs(path_prefix: nil, credentials: @s3_credentials) + + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "recording", + options: %{pathPrefix: @path_prefix} + ) + + prefix = "#{@path_prefix}/#{room_id}/part_" + + assert %{ + "data" => %{ + "id" => id, + "type" => "recording", + "properties" => %{"pathPrefix" => path_prefix1} + } + } = model_response(conn, :created, "ComponentDetailsResponse") + + assert String.starts_with?(path_prefix1, prefix) + + assert_component_created(conn, room_id, id, "recording") + + conn = delete(conn, ~p"/room/#{room_id}/component/#{id}") + assert response(conn, :no_content) + + # Second recording + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "recording", + options: %{pathPrefix: @path_prefix} + ) + + assert %{ + "data" => %{ + "id" => id, + "type" => "recording", + "properties" => %{"pathPrefix" => path_prefix2} + } + } = model_response(conn, :created, "ComponentDetailsResponse") + + assert_component_created(conn, room_id, id, "recording") + + assert String.starts_with?(path_prefix2, prefix) + + assert path_prefix1 != path_prefix2 + + clean_s3_envs() + end + test "renders component when path prefix is passed in config", %{ conn: conn, room_id: room_id diff --git a/test/jellyfish_web/controllers/room_controller_test.exs b/test/jellyfish_web/controllers/room_controller_test.exs index 5902f9b0..42013320 100644 --- a/test/jellyfish_web/controllers/room_controller_test.exs +++ b/test/jellyfish_web/controllers/room_controller_test.exs @@ -174,6 +174,16 @@ defmodule JellyfishWeb.RoomControllerTest do assert json_response(conn, :bad_request)["errors"] == "Expected peerlessPurgeTimeout to be a positive integer, got: nan" + + conn = post(conn, ~p"/room", roomId: "test/path") + + assert json_response(conn, :bad_request)["errors"] == + "Cannot add room with id \"test/path\" - roomId may contain only alphanumeric characters and hyphens" + + conn = post(conn, ~p"/room", roomId: "") + + assert json_response(conn, :bad_request)["errors"] == + "Cannot add room with id \"\" - roomId may contain only alphanumeric characters and hyphens" end end From e9a5f3c68abf68556dab601eb5032b03bf53e592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:03:59 +0200 Subject: [PATCH 16/51] Fix test in recording_component_test.exs (#176) --- .../controllers/component/recording_component_test.exs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/jellyfish_web/controllers/component/recording_component_test.exs b/test/jellyfish_web/controllers/component/recording_component_test.exs index ed4492f9..44d35cbe 100644 --- a/test/jellyfish_web/controllers/component/recording_component_test.exs +++ b/test/jellyfish_web/controllers/component/recording_component_test.exs @@ -153,10 +153,12 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do "data" => %{ "id" => id, "type" => "recording", - "properties" => %{"pathPrefix" => @path_prefix} + "properties" => %{"pathPrefix" => path_prefix} } } = model_response(conn, :created, "ComponentDetailsResponse") + assert String.starts_with?(path_prefix, @path_prefix) + assert_component_created(conn, room_id, id, "recording") clean_s3_envs() From 7980db95452f94cafddaa59606831d073cbee906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:50:05 +0200 Subject: [PATCH 17/51] RTC-510 Recording manual subscribe mode (#174) * WiP * Apply changes * Update deps * Update openapi.yaml * Fix openapi spec * Remove pathPrefix from properties of Recording Component * Changes after review * Remove path_prefix from properties in recording.ex * Rename variable * Change test in hls_component_test.exs --- lib/jellyfish/component/recording.ex | 14 ++- lib/jellyfish/room.ex | 35 +++--- .../api_spec/component/recording.ex | 15 ++- .../controllers/subscription_controller.ex | 26 ++-- lib/jellyfish_web/router.ex | 5 +- mix.exs | 8 +- mix.lock | 2 +- openapi.yaml | 110 +++++++++------- .../component/hls_component_test.exs | 23 ++-- .../component/recording_component_test.exs | 42 ++++++- .../subscription_controller_test.exs | 117 ++++++++++++++++-- 11 files changed, 290 insertions(+), 107 deletions(-) diff --git a/lib/jellyfish/component/recording.ex b/lib/jellyfish/component/recording.ex index 70665807..712a729f 100644 --- a/lib/jellyfish/component/recording.ex +++ b/lib/jellyfish/component/recording.ex @@ -23,6 +23,7 @@ defmodule Jellyfish.Component.Recording do """) with {:ok, serialized_opts} <- serialize_options(options, Options.schema()), + result_opts <- parse_subscribe_mode(serialized_opts), {:ok, credentials} <- get_credentials(serialized_opts, sink_config), {:ok, path_prefix} <- get_path_prefix(serialized_opts, sink_config) do datetime = DateTime.utc_now() |> to_string() @@ -39,10 +40,15 @@ defmodule Jellyfish.Component.Recording do endpoint = %Recording{ rtc_engine: engine, recording_id: options.room_id, - stores: [file_storage, s3_storage] + stores: [file_storage, s3_storage], + subscribe_mode: result_opts.subscribe_mode } - {:ok, %{endpoint: endpoint, properties: %{path_prefix: path_prefix}}} + {:ok, + %{ + endpoint: endpoint, + properties: %{subscribe_mode: result_opts.subscribe_mode} + }} else {:error, [%OpenApiSpex.Cast.Error{reason: :missing_field, name: name} | _rest]} -> {:error, {:missing_parameter, name}} @@ -52,6 +58,10 @@ defmodule Jellyfish.Component.Recording do end end + defp parse_subscribe_mode(opts) do + Map.update!(opts, :subscribe_mode, &String.to_atom/1) + end + defp get_credentials(%{credentials: credentials}, s3_config) do case {credentials, s3_config[:credentials]} do {nil, nil} -> {:error, :missing_s3_credentials} diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index 0da55a99..44a8f3b2 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -133,10 +133,10 @@ defmodule Jellyfish.Room do GenServer.call(registry_id(room_id), {:remove_component, component_id}) end - @spec hls_subscribe(id(), [Peer.id() | Component.id()]) :: + @spec subscribe(id(), Component.id(), [Peer.id() | Component.id()]) :: :ok | {:error, term()} - def hls_subscribe(room_id, origins) do - GenServer.call(registry_id(room_id), {:hls_subscribe, origins}) + def subscribe(room_id, component_id, origins) do + GenServer.call(registry_id(room_id), {:subscribe, component_id, origins}) end @spec dial(id(), Component.id(), String.t()) :: @@ -346,13 +346,19 @@ defmodule Jellyfish.Room do end @impl true - def handle_call({:hls_subscribe, origins}, _from, state) do - hls_component = get_hls_component(state) + def handle_call({:subscribe, component_id, origins}, _from, state) do + component = get_component_by_id(state, component_id) reply = - case validate_hls_subscription(hls_component) do - :ok -> - Endpoint.HLS.subscribe(state.engine_pid, hls_component.id, origins) + case validate_subscription_mode(component) do + :ok when component.type == HLS -> + Endpoint.HLS.subscribe(state.engine_pid, component.id, origins) + + :ok when component.type == Recording -> + Endpoint.Recording.subscribe(state.engine_pid, component.id, origins) + + :ok when component.type not in [HLS, Recording] -> + {:error, :invalid_component_type} {:error, _reason} = error -> error @@ -774,10 +780,10 @@ defmodule Jellyfish.Room do state end - defp get_hls_component(%{components: components}), + defp get_component_by_id(%{components: components}, component_id), do: - Enum.find_value(components, fn {_id, component} -> - if component.type == HLS, do: component + Enum.find_value(components, fn {id, component} -> + if id == component_id, do: component end) defp check_component_allowed(type, %{ @@ -810,12 +816,13 @@ defmodule Jellyfish.Room do defp component_already_present?(type, components), do: components |> Map.values() |> Enum.any?(&(&1.type == type)) - defp validate_hls_subscription(nil), do: {:error, :hls_component_not_exists} + defp validate_subscription_mode(nil), do: {:error, :component_not_exists} - defp validate_hls_subscription(%{properties: %{subscribe_mode: :auto}}), + defp validate_subscription_mode(%{properties: %{subscribe_mode: :auto}}), do: {:error, :invalid_subscribe_mode} - defp validate_hls_subscription(%{properties: %{subscribe_mode: :manual}}), do: :ok + defp validate_subscription_mode(%{properties: %{subscribe_mode: :manual}}), do: :ok + defp validate_subscription_mode(_not_properties), do: {:error, :invalid_component_type} defp get_endpoint_group(state, endpoint_id) when is_map_key(state.components, endpoint_id), do: :components diff --git a/lib/jellyfish_web/api_spec/component/recording.ex b/lib/jellyfish_web/api_spec/component/recording.ex index e4e44f9f..f1c6f7f3 100644 --- a/lib/jellyfish_web/api_spec/component/recording.ex +++ b/lib/jellyfish_web/api_spec/component/recording.ex @@ -17,12 +17,14 @@ defmodule JellyfishWeb.ApiSpec.Component.Recording do description: "Properties specific to the Recording component", type: :object, properties: %{ - pathPrefix: %Schema{ + subscribeMode: %Schema{ type: :string, - description: "Path prefix under which all recording are stored" + description: + "Whether the Recording component should subscribe to tracks automatically or manually", + enum: ["auto", "manual"] } }, - required: [:pathPrefix] + required: [:subscribeMode] }) end @@ -47,6 +49,13 @@ defmodule JellyfishWeb.ApiSpec.Component.Recording do description: "Credentials to AWS S3 bucket.", oneOf: [S3], nullable: true + }, + subscribeMode: %Schema{ + type: :string, + description: + "Whether the Recording component should subscribe to tracks automatically or manually.", + enum: ["auto", "manual"], + default: "auto" } }, required: [] diff --git a/lib/jellyfish_web/controllers/subscription_controller.ex b/lib/jellyfish_web/controllers/subscription_controller.ex index 59405dfb..16261a8a 100644 --- a/lib/jellyfish_web/controllers/subscription_controller.ex +++ b/lib/jellyfish_web/controllers/subscription_controller.ex @@ -9,14 +9,17 @@ defmodule JellyfishWeb.SubscriptionController do action_fallback JellyfishWeb.FallbackController - tags [:hls] + tags [:room] security(%{"authorization" => []}) operation :create, - operation_id: "subscribe_hls_to", - summary: "Subscribe the HLS component to the tracks of peers or components", - parameters: [room_id: [in: :path, description: "Room ID", type: :string]], + operation_id: "subscribe_to", + summary: "Subscribe component to the tracks of peers or components", + parameters: [ + room_id: [in: :path, description: "Room ID", type: :string], + component_id: [in: :path, description: "Component ID", type: :string] + ], request_body: {"Subscribe configuration", "application/json", ApiSpec.Subscription.Origins}, responses: [ created: %Response{description: "Tracks succesfully added."}, @@ -25,10 +28,10 @@ defmodule JellyfishWeb.SubscriptionController do unauthorized: ApiSpec.error("Unauthorized") ] - def create(conn, %{"room_id" => room_id} = params) do + def create(conn, %{"room_id" => room_id, "component_id" => component_id} = params) do with {:ok, origins} <- Map.fetch(params, "origins"), {:ok, _room_pid} <- RoomService.find_room(room_id), - :ok <- Room.hls_subscribe(room_id, origins) do + :ok <- Room.subscribe(room_id, component_id, origins) do send_resp(conn, :created, "Successfully subscribed for tracks") else :error -> @@ -37,11 +40,16 @@ defmodule JellyfishWeb.SubscriptionController do {:error, :room_not_found} -> {:error, :not_found, "Room #{room_id} does not exist"} - {:error, :hls_component_not_exists} -> - {:error, :bad_request, "HLS component does not exist"} + {:error, :component_not_exists} -> + {:error, :bad_request, "Component #{component_id} does not exist"} + + {:error, :invalid_component_type} -> + {:error, :bad_request, + "Subscribe mode is supported only for HLS and Recording components"} {:error, :invalid_subscribe_mode} -> - {:error, :bad_request, "HLS component option `subscribe_mode` is set to :auto"} + {:error, :bad_request, + "Component #{component_id} option `subscribe_mode` is set to :auto"} end end end diff --git a/lib/jellyfish_web/router.ex b/lib/jellyfish_web/router.ex index c8c6d584..69df8212 100644 --- a/lib/jellyfish_web/router.ex +++ b/lib/jellyfish_web/router.ex @@ -21,10 +21,7 @@ defmodule JellyfishWeb.Router do resources("/:room_id/peer", PeerController, only: [:create, :delete]) resources("/:room_id/component", ComponentController, only: [:create, :delete]) - end - - scope "/hls" do - post "/:room_id/subscribe", SubscriptionController, :create + post "/:room_id/component/:component_id/subscribe", SubscriptionController, :create end scope "/sip" do diff --git a/mix.exs b/mix.exs index b8de3fc2..e0713c58 100644 --- a/mix.exs +++ b/mix.exs @@ -73,12 +73,14 @@ defmodule Jellyfish.MixProject do github: "jellyfish-dev/membrane_rtc_engine", sparse: "engine", override: true}, {:membrane_rtc_engine_webrtc, github: "jellyfish-dev/membrane_rtc_engine", sparse: "webrtc", override: true}, - {:membrane_rtc_engine_hls, github: "jellyfish-dev/membrane_rtc_engine", sparse: "hls"}, + {:membrane_rtc_engine_hls, + github: "jellyfish-dev/membrane_rtc_engine", sparse: "hls", override: true}, {:membrane_rtc_engine_recording, - github: "jellyfish-dev/membrane_rtc_engine", sparse: "recording"}, + github: "jellyfish-dev/membrane_rtc_engine", sparse: "recording", override: true}, {:membrane_rtc_engine_rtsp, github: "jellyfish-dev/membrane_rtc_engine", sparse: "rtsp"}, {:membrane_rtc_engine_file, github: "jellyfish-dev/membrane_rtc_engine", sparse: "file"}, - {:membrane_rtc_engine_sip, github: "jellyfish-dev/membrane_rtc_engine", sparse: "sip"}, + {:membrane_rtc_engine_sip, + github: "jellyfish-dev/membrane_rtc_engine", sparse: "sip", override: true}, {:membrane_telemetry_metrics, "~> 0.1.0"}, # HLS endpoints deps diff --git a/mix.lock b/mix.lock index c88e0805..62f8d91d 100644 --- a/mix.lock +++ b/mix.lock @@ -128,7 +128,7 @@ "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, "telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"}, "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"}, - "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "unifex": {:hex, :unifex, "1.2.0", "90d1ec5e6d788350e07e474f7bd8b0ee866d6606beb9ca4e20dbb26328712a84", [: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", "7a8395aabc3ba6cff04bbe5b995de7f899a38eb57f189e49927d6b8b6ccb6883"}, diff --git a/openapi.yaml b/openapi.yaml index fb564dee..92296f89 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -164,6 +164,13 @@ components: description: Path prefix under which all recording are stored nullable: true type: string + subscribeMode: + default: auto + description: Whether the Recording component should subscribe to tracks automatically or manually. + enum: + - auto + - manual + type: string title: ComponentOptionsRecording type: object x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Recording.Options @@ -336,11 +343,14 @@ components: ComponentPropertiesRecording: description: Properties specific to the Recording component properties: - pathPrefix: - description: Path prefix under which all recording are stored + subscribeMode: + description: Whether the Recording component should subscribe to tracks automatically or manually + enum: + - auto + - manual type: string required: - - pathPrefix + - subscribeMode title: ComponentPropertiesRecording type: object x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Recording.Properties @@ -837,50 +847,6 @@ paths: summary: Describes the health of Jellyfish tags: - health - /hls/{room_id}/subscribe: - post: - callbacks: {} - operationId: subscribe_hls_to - parameters: - - description: Room ID - in: path - name: room_id - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/SubscriptionConfig' - description: Subscribe configuration - required: false - responses: - '201': - description: Tracks succesfully added. - '400': - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Invalid request structure - '401': - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Unauthorized - '404': - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Room doesn't exist - security: - - authorization: [] - summary: Subscribe the HLS component to the tracks of peers or components - tags: - - hls /hls/{room_id}/{filename}: get: callbacks: {} @@ -1227,6 +1193,56 @@ paths: summary: Creates the component and adds it to the room tags: - room + /room/{room_id}/component/{component_id}/subscribe: + post: + callbacks: {} + operationId: subscribe_to + parameters: + - description: Room ID + in: path + name: room_id + required: true + schema: + type: string + - description: Component ID + in: path + name: component_id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SubscriptionConfig' + description: Subscribe configuration + required: false + responses: + '201': + description: Tracks succesfully added. + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Invalid request structure + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Unauthorized + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Room doesn't exist + security: + - authorization: [] + summary: Subscribe component to the tracks of peers or components + tags: + - room /room/{room_id}/component/{id}: delete: callbacks: {} diff --git a/test/jellyfish_web/controllers/component/hls_component_test.exs b/test/jellyfish_web/controllers/component/hls_component_test.exs index 3998fe74..9f3ea738 100644 --- a/test/jellyfish_web/controllers/component/hls_component_test.exs +++ b/test/jellyfish_web/controllers/component/hls_component_test.exs @@ -71,7 +71,6 @@ defmodule JellyfishWeb.Component.HlsComponentTest do end setup :set_mox_from_context - setup :verify_on_exit! test "renders component with s3 credentials", %{conn: conn, room_id: room_id} do bucket = "bucket" @@ -100,17 +99,17 @@ defmodule JellyfishWeb.Component.HlsComponentTest do parent = self() ref = make_ref() - expect(ExAws.Request.HttpMock, :request, 4, fn _method, - url, - req_body, - _headers, - _http_opts -> - assert req_body == @body - assert String.contains?(url, bucket) - assert String.ends_with?(url, @files) - - send(parent, {ref, :request}) - {:ok, %{status_code: 200, headers: %{}}} + expect(ExAws.Request.HttpMock, :request, 10, fn + :get, _url, _req_body, _headers, _opts -> + {:ok, %{status_code: 200, headers: %{}}} + + _method, url, req_body, _headers, _http_opts -> + assert req_body == @body + assert String.contains?(url, bucket) + assert String.ends_with?(url, @files) + + send(parent, {ref, :request}) + {:ok, %{status_code: 200, headers: %{}}} end) assert_hls_path(room_id, persistent: false) diff --git a/test/jellyfish_web/controllers/component/recording_component_test.exs b/test/jellyfish_web/controllers/component/recording_component_test.exs index 44d35cbe..ac1642ee 100644 --- a/test/jellyfish_web/controllers/component/recording_component_test.exs +++ b/test/jellyfish_web/controllers/component/recording_component_test.exs @@ -34,10 +34,13 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do "data" => %{ "id" => id, "type" => "recording", - "properties" => %{"pathPrefix" => path_prefix} + "properties" => properties } } = model_response(conn, :created, "ComponentDetailsResponse") + assert properties == %{"subscribeMode" => "auto"} + + path_prefix = get_recording_path_prefix(room_id, id) assert String.starts_with?(path_prefix, prefix) assert_component_created(conn, room_id, id, "recording") @@ -70,10 +73,13 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do "data" => %{ "id" => id, "type" => "recording", - "properties" => %{"pathPrefix" => path_prefix} + "properties" => properties } } = model_response(conn, :created, "ComponentDetailsResponse") + assert properties == %{"subscribeMode" => "auto"} + + path_prefix = get_recording_path_prefix(room_id, id) assert String.starts_with?(path_prefix, prefix) assert_component_created(conn, room_id, id, "recording") @@ -101,10 +107,13 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do "data" => %{ "id" => id, "type" => "recording", - "properties" => %{"pathPrefix" => path_prefix1} + "properties" => properties } } = model_response(conn, :created, "ComponentDetailsResponse") + assert properties == %{"subscribeMode" => "auto"} + + path_prefix1 = get_recording_path_prefix(room_id, id) assert String.starts_with?(path_prefix1, prefix) assert_component_created(conn, room_id, id, "recording") @@ -123,12 +132,16 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do "data" => %{ "id" => id, "type" => "recording", - "properties" => %{"pathPrefix" => path_prefix2} + "properties" => properties } } = model_response(conn, :created, "ComponentDetailsResponse") + assert properties == %{"subscribeMode" => "auto"} + assert_component_created(conn, room_id, id, "recording") + path_prefix2 = get_recording_path_prefix(room_id, id) + assert String.starts_with?(path_prefix2, prefix) assert path_prefix1 != path_prefix2 @@ -153,10 +166,14 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do "data" => %{ "id" => id, "type" => "recording", - "properties" => %{"pathPrefix" => path_prefix} + "properties" => properties } } = model_response(conn, :created, "ComponentDetailsResponse") + assert properties == %{"subscribeMode" => "auto"} + + path_prefix = get_recording_path_prefix(room_id, id) + assert String.starts_with?(path_prefix, @path_prefix) assert_component_created(conn, room_id, id, "recording") @@ -249,4 +266,19 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do defp clean_s3_envs() do Application.put_env(:jellyfish, :s3_config, path_prefix: nil, credentials: nil) end + + defp get_recording_path_prefix(room_id, component_id) do + assert {:ok, room_state} = RoomService.get_room(room_id) + + {_store, %{path_prefix: path_prefix}} = + room_state + |> get_in([:components, component_id, :engine_endpoint]) + |> Map.fetch!(:stores) + |> Enum.find(fn + {_store, %{path_prefix: path_prefix}} -> path_prefix + _other -> false + end) + + path_prefix + end end diff --git a/test/jellyfish_web/controllers/subscription_controller_test.exs b/test/jellyfish_web/controllers/subscription_controller_test.exs index b32916a6..f43ba25e 100644 --- a/test/jellyfish_web/controllers/subscription_controller_test.exs +++ b/test/jellyfish_web/controllers/subscription_controller_test.exs @@ -1,6 +1,19 @@ defmodule JellyfishWeb.SubscriptionControllerTest do use JellyfishWeb.ConnCase + @s3_credentials %{ + accessKeyId: "access_key_id", + secretAccessKey: "secret_access_key", + region: "region", + bucket: "bucket" + } + + @sip_credentials %{ + address: "my-sip-registrar.net", + username: "user-name", + password: "pass-word" + } + setup %{conn: conn} do server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) @@ -17,17 +30,58 @@ defmodule JellyfishWeb.SubscriptionControllerTest do {:ok, %{conn: conn, room_id: id}} end - describe "subscription" do + describe "subscription overall" do test "returns error when room doesn't exist", %{conn: conn} do - conn = post(conn, ~p"/hls/invalid_room_id/subscribe/", origins: ["peer-1", "rtsp-2"]) + conn = + post(conn, ~p"/room/invalid_room_id/component/invalid_component_id/subscribe/", + origins: ["peer-1", "rtsp-2"] + ) + assert json_response(conn, :not_found)["errors"] == "Room invalid_room_id does not exist" end test "returns error when hls component doesn't exist", %{conn: conn, room_id: room_id} do - conn = post(conn, ~p"/hls/#{room_id}/subscribe/", origins: ["peer-1", "file-2"]) - assert json_response(conn, :bad_request)["errors"] == "HLS component does not exist" + conn = + post(conn, ~p"/room/#{room_id}/component/invalid_component_id/subscribe/", + origins: ["peer-1", "file-2"] + ) + + assert json_response(conn, :bad_request)["errors"] == + "Component invalid_component_id does not exist" end + test "returns error when subscribe on component that is not HLS or Recording", %{ + conn: conn, + room_id: room_id + } do + Application.put_env(:jellyfish, :sip_config, sip_used?: true, sip_external_ip: "127.0.0.1") + + on_exit(fn -> + Application.put_env(:jellyfish, :sip_config, sip_used?: false, sip_external_ip: nil) + end) + + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "sip", + options: %{registrarCredentials: @sip_credentials} + ) + + assert %{ + "data" => %{ + "id" => id, + "type" => "sip" + } + } = json_response(conn, :created) + + conn = + post(conn, ~p"/room/#{room_id}/component/#{id}/subscribe/", origins: ["peer-1", "file-2"]) + + assert json_response(conn, :bad_request)["errors"] == + "Subscribe mode is supported only for HLS and Recording components" + end + end + + describe "hls endpoint tests" do test "returns error when subscribe mode is :auto", %{conn: conn, room_id: room_id} do conn = post(conn, ~p"/room/#{room_id}/component", @@ -37,15 +91,17 @@ defmodule JellyfishWeb.SubscriptionControllerTest do assert %{ "data" => %{ + "id" => id, "type" => "hls", "properties" => %{"subscribeMode" => "auto"} } } = json_response(conn, :created) - conn = post(conn, ~p"/hls/#{room_id}/subscribe/", origins: ["peer-1", "rtsp-2"]) + conn = + post(conn, ~p"/room/#{room_id}/component/#{id}/subscribe", origins: ["peer-1", "rtsp-2"]) assert json_response(conn, :bad_request)["errors"] == - "HLS component option `subscribe_mode` is set to :auto" + "Component #{id} option `subscribe_mode` is set to :auto" end test "return success when subscribe mode is :manual", %{conn: conn, room_id: room_id} do @@ -57,12 +113,59 @@ defmodule JellyfishWeb.SubscriptionControllerTest do assert %{ "data" => %{ + "id" => id, "type" => "hls", "properties" => %{"subscribeMode" => "manual"} } } = json_response(conn, :created) - conn = post(conn, ~p"/hls/#{room_id}/subscribe/", origins: ["peer-1", "file-2"]) + conn = + post(conn, ~p"/room/#{room_id}/component/#{id}/subscribe", origins: ["peer-1", "file-2"]) + + assert response(conn, :created) == "Successfully subscribed for tracks" + end + end + + describe "recording endpoint tests" do + test "returns error when subscribe mode is :auto", %{conn: conn, room_id: room_id} do + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "recording", + options: %{credentials: Enum.into(@s3_credentials, %{}), subscribeMode: "auto"} + ) + + assert %{ + "data" => %{ + "id" => id, + "type" => "recording", + "properties" => %{"subscribeMode" => "auto"} + } + } = json_response(conn, :created) + + conn = + post(conn, ~p"/room/#{room_id}/component/#{id}/subscribe", origins: ["peer-1", "rtsp-2"]) + + assert json_response(conn, :bad_request)["errors"] == + "Component #{id} option `subscribe_mode` is set to :auto" + end + + test "return success when subscribe mode is :manual", %{conn: conn, room_id: room_id} do + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "recording", + options: %{credentials: Enum.into(@s3_credentials, %{}), subscribeMode: "manual"} + ) + + assert %{ + "data" => %{ + "id" => id, + "type" => "recording", + "properties" => %{"subscribeMode" => "manual"} + } + } = json_response(conn, :created) + + conn = + post(conn, ~p"/room/#{room_id}/component/#{id}/subscribe", origins: ["peer-1", "file-2"]) assert response(conn, :created) == "Successfully subscribed for tracks" end From d9639783a5e0a5a25554df2ab2e36a9e4a9bd66d Mon Sep 17 00:00:00 2001 From: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:05:03 +0200 Subject: [PATCH 18/51] Bump membrane_core to 1.1.0-rc0 (#177) --- mix.exs | 1 + mix.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mix.exs b/mix.exs index e0713c58..b97f25bc 100644 --- a/mix.exs +++ b/mix.exs @@ -69,6 +69,7 @@ defmodule Jellyfish.MixProject do {:protobuf, "~> 0.12.0"}, # Membrane deps + {:membrane_core, "1.1.0-rc0", override: true}, {:membrane_rtc_engine, github: "jellyfish-dev/membrane_rtc_engine", sparse: "engine", override: true}, {:membrane_rtc_engine_webrtc, diff --git a/mix.lock b/mix.lock index 62f8d91d..e34a7f8d 100644 --- a/mix.lock +++ b/mix.lock @@ -45,7 +45,7 @@ "membrane_audio_mix_plugin": {:hex, :membrane_audio_mix_plugin, "0.16.0", "34997707ee186683c6d7bd87572944e5e37c0249235cc44915d181d653c5c40e", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, 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_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", "a4a8c723f0da8d9cf9ac11bf657a732770ea0b8db4eff2efc16caa3a1819f435"}, "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.0.1", "08aa546c0d131c66f8b906b3dfb2b8f2749b56859f6fc52bd3ac846b944b3baa", [: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", "a35ed68561bdf0a2dbb2f994333be78cf4e1c4d734e4cd927d77d92049bb1273"}, + "membrane_core": {:hex, :membrane_core, "1.1.0-rc0", "ff24f9aa86c02014a778ce971d8b5ac25099351a71e444d2a7daa2ce8b9ddc68", [: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", "e552e6c0fcb8632a7f3dddca1f8250b68262ad536426adc641e029cc77a012ae"}, "membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.19.2", "c5e23f124f0b06283c1119525bf5d1fe595808fd6aeddf3b751c337499204910", [: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", "3c66264ee1d94c4d076f4e431014553a886730a909ad96a07714167656e0b8f2"}, "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.16.0", "7917f6682c22b9bcfc2ca20ed960eee0f7d03ad31fd5f59ed850f1fe3ddd545a", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "b0727998f75a9b4dab8a2baefdfc13c3eac00a04e061ab1b0e61dc5566927acc"}, "membrane_framerate_converter_plugin": {:hex, :membrane_framerate_converter_plugin, "0.8.0", "a6f89dc89bc41e23c70f6af96e447c0577d54c0f0457a8383b652650088cb864", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}], "hexpm", "15faf8267f4d0ea3f511085bec14b90e93b6140ea4047f419e5cfbdef1543c3c"}, @@ -68,13 +68,13 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "engine"]}, - "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "file"]}, - "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "hls"]}, - "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "recording"]}, - "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "rtsp"]}, - "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "sip"]}, - "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "340ccc85a07eb729917c80a1b848d59c40d22ff7", [sparse: "webrtc"]}, + "membrane_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "engine"]}, + "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "file"]}, + "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "hls"]}, + "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "recording"]}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "rtsp"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "sip"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "webrtc"]}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.0", "112bfedc14fb83bdb549ef1a03da23908feedeb165fd3e4512a549f1af532ae7", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "76fd159e7406cadbef15124cba30eca3fffcf71a7420964f26e23d4cffd9b29d"}, From b6b542099aca3dcdca4f70d9a9213ae304620123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:27:24 +0200 Subject: [PATCH 19/51] Update deps (#179) --- mix.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mix.lock b/mix.lock index e34a7f8d..bf66c6eb 100644 --- a/mix.lock +++ b/mix.lock @@ -68,13 +68,13 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "engine"]}, - "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "file"]}, - "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "hls"]}, - "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "recording"]}, - "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "rtsp"]}, - "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "sip"]}, - "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "c699dbdda0dec2944a8aa157c30737aeb8bf167d", [sparse: "webrtc"]}, + "membrane_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "engine"]}, + "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "file"]}, + "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "hls"]}, + "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "recording"]}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "rtsp"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "sip"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "webrtc"]}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.0", "112bfedc14fb83bdb549ef1a03da23908feedeb165fd3e4512a549f1af532ae7", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "76fd159e7406cadbef15124cba30eca3fffcf71a7420964f26e23d4cffd9b29d"}, From cd8bb9bdf7a20488bb9596fa26ed0a94ed288254 Mon Sep 17 00:00:00 2001 From: Karol Konkol <56369082+Karolk99@users.noreply.github.com> Date: Thu, 11 Apr 2024 18:10:55 +0200 Subject: [PATCH 20/51] Add ResourceManager that will remove raw recordings that exceeds timeout (#175) * Add ResourceManager that will remove raw recordings that exceeds timeout * Requested changes * Requested changes * Adjust ResourceManager to changes due to rebase * Fix tests * Fix credo warning --- lib/jellyfish/application.ex | 5 ++ lib/jellyfish/component/recording.ex | 6 +- lib/jellyfish/resource_manager.ex | 102 +++++++++++++++++++++++ test/jellyfish/resource_manager_test.exs | 99 ++++++++++++++++++++++ 4 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 lib/jellyfish/resource_manager.ex create mode 100644 test/jellyfish/resource_manager_test.exs diff --git a/lib/jellyfish/application.ex b/lib/jellyfish/application.ex index c55fc00d..8e24612b 100644 --- a/lib/jellyfish/application.ex +++ b/lib/jellyfish/application.ex @@ -7,6 +7,9 @@ defmodule Jellyfish.Application do require Logger + # seconds + @resource_manager_opts %{interval: 600, recording_timeout: 3_600} + @impl true def start(_type, _args) do scrape_interval = Application.fetch_env!(:jellyfish, :webrtc_metrics_scrape_interval) @@ -30,6 +33,8 @@ defmodule Jellyfish.Application do JellyfishWeb.Endpoint, # Start the RoomService Jellyfish.RoomService, + # Start the ResourceManager, responsible for cleaning old recordings + {Jellyfish.ResourceManager, @resource_manager_opts}, Jellyfish.WebhookNotifier, {Registry, keys: :unique, name: Jellyfish.RoomRegistry}, {Registry, keys: :unique, name: Jellyfish.RequestHandlerRegistry}, diff --git a/lib/jellyfish/component/recording.ex b/lib/jellyfish/component/recording.ex index 712a729f..d6d1a6bc 100644 --- a/lib/jellyfish/component/recording.ex +++ b/lib/jellyfish/component/recording.ex @@ -58,6 +58,9 @@ defmodule Jellyfish.Component.Recording do end end + def get_base_path(), + do: :jellyfish |> Application.fetch_env!(:media_files_path) |> Path.join("raw_recordings") + defp parse_subscribe_mode(opts) do Map.update!(opts, :subscribe_mode, &String.to_atom/1) end @@ -79,7 +82,4 @@ defmodule Jellyfish.Component.Recording do _else -> {:error, :overridding_path_prefix} end end - - defp get_base_path(), - do: :jellyfish |> Application.fetch_env!(:media_files_path) |> Path.join("raw_recordings") end diff --git a/lib/jellyfish/resource_manager.ex b/lib/jellyfish/resource_manager.ex new file mode 100644 index 00000000..5e5e0f1f --- /dev/null +++ b/lib/jellyfish/resource_manager.ex @@ -0,0 +1,102 @@ +defmodule Jellyfish.ResourceManager do + @moduledoc """ + Module responsible for deleting outdated resources. + Right now it only removes outdated resources created by recording component. + """ + + use GenServer, restart: :permanent + + require Logger + + alias Jellyfish.Component.Recording + alias Jellyfish.RoomService + + @type seconds :: pos_integer() + @type opts :: %{interval: seconds(), recording_timeout: seconds()} + + @spec start(opts()) :: {:ok, pid()} | {:error, term()} + def start(opts), do: GenServer.start(__MODULE__, opts) + + @spec start_link(opts()) :: GenServer.on_start() + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end + + @impl true + def init(opts) do + Logger.debug("Initialize resource manager") + + schedule_free_resources(opts.interval) + + {:ok, opts} + end + + @impl true + def handle_info(:free_resources, state) do + base_path = Recording.get_base_path() + current_time = System.system_time(:second) + + rooms_list = File.ls!(base_path) + + recordings_list = + rooms_list + |> Enum.map(fn room -> + room_path = Path.join(base_path, room) + + room_path + |> File.ls!() + |> Enum.map(fn recording -> {room, Path.join(room_path, recording)} end) + end) + |> Enum.concat() + + Enum.each( + recordings_list, + &remove_recording_if_obsolete(current_time, state.recording_timeout, &1) + ) + + Enum.each(rooms_list, &remove_room_if_obsolete(&1, base_path)) + + schedule_free_resources(state.interval) + + {:noreply, state} + end + + defp schedule_free_resources(interval), + do: Process.send_after(self(), :free_resources, :timer.seconds(interval)) + + defp remove_recording_if_obsolete(current_time, recording_timeout, {room, recording_path}) do + with {:error, :room_not_found} <- RoomService.find_room(room) do + case File.ls!(recording_path) do + [] -> + File.rm_rf!(recording_path) + + files -> + # select the most recently modified file + %{mtime: mtime} = + files + |> Enum.map(fn file -> + recording_path |> Path.join(file) |> File.lstat!(time: :posix) + end) + |> Enum.sort_by(fn stats -> stats.mtime end, :desc) + |> List.first() + + should_remove_recording?(current_time, mtime, recording_timeout) && + File.rm_rf!(recording_path) + end + end + end + + defp remove_room_if_obsolete(room_id, base_path) do + state_of_room = RoomService.find_room(room_id) + room_path = Path.join(base_path, room_id) + content = File.ls!(room_path) + + if should_remove_room?(content, state_of_room), do: File.rmdir!(room_path) + end + + defp should_remove_room?([], {:error, :room_not_found}), do: true + defp should_remove_room?(_content, _state_of_room), do: false + + defp should_remove_recording?(current_time, mtime, recording_timeout), + do: current_time - mtime > recording_timeout +end diff --git a/test/jellyfish/resource_manager_test.exs b/test/jellyfish/resource_manager_test.exs new file mode 100644 index 00000000..d5f760ab --- /dev/null +++ b/test/jellyfish/resource_manager_test.exs @@ -0,0 +1,99 @@ +defmodule Jellyfish.ResourceManagerTest do + use JellyfishWeb.ComponentCase, async: true + + alias Jellyfish.Component.Recording + alias Jellyfish.ResourceManager + + @hour 3_600 + + setup do + base_path = Recording.get_base_path() + {:ok, pid} = ResourceManager.start(%{interval: 1, recording_timeout: @hour}) + + on_exit(fn -> Process.exit(pid, :force) end) + + %{base_path: base_path} + end + + test "room directory removal", %{room_id: room_id, base_path: base_path} do + case_1 = Path.join([base_path, room_id]) + case_2 = Path.join([base_path, "not_existing_room_1"]) + case_3 = Path.join([base_path, "not_existing_room_2", "part_1"]) + + File.mkdir_p!(case_1) + File.mkdir_p!(case_2) + File.mkdir_p!(case_3) + + case_3 |> Path.join("report.json") |> File.touch() + + # Wait double the interval + Process.sleep(2_000) + + # doesn't remove recordings if room exists + assert {:ok, []} = File.ls(case_1) + + # removes empty recordings if room doesn't exists + assert {:error, :enoent} = File.ls(case_2) + + # doesn't remove recordings including parts + assert {:ok, _} = File.ls(case_3) + + clean_recordings([ + room_id, + "not_existing_room_1", + "not_existing_room_2" + ]) + end + + test "recording part directory removal", %{room_id: room_id, base_path: base_path} do + case_1 = Path.join([base_path, room_id, "part_1"]) + case_2 = Path.join([base_path, "not_existing_room_4", "part_1"]) + + File.mkdir_p!(case_1) + File.mkdir_p!(case_2) + + # Wait double the interval + Process.sleep(2_000) + + # doesn't remove empty part if room exists + assert {:ok, []} = File.ls(case_1) + + # removes empty part if room doesn't exists + assert {:error, :enoent} = File.ls(case_2) + + clean_recordings([room_id, "not_existing_room_4"]) + end + + test "recording files removal", %{room_id: room_id, base_path: base_path} do + case_1 = Path.join([base_path, room_id, "part_1"]) + case_2 = Path.join([base_path, "not_existing_room_5", "part_1"]) + case_3 = Path.join([base_path, "not_existing_room_5", "part_2"]) + + File.mkdir_p!(case_1) + File.mkdir_p!(case_2) + File.mkdir_p!(case_3) + + # modify creation time + case_1 |> Path.join("report.json") |> File.touch!(System.os_time(:second) - 2 * @hour) + case_2 |> Path.join("report.json") |> File.touch!(System.os_time(:second) - 2 * @hour) + case_3 |> Path.join("report.json") |> File.touch!(System.os_time(:second)) + + # Wait double the interval + Process.sleep(2_000) + + # doesn't remove recording if room exists + assert {:ok, ["report.json"]} = File.ls(case_1) + + # removes recording if exceeds timeout and room doesn't exist + assert {:error, :enoent} = File.ls(case_2) + + # doesn't remove recording if doesn't exceed timeout + assert {:ok, ["report.json"]} = File.ls(case_3) + + clean_recordings([room_id, "not_existing_room_5"]) + end + + defp clean_recordings(dirs) do + Enum.each(dirs, fn dir -> Recording.get_base_path() |> Path.join(dir) |> File.rm_rf!() end) + end +end From 7045ea4a2b2f324dfef27986b4178969948e083f Mon Sep 17 00:00:00 2001 From: Karol Konkol <56369082+Karolk99@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:46:15 +0200 Subject: [PATCH 21/51] Fix ex aws requests (#180) --- lib/jellyfish/component/hls/httpoison.ex | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/jellyfish/component/hls/httpoison.ex b/lib/jellyfish/component/hls/httpoison.ex index 0db6ec9a..d7364375 100644 --- a/lib/jellyfish/component/hls/httpoison.ex +++ b/lib/jellyfish/component/hls/httpoison.ex @@ -6,14 +6,26 @@ defmodule Jellyfish.Component.HLS.HTTPoison do @impl true def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do case HTTPoison.request(method, url, body, headers, http_opts) do - {:ok, %HTTPoison.Response{status_code: status, headers: headers, body: body}} -> - {:ok, %{status_code: status, headers: headers, body: body}} - - {:ok, %HTTPoison.Response{status_code: status, headers: headers}} -> - {:ok, %{status_code: status, headers: headers}} + {:ok, %HTTPoison.Response{} = response} -> + {:ok, adapt_response(response)} {:error, %HTTPoison.Error{reason: reason}} -> - {:error, reason} + {:error, %{reason: reason}} end end + + defp adapt_response(response) do + # adapt the response to fit the shape expected by ExAWS + flat_headers = + Enum.flat_map(response.headers, fn + {name, vals} when is_list(vals) -> Enum.map(vals, &{name, &1}) + {name, val} -> [{name, val}] + end) + + %{ + body: response.body, + status_code: response.status_code, + headers: flat_headers + } + end end From 67bf454fe58fb46a99b3a4aa4b2c497a015352f1 Mon Sep 17 00:00:00 2001 From: Karol Konkol <56369082+Karolk99@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:52:51 +0200 Subject: [PATCH 22/51] Extend recv_timeout for s3 requests (#182) * Extend recv_timeout for s3 requests * Update deps * Clean unused dependencies --- lib/jellyfish/component/hls/httpoison.ex | 2 +- mix.lock | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/jellyfish/component/hls/httpoison.ex b/lib/jellyfish/component/hls/httpoison.ex index d7364375..4a506e0f 100644 --- a/lib/jellyfish/component/hls/httpoison.ex +++ b/lib/jellyfish/component/hls/httpoison.ex @@ -5,7 +5,7 @@ defmodule Jellyfish.Component.HLS.HTTPoison do @impl true def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do - case HTTPoison.request(method, url, body, headers, http_opts) do + case HTTPoison.request(method, url, body, headers, http_opts ++ [recv_timeout: 10_000]) do {:ok, %HTTPoison.Response{} = response} -> {:ok, adapt_response(response)} diff --git a/mix.lock b/mix.lock index bf66c6eb..afde6f0c 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "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.0", "295472109d6c8a2fb59712523f20f9502105e3306edc3b490aa1cc4df3f9c5cf", [: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", "e5841de5380b9e99eef347656a71f3d6fab7ff34dee41da3e70c256d39cfdf77"}, + "bundlex": {:hex, :bundlex, "1.4.6", "6d93c4ca3bfb2282445e1e76ea263cedae49ba8524bf4cce94eb8124421c81fc", [: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", "549115f64f55e29b32f566ae054caa5557b334aceab279e0b820055ad0bfc8b6"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, @@ -68,13 +68,13 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "engine"]}, - "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "file"]}, - "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "hls"]}, - "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "recording"]}, - "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "rtsp"]}, - "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "sip"]}, - "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "55000161868a371e70db1876099c1942afc14e14", [sparse: "webrtc"]}, + "membrane_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "engine"]}, + "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "file"]}, + "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "hls"]}, + "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "recording"]}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "rtsp"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "sip"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "webrtc"]}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.0", "112bfedc14fb83bdb549ef1a03da23908feedeb165fd3e4512a549f1af532ae7", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "76fd159e7406cadbef15124cba30eca3fffcf71a7420964f26e23d4cffd9b29d"}, @@ -88,7 +88,6 @@ "membrane_udp_plugin": {:hex, :membrane_udp_plugin, "0.13.0", "c4d10b4cb152a95779e36fac4338e11ef0b0cb545c78ca337d7676f6df5d5709", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "47a1661038ef65025fe36cfcae8ce23c022f9dc0867b8340c46dd4963f5a1bcb"}, "membrane_video_compositor_plugin": {:hex, :membrane_video_compositor_plugin, "0.7.0", "2273743fd0a47660880276e0bbb8a9f8848e09b05b425101d1cc0c5d245ff8ea", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_framerate_converter_plugin, "~> 0.8.0", [hex: :membrane_framerate_converter_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.1", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:rustler, "~> 0.26.0", [hex: :rustler, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "68ea6a5770cf053464d6fa009a20f85ca559267f5a454370506c6d40965985de"}, "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.4.0", "6c29ec67479edfbab27b11266dc92f18f3baf4421262c5c31af348c33e5b92c7", [:mix], [], "hexpm", "8bb005ede61db8fcb3535a883f32168b251c2dfd1109197c8c3b39ce28ed08e2"}, - "membrane_webrtc_plugin": {:hex, :membrane_webrtc_plugin, "0.18.2", "b8b3528b45a7a61c987435bec741c6204df3584b044ab165776bb4d7a01b8455", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_libsrtp, ">= 0.0.0", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, 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_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_ice_plugin, "~> 0.18.0", [hex: :membrane_ice_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.19.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, ">= 0.9.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.0", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.0", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "7dc127f3e0a199529374797256756e67f0beea89ba8992f9ec84e5ebc3037fee"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, @@ -103,7 +102,7 @@ "p1_utils": {:hex, :p1_utils, "1.0.23", "7f94466ada69bd982ea7bb80fbca18e7053e7d0b82c9d9e37621fa508587069b", [:rebar3], [], "hexpm", "47f21618694eeee5006af1c88731ad86b757161e7823c29b6f73921b571c8502"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "patiently": {:hex, :patiently, "0.2.0", "67eb139591e10c4b363ae0198e832552f191c58894731efd3bf124ec4722267a", [:mix], [], "hexpm", "c08cc5edc27def565647a9b55a0bea8025a5f81a4472e57692f28f2292c44c94"}, - "phoenix": {:hex, :phoenix, "1.7.11", "1d88fc6b05ab0c735b250932c4e6e33bfa1c186f76dcf623d8dd52f07d6379c7", [: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", "b1ec57f2e40316b306708fe59b92a16b9f6f4bf50ccfa41aa8c7feb79e0ec02a"}, + "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [: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", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"}, "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [: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]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{: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", [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]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"}, @@ -131,7 +130,7 @@ "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, - "unifex": {:hex, :unifex, "1.2.0", "90d1ec5e6d788350e07e474f7bd8b0ee866d6606beb9ca4e20dbb26328712a84", [: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", "7a8395aabc3ba6cff04bbe5b995de7f899a38eb57f189e49927d6b8b6ccb6883"}, + "unifex": {:hex, :unifex, "1.1.1", "e8445ff780ea07c10657428051e4cf84359f2770e27d24e9d8636430662691ff", [: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", "3e2867237a5582a40cb7c88d9ed0955071ebb1c4d525345513544adc0abd3b4b"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [: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", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"}, "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"}, From 9cfc3e028e858cf62c4993668dfca51f48e5df25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:09:56 +0200 Subject: [PATCH 23/51] RTC-512 Add Room.State module and peer disconnected timeout (#178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Room.State module * WiP * OpenApi changes * Fix test * Update type * Fix type of peer * Send RoomDeleted after peerlessPurge * Fix tests * Update lib/jellyfish_web/api_spec/room.ex Co-authored-by: Przemysław Rożnawski <48837433+roznawsk@users.noreply.github.com> * Move Event.broadcast_server_notification to State module and telemetry.execute * Update openapi spec * Add logs in peer_socket.ex * Add peer_id metadata in logger --------- Co-authored-by: Przemysław Rożnawski <48837433+roznawsk@users.noreply.github.com> --- .gitignore | 1 + config/config.exs | 2 +- lib/jellyfish/peer.ex | 12 +- lib/jellyfish/room.ex | 461 ++++-------------- lib/jellyfish/room/config.ex | 27 +- lib/jellyfish/room/state.ex | 459 +++++++++++++++++ lib/jellyfish/room_service.ex | 3 +- lib/jellyfish_web/api_spec/room.ex | 8 + .../controllers/room_controller.ex | 7 +- lib/jellyfish_web/peer_socket.ex | 9 +- openapi.yaml | 6 + test/jellyfish/config_reader_test.exs | 6 +- .../controllers/room_controller_test.exs | 118 +++++ .../integration/server_notification_test.exs | 82 ++++ 14 files changed, 818 insertions(+), 383 deletions(-) create mode 100644 lib/jellyfish/room/state.ex diff --git a/.gitignore b/.gitignore index 3931b28f..031ab898 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.lexical/* compile_commands.json .gdb_history bundlex.sh diff --git a/config/config.exs b/config/config.exs index 4610a2da..086f6f3d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -17,7 +17,7 @@ config :membrane_telemetry_metrics, enabled: true config :logger, :console, format: "$time $metadata[$level] $message\n", - metadata: [:request_id, :room_id] + metadata: [:request_id, :room_id, :peer_id] config :logger_json, :backend, metadata: [:request_id, :room_id], diff --git a/lib/jellyfish/peer.ex b/lib/jellyfish/peer.ex index 621fee58..78dcad36 100644 --- a/lib/jellyfish/peer.ex +++ b/lib/jellyfish/peer.ex @@ -13,7 +13,14 @@ defmodule Jellyfish.Peer do :type, :engine_endpoint ] - defstruct @enforce_keys ++ [status: :disconnected, socket_pid: nil, tracks: %{}, metadata: nil] + defstruct @enforce_keys ++ + [ + status: :disconnected, + socket_pid: nil, + tracks: %{}, + metadata: nil, + last_time_connected: nil + ] @type id :: String.t() @type peer :: WebRTC @@ -32,7 +39,8 @@ defmodule Jellyfish.Peer do socket_pid: pid() | nil, engine_endpoint: Membrane.ChildrenSpec.child_definition(), tracks: %{Track.id() => Track.t()}, - metadata: any() + metadata: any(), + last_time_connected: integer() | nil } @spec parse_type(String.t()) :: {:ok, peer()} | {:error, :invalid_type} diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index 44a8f3b2..fa25f80b 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -6,16 +6,15 @@ defmodule Jellyfish.Room do use Bunch.Access use GenServer + import Jellyfish.Room.State + require Logger alias Jellyfish.Component - alias Jellyfish.Component.{HLS, Recording, RTSP, SIP} - alias Jellyfish.Event + alias Jellyfish.Component.{HLS, Recording, SIP} alias Jellyfish.Peer - alias Jellyfish.Room.Config - alias Jellyfish.Track + alias Jellyfish.Room.{Config, State} - alias Membrane.ICE.TURNManager alias Membrane.RTC.Engine alias Membrane.RTC.Engine.Endpoint @@ -30,38 +29,8 @@ defmodule Jellyfish.Room do TrackRemoved } - @enforce_keys [ - :id, - :config, - :engine_pid, - :network_options - ] - defstruct @enforce_keys ++ [components: %{}, peers: %{}, last_peer_left: 0] - @type id :: String.t() - - @typedoc """ - This module contains: - * `id` - room id - * `config` - configuration of room. For example you can specify maximal number of peers - * `components` - map of components - * `peers` - map of peers - * `engine` - pid of engine - * `network_options` - network options - * `last_peer_left` - arbitrary timestamp with latest occurence of the room becoming peerless - """ - @type t :: %__MODULE__{ - id: id(), - config: Config.t(), - components: %{Component.id() => Component.t()}, - peers: %{Peer.id() => Peer.t()}, - engine_pid: pid(), - network_options: map(), - last_peer_left: integer() - } - - defguardp endpoint_exists?(state, endpoint_id) - when is_map_key(state.components, endpoint_id) or is_map_key(state.peers, endpoint_id) + @type t :: State.t() def registry_id(room_id), do: {:via, Registry, {Jellyfish.RoomRegistry, room_id}} @@ -75,7 +44,7 @@ defmodule Jellyfish.Room do end end - @spec get_state(id()) :: t() | nil + @spec get_state(id()) :: State.t() | nil def get_state(room_id) do registry_room_id = registry_id(room_id) @@ -158,7 +127,8 @@ defmodule Jellyfish.Room do @impl true def init([id, config]) do - state = new(id, config) + state = State.new(id, config) + Logger.metadata(room_id: id) Logger.info("Initialize room") @@ -171,52 +141,33 @@ defmodule Jellyfish.Room do end @impl true - def handle_call({:add_peer, peer_type, options}, _from, state) do - {reply, state} = - if Enum.count(state.peers) == state.config.max_peers do - {{:error, :reached_peers_limit}, state} - else - options = - Map.merge( - %{ - engine_pid: state.engine_pid, - network_options: state.network_options, - video_codec: state.config.video_codec, - room_id: state.id - }, - options - ) - - with {:ok, peer} <- Peer.new(peer_type, options) do - state = put_in(state, [:peers, peer.id], peer) |> maybe_schedule_peerless_purge() - - Logger.info("Added peer #{inspect(peer.id)}") - - {{:ok, peer}, state} - else - {:error, reason} -> - Logger.warning("Unable to add peer: #{inspect(reason)}") - {:error, state} - end - end + def handle_call({:add_peer, peer_type, override_options}, _from, state) do + with false <- State.reached_peers_limit?(state), + options <- State.generate_peer_options(state, override_options), + {:ok, peer} <- Peer.new(peer_type, options) do + state = State.put_peer(state, peer) - {:reply, reply, state} + Logger.info("Added peer #{inspect(peer.id)}") + + {:reply, {:ok, peer}, state} + else + true -> + {:reply, {:error, :reached_peers_limit}, state} + + {:error, reason} -> + Logger.warning("Unable to add peer: #{inspect(reason)}") + {:reply, :error, state} + end end @impl true def handle_call({:set_peer_connected, peer_id}, {socket_pid, _tag}, state) do {reply, state} = - case Map.fetch(state.peers, peer_id) do + case State.fetch_peer(state, peer_id) do {:ok, %{status: :disconnected} = peer} -> Process.monitor(socket_pid) - peer = %{peer | status: :connected, socket_pid: socket_pid} - state = put_in(state, [:peers, peer_id], peer) - - :ok = Engine.add_endpoint(state.engine_pid, peer.engine_endpoint, id: peer_id) - - Logger.info("Peer #{inspect(peer_id)} connected") - :telemetry.execute([:jellyfish, :room], %{peer_connects: 1}, %{room_id: state.id}) + state = State.connect_peer(state, peer, socket_pid) {:ok, state} @@ -233,7 +184,7 @@ defmodule Jellyfish.Room do @impl true def handle_call({:get_peer_connection_status, peer_id}, _from, state) do reply = - case Map.fetch(state.peers, peer_id) do + case State.fetch_peer(state, peer_id) do {:ok, peer} -> {:ok, peer.status} :error -> {:error, :peer_not_found} end @@ -244,10 +195,8 @@ defmodule Jellyfish.Room do @impl true def handle_call({:remove_peer, peer_id}, _from, state) do {reply, state} = - if Map.has_key?(state.peers, peer_id) do - state = - handle_remove_peer(peer_id, state, :peer_removed) - |> maybe_schedule_peerless_purge() + if peer_exists?(state, peer_id) do + state = State.remove_peer(state, peer_id, :peer_removed) {:ok, state} else @@ -259,19 +208,21 @@ defmodule Jellyfish.Room do @impl true def handle_call({:add_component, component_type, options}, _from, state) do + engine_pid = State.engine_pid(state) + options = Map.merge( - %{engine_pid: state.engine_pid, room_id: state.id}, + %{engine_pid: engine_pid, room_id: State.id(state)}, options ) with :ok <- check_component_allowed(component_type, state), {:ok, component} <- Component.new(component_type, options) do - state = put_in(state, [:components, component.id], component) + state = State.put_component(state, component) component_type.after_init(state, component, options) - :ok = Engine.add_endpoint(state.engine_pid, component.engine_endpoint, id: component.id) + :ok = Engine.add_endpoint(engine_pid, component.engine_endpoint, id: component.id) Logger.info("Added component #{inspect(component.id)}") @@ -335,8 +286,8 @@ defmodule Jellyfish.Room do @impl true def handle_call({:remove_component, component_id}, _from, state) do {reply, state} = - if Map.has_key?(state.components, component_id) do - state = handle_remove_component(component_id, state, :component_removed) + if component_exists?(state, component_id) do + state = State.remove_component(state, component_id, :component_removed) {:ok, state} else {{:error, :component_not_found}, state} @@ -347,15 +298,17 @@ defmodule Jellyfish.Room do @impl true def handle_call({:subscribe, component_id, origins}, _from, state) do - component = get_component_by_id(state, component_id) + component = State.get_component_by_id(state, component_id) + + engine_pid = State.engine_pid(state) reply = case validate_subscription_mode(component) do :ok when component.type == HLS -> - Endpoint.HLS.subscribe(state.engine_pid, component.id, origins) + Endpoint.HLS.subscribe(engine_pid, component.id, origins) :ok when component.type == Recording -> - Endpoint.Recording.subscribe(state.engine_pid, component.id, origins) + Endpoint.Recording.subscribe(engine_pid, component.id, origins) :ok when component.type not in [HLS, Recording] -> {:error, :invalid_component_type} @@ -369,15 +322,22 @@ defmodule Jellyfish.Room do @impl true def handle_call(:get_num_forwarded_tracks, _from, state) do - forwarded_tracks = Engine.get_num_forwarded_tracks(state.engine_pid) + forwarded_tracks = + state + |> State.engine_pid() + |> Engine.get_num_forwarded_tracks() + {:reply, forwarded_tracks, state} end @impl true def handle_call({:dial, component_id, phone_number}, _from, state) do - case Map.fetch(state.components, component_id) do + case State.fetch_component(state, component_id) do {:ok, component} when component.type == SIP -> - Endpoint.SIP.dial(state.engine_pid, component_id, phone_number) + state + |> State.engine_pid() + |> Endpoint.SIP.dial(component_id, phone_number) + {:reply, :ok, state} {:ok, _component} -> @@ -390,9 +350,12 @@ defmodule Jellyfish.Room do @impl true def handle_call({:end_call, component_id}, _from, state) do - case Map.fetch(state.components, component_id) do + case State.fetch_component(state, component_id) do {:ok, component} when component.type == SIP -> - Endpoint.SIP.end_call(state.engine_pid, component_id) + state + |> State.engine_pid() + |> Endpoint.SIP.end_call(component_id) + {:reply, :ok, state} :error -> @@ -405,13 +368,16 @@ defmodule Jellyfish.Room do @impl true def handle_cast({:media_event, peer_id, event}, state) do - Engine.message_endpoint(state.engine_pid, peer_id, {:media_event, event}) + state + |> State.engine_pid() + |> Engine.message_endpoint(peer_id, {:media_event, event}) + {:noreply, state} end @impl true def handle_info(%EndpointMessage{endpoint_id: to, message: {:media_event, data}}, state) do - with {:ok, peer} <- Map.fetch(state.peers, to), + with {:ok, peer} <- State.fetch_peer(state, to), socket_pid when is_pid(socket_pid) <- Map.get(peer, :socket_pid) do send(socket_pid, {:media_event, data}) else @@ -434,10 +400,10 @@ defmodule Jellyfish.Room do Logger.error("RTC Engine endpoint #{inspect(endpoint_id)} crashed: #{inspect(reason)}") state = - if Map.has_key?(state.peers, endpoint_id) do - handle_remove_peer(endpoint_id, state, {:peer_crashed, parse_crash_reason(reason)}) + if peer_exists?(state, endpoint_id) do + State.remove_peer(state, endpoint_id, {:peer_crashed, parse_crash_reason(reason)}) else - handle_remove_component(endpoint_id, state, :component_crashed) + State.remove_component(state, endpoint_id, :component_crashed) end {:noreply, state} @@ -445,29 +411,7 @@ defmodule Jellyfish.Room do @impl true def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do - state = - case Enum.find(state.peers, fn {_id, peer} -> peer.socket_pid == pid end) do - nil -> - state - - {peer_id, peer} -> - :ok = Engine.remove_endpoint(state.engine_pid, peer_id) - Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) - :telemetry.execute([:jellyfish, :room], %{peer_disconnects: 1}, %{room_id: state.id}) - - peer.tracks - |> Map.values() - |> Enum.each( - &Event.broadcast_server_notification( - {:track_removed, state.id, {:peer_id, peer_id}, &1} - ) - ) - - peer = %{peer | status: :disconnected, socket_pid: nil, tracks: %{}} - - put_in(state, [:peers, peer_id], peer) - |> maybe_schedule_peerless_purge() - end + state = State.disconnect_peer(state, pid) {:noreply, state} end @@ -477,15 +421,7 @@ defmodule Jellyfish.Room do @impl true def handle_info({:playlist_playable, :video, _playlist_id}, state) do - endpoint_id = - Enum.find_value(state.components, fn {id, %{type: type}} -> - if type == HLS, do: id - end) - - Event.broadcast_server_notification({:hls_playable, state.id, endpoint_id}) - - state = - update_in(state, [:components, endpoint_id, :properties], &Map.put(&1, :playable, true)) + state = State.set_hls_playable(state) {:noreply, state} end @@ -507,15 +443,15 @@ defmodule Jellyfish.Room do @impl true def handle_info(%EndpointRemoved{endpoint_id: endpoint_id}, state) - when is_map_key(state.peers, endpoint_id) do + when peer_exists?(state, endpoint_id) do # The peer has been either removed, crashed or disconnected # The changes in state are applied in appropriate callbacks {:noreply, state} end def handle_info(%EndpointRemoved{endpoint_id: endpoint_id}, state) - when is_map_key(state.components, endpoint_id) do - state = handle_remove_component(endpoint_id, state, :component_finished) + when component_exists?(state, endpoint_id) do + state = State.remove_component(state, endpoint_id, :component_finished) {:noreply, state} end @@ -533,11 +469,10 @@ defmodule Jellyfish.Room do %EndpointMetadataUpdated{endpoint_id: endpoint_id, endpoint_metadata: metadata}, state ) - when is_map_key(state.peers, endpoint_id) do + when peer_exists?(state, endpoint_id) do Logger.info("Peer #{endpoint_id} metadata updated: #{inspect(metadata)}") - Event.broadcast_server_notification({:peer_metadata_updated, state.id, endpoint_id, metadata}) - state = put_in(state, [:peers, endpoint_id, :metadata], metadata) + state = State.update_peer_metadata(state, endpoint_id, metadata) {:noreply, state} end @@ -549,19 +484,7 @@ defmodule Jellyfish.Room do @impl true def handle_info(%TrackAdded{endpoint_id: endpoint_id} = track_info, state) when endpoint_exists?(state, endpoint_id) do - endpoint_id_type = get_endpoint_id_type(state, endpoint_id) - - Logger.info("Track #{track_info.track_id} added, #{endpoint_id_type}: #{endpoint_id}") - - Event.broadcast_server_notification( - {:track_added, state.id, {endpoint_id_type, endpoint_id}, track_info} - ) - - endpoint_group = get_endpoint_group(state, track_info.endpoint_id) - access_path = [endpoint_group, track_info.endpoint_id, :tracks, track_info.track_id] - - track = Track.from_track_message(track_info) - state = put_in(state, access_path, track) + state = State.put_track(state, track_info) {:noreply, state} end @@ -575,25 +498,7 @@ defmodule Jellyfish.Room do @impl true def handle_info(%TrackMetadataUpdated{endpoint_id: endpoint_id} = track_info, state) when endpoint_exists?(state, endpoint_id) do - endpoint_group = get_endpoint_group(state, endpoint_id) - access_path = [endpoint_group, endpoint_id, :tracks, track_info.track_id] - - state = - update_in(state, access_path, fn - %Track{} = track -> - endpoint_id_type = get_endpoint_id_type(state, endpoint_id) - updated_track = %Track{track | metadata: track_info.track_metadata} - - Logger.info( - "Track #{updated_track.id}, #{endpoint_id_type}: #{endpoint_id} - metadata updated: #{inspect(updated_track.metadata)}" - ) - - Event.broadcast_server_notification( - {:track_metadata_updated, state.id, {endpoint_id_type, endpoint_id}, updated_track} - ) - - updated_track - end) + state = State.update_track(state, track_info) {:noreply, state} end @@ -607,17 +512,7 @@ defmodule Jellyfish.Room do @impl true def handle_info(%TrackRemoved{endpoint_id: endpoint_id} = track_info, state) when endpoint_exists?(state, endpoint_id) do - endpoint_group = get_endpoint_group(state, endpoint_id) - access_path = [endpoint_group, endpoint_id, :tracks, track_info.track_id] - - {track, state} = pop_in(state, access_path) - - endpoint_id_type = get_endpoint_id_type(state, endpoint_id) - Logger.info("Track removed: #{track.id}, #{endpoint_id_type}: #{endpoint_id}") - - Event.broadcast_server_notification( - {:track_removed, state.id, {endpoint_id_type, endpoint_id}, track} - ) + state = State.remove_track(state, track_info) {:noreply, state} end @@ -630,211 +525,51 @@ defmodule Jellyfish.Room do @impl true def handle_info(:peerless_purge, state) do - if peerless_long_enough?(state) do + if State.peerless_long_enough?(state) do Logger.info( - "Removing room because it was peerless for #{state.config.peerless_purge_timeout} seconds" + "Removing room because it was peerless for #{State.peerless_purge_timeout(state)} seconds" ) {:stop, :normal, state} else + Logger.debug("Ignore peerless purge message") + {:noreply, state} end end @impl true - def handle_info(info, state) do - Logger.warning("Received unexpected info: #{inspect(info)}") - {:noreply, state} - end - - @impl true - def terminate(_reason, %{engine_pid: engine_pid} = state) do - Engine.terminate(engine_pid, asynchronous?: true, timeout: 10_000) - - state.peers - |> Map.values() - |> Enum.each(&handle_remove_peer(&1.id, state, :room_stopped)) - - state.components - |> Map.values() - |> Enum.each(&handle_remove_component(&1.id, state, :room_stopped)) - - :ok - end - - defp new(id, config) do - rtc_engine_options = [ - id: id - ] - - {:ok, pid} = Engine.start_link(rtc_engine_options, []) - Engine.register(pid, self()) - - webrtc_config = Application.fetch_env!(:jellyfish, :webrtc_config) - - turn_options = - if webrtc_config[:webrtc_used?] do - turn_ip = webrtc_config[:turn_listen_ip] - turn_mock_ip = webrtc_config[:turn_ip] - - [ - ip: turn_ip, - mock_ip: turn_mock_ip, - ports_range: webrtc_config[:turn_port_range] - ] - else - [] - end - - tcp_turn_port = webrtc_config[:turn_tcp_port] - - if webrtc_config[:webrtc_used?] and tcp_turn_port != nil do - TURNManager.ensure_tcp_turn_launched(turn_options, port: tcp_turn_port) - end - - state = - %__MODULE__{ - id: id, - config: config, - engine_pid: pid, - network_options: [turn_options: turn_options] - } - |> maybe_schedule_peerless_purge() - - state - end - - defp maybe_schedule_peerless_purge(%{config: %{peerless_purge_timeout: nil}} = state), do: state - - defp maybe_schedule_peerless_purge(%{config: config, peers: peers} = state) do - if all_peers_disconnected?(peers) do - last_peer_left = Klotho.monotonic_time(:millisecond) - - Klotho.send_after(config.peerless_purge_timeout * 1000, self(), :peerless_purge) - - %{state | last_peer_left: last_peer_left} - else - state - end - end - - defp peerless_long_enough?(%{config: config, peers: peers, last_peer_left: last_peer_left}) do - if all_peers_disconnected?(peers) do - Klotho.monotonic_time(:millisecond) >= last_peer_left + config.peerless_purge_timeout * 1000 - else - false - end - end - - defp all_peers_disconnected?(peers) do - peers |> Map.values() |> Enum.all?(&(&1.status == :disconnected)) - end - - defp handle_remove_component(component_id, state, reason) do - {component, state} = pop_in(state, [:components, component_id]) - :ok = Engine.remove_endpoint(state.engine_pid, component_id) - - component.tracks - |> Map.values() - |> Enum.each( - &Event.broadcast_server_notification( - {:track_removed, state.id, {:component_id, component_id}, &1} + def handle_info({:peer_purge, peer_id}, state) do + with {:ok, peer} <- State.fetch_peer(state, peer_id), + true <- State.peer_disconnected_long_enough?(state, peer) do + Logger.info( + "Removing peer because it was disconnected for #{State.peerless_purge_timeout(state)} seconds" ) - ) - Logger.info("Removed component #{inspect(component_id)}: #{inspect(reason)}") + state = State.remove_peer(state, peer_id, :timeout) - component.type.on_remove(state, component) - - if reason == :component_crashed, - do: Event.broadcast_server_notification({:component_crashed, state.id, component_id}) - - state - end - - defp handle_remove_peer(peer_id, state, reason) do - {peer, state} = pop_in(state, [:peers, peer_id]) - :ok = Engine.remove_endpoint(state.engine_pid, peer_id) - - if is_pid(peer.socket_pid), - do: send(peer.socket_pid, {:stop_connection, reason}) - - peer.tracks - |> Map.values() - |> Enum.each( - &Event.broadcast_server_notification({:track_removed, state.id, {:peer_id, peer_id}, &1}) - ) - - Logger.info("Removed peer #{inspect(peer_id)} from room #{inspect(state.id)}") - - if peer.status == :connected and reason == :peer_removed do - Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) - :telemetry.execute([:jellyfish, :room], %{peer_disconnects: 1}, %{room_id: state.id}) - end + {:noreply, state} + else + _other -> + Logger.debug("Ignore peer purge message for peer: #{peer_id}") - with {:peer_crashed, crash_reason} <- reason do - Event.broadcast_server_notification({:peer_crashed, state.id, peer_id, crash_reason}) - :telemetry.execute([:jellyfish, :room], %{peer_crashes: 1}, %{room_id: state.id}) + {:noreply, state} end - - state end - defp get_component_by_id(%{components: components}, component_id), - do: - Enum.find_value(components, fn {id, component} -> - if id == component_id, do: component - end) - - defp check_component_allowed(type, %{ - config: %{video_codec: video_codec}, - components: components - }) - when type in [HLS, Recording] do - cond do - video_codec != :h264 -> - {:error, :incompatible_codec} - - component_already_present?(type, components) -> - {:error, :reached_components_limit} - - true -> - :ok - end - end - - defp check_component_allowed(RTSP, %{config: %{video_codec: video_codec}}) do - # Right now, RTSP component can only publish H264, so there's no point adding it - # to a room which allows another video codec, e.g. VP8 - if video_codec == :h264, - do: :ok, - else: {:error, :incompatible_codec} + @impl true + def handle_info(info, state) do + Logger.warning("Received unexpected info: #{inspect(info)}") + {:noreply, state} end - defp check_component_allowed(_component_type, _state), do: :ok - - defp component_already_present?(type, components), - do: components |> Map.values() |> Enum.any?(&(&1.type == type)) - - defp validate_subscription_mode(nil), do: {:error, :component_not_exists} - - defp validate_subscription_mode(%{properties: %{subscribe_mode: :auto}}), - do: {:error, :invalid_subscribe_mode} - - defp validate_subscription_mode(%{properties: %{subscribe_mode: :manual}}), do: :ok - defp validate_subscription_mode(_not_properties), do: {:error, :invalid_component_type} - - defp get_endpoint_group(state, endpoint_id) when is_map_key(state.components, endpoint_id), - do: :components + @impl true + def terminate(_reason, %{engine_pid: engine_pid} = state) do + Engine.terminate(engine_pid, asynchronous?: true, timeout: 10_000) - defp get_endpoint_group(state, endpoint_id) when is_map_key(state.peers, endpoint_id), - do: :peers + State.remove_all_endpoints(state) - defp get_endpoint_id_type(state, endpoint_id) do - case get_endpoint_group(state, endpoint_id) do - :peers -> :peer_id - :components -> :component_id - end + :ok end defp parse_crash_reason( diff --git a/lib/jellyfish/room/config.ex b/lib/jellyfish/room/config.ex index d833163c..c8cb5053 100644 --- a/lib/jellyfish/room/config.ex +++ b/lib/jellyfish/room/config.ex @@ -2,7 +2,14 @@ defmodule Jellyfish.Room.Config do @moduledoc """ Room configuration """ - @enforce_keys [:room_id, :max_peers, :video_codec, :webhook_url, :peerless_purge_timeout] + @enforce_keys [ + :room_id, + :max_peers, + :video_codec, + :webhook_url, + :peerless_purge_timeout, + :peer_disconnected_timeout + ] defstruct @enforce_keys @@ -10,14 +17,15 @@ defmodule Jellyfish.Room.Config do @type max_peers :: non_neg_integer() | nil @type video_codec :: :h264 | :vp8 | nil @type webhook_url :: String.t() - @type peerless_purge_timeout :: pos_integer() | nil + @type purge_timeout :: pos_integer() | nil @type t :: %__MODULE__{ room_id: room_id(), max_peers: max_peers(), video_codec: video_codec(), webhook_url: URI.t(), - peerless_purge_timeout: peerless_purge_timeout() + peerless_purge_timeout: purge_timeout(), + peer_disconnected_timeout: purge_timeout() } @spec from_params(map()) :: {:ok, __MODULE__.t()} | {:error, atom()} @@ -27,19 +35,22 @@ defmodule Jellyfish.Room.Config do video_codec = Map.get(params, "videoCodec") webhook_url = Map.get(params, "webhookUrl") peerless_purge_timeout = Map.get(params, "peerlessPurgeTimeout") + peer_disconnected_timeout = Map.get(params, "peerDisconnectedTimeout") with {:ok, room_id} <- parse_room_id(room_id), :ok <- validate_max_peers(max_peers), {:ok, video_codec} <- codec_to_atom(video_codec), :ok <- validate_webhook_url(webhook_url), - :ok <- validate_purge_timeout(peerless_purge_timeout) do + :ok <- validate_purge_timeout(peerless_purge_timeout, "peerlessPurgeTimeout"), + :ok <- validate_purge_timeout(peer_disconnected_timeout, "peerDisconnectedTimeout") do {:ok, %__MODULE__{ room_id: room_id, max_peers: max_peers, video_codec: video_codec, webhook_url: webhook_url, - peerless_purge_timeout: peerless_purge_timeout + peerless_purge_timeout: peerless_purge_timeout, + peer_disconnected_timeout: peer_disconnected_timeout }} end end @@ -79,7 +90,7 @@ defmodule Jellyfish.Room.Config do defp codec_to_atom(nil), do: {:ok, nil} defp codec_to_atom(_codec), do: {:error, :invalid_video_codec} - defp validate_purge_timeout(nil), do: :ok - defp validate_purge_timeout(timeout) when is_integer(timeout) and timeout > 0, do: :ok - defp validate_purge_timeout(_timeout), do: {:error, :invalid_peerless_purge_timeout} + defp validate_purge_timeout(nil, _param), do: :ok + defp validate_purge_timeout(timeout, _param) when is_integer(timeout) and timeout > 0, do: :ok + defp validate_purge_timeout(_timeout, param), do: {:error, :invalid_purge_timeout, param} end diff --git a/lib/jellyfish/room/state.ex b/lib/jellyfish/room/state.ex new file mode 100644 index 00000000..1a6c96a4 --- /dev/null +++ b/lib/jellyfish/room/state.ex @@ -0,0 +1,459 @@ +defmodule Jellyfish.Room.State do + @moduledoc false + + use Bunch.Access + + require Logger + + alias Jellyfish.{Component, Event, Peer, Room, Track} + alias Jellyfish.Component.{HLS, Recording, RTSP} + alias Jellyfish.Room.Config + alias Membrane.ICE.TURNManager + alias Membrane.RTC.Engine + + alias Membrane.RTC.Engine.Message.{ + TrackAdded, + TrackMetadataUpdated, + TrackRemoved + } + + @enforce_keys [ + :id, + :config, + :engine_pid, + :network_options + ] + defstruct @enforce_keys ++ [components: %{}, peers: %{}, last_peer_left: 0] + + @type reason_t :: any() + @type endpoint_id :: Component.id() | Peer.id() + + @typedoc """ + This module contains: + * `id` - room id + * `config` - configuration of room. For example you can specify maximal number of peers + * `components` - map of components + * `peers` - map of peers + * `engine` - pid of engine + * `network_options` - network options + * `last_peer_left` - arbitrary timestamp with latest occurence of the room becoming peerless + """ + @type t :: %__MODULE__{ + id: Room.id(), + config: Config.t(), + components: %{Component.id() => Component.t()}, + peers: %{Peer.id() => Peer.t()}, + engine_pid: pid(), + network_options: map(), + last_peer_left: integer() + } + + defguard peer_exists?(state, endpoint_id) when is_map_key(state.peers, endpoint_id) + + defguard component_exists?(state, endpoint_id) when is_map_key(state.components, endpoint_id) + + defguard endpoint_exists?(state, endpoint_id) + when peer_exists?(state, endpoint_id) or component_exists?(state, endpoint_id) + + @spec new(id :: Room.id(), config :: Config.t()) :: t() + def new(id, config) do + rtc_engine_options = [ + id: id + ] + + {:ok, pid} = Engine.start_link(rtc_engine_options, []) + Engine.register(pid, self()) + + webrtc_config = Application.fetch_env!(:jellyfish, :webrtc_config) + + turn_options = + if webrtc_config[:webrtc_used?] do + turn_ip = webrtc_config[:turn_listen_ip] + turn_mock_ip = webrtc_config[:turn_ip] + + [ + ip: turn_ip, + mock_ip: turn_mock_ip, + ports_range: webrtc_config[:turn_port_range] + ] + else + [] + end + + tcp_turn_port = webrtc_config[:turn_tcp_port] + + if webrtc_config[:webrtc_used?] and tcp_turn_port != nil do + TURNManager.ensure_tcp_turn_launched(turn_options, port: tcp_turn_port) + end + + state = + %__MODULE__{ + id: id, + config: config, + engine_pid: pid, + network_options: [turn_options: turn_options] + } + |> maybe_schedule_peerless_purge() + + state + end + + @spec id(state :: t()) :: Room.id() + def id(state), do: state.id + + @spec engine_pid(state :: t()) :: pid() + def engine_pid(state), do: state.engine_pid + + @spec peerless_purge_timeout(state :: t()) :: Config.purge_timeout() + def peerless_purge_timeout(state), do: state.config.peerless_purge_timeout + + @spec peerless_long_enough?(state :: t()) :: boolean() + def peerless_long_enough?(%{config: config, peers: peers, last_peer_left: last_peer_left}) do + if all_peers_disconnected?(peers) do + Klotho.monotonic_time(:millisecond) >= last_peer_left + config.peerless_purge_timeout * 1000 + else + false + end + end + + @spec peer_disconnected_long_enough?(state :: t(), peer :: Peer.t()) :: boolean() + def peer_disconnected_long_enough?(_state, peer) when peer.status != :disconnected, do: false + + def peer_disconnected_long_enough?(state, peer) do + remove_timestamp = peer.last_time_connected + state.config.peer_disconnected_timeout * 1000 + + now = Klotho.monotonic_time(:millisecond) + + now >= remove_timestamp + end + + @spec put_peer(state :: t(), peer :: Peer.t()) :: t() + def put_peer(state, peer) do + state + |> put_in([:peers, peer.id], peer) + |> maybe_schedule_peer_purge(peer) + |> maybe_schedule_peerless_purge() + end + + @spec put_component(state :: t(), component :: Component.t()) :: t() + def put_component(state, component) do + put_in(state, [:components, component.id], component) + end + + @spec put_track(state :: t(), track_info :: TrackAdded.t()) :: t() + def put_track(state, %TrackAdded{endpoint_id: endpoint_id} = track_info) do + track = Track.from_track_message(track_info) + endpoint_id_type = get_endpoint_id_type(state, endpoint_id) + + Logger.info("Track #{track.id} added, #{endpoint_id_type}: #{endpoint_id}") + + Event.broadcast_server_notification( + {:track_added, state.id, {endpoint_id_type, endpoint_id}, track_info} + ) + + track = Track.from_track_message(track_info) + + endpoint_group = get_endpoint_group(state, endpoint_id) + access_path = [endpoint_group, endpoint_id, :tracks, track.id] + + put_in(state, access_path, track) + end + + @spec update_track(state :: t(), track_info :: TrackMetadataUpdated.t()) :: t() + def update_track(state, %TrackMetadataUpdated{endpoint_id: endpoint_id} = track_info) do + endpoint_group = get_endpoint_group(state, endpoint_id) + access_path = [endpoint_group, endpoint_id, :tracks, track_info.track_id] + + update_in(state, access_path, fn + %Track{} = track -> + endpoint_id_type = get_endpoint_id_type(state, endpoint_id) + updated_track = %Track{track | metadata: track_info.track_metadata} + + Logger.info( + "Track #{updated_track.id}, #{endpoint_id_type}: #{endpoint_id} - metadata updated: #{inspect(updated_track.metadata)}" + ) + + Event.broadcast_server_notification( + {:track_metadata_updated, state.id, {endpoint_id_type, endpoint_id}, updated_track} + ) + + updated_track + end) + end + + @spec remove_track(state :: t(), track_info :: TrackRemoved.t()) :: t() + def remove_track(state, %TrackRemoved{endpoint_id: endpoint_id} = track_info) do + endpoint_group = get_endpoint_group(state, endpoint_id) + access_path = [endpoint_group, endpoint_id, :tracks, track_info.track_id] + + {track, state} = pop_in(state, access_path) + + endpoint_id_type = get_endpoint_id_type(state, endpoint_id) + Logger.info("Track removed: #{track.id}, #{endpoint_id_type}: #{endpoint_id}") + + Event.broadcast_server_notification( + {:track_removed, state.id, {endpoint_id_type, endpoint_id}, track} + ) + + state + end + + @spec fetch_peer(state :: t(), peer_id :: Peer.id()) :: {:ok, Peer.t()} | :error + def fetch_peer(state, peer_id), do: Map.fetch(state.peers, peer_id) + + @spec fetch_component(state :: t(), component_id :: Component.id()) :: + {:ok, Component.t()} | :error + def fetch_component(state, component_id), do: Map.fetch(state.components, component_id) + + @spec update_peer_metadata(state :: t(), peer_id :: Peer.id(), metadata :: any()) :: t() + def update_peer_metadata(state, peer_id, metadata) do + Event.broadcast_server_notification({:peer_metadata_updated, state.id, peer_id, metadata}) + + put_in(state, [:peers, peer_id, :metadata], metadata) + end + + @spec connect_peer(state :: t(), peer :: Peer.t(), socket_pid :: pid()) :: t() + def connect_peer(state, peer, socket_pid) do + peer = %{peer | status: :connected, socket_pid: socket_pid} + + state = put_peer(state, peer) + + :ok = Engine.add_endpoint(state.engine_pid, peer.engine_endpoint, id: peer.id) + + Logger.info("Peer #{inspect(peer.id)} connected") + + :telemetry.execute([:jellyfish, :room], %{peer_connects: 1}, %{room_id: state.id}) + + state + end + + @spec disconnect_peer(state :: t(), peer_ws_pid :: pid()) :: t() + def disconnect_peer(state, peer_ws_pid) do + case find_peer_with_pid(state, peer_ws_pid) do + nil -> + state + + {peer_id, peer} -> + :ok = Engine.remove_endpoint(state.engine_pid, peer_id) + + Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) + :telemetry.execute([:jellyfish, :room], %{peer_disconnects: 1}, %{room_id: state.id}) + + peer.tracks + |> Map.values() + |> Enum.each( + &Event.broadcast_server_notification( + {:track_removed, state.id, {:peer_id, peer_id}, &1} + ) + ) + + peer = %{peer | status: :disconnected, socket_pid: nil, tracks: %{}} + + put_peer(state, peer) + end + end + + @spec remove_peer(state :: t(), peer_id :: Peer.id(), reason :: any()) :: t() + def remove_peer(state, peer_id, :timeout) do + {_peer, state} = pop_in(state, [:peers, peer_id]) + + maybe_schedule_peerless_purge(state) + end + + def remove_peer(state, peer_id, reason) do + {peer, state} = pop_in(state, [:peers, peer_id]) + :ok = Engine.remove_endpoint(state.engine_pid, peer_id) + + if is_pid(peer.socket_pid), + do: send(peer.socket_pid, {:stop_connection, reason}) + + peer.tracks + |> Map.values() + |> Enum.each( + &Event.broadcast_server_notification({:track_removed, state.id, {:peer_id, peer_id}, &1}) + ) + + Logger.info("Removed peer #{inspect(peer_id)} from room #{inspect(state.id)}") + + if peer.status == :connected and reason == :peer_removed do + Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) + :telemetry.execute([:jellyfish, :room], %{peer_disconnects: 1}, %{room_id: state.id}) + end + + with {:peer_crashed, crash_reason} <- reason do + Event.broadcast_server_notification({:peer_crashed, state.id, peer_id, crash_reason}) + :telemetry.execute([:jellyfish, :room], %{peer_crashes: 1}, %{room_id: state.id}) + end + + maybe_schedule_peerless_purge(state) + end + + @spec remove_component(state :: t(), component_id :: Component.id(), reason :: reason_t()) :: + t() + def remove_component(state, component_id, reason) do + {component, state} = pop_in(state, [:components, component_id]) + :ok = Engine.remove_endpoint(state.engine_pid, component_id) + + component.tracks + |> Map.values() + |> Enum.each( + &Event.broadcast_server_notification( + {:track_removed, state.id, {:component_id, component_id}, &1} + ) + ) + + Logger.info("Removed component #{inspect(component_id)}: #{inspect(reason)}") + + component.type.on_remove(state, component) + + if reason == :component_crashed, + do: Event.broadcast_server_notification({:component_crashed, state.id, component_id}) + + state + end + + @spec remove_all_endpoints(state :: t()) :: :ok + def remove_all_endpoints(state) do + state.peers + |> Map.values() + |> Enum.each(&remove_peer(state, &1.id, :room_stopped)) + + state.components + |> Map.values() + |> Enum.each(&remove_component(state, &1.id, :room_stopped)) + end + + @spec find_peer_with_pid(state :: t(), pid :: pid()) :: {Peer.id(), Peer.t()} | nil + defp find_peer_with_pid(state, pid), + do: Enum.find(state.peers, fn {_id, peer} -> peer.socket_pid == pid end) + + @spec get_component_by_id(state :: t(), component_id :: Component.id()) :: Component.t() | nil + def get_component_by_id(state, component_id) do + Enum.find_value(state.components, fn {id, component} -> + if id == component_id, do: component + end) + end + + @spec set_hls_playable(state :: t()) :: t() + def set_hls_playable(state) do + endpoint_id = find_hls_component_id(state) + + Event.broadcast_server_notification({:hls_playable, state.id, endpoint_id}) + + update_in(state, [:components, endpoint_id, :properties], &Map.put(&1, :playable, true)) + end + + @spec find_hls_component_id(state :: t()) :: Component.t() | nil + def find_hls_component_id(state), + do: + Enum.find_value(state.components, fn {id, %{type: type}} -> + if type == HLS, do: id + end) + + @spec reached_peers_limit?(state :: t()) :: boolean() + def reached_peers_limit?(state), do: Enum.count(state.peers) == state.config.max_peers + + @spec generate_peer_options(state :: t(), override_options :: map()) :: map() + def generate_peer_options(state, override_options) do + Map.merge( + %{ + engine_pid: state.engine_pid, + network_options: state.network_options, + video_codec: state.config.video_codec, + room_id: state.id + }, + override_options + ) + end + + def check_component_allowed(type, %{ + config: %{video_codec: video_codec}, + components: components + }) + when type in [HLS, Recording] do + cond do + video_codec != :h264 -> + {:error, :incompatible_codec} + + component_already_present?(type, components) -> + {:error, :reached_components_limit} + + true -> + :ok + end + end + + def check_component_allowed(RTSP, %{config: %{video_codec: video_codec}}) do + # Right now, RTSP component can only publish H264, so there's no point adding it + # to a room which allows another video codec, e.g. VP8 + if video_codec == :h264, + do: :ok, + else: {:error, :incompatible_codec} + end + + def check_component_allowed(_component_type, _state), do: :ok + + @spec get_endpoint_id_type(state :: t(), endpoint_id :: endpoint_id()) :: + :peer_id | :component_id + def get_endpoint_id_type(state, endpoint_id) do + case get_endpoint_group(state, endpoint_id) do + :peers -> :peer_id + :components -> :component_id + end + end + + @spec get_endpoint_group(state :: t(), endpoint_id :: endpoint_id()) :: + :components | :peers + def get_endpoint_group(state, endpoint_id) when component_exists?(state, endpoint_id), + do: :components + + def get_endpoint_group(state, endpoint_id) when peer_exists?(state, endpoint_id), + do: :peers + + @spec validate_subscription_mode(component :: Component.t() | nil) :: :ok | {:error, any()} + def validate_subscription_mode(nil), do: {:error, :component_not_exists} + + def validate_subscription_mode(%{properties: %{subscribe_mode: :auto}}), + do: {:error, :invalid_subscribe_mode} + + def validate_subscription_mode(%{properties: %{subscribe_mode: :manual}}), do: :ok + def validate_subscription_mode(_not_properties), do: {:error, :invalid_component_type} + + defp component_already_present?(type, components), + do: components |> Map.values() |> Enum.any?(&(&1.type == type)) + + defp maybe_schedule_peerless_purge(%{config: %{peerless_purge_timeout: nil}} = state), do: state + + defp maybe_schedule_peerless_purge(%{config: config, peers: peers} = state) do + if all_peers_disconnected?(peers) do + last_peer_left = Klotho.monotonic_time(:millisecond) + + Klotho.send_after(config.peerless_purge_timeout * 1000, self(), :peerless_purge) + + %{state | last_peer_left: last_peer_left} + else + state + end + end + + defp maybe_schedule_peer_purge(%{config: %{peer_disconnected_timeout: nil}} = state, _peer), + do: state + + defp maybe_schedule_peer_purge(%{config: config} = state, peer) do + case fetch_peer(state, peer.id) do + {:ok, peer} when peer.status == :disconnected -> + last_time_connected = Klotho.monotonic_time(:millisecond) + + Klotho.send_after(config.peer_disconnected_timeout * 1000, self(), {:peer_purge, peer.id}) + + put_in(state, [:peers, peer.id, :last_time_connected], last_time_connected) + + _other -> + state + end + end + + defp all_peers_disconnected?(peers) do + peers |> Map.values() |> Enum.all?(&(&1.status == :disconnected)) + end +end diff --git a/lib/jellyfish/room_service.ex b/lib/jellyfish/room_service.ex index 350e1c35..c8577c8c 100644 --- a/lib/jellyfish/room_service.ex +++ b/lib/jellyfish/room_service.ex @@ -205,6 +205,7 @@ defmodule Jellyfish.RoomService do Logger.debug("Room #{room_id} is down with reason: normal") Phoenix.PubSub.broadcast(Jellyfish.PubSub, room_id, :room_stopped) + Event.broadcast_server_notification({:room_deleted, room_id}) clear_room_metrics(room_id) {:noreply, state} @@ -256,8 +257,6 @@ defmodule Jellyfish.RoomService do try do :ok = GenServer.stop(room, :normal) Logger.info("Deleted room #{inspect(room_id)}") - - Event.broadcast_server_notification({:room_deleted, room_id}) catch :exit, {:noproc, {GenServer, :stop, [^room, :normal, :infinity]}} -> Logger.warning("Room process with id #{inspect(room_id)} doesn't exist") diff --git a/lib/jellyfish_web/api_spec/room.ex b/lib/jellyfish_web/api_spec/room.ex index 73027832..c9ab507f 100644 --- a/lib/jellyfish_web/api_spec/room.ex +++ b/lib/jellyfish_web/api_spec/room.ex @@ -46,6 +46,14 @@ defmodule JellyfishWeb.ApiSpec.Room do minimum: 1, example: 60, nullable: true + }, + peerDisconnectedTimeout: %Schema{ + description: + "Duration (in seconds) after which the peer will be removed if it is disconnected. If not provided, this feature is disabled.", + type: :integer, + minimum: 1, + example: 60, + nullable: true } } }) diff --git a/lib/jellyfish_web/controllers/room_controller.ex b/lib/jellyfish_web/controllers/room_controller.ex index 9fe170e4..00e9e419 100644 --- a/lib/jellyfish_web/controllers/room_controller.ex +++ b/lib/jellyfish_web/controllers/room_controller.ex @@ -95,11 +95,10 @@ defmodule JellyfishWeb.RoomController do webhook_url = Map.get(params, "webhookUrl") {:error, :bad_request, "Expected webhookUrl to be valid URL, got: #{webhook_url}"} - {:error, :invalid_peerless_purge_timeout} -> - timeout = Map.get(params, "peerlessPurgeTimeout") + {:error, :invalid_purge_timeout, param} -> + timeout = Map.get(params, param) - {:error, :bad_request, - "Expected peerlessPurgeTimeout to be a positive integer, got: #{timeout}"} + {:error, :bad_request, "Expected #{param} to be a positive integer, got: #{timeout}"} {:error, :room_already_exists} -> room_id = Map.get(params, "roomId") diff --git a/lib/jellyfish_web/peer_socket.ex b/lib/jellyfish_web/peer_socket.ex index 54f0e9ee..1f1d605d 100644 --- a/lib/jellyfish_web/peer_socket.ex +++ b/lib/jellyfish_web/peer_socket.ex @@ -49,6 +49,7 @@ defmodule JellyfishWeb.PeerSocket do }) Event.broadcast_server_notification({:peer_connected, room_id, peer_id}) + Logger.metadata(room_id: room_id, peer_id: peer_id) {:reply, :ok, {:binary, encoded_message}, state} else @@ -115,32 +116,38 @@ defmodule JellyfishWeb.PeerSocket do @impl true def handle_info({:stop_connection, :peer_removed}, state) do + Logger.info("Peer socket stopped because peer removed") {:stop, :closed, {1000, "Peer removed"}, state} end @impl true def handle_info({:stop_connection, :room_stopped}, state) do + Logger.info("Peer socket stopped because room stopped") {:stop, :closed, {1000, "Room stopped"}, state} end @impl true def handle_info({:stop_connection, {:peer_crashed, crash_reason}}, state) when crash_reason != nil do + Logger.warning("Peer socket stopped because peer crashed with reason: #{crash_reason}") {:stop, :closed, {1011, crash_reason}, state} end @impl true def handle_info({:stop_connection, {:peer_crashed, _reason}}, state) do + Logger.warning("Peer socket stopped because peer crashed with unknown reason") {:stop, :closed, {1011, "Internal server error"}, state} end @impl true def handle_info(:room_crashed, state) do + Logger.warning("Peer socket stopped because room crashed") {:stop, :closed, {1011, "Internal server error"}, state} end @impl true - def terminate(_reason, _state) do + def terminate(reason, _state) do + Logger.info("Peer socket terminates with reason #{reason}") :ok end diff --git a/openapi.yaml b/openapi.yaml index 92296f89..42dbbcaa 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -787,6 +787,12 @@ components: minimum: 1 nullable: true type: integer + peerDisconnectedTimeout: + description: Duration (in seconds) after which the peer will be removed if it is disconnected. If not provided, this feature is disabled. + example: 60 + minimum: 1 + nullable: true + type: integer peerlessPurgeTimeout: description: Duration (in seconds) after which the room will be removed if no peers are connected. If not provided, this feature is disabled. example: 60 diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs index 2e2093ef..4f71e3c4 100644 --- a/test/jellyfish/config_reader_test.exs +++ b/test/jellyfish/config_reader_test.exs @@ -181,7 +181,8 @@ defmodule Jellyfish.ConfigReaderTest do "JF_DIST_MODE", "JF_DIST_COOKIE", "JF_DIST_NODE_NAME", - "JF_DIST_NODES" + "JF_DIST_NODES", + "JF_DIST_POLLING_INTERVAL" ] do {:ok, hostname} = :inet.gethostname() @@ -237,7 +238,8 @@ defmodule Jellyfish.ConfigReaderTest do "JF_DIST_COOKIE", "JF_DIST_NODE_NAME", "JF_DIST_NODES", - "JF_DIST_STRATEGY_NAME" + "JF_DIST_STRATEGY_NAME", + "JF_DIST_POLLING_INTERVAL" ] do assert ConfigReader.read_dist_config() == [ enabled: false, diff --git a/test/jellyfish_web/controllers/room_controller_test.exs b/test/jellyfish_web/controllers/room_controller_test.exs index 42013320..55bc2503 100644 --- a/test/jellyfish_web/controllers/room_controller_test.exs +++ b/test/jellyfish_web/controllers/room_controller_test.exs @@ -175,6 +175,11 @@ defmodule JellyfishWeb.RoomControllerTest do assert json_response(conn, :bad_request)["errors"] == "Expected peerlessPurgeTimeout to be a positive integer, got: nan" + conn = post(conn, ~p"/room", peerDisconnectedTimeout: "nan") + + assert json_response(conn, :bad_request)["errors"] == + "Expected peerDisconnectedTimeout to be a positive integer, got: nan" + conn = post(conn, ~p"/room", roomId: "test/path") assert json_response(conn, :bad_request)["errors"] == @@ -306,6 +311,119 @@ defmodule JellyfishWeb.RoomControllerTest do end end + describe "peer disconnect purge" do + setup %{conn: conn} do + conn = post(conn, ~p"/room", peerDisconnectedTimeout: @purge_timeout_s) + assert %{"id" => id} = json_response(conn, :created)["data"]["room"] + %{conn: conn, id: id} + end + + test "happens if peer added, but not joined", %{conn: conn, id: id} do + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + + assert %{"token" => _token, "peer" => %{"id" => _peer_id}} = + json_response(conn, :created)["data"] + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + assert json_response(conn, :ok)["data"]["peers"] |> Enum.empty?() + end + + test "happens if peer joined, then disconnected", %{conn: conn, id: id} do + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + assert %{"token" => token} = json_response(conn, :created)["data"] + + ws = connect_peer(token) + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + + GenServer.stop(ws) + Process.sleep(10) + + conn = get(conn, ~p"/room/#{id}") + + assert %{"status" => "disconnected"} = + json_response(conn, :ok)["data"]["peers"] |> List.first() + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + + conn = get(conn, ~p"/room/#{id}") + assert json_response(conn, :ok)["data"]["peers"] |> Enum.empty?() + end + + test "does not happen if peers rejoined quickly", %{conn: conn, id: id} do + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + assert %{"token" => token} = json_response(conn, :created)["data"] + + ws = connect_peer(token) + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + + GenServer.stop(ws) + Process.sleep(10) + conn = get(conn, ~p"/room/#{id}") + + assert %{"status" => "disconnected"} = + json_response(conn, :ok)["data"]["peers"] |> List.first() + + Klotho.Mock.warp_by(@purge_timeout_ms |> div(2)) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + assert peers = json_response(conn, :ok)["data"]["peers"] + assert not Enum.empty?(peers) + + connect_peer(token) + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + assert response(conn, :ok) + end + + test "does not happen when not configured", %{conn: conn} do + conn = post(conn, ~p"/room") + assert %{"id" => id} = json_response(conn, :created)["data"]["room"] + + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + + assert %{"token" => _token, "peer" => %{"id" => _peer_id}} = + json_response(conn, :created)["data"] + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + peers = json_response(conn, :ok)["data"]["peers"] + + assert not Enum.empty?(peers) + end + + test "does work with peerlessPurgeTimeout", %{conn: conn} do + conn = + post(conn, ~p"/room", + peerlessPurgeTimeout: @purge_timeout_s * 2, + peerDisconnectedTimeout: @purge_timeout_s + ) + + assert %{"id" => id} = json_response(conn, :created)["data"]["room"] + + conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") + + assert %{"token" => _token, "peer" => %{"id" => _peer_id}} = + json_response(conn, :created)["data"] + + Klotho.Mock.warp_by(@purge_timeout_ms + 10) + conn = get(conn, ~p"/room/#{id}") + peers = json_response(conn, :ok)["data"]["peers"] + + assert Enum.empty?(peers) + + Klotho.Mock.warp_by(@purge_timeout_ms * 2 + 10) + conn = get(conn, ~p"/room") + assert Enum.all?(json_response(conn, :ok)["data"], &(&1["id"] != id)) + end + end + describe "delete room" do setup [:create_room] diff --git a/test/jellyfish_web/integration/server_notification_test.exs b/test/jellyfish_web/integration/server_notification_test.exs index f83810ed..6fc28cbe 100644 --- a/test/jellyfish_web/integration/server_notification_test.exs +++ b/test/jellyfish_web/integration/server_notification_test.exs @@ -63,6 +63,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do password: "yourpassword" } + @purge_timeout_s 1 + @purge_timeout_ms @purge_timeout_s * 1000 + Application.put_env( :jellyfish, Endpoint, @@ -109,6 +112,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) + Klotho.Mock.reset() + Klotho.Mock.freeze() + on_exit(fn -> conn = get(conn, ~p"/room") rooms = json_response(conn, :ok)["data"] @@ -193,6 +199,37 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do assert_receive {:webhook_notification, %RoomDeleted{room_id: ^room_id}}, 1_000 end + test "sends a message when room gets created and deleted by peerless purge", %{conn: conn} do + server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + ws = create_and_authenticate() + + subscribe(ws, :server_notification) + + conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) + + conn = + post(conn, ~p"/room", + maxPeers: 1, + webhookUrl: @webhook_url, + peerlessPurgeTimeout: @purge_timeout_s + ) + + assert %{"id" => room_id} = json_response(conn, :created)["data"]["room"] + + assert_receive %RoomCreated{room_id: ^room_id} + assert_receive {:webhook_notification, %RoomCreated{room_id: ^room_id}}, 1_000 + + {peer_id, _token, _conn} = add_peer(conn, room_id) + + conn = delete(conn, ~p"/room/#{room_id}/peer/#{peer_id}") + assert response(conn, :no_content) + + Klotho.Mock.warp_by(@purge_timeout_ms + 25) + + assert_receive %RoomDeleted{room_id: ^room_id}, 1_000 + assert_receive {:webhook_notification, %RoomDeleted{room_id: ^room_id}}, 1_000 + end + describe "WebRTC Peer" do test "sends a message when peer connects and room is deleted", %{conn: conn} do {room_id, peer_id, conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) @@ -359,6 +396,51 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do assert Enum.empty?(tracks) end + + test "sends a message when peer gets created and deleted by disconnected purge", %{conn: conn} do + server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + ws = create_and_authenticate() + + subscribe(ws, :server_notification) + + conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) + + conn = + post(conn, ~p"/room", + maxPeers: 1, + webhookUrl: @webhook_url, + peerlessPurgeTimeout: @purge_timeout_s, + peerDisconnectedTimeout: @purge_timeout_s + ) + + assert %{"id" => room_id} = json_response(conn, :created)["data"]["room"] + + assert_receive %RoomCreated{room_id: ^room_id} + assert_receive {:webhook_notification, %RoomCreated{room_id: ^room_id}}, 1_000 + + {peer_id, token, _conn} = add_peer(conn, room_id) + {:ok, peer_ws} = WS.start("ws://127.0.0.1:#{@port}/socket/peer/websocket", :peer) + WS.send_auth_request(peer_ws, token) + + assert_receive %PeerConnected{peer_id: ^peer_id, room_id: ^room_id} + + assert_receive {:webhook_notification, + %PeerConnected{peer_id: ^peer_id, room_id: ^room_id}}, + 1000 + + :ok = GenServer.stop(peer_ws) + + assert_receive %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id} + + assert_receive {:webhook_notification, + %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}}, + 1_000 + + Klotho.Mock.warp_by(@purge_timeout_ms * 3) + + assert_receive %RoomDeleted{room_id: ^room_id}, 1_000 + assert_receive {:webhook_notification, %RoomDeleted{room_id: ^room_id}}, 1_000 + end end test "sends message when File adds or removes tracks", %{conn: conn} do From 69e3a6124d3846abfa00638a4167221e03948929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:37:51 +0200 Subject: [PATCH 24/51] Return peer_websocket_url in PeerDetailsResponse (#181) * Return peer_websocket_url in peer creation * Fix OpenAPI spec * Fix get_peer_websocket_address * Move new code from application.ex to jellyfish.ex --- lib/jellyfish.ex | 10 ++++++++++ lib/jellyfish/resource_manager.ex | 11 +++++++++++ lib/jellyfish/room_service.ex | 2 +- lib/jellyfish_web/api_spec/peer.ex | 13 +++++++++++++ lib/jellyfish_web/api_spec/responses.ex | 6 +++++- .../controllers/peer_controller.ex | 6 +++++- lib/jellyfish_web/controllers/peer_json.ex | 4 ++-- openapi.yaml | 8 ++++++++ .../controllers/peer_controller_test.exs | 17 +++++++++++++---- 9 files changed, 68 insertions(+), 9 deletions(-) diff --git a/lib/jellyfish.ex b/lib/jellyfish.ex index 52a1505a..144ed968 100644 --- a/lib/jellyfish.ex +++ b/lib/jellyfish.ex @@ -10,4 +10,14 @@ defmodule Jellyfish do @version Mix.Project.config()[:version] def version(), do: @version + + @spec address() :: binary() + def address() do + Application.fetch_env!(:jellyfish, :address) + end + + @spec peer_websocket_address() :: binary() + def peer_websocket_address() do + Application.fetch_env!(:jellyfish, :address) <> "/socket/peer/websocket" + end end diff --git a/lib/jellyfish/resource_manager.ex b/lib/jellyfish/resource_manager.ex index 5e5e0f1f..e164c4d5 100644 --- a/lib/jellyfish/resource_manager.ex +++ b/lib/jellyfish/resource_manager.ex @@ -26,6 +26,17 @@ defmodule Jellyfish.ResourceManager do def init(opts) do Logger.debug("Initialize resource manager") + base_path = Recording.get_base_path() + dir_result = File.mkdir_p(base_path) + + case dir_result do + {:error, reason} -> + Logger.error("Can't create directory at #{base_path} with reason: #{reason}") + + :ok -> + nil + end + schedule_free_resources(opts.interval) {:ok, opts} diff --git a/lib/jellyfish/room_service.ex b/lib/jellyfish/room_service.ex index c8577c8c..379f1fa5 100644 --- a/lib/jellyfish/room_service.ex +++ b/lib/jellyfish/room_service.ex @@ -146,7 +146,7 @@ defmodule Jellyfish.RoomService do Event.broadcast_server_notification({:room_created, room_id}) - {:reply, {:ok, room, Application.fetch_env!(:jellyfish, :address)}, state} + {:reply, {:ok, room, Jellyfish.address()}, state} else {:error, :room_already_exists} = error -> {:reply, error, state} diff --git a/lib/jellyfish_web/api_spec/peer.ex b/lib/jellyfish_web/api_spec/peer.ex index a44a63fd..3f2c1b6d 100644 --- a/lib/jellyfish_web/api_spec/peer.ex +++ b/lib/jellyfish_web/api_spec/peer.ex @@ -61,6 +61,19 @@ defmodule JellyfishWeb.ApiSpec.Peer do }) end + defmodule WebSocketUrl do + @moduledoc false + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "WebsocketURL", + description: "Websocket URL to which peer has to connect", + type: :string, + example: "www.jellyfish.org/socket/peer" + }) + end + defmodule PeerMetadata do @moduledoc false diff --git a/lib/jellyfish_web/api_spec/responses.ex b/lib/jellyfish_web/api_spec/responses.ex index a710032b..e8237e17 100644 --- a/lib/jellyfish_web/api_spec/responses.ex +++ b/lib/jellyfish_web/api_spec/responses.ex @@ -2,7 +2,11 @@ {PeerDetailsResponse, "Response containing peer details and their token", %OpenApiSpex.Schema{ type: :object, - properties: %{peer: JellyfishWeb.ApiSpec.Peer, token: JellyfishWeb.ApiSpec.Peer.Token}, + properties: %{ + peer: JellyfishWeb.ApiSpec.Peer, + token: JellyfishWeb.ApiSpec.Peer.Token, + peer_websocket_url: JellyfishWeb.ApiSpec.Peer.WebSocketUrl + }, required: [:peer, :token] }}, {RoomDetailsResponse, "Response containing room details", JellyfishWeb.ApiSpec.Room}, diff --git a/lib/jellyfish_web/controllers/peer_controller.ex b/lib/jellyfish_web/controllers/peer_controller.ex index 104360af..4be11780 100644 --- a/lib/jellyfish_web/controllers/peer_controller.ex +++ b/lib/jellyfish_web/controllers/peer_controller.ex @@ -74,7 +74,11 @@ defmodule JellyfishWeb.PeerController do {:ok, peer_type} <- Peer.parse_type(peer_type_string), {:ok, _room_pid} <- RoomService.find_room(room_id), {:ok, peer} <- Room.add_peer(room_id, peer_type, peer_options) do - assigns = [peer: peer, token: PeerToken.generate(%{peer_id: peer.id, room_id: room_id})] + assigns = [ + peer: peer, + token: PeerToken.generate(%{peer_id: peer.id, room_id: room_id}), + peer_websocket_url: Jellyfish.peer_websocket_address() + ] conn |> put_resp_content_type("application/json") diff --git a/lib/jellyfish_web/controllers/peer_json.ex b/lib/jellyfish_web/controllers/peer_json.ex index f4f87397..974afee2 100644 --- a/lib/jellyfish_web/controllers/peer_json.ex +++ b/lib/jellyfish_web/controllers/peer_json.ex @@ -2,8 +2,8 @@ defmodule JellyfishWeb.PeerJSON do @moduledoc false alias Jellyfish.Peer.WebRTC - def show(%{peer: peer, token: token}) do - %{data: %{peer: data(peer), token: token}} + def show(%{peer: peer, token: token, peer_websocket_url: ws_url}) do + %{data: %{peer: data(peer), token: token, peer_websocket_url: ws_url}} end def show(%{peer: peer}) do diff --git a/openapi.yaml b/openapi.yaml index 42dbbcaa..d9ca2bb3 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -397,6 +397,12 @@ components: title: HlsPart type: integer x-struct: Elixir.JellyfishWeb.ApiSpec.HLS.Params.HlsPart + WebsocketURL: + description: Websocket URL to which peer has to connect + example: www.jellyfish.org/socket/peer + title: WebsocketURL + type: string + x-struct: Elixir.JellyfishWeb.ApiSpec.Peer.WebSocketUrl ComponentDetailsResponse: description: Response containing component details properties: @@ -681,6 +687,8 @@ components: properties: peer: $ref: '#/components/schemas/Peer' + peer_websocket_url: + $ref: '#/components/schemas/WebsocketURL' token: $ref: '#/components/schemas/AuthToken' required: diff --git a/test/jellyfish_web/controllers/peer_controller_test.exs b/test/jellyfish_web/controllers/peer_controller_test.exs index 0d1de567..81d33c7d 100644 --- a/test/jellyfish_web/controllers/peer_controller_test.exs +++ b/test/jellyfish_web/controllers/peer_controller_test.exs @@ -22,17 +22,26 @@ defmodule JellyfishWeb.PeerControllerTest do assert response(conn, :no_content) end) - {:ok, %{conn: conn, room_id: id}} + peer_ws_url = Jellyfish.peer_websocket_address() + + {:ok, %{conn: conn, room_id: id, peer_ws_url: peer_ws_url}} end describe "create peer" do - test "renders peer when data is valid", %{conn: conn, room_id: room_id} do + test "renders peer when data is valid", %{ + conn: conn, + room_id: room_id, + peer_ws_url: peer_ws_url + } do conn = post(conn, ~p"/room/#{room_id}/peer", type: @peer_type) response = json_response(conn, :created) assert_response_schema(response, "PeerDetailsResponse", @schema) - assert %{"peer" => %{"id" => peer_id, "type" => @peer_type}, "token" => token} = - response["data"] + assert %{ + "peer" => %{"id" => peer_id, "type" => @peer_type}, + "token" => token, + "peer_websocket_url" => ^peer_ws_url + } = response["data"] conn = get(conn, ~p"/room/#{room_id}") From 325318e4a5d231b9a349c0a9c4d2d7fc4bd9aed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:15:50 +0200 Subject: [PATCH 25/51] RTC-520 add notifications: PeerAdded, PeerDeleted (#183) * WiP * Update protos * Use PeerAdded and PeerDeleted in tests * Fix credo issue * Update protos --- lib/jellyfish/event.ex | 8 +++++ lib/jellyfish/room.ex | 4 +-- lib/jellyfish/room/state.ex | 24 +++++++++++--- .../jellyfish/server_notifications.pb.ex | 25 +++++++++++++++ mix.lock | 2 +- protos | 2 +- .../integration/server_notification_test.exs | 32 +++++++++++++++---- 7 files changed, 82 insertions(+), 15 deletions(-) diff --git a/lib/jellyfish/event.ex b/lib/jellyfish/event.ex index ffed71f6..918fd843 100644 --- a/lib/jellyfish/event.ex +++ b/lib/jellyfish/event.ex @@ -7,8 +7,10 @@ defmodule Jellyfish.Event do HlsUploadCrashed, HlsUploaded, MetricsReport, + PeerAdded, PeerConnected, PeerCrashed, + PeerDeleted, PeerDisconnected, PeerMetadataUpdated, RoomCrashed, @@ -54,6 +56,12 @@ defmodule Jellyfish.Event do defp to_proto_server_notification({:room_crashed, room_id}), do: {:room_crashed, %RoomCrashed{room_id: room_id}} + defp to_proto_server_notification({:peer_added, room_id, peer_id}), + do: {:peer_added, %PeerAdded{room_id: room_id, peer_id: peer_id}} + + defp to_proto_server_notification({:peer_deleted, room_id, peer_id}), + do: {:peer_deleted, %PeerDeleted{room_id: room_id, peer_id: peer_id}} + defp to_proto_server_notification({:peer_connected, room_id, peer_id}), do: {:peer_connected, %PeerConnected{room_id: room_id, peer_id: peer_id}} diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index fa25f80b..47be9ebf 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -145,9 +145,7 @@ defmodule Jellyfish.Room do with false <- State.reached_peers_limit?(state), options <- State.generate_peer_options(state, override_options), {:ok, peer} <- Peer.new(peer_type, options) do - state = State.put_peer(state, peer) - - Logger.info("Added peer #{inspect(peer.id)}") + state = State.add_peer(state, peer) {:reply, {:ok, peer}, state} else diff --git a/lib/jellyfish/room/state.ex b/lib/jellyfish/room/state.ex index 1a6c96a4..1f239cf6 100644 --- a/lib/jellyfish/room/state.ex +++ b/lib/jellyfish/room/state.ex @@ -212,6 +212,16 @@ defmodule Jellyfish.Room.State do put_in(state, [:peers, peer_id, :metadata], metadata) end + @spec add_peer(state :: t(), peer :: Peer.t()) :: t() + def add_peer(state, peer) do + state = put_peer(state, peer) + + Logger.info("Added peer #{inspect(peer.id)}") + Event.broadcast_server_notification({:peer_added, state.id, peer.id}) + + state + end + @spec connect_peer(state :: t(), peer :: Peer.t(), socket_pid :: pid()) :: t() def connect_peer(state, peer, socket_pid) do peer = %{peer | status: :connected, socket_pid: socket_pid} @@ -255,7 +265,9 @@ defmodule Jellyfish.Room.State do @spec remove_peer(state :: t(), peer_id :: Peer.id(), reason :: any()) :: t() def remove_peer(state, peer_id, :timeout) do - {_peer, state} = pop_in(state, [:peers, peer_id]) + {peer, state} = pop_in(state, [:peers, peer_id]) + + Event.broadcast_server_notification({:peer_deleted, state.id, peer.id}) maybe_schedule_peerless_purge(state) end @@ -280,9 +292,13 @@ defmodule Jellyfish.Room.State do :telemetry.execute([:jellyfish, :room], %{peer_disconnects: 1}, %{room_id: state.id}) end - with {:peer_crashed, crash_reason} <- reason do - Event.broadcast_server_notification({:peer_crashed, state.id, peer_id, crash_reason}) - :telemetry.execute([:jellyfish, :room], %{peer_crashes: 1}, %{room_id: state.id}) + case reason do + {:peer_crashed, crash_reason} -> + Event.broadcast_server_notification({:peer_crashed, state.id, peer_id, crash_reason}) + :telemetry.execute([:jellyfish, :room], %{peer_crashes: 1}, %{room_id: state.id}) + + _other -> + Event.broadcast_server_notification({:peer_deleted, state.id, peer.id}) end maybe_schedule_peerless_purge(state) diff --git a/lib/protos/jellyfish/server_notifications.pb.ex b/lib/protos/jellyfish/server_notifications.pb.ex index d00fc40b..5e599133 100644 --- a/lib/protos/jellyfish/server_notifications.pb.ex +++ b/lib/protos/jellyfish/server_notifications.pb.ex @@ -26,6 +26,24 @@ defmodule Jellyfish.ServerMessage.RoomCrashed do field :room_id, 1, type: :string, json_name: "roomId" end +defmodule Jellyfish.ServerMessage.PeerAdded do + @moduledoc false + + use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" + + field :room_id, 1, type: :string, json_name: "roomId" + field :peer_id, 2, type: :string, json_name: "peerId" +end + +defmodule Jellyfish.ServerMessage.PeerDeleted do + @moduledoc false + + use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" + + field :room_id, 1, type: :string, json_name: "roomId" + field :peer_id, 2, type: :string, json_name: "peerId" +end + defmodule Jellyfish.ServerMessage.PeerConnected do @moduledoc false @@ -305,4 +323,11 @@ defmodule Jellyfish.ServerMessage do type: Jellyfish.ServerMessage.TrackMetadataUpdated, json_name: "trackMetadataUpdated", oneof: 0 + + field :peer_added, 20, type: Jellyfish.ServerMessage.PeerAdded, json_name: "peerAdded", oneof: 0 + + field :peer_deleted, 21, + type: Jellyfish.ServerMessage.PeerDeleted, + json_name: "peerDeleted", + oneof: 0 end diff --git a/mix.lock b/mix.lock index afde6f0c..bfa58dc0 100644 --- a/mix.lock +++ b/mix.lock @@ -39,7 +39,7 @@ "klotho": {:hex, :klotho, "0.1.2", "3b1f1a569703e0cdce1ba964f41711351a7b06846c38fcbd601faa407e712bf2", [:mix], [], "hexpm", "a6a387982753582e30a5246fe9561721c6b9a4dd27678296cf2cd44faa6f3733"}, "libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"}, "logger_json": {:hex, :logger_json, "5.1.4", "9e30a4f2e31a8b9e402bdc20bd37cf9b67d3a31f19d0b33082a19a06b4c50f6d", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "3f20eea58e406a33d3eb7814c7dff5accb503bab2ee8601e84da02976fa3934c"}, - "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.7", "4d9af018c22d9291b72d6025941452dd53c7921bcdbc826da8866bb6ecefa8cb", [: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", "79904c3b78882bd0cec15b02928e6b53780602e64a359941acbc9a2408e7b74b"}, + "membrane_aac_fdk_plugin": {:hex, :membrane_aac_fdk_plugin, "0.18.8", "88d47923805cbd9a977fc7e5d3eb8d3028a2e358ad9ad7b124684adc78c2e8ee", [: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", "bb9e706d0949954affd4e295f5d3d4660096997756b5422119800d961c46cc63"}, "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.18.1", "30433bffd4d5d773f79448dd9afd55d77338721688f09a89b20d742a68cc2c3d", [: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", "8fd048c47d5d2949eb557e19f43f62d534d3af5096187f1a1a3a1694d14b772c"}, "membrane_audio_mix_plugin": {:hex, :membrane_audio_mix_plugin, "0.16.0", "34997707ee186683c6d7bd87572944e5e37c0249235cc44915d181d653c5c40e", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, 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_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", "a4a8c723f0da8d9cf9ac11bf657a732770ea0b8db4eff2efc16caa3a1819f435"}, diff --git a/protos b/protos index b9683a3f..7da5da12 160000 --- a/protos +++ b/protos @@ -1 +1 @@ -Subproject commit b9683a3faec93b90b5f39e68cae387390fceba05 +Subproject commit 7da5da127c8e018ee0c845c921f598b10209271c diff --git a/test/jellyfish_web/integration/server_notification_test.exs b/test/jellyfish_web/integration/server_notification_test.exs index 6fc28cbe..9eabe3d6 100644 --- a/test/jellyfish_web/integration/server_notification_test.exs +++ b/test/jellyfish_web/integration/server_notification_test.exs @@ -20,7 +20,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do HlsUploadCrashed, HlsUploaded, MetricsReport, + PeerAdded, PeerConnected, + PeerDeleted, PeerDisconnected, PeerMetadataUpdated, RoomCrashed, @@ -172,7 +174,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do test "doesn't send messages if not subscribed", %{conn: conn} do create_and_authenticate() - trigger_notification(conn) + trigger_notification(conn, false) refute_receive %PeerConnected{}, 200 end @@ -224,6 +226,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do conn = delete(conn, ~p"/room/#{room_id}/peer/#{peer_id}") assert response(conn, :no_content) + assert_receive %PeerDeleted{room_id: ^room_id, peer_id: ^peer_id} + assert_receive {:webhook_notification, %PeerDeleted{room_id: ^room_id, peer_id: ^peer_id}} + Klotho.Mock.warp_by(@purge_timeout_ms + 25) assert_receive %RoomDeleted{room_id: ^room_id}, 1_000 @@ -244,6 +249,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do refute_received {:webhook_notification, %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}} + + assert_receive %PeerDeleted{room_id: ^room_id, peer_id: ^peer_id} + assert_receive {:webhook_notification, %PeerDeleted{room_id: ^room_id, peer_id: ^peer_id}} end test "sends a message when peer connects and peer is removed", %{conn: conn} do @@ -258,6 +266,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do %PeerDisconnected{room_id: ^room_id, peer_id: ^peer_id}}, 1_000 + assert_receive %PeerDeleted{room_id: ^room_id, peer_id: ^peer_id} + assert_receive {:webhook_notification, %PeerDeleted{room_id: ^room_id, peer_id: ^peer_id}} + _conn = delete(conn, ~p"/room/#{room_id}") assert_receive %RoomDeleted{room_id: ^room_id} @@ -419,6 +430,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do assert_receive {:webhook_notification, %RoomCreated{room_id: ^room_id}}, 1_000 {peer_id, token, _conn} = add_peer(conn, room_id) + {:ok, peer_ws} = WS.start("ws://127.0.0.1:#{@port}/socket/peer/websocket", :peer) WS.send_auth_request(peer_ws, token) @@ -438,6 +450,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do Klotho.Mock.warp_by(@purge_timeout_ms * 3) + assert_receive %PeerDeleted{room_id: ^room_id, peer_id: ^peer_id} + assert_receive {:webhook_notification, %PeerDeleted{room_id: ^room_id, peer_id: ^peer_id}} + assert_receive %RoomDeleted{room_id: ^room_id}, 1_000 assert_receive {:webhook_notification, %RoomDeleted{room_id: ^room_id}}, 1_000 end @@ -592,9 +607,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do ws end - defp add_room_and_peer(conn) do + defp add_room_and_peer(conn, assert_notifications? \\ true) do {room_id, conn} = add_room(conn) - {peer_id, token, conn} = add_peer(conn, room_id) + {peer_id, token, conn} = add_peer(conn, room_id, assert_notifications?) {room_id, peer_id, token, conn} end @@ -612,12 +627,17 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do {room_id, conn} end - defp add_peer(conn, room_id) do + defp add_peer(conn, room_id, assert_notifications? \\ true) do conn = post(conn, ~p"/room/#{room_id}/peer", type: "webrtc") assert %{"token" => peer_token, "peer" => %{"id" => peer_id}} = json_response(conn, :created)["data"] + if assert_notifications? do + assert_receive %PeerAdded{room_id: ^room_id, peer_id: ^peer_id} + assert_receive {:webhook_notification, %PeerAdded{room_id: ^room_id, peer_id: ^peer_id}} + end + {peer_id, peer_token, conn} end @@ -670,8 +690,8 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do {conn, component_id} end - defp trigger_notification(conn) do - {_room_id, _peer_id, peer_token, _conn} = add_room_and_peer(conn) + defp trigger_notification(conn, assert_notifications?) do + {_room_id, _peer_id, peer_token, _conn} = add_room_and_peer(conn, assert_notifications?) {:ok, peer_ws} = WS.start_link("ws://127.0.0.1:#{@port}/socket/peer/websocket", :peer) WS.send_auth_request(peer_ws, peer_token) From 1d7db2a2b28b9e5ae4078113fee3d6f781db164a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= <48837433+roznawsk@users.noreply.github.com> Date: Wed, 24 Apr 2024 15:03:06 +0200 Subject: [PATCH 26/51] Release v0.5.0-rc0 (#184) * Release v0.5.0-rc0 * Lock engine deps --------- Co-authored-by: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> --- mix.exs | 21 ++++++++------------- mix.lock | 14 +++++++------- openapi.yaml | 2 +- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/mix.exs b/mix.exs index b97f25bc..072bea2d 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Jellyfish.MixProject do def project do [ app: :jellyfish, - version: "0.4.2", + version: "0.5.0-rc0", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, @@ -70,18 +70,13 @@ defmodule Jellyfish.MixProject do # Membrane deps {:membrane_core, "1.1.0-rc0", override: true}, - {:membrane_rtc_engine, - github: "jellyfish-dev/membrane_rtc_engine", sparse: "engine", override: true}, - {:membrane_rtc_engine_webrtc, - github: "jellyfish-dev/membrane_rtc_engine", sparse: "webrtc", override: true}, - {:membrane_rtc_engine_hls, - github: "jellyfish-dev/membrane_rtc_engine", sparse: "hls", override: true}, - {:membrane_rtc_engine_recording, - github: "jellyfish-dev/membrane_rtc_engine", sparse: "recording", override: true}, - {:membrane_rtc_engine_rtsp, github: "jellyfish-dev/membrane_rtc_engine", sparse: "rtsp"}, - {:membrane_rtc_engine_file, github: "jellyfish-dev/membrane_rtc_engine", sparse: "file"}, - {:membrane_rtc_engine_sip, - github: "jellyfish-dev/membrane_rtc_engine", sparse: "sip", override: true}, + {:membrane_rtc_engine, "~> 0.22.0"}, + {:membrane_rtc_engine_webrtc, "~> 0.8.0"}, + {:membrane_rtc_engine_hls, "~> 0.7.0"}, + {:membrane_rtc_engine_recording, "~> 0.1.0"}, + {:membrane_rtc_engine_rtsp, "~> 0.7.0"}, + {:membrane_rtc_engine_file, "~> 0.5.0"}, + {:membrane_rtc_engine_sip, "~> 0.3.0"}, {:membrane_telemetry_metrics, "~> 0.1.0"}, # HLS endpoints deps diff --git a/mix.lock b/mix.lock index bfa58dc0..e8ef3e49 100644 --- a/mix.lock +++ b/mix.lock @@ -68,13 +68,13 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "engine"]}, - "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "file"]}, - "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "hls"]}, - "membrane_rtc_engine_recording": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "recording"]}, - "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "rtsp"]}, - "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "sip"]}, - "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "b2791b0412e53a5e594ba41d4754b7fb42912763", [sparse: "webrtc"]}, + "membrane_rtc_engine": {:hex, :membrane_rtc_engine, "0.22.0", "7c7cb477ccf17f8a4604e61e55bb484995a77701c0db4066ce5fdc63755a1b57", [:mix], [{:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.1", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:statistics, "~> 0.6.0", [hex: :statistics, repo: "hexpm", optional: false]}], "hexpm", "064cd03961b29a2972477574c2df53ed4b6c33fe3cb2bf538bb6e316f35b7e04"}, + "membrane_rtc_engine_file": {:hex, :membrane_rtc_engine_file, "0.5.0", "6936d803e9ffd01bddc4f7bbc5d2a5893bd8e60117f3ab7c0ec66ec7568cf1f4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_ogg_plugin, "~> 0.3.0", [hex: :membrane_ogg_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_realtimer_plugin, "~> 0.9.0", [hex: :membrane_realtimer_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}], "hexpm", "150e42a34baa6971d341b64d2ada06ee066d7fcca9aee9a73a5d6a09fe192c0e"}, + "membrane_rtc_engine_hls": {:hex, :membrane_rtc_engine_hls, "0.7.0", "03a7797389c493c824c1262afaf8fe4e1314406ec93d5d99de11e5528c5094b1", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.5", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_audio_mix_plugin, "~> 0.16.0", [hex: :membrane_audio_mix_plugin, repo: "hexpm", optional: true]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.31.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_http_adaptive_stream_plugin, "~> 0.18.2", [hex: :membrane_http_adaptive_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_video_compositor_plugin, "~> 0.7.0", [hex: :membrane_video_compositor_plugin, repo: "hexpm", optional: true]}], "hexpm", "88b39725792f1fa41439ef1e8260d016b4eb9173eb8568a307769d3ddd721f52"}, + "membrane_rtc_engine_recording": {:hex, :membrane_rtc_engine_recording, "0.1.0", "49f01747a92755ee9a37925873ac4a54a36e6d7946685ae6260bd714bb9874b0", [:mix], [{:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: false]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_stream_plugin, "~> 0.4.0", [hex: :membrane_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: false]}], "hexpm", "1ba142c25757c1727f5d8f3588b40349c0b1cd227011cc1411a50bdb2fd79cb1"}, + "membrane_rtc_engine_rtsp": {:hex, :membrane_rtc_engine_rtsp, "0.7.0", "b57a685afef887c6011ba446e35ff25ee0048bfa003b4822c513ae66ca32b35d", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, 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_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.19.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.1", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.5.1", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.13.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}], "hexpm", "ee49ed47b952e5be056a5a6be01668c953bf9a021601b47f0ecf8d71dcd5d1af"}, + "membrane_rtc_engine_sip": {:hex, :membrane_rtc_engine_sip, "0.3.0", "eea1402db258bcc95d0ac88fc42755ec0c277eb4f9542dcba77575c966277b21", [:mix], [{:ex_sdp, "~> 0.11", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_audio_mix_plugin, "~> 0.16.0", [hex: :membrane_audio_mix_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.19.1", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_g711_plugin, "~> 0.1.0", [hex: :membrane_g711_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:membrane_raw_audio_parser_plugin, "~> 0.4.0", [hex: :membrane_raw_audio_parser_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_g711_plugin, "~> 0.2.0", [hex: :membrane_rtp_g711_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.13.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}, {:sippet, "~> 1.0.11", [hex: :sippet, repo: "hexpm", optional: false]}], "hexpm", "9bd665fd8ca11cbe9c5e9348ae37f48025bbca97e2d75d4b624feb91d3b664bb"}, + "membrane_rtc_engine_webrtc": {:hex, :membrane_rtc_engine_webrtc, "0.8.0", "66138f2b39276e699b1f831cf93f7bfe22dedb97675a09a7b8cb9cf5f249d2dc", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "1.4.6", [hex: :bundlex, repo: "hexpm", optional: false]}, {:ex_libsrtp, ">= 0.0.0", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_ice_plugin, "~> 0.18.0", [hex: :membrane_ice_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.19.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, ">= 0.9.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.1", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.0", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:unifex, "1.1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "84ecd82cb6d74be719cc7c8e78f3a36b2cfa52949e871df4ecf62a456a89566a"}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.0", "112bfedc14fb83bdb549ef1a03da23908feedeb165fd3e4512a549f1af532ae7", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "76fd159e7406cadbef15124cba30eca3fffcf71a7420964f26e23d4cffd9b29d"}, diff --git a/openapi.yaml b/openapi.yaml index d9ca2bb3..9d81ad21 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -835,7 +835,7 @@ info: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 title: Jellyfish Media Server - version: 0.4.2 + version: 0.5.0-rc0 openapi: 3.0.0 paths: /health: From 5a964344ed3d8dc4f2cb3f9c6b6bca7fe9fc85b3 Mon Sep 17 00:00:00 2001 From: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> Date: Thu, 9 May 2024 13:32:43 +0200 Subject: [PATCH 27/51] Allow passing hostname in `JF_WEBRTC_TURN_LISTEN_IP` (#188) --- lib/jellyfish/config_reader.ex | 38 ++++++++++++++------------- test/jellyfish/config_reader_test.exs | 23 ++++++++++++++++ 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex index 7fdf2a54..8b5084b7 100644 --- a/lib/jellyfish/config_reader.ex +++ b/lib/jellyfish/config_reader.ex @@ -36,6 +36,15 @@ defmodule Jellyfish.ConfigReader do end end + def read_and_resolve_hostname(env) do + if value = System.get_env(env) do + # resolve_hostname will raise if address is invalid/unresolvable + {:ok, resolved_ip} = value |> resolve_hostname() |> to_charlist() |> :inet.parse_address() + + resolved_ip + end + end + def read_port(env) do if value = System.get_env(env) do case Integer.parse(value) do @@ -104,7 +113,7 @@ defmodule Jellyfish.ConfigReader do [ webrtc_used?: true, turn_ip: read_ip("JF_WEBRTC_TURN_IP") || {127, 0, 0, 1}, - turn_listen_ip: read_ip("JF_WEBRTC_TURN_LISTEN_IP") || {127, 0, 0, 1}, + turn_listen_ip: read_and_resolve_hostname("JF_WEBRTC_TURN_LISTEN_IP") || {127, 0, 0, 1}, turn_port_range: read_port_range("JF_WEBRTC_TURN_PORT_RANGE") || {50_000, 59_999}, turn_tcp_port: read_port("JF_WEBRTC_TURN_TCP_PORT") ] @@ -256,7 +265,8 @@ defmodule Jellyfish.ConfigReader do end defp do_read_dns_config(node_name_value, cookie, mode) do - node_name = parse_node_name(node_name_value) + # Verify the node name is formatted correctly + _node_name = parse_node_name(node_name_value) query_value = System.get_env("JF_DIST_QUERY") @@ -265,21 +275,8 @@ defmodule Jellyfish.ConfigReader do end [node_basename, hostname | []] = String.split(node_name_value, "@") - - node_name = - if ip_address?(hostname) do - node_name - else - Logger.info( - "Resolving hostname part of JF node name as DNS cluster strategy requires IP address." - ) - - resolved_hostname = resolve_hostname(hostname) - - Logger.info("Resolved #{hostname} as #{resolved_hostname}") - - String.to_atom("#{node_basename}@#{resolved_hostname}") - end + resolved_hostname = resolve_hostname(hostname) + node_name = String.to_atom("#{node_basename}@#{resolved_hostname}") polling_interval = parse_polling_interval() @@ -344,7 +341,12 @@ defmodule Jellyfish.ConfigReader do # Assert there is at least one ip address. # In other case, this is fatal error [h | _] = h_addr_list - "#{:inet.ntoa(h)}" + resolved_hostname = "#{:inet.ntoa(h)}" + + if resolved_hostname != hostname, + do: Logger.info("Resolved #{hostname} as #{resolved_hostname}") + + resolved_hostname {:error, reason} -> raise """ diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs index 4f71e3c4..84c73c27 100644 --- a/test/jellyfish/config_reader_test.exs +++ b/test/jellyfish/config_reader_test.exs @@ -43,6 +43,29 @@ defmodule Jellyfish.ConfigReaderTest do end end + test "read_and_resolve_hostname/1" do + env_name = "JF_CONF_READER_TEST_HOSTNAME" + + with_env env_name do + System.put_env(env_name, "127.0.0.1") + assert ConfigReader.read_and_resolve_hostname(env_name) == {127, 0, 0, 1} + + # On most systems, both of these hostnames will resolve to {127, 0, 0, 1} + # However, since we can't expect this to always be true, + # let's settle for asserting that these calls return and not raise an error + System.put_env(env_name, "localhost") + assert ConfigReader.read_and_resolve_hostname(env_name) + {:ok, hostname} = :inet.gethostname() + System.put_env(env_name, "#{hostname}") + assert ConfigReader.read_and_resolve_hostname(env_name) + + System.delete_env(env_name) + assert ConfigReader.read_and_resolve_hostname(env_name) == nil + System.put_env(env_name, "unresolvable-hostname") + assert_raise RuntimeError, fn -> ConfigReader.read_and_resolve_hostname(env_name) end + end + end + test "read_port/1" do env_name = "JF_CONF_READER_TEST_PORT" From ee3f3870311aa2818f6188a2e81747382d6fa304 Mon Sep 17 00:00:00 2001 From: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> Date: Tue, 14 May 2024 11:17:48 +0200 Subject: [PATCH 28/51] [RTC-515] Fix `ensure_epmd_started!` (#190) * [RTC-515] Fix `ensure_epmd_started!` * Fix typo --- lib/jellyfish/application.ex | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/jellyfish/application.ex b/lib/jellyfish/application.ex index 8e24612b..ebf8353f 100644 --- a/lib/jellyfish/application.ex +++ b/lib/jellyfish/application.ex @@ -7,9 +7,13 @@ defmodule Jellyfish.Application do require Logger - # seconds + # in seconds @resource_manager_opts %{interval: 600, recording_timeout: 3_600} + # in milliseconds + @epmd_timeout 5_000 + @epmd_pgrep_interval 500 + @impl true def start(_type, _args) do scrape_interval = Application.fetch_env!(:jellyfish, :webrtc_metrics_scrape_interval) @@ -104,14 +108,16 @@ defmodule Jellyfish.Application do end defp ensure_epmd_started!() do - case System.cmd("epmd", ["-daemon"]) do - {_output, 0} -> - :ok + try do + {_output, 0} = System.cmd("epmd", ["-daemon"]) + :ok = Task.async(&ensure_epmd_running/0) |> Task.await(@epmd_timeout) - _other -> + :ok + catch + _exit_or_error, _e -> raise """ Couldn't start epmd daemon. - Epmd is required to run Jellyfish in a distributed mode. + Epmd is required to run Jellyfish in distributed mode. You can try to start it manually with: epmd -daemon @@ -119,5 +125,21 @@ defmodule Jellyfish.Application do and run Jellyfish again. """ end + + :ok + end + + defp ensure_epmd_running() do + with {:pgrep, {_output, 0}} <- {:pgrep, System.cmd("pgrep", ["epmd"])}, + {:epmd, {_output, 0}} <- {:epmd, System.cmd("epmd", ["-names"])} do + :ok + else + {:pgrep, _other} -> + Process.sleep(@epmd_pgrep_interval) + ensure_epmd_running() + + {:epmd, _other} -> + :error + end end end From 38f80fe7c15c861689056d6172d42a5e1e823702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Tue, 14 May 2024 16:48:35 +0200 Subject: [PATCH 29/51] Don't normalize aws path (#193) --- config/config.exs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index 086f6f3d..e9a89f74 100644 --- a/config/config.exs +++ b/config/config.exs @@ -41,7 +41,9 @@ config :jellyfish, divo: "docker-compose.yaml", divo_wait: [dwell: 1_500, max_tries: 50] -config :ex_aws, :http_client, Jellyfish.Component.HLS.HTTPoison +config :ex_aws, + http_client: Jellyfish.Component.HLS.HTTPoison, + normalize_path: false config :bundlex, :disable_precompiled_os_deps, apps: [:membrane_h264_ffmpeg_plugin, :ex_libsrtp] From c33af360e79b528ae268e6160f8c76740e92f4bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= <48837433+roznawsk@users.noreply.github.com> Date: Wed, 15 May 2024 09:04:37 +0200 Subject: [PATCH 30/51] Release v0.5.0 (#194) --- mix.exs | 2 +- openapi.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 072bea2d..fffd98d7 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Jellyfish.MixProject do def project do [ app: :jellyfish, - version: "0.5.0-rc0", + version: "0.5.0", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, diff --git a/openapi.yaml b/openapi.yaml index 9d81ad21..fc5e1637 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -835,7 +835,7 @@ info: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 title: Jellyfish Media Server - version: 0.5.0-rc0 + version: 0.5.0 openapi: 3.0.0 paths: /health: From 582b21fb699c4cbc29da4e290b42860f4f5aba5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Thu, 16 May 2024 11:57:52 +0200 Subject: [PATCH 31/51] Rename to Fishjam in README (#195) --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b4d0a698..87ce9e03 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,46 @@ -# Jellyfish +# Fishjam -[![codecov](https://codecov.io/gh/jellyfish-dev/jellyfish/branch/main/graph/badge.svg?token=ANWFKV2EDP)](https://codecov.io/gh/jellyfish-dev/jellyfish) -[![CircleCI](https://circleci.com/gh/jellyfish-dev/jellyfish.svg?style=svg)](https://circleci.com/gh/jellyfish-dev/jellyfish) +[![codecov](https://codecov.io/gh/fishjam-dev/fishjam/branch/main/graph/badge.svg?token=ANWFKV2EDP)](https://codecov.io/gh/fishjam-dev/fishjam) +[![CircleCI](https://circleci.com/gh/fishjam-dev/fishjam.svg?style=svg)](https://circleci.com/gh/fishjam-dev/fishjam) -Jellyfish is an open-source, general-purpose media server that ships with support for multiple media protocols. +Fishjam is an open-source, general-purpose media server that ships with support for multiple media protocols. It can be thought of as a multimedia bridge meant for creating different types of multimedia systems that lets you easily create a real-time video conferencing system, a broadcasting solution, or both at the same time. -It leverages the [Membrane RTC Engine](https://github.com/jellyfish-dev/membrane_rtc_engine), a real-time communication engine/SFU library built with [Membrane](https://membrane.stream/). +It leverages the [Membrane RTC Engine](https://github.com/fishjam-dev/membrane_rtc_engine), a real-time communication engine/SFU library built with [Membrane](https://membrane.stream/). ## Installation -There are two ways of running Jellyfish: +There are two ways of running Fishjam: - building from source (requires Elixir and native dependencies) -- using Jellyfish Docker images +- using Fishjam Docker images -To learn more, refer to [Installation page](https://jellyfish-dev.github.io/jellyfish-docs/getting_started/installation) in Jellyfish docs. +To learn more, refer to [Installation page](https://fishjam-dev.github.io/fishjam-docs/getting_started/installation) in Fishjam docs. ## SDKs -Jellyfish provides server SDKs (used to manage the state of Jellyfish server) and client SDKs (used to connect to the Jellyfish instance, receive media, etc.). +Fishjam provides server SDKs (used to manage the state of Fishjam server) and client SDKs (used to connect to the Fishjam instance, receive media, etc.). -To get the list of all available SDKs, go to [SDKs page](https://jellyfish-dev.github.io/jellyfish-docs/getting_started/sdks) in Jellyfish docs. +To get the list of all available SDKs, go to [SDKs page](https://fishjam-dev.github.io/fishjam-docs/getting_started/sdks) in Fishjam docs. ## Examples - WebRTC Dashboard - A standalone dashboard that can create rooms, add peers and send media between the peers. Available [here](https://github.com/jellyfish-dev/jellyfish-dashboard). -To use the dashboard, you need to set up Jellyfish with WebRTC, refer to [WebRTC peer page](https://jellyfish-dev.github.io/jellyfish-docs/getting_started/peers/webrtc) in Jellyfish docs to learn how to do that. -Dashboard makes HTTP requests to Jellyfish that need to be authorized and requires a token to do so, learn more from [Authentication page](https://jellyfish-dev.github.io/jellyfish-docs/getting_started/authentication) in Jellyfish docs. + A standalone dashboard that can create rooms, add peers and send media between the peers. Available [here](https://github.com/fishjam-dev/fishjam-dashboard). +To use the dashboard, you need to set up Fishjam with WebRTC, refer to [WebRTC peer page](https://fishjam-dev.github.io/fishjam-docs/getting_started/peers/webrtc) in Fishjam docs to learn how to do that. +Dashboard makes HTTP requests to Fishjam that need to be authorized and requires a token to do so, learn more from [Authentication page](https://fishjam-dev.github.io/fishjam-docs/getting_started/authentication) in Fishjam docs. ## Documentation -Everything you need to get started with Jellyfish is available in the [Jellyfish docs](https://jellyfish-dev.github.io/jellyfish-docs/). +Everything you need to get started with Fishjam is available in the [Fishjam docs](https://fishjam-dev.github.io/fishjam-docs/). -You can read about theoretical concepts and problems we encountered in the [Jellybook](https://jellyfish-dev.github.io/book/). +You can read about theoretical concepts and problems we encountered in the [Fishjambook](https://fishjam-dev.github.io/book/). ## Copyright and License -Copyright 2022, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=jellyfish) +Copyright 2022, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=fishjam) -[![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=jellyfish) +[![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=fishjam) Licensed under the [Apache License, Version 2.0](LICENSE) From d3fb68f456f5a1b08a7c2fe1d7fc1f240f1fb5d5 Mon Sep 17 00:00:00 2001 From: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> Date: Thu, 16 May 2024 15:18:00 +0200 Subject: [PATCH 32/51] [RTC-526] Add env vars to configure allowed components (#189) * [RTC-526] Add env vars to configure allowed components * lint * Changes after review * Print allowed components at JF startup --- config/runtime.exs | 7 ++- lib/jellyfish/application.ex | 2 + lib/jellyfish/component/recording.ex | 7 --- lib/jellyfish/component/sip.ex | 11 +--- lib/jellyfish/config_reader.ex | 31 ++++++---- lib/jellyfish/peer.ex | 8 +++ lib/jellyfish/peer/webrtc.ex | 6 -- lib/jellyfish/room.ex | 47 ++++++++++---- lib/jellyfish/room/state.ex | 61 +++++++++++++------ .../controllers/component_controller.ex | 48 ++++++++------- .../controllers/peer_controller.ex | 11 ++-- test/jellyfish/config_reader_test.exs | 31 +++++++--- .../component/file_component_test.exs | 7 ++- .../component/hls_component_test.exs | 10 ++- .../component/recording_component_test.exs | 10 ++- .../component/rtsp_component_test.exs | 8 +++ .../component/sip_component_test.exs | 6 +- .../controllers/component_controller_test.exs | 27 ++++++++ .../controllers/dial_controller_test.exs | 10 ++- .../controllers/peer_controller_test.exs | 15 ++++- .../subscription_controller_test.exs | 21 +++++-- .../integration/server_notification_test.exs | 13 +++- 22 files changed, 283 insertions(+), 114 deletions(-) diff --git a/config/runtime.exs b/config/runtime.exs index ca8b3fbf..f00aedca 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -25,6 +25,9 @@ host = other -> other end +components_used = ConfigReader.read_components_used() +sip_used? = Jellyfish.Component.SIP in components_used + config :jellyfish, jwt_max_age: 24 * 3600, media_files_path: @@ -34,8 +37,8 @@ config :jellyfish, metrics_port: ConfigReader.read_port("JF_METRICS_PORT") || 9568, dist_config: ConfigReader.read_dist_config(), webrtc_config: ConfigReader.read_webrtc_config(), - sip_config: ConfigReader.read_sip_config(), - recording_config: ConfigReader.read_recording_config(), + components_used: components_used, + sip_config: ConfigReader.read_sip_config(sip_used?), s3_config: ConfigReader.read_s3_config(), git_commit: ConfigReader.read_git_commit() diff --git a/lib/jellyfish/application.ex b/lib/jellyfish/application.ex index ebf8353f..1beba9d8 100644 --- a/lib/jellyfish/application.ex +++ b/lib/jellyfish/application.ex @@ -20,10 +20,12 @@ defmodule Jellyfish.Application do dist_config = Application.fetch_env!(:jellyfish, :dist_config) webrtc_config = Application.fetch_env!(:jellyfish, :webrtc_config) git_commit = Application.get_env(:jellyfish, :git_commit) + components_used = Application.get_env(:jellyfish, :components_used) Logger.info("Starting Jellyfish v#{Jellyfish.version()} (#{git_commit})") Logger.info("Distribution config: #{inspect(Keyword.delete(dist_config, :cookie))}") Logger.info("WebRTC config: #{inspect(webrtc_config)}") + Logger.info("Allowed components: #{inspect(components_used)}") children = [ diff --git a/lib/jellyfish/component/recording.ex b/lib/jellyfish/component/recording.ex index d6d1a6bc..b8036990 100644 --- a/lib/jellyfish/component/recording.ex +++ b/lib/jellyfish/component/recording.ex @@ -13,15 +13,8 @@ defmodule Jellyfish.Component.Recording do @impl true def config(%{engine_pid: engine} = options) do - recording_config = Application.fetch_env!(:jellyfish, :recording_config) sink_config = Application.fetch_env!(:jellyfish, :s3_config) - unless recording_config[:recording_used?], - do: - raise(""" - Recording components can only be used if JF_RECORDING_USED environmental variable is set to \"true\" - """) - with {:ok, serialized_opts} <- serialize_options(options, Options.schema()), result_opts <- parse_subscribe_mode(serialized_opts), {:ok, credentials} <- get_credentials(serialized_opts, sink_config), diff --git a/lib/jellyfish/component/sip.ex b/lib/jellyfish/component/sip.ex index 589db844..cf90c164 100644 --- a/lib/jellyfish/component/sip.ex +++ b/lib/jellyfish/component/sip.ex @@ -21,16 +21,7 @@ defmodule Jellyfish.Component.SIP do @impl true def config(%{engine_pid: engine} = options) do - sip_config = Application.fetch_env!(:jellyfish, :sip_config) - - external_ip = - if sip_config[:sip_used?] do - Application.fetch_env!(:jellyfish, :sip_config)[:sip_external_ip] - else - raise """ - SIP components can only be used if JF_SIP_USED environmental variable is set to \"true\" - """ - end + external_ip = Application.fetch_env!(:jellyfish, :sip_config)[:sip_external_ip] with {:ok, serialized_opts} <- serialize_options(options, Options.schema()) do endpoint_spec = %SIP{ diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex index 8b5084b7..572abb5e 100644 --- a/lib/jellyfish/config_reader.ex +++ b/lib/jellyfish/config_reader.ex @@ -128,36 +128,45 @@ defmodule Jellyfish.ConfigReader do end end - def read_sip_config() do - sip_used? = read_boolean("JF_SIP_USED") + def read_components_used() do + components_used = System.get_env("JF_COMPONENTS_USED") || "" + + components_used + |> String.split(" ", trim: true) + |> Enum.map(fn type -> + case Jellyfish.Component.parse_type(type) do + {:ok, component} -> + component + + {:error, :invalid_type} -> + raise( + "Invalid value in JF_COMPONENTS_USED. Expected a lowercase component name, got: #{type}" + ) + end + end) + end + + def read_sip_config(sip_used?) do sip_ip = System.get_env("JF_SIP_IP") || "" cond do sip_used? != true -> [ - sip_used?: false, sip_external_ip: nil ] ip_address?(sip_ip) -> [ - sip_used?: true, sip_external_ip: sip_ip ] true -> raise """ - JF_SIP_USED has been set to true but incorrect IP address was provided as `JF_SIP_IP` + SIP components are allowed, but incorrect IP address was provided as `JF_SIP_IP` """ end end - def read_recording_config() do - [ - recording_used?: read_boolean("JF_RECORDING_USED") != false - ] - end - def read_s3_config() do credentials = [ bucket: System.get_env("JF_S3_BUCKET"), diff --git a/lib/jellyfish/peer.ex b/lib/jellyfish/peer.ex index 78dcad36..ae00ef01 100644 --- a/lib/jellyfish/peer.ex +++ b/lib/jellyfish/peer.ex @@ -51,6 +51,14 @@ defmodule Jellyfish.Peer do end end + @spec to_string!(module()) :: String.t() + def to_string!(peer) do + case peer do + WebRTC -> "webrtc" + _other -> raise "Invalid peer" + end + end + @spec new(peer(), map()) :: {:ok, t()} | {:error, term()} def new(type, options) do id = UUID.uuid4() diff --git a/lib/jellyfish/peer/webrtc.ex b/lib/jellyfish/peer/webrtc.ex index a3a34807..aa34bec3 100644 --- a/lib/jellyfish/peer/webrtc.ex +++ b/lib/jellyfish/peer/webrtc.ex @@ -14,12 +14,6 @@ defmodule Jellyfish.Peer.WebRTC do @impl true def config(options) do - if not Application.fetch_env!(:jellyfish, :webrtc_config)[:webrtc_used?] do - raise( - "WebRTC peers can only be used if JF_WEBRTC_USED environmental variable is not set to \"false\"" - ) - end - with {:ok, valid_opts} <- OpenApiSpex.cast_value(options, ApiSpec.Peer.WebRTC.schema()) do handshake_options = [ client_mode: false, diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index 47be9ebf..62de054c 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -67,7 +67,9 @@ defmodule Jellyfish.Room do end @spec add_peer(id(), Peer.peer(), map()) :: - {:ok, Peer.t()} | :error | {:error, :reached_peers_limit} + {:ok, Peer.t()} + | :error + | {:error, {:peer_disabled_globally, String.t()} | {:reached_peers_limit, String.t()}} def add_peer(room_id, peer_type, options \\ %{}) do GenServer.call(registry_id(room_id), {:add_peer, peer_type, options}) end @@ -92,7 +94,19 @@ defmodule Jellyfish.Room do @spec add_component(id(), Component.component(), map()) :: {:ok, Component.t()} | :error - | {:error, :incompatible_codec | :reached_components_limit_hls} + | {:error, + {:component_disabled_globally, String.t()} + | :incompatible_codec + | {:reached_components_limit, String.t()} + | :file_does_not_exist + | :bad_parameter_framerate_for_audio + | :invalid_framerate + | :invalid_file_path + | :unsupported_file_type + | {:missing_parameter, term()} + | :missing_s3_credentials + | :overriding_credentials + | :overriding_path_prefix} def add_component(room_id, component_type, options \\ %{}) do GenServer.call(registry_id(room_id), {:add_component, component_type, options}) end @@ -142,15 +156,22 @@ defmodule Jellyfish.Room do @impl true def handle_call({:add_peer, peer_type, override_options}, _from, state) do - with false <- State.reached_peers_limit?(state), + with :ok <- State.check_peer_allowed(peer_type, state), options <- State.generate_peer_options(state, override_options), {:ok, peer} <- Peer.new(peer_type, options) do state = State.add_peer(state, peer) {:reply, {:ok, peer}, state} else - true -> - {:reply, {:error, :reached_peers_limit}, state} + {:error, :peer_disabled_globally} -> + type = Peer.to_string!(peer_type) + Logger.warning("Unable to add peer: #{type} peers are disabled globally") + {:reply, {:error, {:peer_disabled_globally, type}}, state} + + {:error, :reached_peers_limit} -> + type = Peer.to_string!(peer_type) + Logger.warning("Unable to add peer: Reached #{type} peers limit") + {:reply, {:error, {:reached_peers_limit, type}}, state} {:error, reason} -> Logger.warning("Unable to add peer: #{inspect(reason)}") @@ -214,7 +235,7 @@ defmodule Jellyfish.Room do options ) - with :ok <- check_component_allowed(component_type, state), + with :ok <- State.check_component_allowed(component_type, state), {:ok, component} <- Component.new(component_type, options) do state = State.put_component(state, component) @@ -226,13 +247,18 @@ defmodule Jellyfish.Room do {:reply, {:ok, component}, state} else + {:error, :component_disabled_globally} -> + type = Component.to_string!(component_type) + Logger.warning("Unable to add component: #{type} components are disabled globally") + {:reply, {:error, {:component_disabled_globally, type}}, state} + {:error, :incompatible_codec} -> Logger.warning("Unable to add component: incompatible codec") {:reply, {:error, :incompatible_codec}, state} {:error, :reached_components_limit} -> type = Component.to_string!(component_type) - Logger.warning("Unable to add component: reached components limit #{type}") + Logger.warning("Unable to add component: reached #{type} components limit") {:reply, {:error, {:reached_components_limit, type}}, state} {:error, :file_does_not_exist} -> @@ -240,13 +266,12 @@ defmodule Jellyfish.Room do {:reply, {:error, :file_does_not_exist}, state} {:error, :bad_parameter_framerate_for_audio} -> - Logger.warning("Attempted to set framerate for audio component which is not supported.") - + Logger.warning("Unable to add component: attempted to set framerate for audio component") {:reply, {:error, :bad_parameter_framerate_for_audio}, state} {:error, {:invalid_framerate, passed_framerate}} -> Logger.warning( - "Invalid framerate value: #{passed_framerate}. It has to be a positivie integer." + "Unable to add component: expected framerate to be a positive integer, got: #{passed_framerate}" ) {:reply, {:error, :invalid_framerate}, state} @@ -256,7 +281,7 @@ defmodule Jellyfish.Room do {:reply, {:error, :invalid_file_path}, state} {:error, :unsupported_file_type} -> - Logger.warning("Unable to add component: unsupported file path") + Logger.warning("Unable to add component: unsupported file type") {:reply, {:error, :unsupported_file_type}, state} {:error, {:missing_parameter, name}} -> diff --git a/lib/jellyfish/room/state.ex b/lib/jellyfish/room/state.ex index 1f239cf6..8cf9fe63 100644 --- a/lib/jellyfish/room/state.ex +++ b/lib/jellyfish/room/state.ex @@ -382,33 +382,33 @@ defmodule Jellyfish.Room.State do ) end - def check_component_allowed(type, %{ - config: %{video_codec: video_codec}, - components: components - }) - when type in [HLS, Recording] do + @spec check_peer_allowed(Peer.peer(), t()) :: + :ok | {:error, :peer_disabled_globally | :reached_peers_limit} + def check_peer_allowed(Peer.WebRTC, state) do cond do - video_codec != :h264 -> - {:error, :incompatible_codec} + not Application.fetch_env!(:jellyfish, :webrtc_config)[:webrtc_used?] -> + {:error, :peer_disabled_globally} - component_already_present?(type, components) -> - {:error, :reached_components_limit} + Enum.count(state.peers) >= state.config.max_peers -> + {:error, :reached_peers_limit} true -> :ok end end - def check_component_allowed(RTSP, %{config: %{video_codec: video_codec}}) do - # Right now, RTSP component can only publish H264, so there's no point adding it - # to a room which allows another video codec, e.g. VP8 - if video_codec == :h264, - do: :ok, - else: {:error, :incompatible_codec} + @spec check_component_allowed(Component.component(), t()) :: + :ok + | {:error, + :component_disabled_globally | :incompatible_codec | :reached_components_limit} + def check_component_allowed(type, state) do + if type in Application.fetch_env!(:jellyfish, :components_used) do + check_component_allowed_in_room(type, state) + else + {:error, :component_disabled_globally} + end end - def check_component_allowed(_component_type, _state), do: :ok - @spec get_endpoint_id_type(state :: t(), endpoint_id :: endpoint_id()) :: :peer_id | :component_id def get_endpoint_id_type(state, endpoint_id) do @@ -435,6 +435,33 @@ defmodule Jellyfish.Room.State do def validate_subscription_mode(%{properties: %{subscribe_mode: :manual}}), do: :ok def validate_subscription_mode(_not_properties), do: {:error, :invalid_component_type} + defp check_component_allowed_in_room(type, %{ + config: %{video_codec: video_codec}, + components: components + }) + when type in [HLS, Recording] do + cond do + video_codec != :h264 -> + {:error, :incompatible_codec} + + component_already_present?(type, components) -> + {:error, :reached_components_limit} + + true -> + :ok + end + end + + defp check_component_allowed_in_room(RTSP, %{config: %{video_codec: video_codec}}) do + # Right now, RTSP component can only publish H264, so there's no point adding it + # to a room which allows another video codec, e.g. VP8 + if video_codec == :h264, + do: :ok, + else: {:error, :incompatible_codec} + end + + defp check_component_allowed_in_room(_component_type, _state), do: :ok + defp component_already_present?(type, components), do: components |> Map.values() |> Enum.any?(&(&1.type == type)) diff --git a/lib/jellyfish_web/controllers/component_controller.ex b/lib/jellyfish_web/controllers/component_controller.ex index efc9fe92..443fdc3b 100644 --- a/lib/jellyfish_web/controllers/component_controller.ex +++ b/lib/jellyfish_web/controllers/component_controller.ex @@ -76,49 +76,51 @@ defmodule JellyfishWeb.ComponentController do :error -> {:error, :bad_request, "Invalid request body structure"} - {:error, {:missing_parameter, name}} -> - {:error, :bad_request, "Required field \"#{Atom.to_string(name)}\" missing"} - - {:error, :missing_s3_credentials} -> - {:error, :bad_request, - "S3 credentials has to be passed either by request or at application startup as envs"} - - {:error, :overridding_credentials} -> - {:error, :bad_request, - "Conflicting S3 credentials supplied via environment variables and the REST API. Overrides on existing values are disallowed"} - - {:error, :overridding_path_prefix} -> - {:error, :bad_request, - "Conflicting S3 path prefix supplied via environment variables and the REST API. Overrides on existing values are disallowed"} + {:error, :room_not_found} -> + {:error, :not_found, "Room #{room_id} does not exist"} {:error, :invalid_type} -> {:error, :bad_request, "Invalid component type"} - {:error, :room_not_found} -> - {:error, :not_found, "Room #{room_id} does not exist"} + {:error, {:component_disabled_globally, type}} -> + {:error, :bad_request, "Components of type #{type} are disabled on this Jellyfish"} {:error, :incompatible_codec} -> {:error, :bad_request, "Incompatible video codec enforced in room #{room_id}"} - {:error, :invalid_framerate} -> - {:error, :bad_request, "Invalid framerate passed"} + {:error, {:reached_components_limit, type}} -> + {:error, :bad_request, "Reached #{type} components limit in room #{room_id}"} + + {:error, :file_does_not_exist} -> + {:error, :not_found, "File not found"} {:error, :bad_parameter_framerate_for_audio} -> {:error, :bad_request, "Attempted to set framerate for audio component which is not supported."} + {:error, :invalid_framerate} -> + {:error, :bad_request, "Invalid framerate passed"} + {:error, :invalid_file_path} -> {:error, :bad_request, "Invalid file path"} - {:error, :file_does_not_exist} -> - {:error, :not_found, "File not found"} - {:error, :unsupported_file_type} -> {:error, :bad_request, "Unsupported file type"} - {:error, {:reached_components_limit, type}} -> + {:error, {:missing_parameter, name}} -> + {:error, :bad_request, "Required field \"#{Atom.to_string(name)}\" missing"} + + {:error, :missing_s3_credentials} -> + {:error, :bad_request, + "S3 credentials has to be passed either by request or at application startup as envs"} + + {:error, :overridding_credentials} -> {:error, :bad_request, - "Reached #{type} components limit for component in room #{room_id}"} + "Conflicting S3 credentials supplied via environment variables and the REST API. Overrides on existing values are disallowed"} + + {:error, :overridding_path_prefix} -> + {:error, :bad_request, + "Conflicting S3 path prefix supplied via environment variables and the REST API. Overrides on existing values are disallowed"} end end diff --git a/lib/jellyfish_web/controllers/peer_controller.ex b/lib/jellyfish_web/controllers/peer_controller.ex index 4be11780..39d846ba 100644 --- a/lib/jellyfish_web/controllers/peer_controller.ex +++ b/lib/jellyfish_web/controllers/peer_controller.ex @@ -88,14 +88,17 @@ defmodule JellyfishWeb.PeerController do :error -> {:error, :bad_request, "Invalid request body structure"} + {:error, :room_not_found} -> + {:error, :not_found, "Room #{room_id} does not exist"} + {:error, :invalid_type} -> {:error, :bad_request, "Invalid peer type"} - {:error, :room_not_found} -> - {:error, :not_found, "Room #{room_id} does not exist"} + {:error, {:peer_disabled_globally, type}} -> + {:error, :bad_request, "Peers of type #{type} are disabled on this Jellyfish"} - {:error, :reached_peers_limit} -> - {:error, :service_unavailable, "Reached peer limit in room #{room_id}"} + {:error, {:reached_peers_limit, type}} -> + {:error, :service_unavailable, "Reached #{type} peers limit in room #{room_id}"} end end diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs index 84c73c27..05c6eb92 100644 --- a/test/jellyfish/config_reader_test.exs +++ b/test/jellyfish/config_reader_test.exs @@ -151,18 +151,35 @@ defmodule Jellyfish.ConfigReaderTest do end end - test "read_sip_config/0" do - with_env ["JF_SIP_USED", "JF_SIP_IP"] do - assert ConfigReader.read_sip_config() == [sip_used?: false, sip_external_ip: nil] + test "read_components_used/0" do + with_env ["JF_COMPONENTS_USED"] do + assert ConfigReader.read_components_used() == [] - System.put_env("JF_SIP_USED", "true") - assert_raise RuntimeError, fn -> ConfigReader.read_sip_config() end + System.put_env("JF_COMPONENTS_USED", "hls") + assert ConfigReader.read_components_used() == [Jellyfish.Component.HLS] + + System.put_env("JF_COMPONENTS_USED", "recording rtsp sip ") + + assert ConfigReader.read_components_used() |> Enum.sort() == + [Jellyfish.Component.Recording, Jellyfish.Component.RTSP, Jellyfish.Component.SIP] + |> Enum.sort() + + System.put_env("JF_COMPONENTS_USED", "file rtsp invalid_component") + assert_raise RuntimeError, fn -> ConfigReader.read_components_used() end + end + end + + test "read_sip_config/1" do + with_env ["JF_SIP_IP"] do + assert ConfigReader.read_sip_config(false) == [sip_external_ip: nil] + + assert_raise RuntimeError, fn -> ConfigReader.read_sip_config(true) end System.put_env("JF_SIP_IP", "abcdefg") - assert_raise RuntimeError, fn -> ConfigReader.read_sip_config() end + assert_raise RuntimeError, fn -> ConfigReader.read_sip_config(true) end System.put_env("JF_SIP_IP", "127.0.0.1") - assert ConfigReader.read_sip_config() == [sip_used?: true, sip_external_ip: "127.0.0.1"] + assert ConfigReader.read_sip_config(true) == [sip_external_ip: "127.0.0.1"] end end diff --git a/test/jellyfish_web/controllers/component/file_component_test.exs b/test/jellyfish_web/controllers/component/file_component_test.exs index 856abd5d..6055a8e2 100644 --- a/test/jellyfish_web/controllers/component/file_component_test.exs +++ b/test/jellyfish_web/controllers/component/file_component_test.exs @@ -21,6 +21,8 @@ defmodule JellyfishWeb.Component.FileComponentTest do @auth_response %Authenticated{} setup_all _tags do + Application.put_env(:jellyfish, :components_used, [Jellyfish.Component.File]) + media_sources_directory = Application.fetch_env!(:jellyfish, :media_files_path) |> Path.join(@file_component_directory) @@ -30,7 +32,10 @@ defmodule JellyfishWeb.Component.FileComponentTest do File.cp_r!(@fixtures_directory, media_sources_directory) - on_exit(fn -> :file.del_dir_r(media_sources_directory) end) + on_exit(fn -> + :file.del_dir_r(media_sources_directory) + Application.put_env(:jellyfish, :components_used, []) + end) {:ok, %{media_sources_directory: media_sources_directory}} end diff --git a/test/jellyfish_web/controllers/component/hls_component_test.exs b/test/jellyfish_web/controllers/component/hls_component_test.exs index 9f3ea738..aafe0f21 100644 --- a/test/jellyfish_web/controllers/component/hls_component_test.exs +++ b/test/jellyfish_web/controllers/component/hls_component_test.exs @@ -21,6 +21,14 @@ defmodule JellyfishWeb.Component.HlsComponentTest do } |> map_keys_to_string() + setup_all do + Application.put_env(:jellyfish, :components_used, [HLS]) + + on_exit(fn -> + Application.put_env(:jellyfish, :components_used, []) + end) + end + describe "create hls component" do setup [:create_h264_room] @@ -46,7 +54,7 @@ defmodule JellyfishWeb.Component.HlsComponentTest do conn = post(conn, ~p"/room/#{room_id}/component", type: "hls") assert model_response(conn, :bad_request, "Error")["errors"] == - "Reached hls components limit for component in room #{room_id}" + "Reached hls components limit in room #{room_id}" conn = delete(conn, ~p"/room/#{room_id}") assert response(conn, :no_content) diff --git a/test/jellyfish_web/controllers/component/recording_component_test.exs b/test/jellyfish_web/controllers/component/recording_component_test.exs index ac1642ee..1dfaa3b0 100644 --- a/test/jellyfish_web/controllers/component/recording_component_test.exs +++ b/test/jellyfish_web/controllers/component/recording_component_test.exs @@ -15,6 +15,14 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do @path_prefix "path_prefix" + setup_all do + Application.put_env(:jellyfish, :components_used, [Jellyfish.Component.Recording]) + + on_exit(fn -> + Application.put_env(:jellyfish, :components_used, []) + end) + end + describe "create recording component" do setup [:create_h264_room] setup :set_mox_from_context @@ -49,7 +57,7 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do conn = post(conn, ~p"/room/#{room_id}/component", type: "recording") assert model_response(conn, :bad_request, "Error")["errors"] == - "Reached recording components limit for component in room #{room_id}" + "Reached recording components limit in room #{room_id}" end setup :set_mox_from_context diff --git a/test/jellyfish_web/controllers/component/rtsp_component_test.exs b/test/jellyfish_web/controllers/component/rtsp_component_test.exs index cc61b56f..9372db6b 100644 --- a/test/jellyfish_web/controllers/component/rtsp_component_test.exs +++ b/test/jellyfish_web/controllers/component/rtsp_component_test.exs @@ -22,6 +22,14 @@ defmodule JellyfishWeb.Component.RTSPComponentTest do } @rtsp_custom_properties @rtsp_custom_options |> map_keys_to_string() + setup_all do + Application.put_env(:jellyfish, :components_used, [Jellyfish.Component.RTSP]) + + on_exit(fn -> + Application.put_env(:jellyfish, :components_used, []) + end) + end + describe "create rtsp component" do setup [:create_h264_room] diff --git a/test/jellyfish_web/controllers/component/sip_component_test.exs b/test/jellyfish_web/controllers/component/sip_component_test.exs index 85994fc3..9a3a4826 100644 --- a/test/jellyfish_web/controllers/component/sip_component_test.exs +++ b/test/jellyfish_web/controllers/component/sip_component_test.exs @@ -14,10 +14,12 @@ defmodule JellyfishWeb.Component.SIPComponentTest do |> map_keys_to_string() setup_all do - Application.put_env(:jellyfish, :sip_config, sip_used?: true, sip_external_ip: "127.0.0.1") + Application.put_env(:jellyfish, :sip_config, sip_external_ip: "127.0.0.1") + Application.put_env(:jellyfish, :components_used, [Jellyfish.Component.SIP]) on_exit(fn -> - Application.put_env(:jellyfish, :sip_config, sip_used?: false, sip_external_ip: nil) + Application.put_env(:jellyfish, :sip_config, sip_external_ip: nil) + Application.put_env(:jellyfish, :components_used, []) end) end diff --git a/test/jellyfish_web/controllers/component_controller_test.exs b/test/jellyfish_web/controllers/component_controller_test.exs index 3d5c3b75..cb73719a 100644 --- a/test/jellyfish_web/controllers/component_controller_test.exs +++ b/test/jellyfish_web/controllers/component_controller_test.exs @@ -4,6 +4,17 @@ defmodule JellyfishWeb.ComponentControllerTest do @source_uri "rtsp://placeholder-19inrifjbsjb.it:12345/afwefae" + setup_all do + Application.put_env(:jellyfish, :components_used, [ + Jellyfish.Component.RTSP, + Jellyfish.Component.HLS + ]) + + on_exit(fn -> + Application.put_env(:jellyfish, :components_used, []) + end) + end + describe "create component" do test "renders errors when component type is invalid", %{conn: conn, room_id: room_id} do conn = post(conn, ~p"/room/#{room_id}/component", type: "invalid_type") @@ -19,6 +30,22 @@ defmodule JellyfishWeb.ComponentControllerTest do response = model_response(conn, :not_found, "Error") assert response["errors"] == "Room #{room_id} does not exist" end + + test "renders errors when component isn't allowed globally", %{conn: conn, room_id: room_id} do + Application.put_env(:jellyfish, :components_used, []) + + on_exit(fn -> + Application.put_env(:jellyfish, :components_used, [ + Jellyfish.Component.RTSP, + Jellyfish.Component.HLS + ]) + end) + + conn = post(conn, ~p"/room/#{room_id}/component", type: "hls") + + response = model_response(conn, :bad_request, "Error") + assert response["errors"] == "Components of type hls are disabled on this Jellyfish" + end end describe "delete component" do diff --git a/test/jellyfish_web/controllers/dial_controller_test.exs b/test/jellyfish_web/controllers/dial_controller_test.exs index 981d6d98..17c4c09f 100644 --- a/test/jellyfish_web/controllers/dial_controller_test.exs +++ b/test/jellyfish_web/controllers/dial_controller_test.exs @@ -10,10 +10,16 @@ defmodule JellyfishWeb.DialControllerTest do } setup_all do - Application.put_env(:jellyfish, :sip_config, sip_used?: true, sip_external_ip: "127.0.0.1") + Application.put_env(:jellyfish, :sip_config, sip_external_ip: "127.0.0.1") + + Application.put_env(:jellyfish, :components_used, [ + Jellyfish.Component.SIP, + Jellyfish.Component.RTSP + ]) on_exit(fn -> - Application.put_env(:jellyfish, :sip_config, sip_used?: false, sip_external_ip: nil) + Application.put_env(:jellyfish, :sip_config, sip_external_ip: nil) + Application.put_env(:jellyfish, :components_used, []) end) end diff --git a/test/jellyfish_web/controllers/peer_controller_test.exs b/test/jellyfish_web/controllers/peer_controller_test.exs index 81d33c7d..646397c5 100644 --- a/test/jellyfish_web/controllers/peer_controller_test.exs +++ b/test/jellyfish_web/controllers/peer_controller_test.exs @@ -76,7 +76,7 @@ defmodule JellyfishWeb.PeerControllerTest do conn = post(conn, ~p"/room/#{room_id}/peer", type: @peer_type) assert json_response(conn, :service_unavailable)["errors"] == - "Reached peer limit in room #{room_id}" + "Reached webrtc peers limit in room #{room_id}" end test "renders errors when room doesn't exist", %{conn: conn} do @@ -89,6 +89,19 @@ defmodule JellyfishWeb.PeerControllerTest do conn = post(conn, ~p"/room/#{room_id}/peer", invalid_param: @peer_type) assert json_response(conn, :bad_request)["errors"] == "Invalid request body structure" end + + test "renders errors when peer isn't allowed globally", %{conn: conn, room_id: room_id} do + Application.put_env(:jellyfish, :webrtc_config, webrtc_used?: false) + + on_exit(fn -> + Application.put_env(:jellyfish, :webrtc_config, webrtc_used?: true) + end) + + conn = post(conn, ~p"/room/#{room_id}/peer", type: @peer_type) + + assert json_response(conn, :bad_request)["errors"] == + "Peers of type webrtc are disabled on this Jellyfish" + end end describe "delete peer" do diff --git a/test/jellyfish_web/controllers/subscription_controller_test.exs b/test/jellyfish_web/controllers/subscription_controller_test.exs index f43ba25e..6fe03d3d 100644 --- a/test/jellyfish_web/controllers/subscription_controller_test.exs +++ b/test/jellyfish_web/controllers/subscription_controller_test.exs @@ -14,6 +14,21 @@ defmodule JellyfishWeb.SubscriptionControllerTest do password: "pass-word" } + setup_all do + Application.put_env(:jellyfish, :sip_config, sip_external_ip: "127.0.0.1") + + Application.put_env(:jellyfish, :components_used, [ + Jellyfish.Component.SIP, + Jellyfish.Component.HLS, + Jellyfish.Component.Recording + ]) + + on_exit(fn -> + Application.put_env(:jellyfish, :sip_config, sip_external_ip: nil) + Application.put_env(:jellyfish, :components_used, []) + end) + end + setup %{conn: conn} do server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) @@ -54,12 +69,6 @@ defmodule JellyfishWeb.SubscriptionControllerTest do conn: conn, room_id: room_id } do - Application.put_env(:jellyfish, :sip_config, sip_used?: true, sip_external_ip: "127.0.0.1") - - on_exit(fn -> - Application.put_env(:jellyfish, :sip_config, sip_used?: false, sip_external_ip: nil) - end) - conn = post(conn, ~p"/room/#{room_id}/component", type: "sip", diff --git a/test/jellyfish_web/integration/server_notification_test.exs b/test/jellyfish_web/integration/server_notification_test.exs index 9eabe3d6..efdae952 100644 --- a/test/jellyfish_web/integration/server_notification_test.exs +++ b/test/jellyfish_web/integration/server_notification_test.exs @@ -9,6 +9,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do alias Membrane.RTC.Engine + alias Jellyfish.Component alias Jellyfish.Component.HLS alias Jellyfish.Component.HLS.Manager alias Jellyfish.{PeerMessage, Room, RoomService, ServerMessage} @@ -93,10 +94,18 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do end setup_all do - Application.put_env(:jellyfish, :sip_config, sip_used?: true, sip_external_ip: "127.0.0.1") + Application.put_env(:jellyfish, :sip_config, sip_external_ip: "127.0.0.1") + + Application.put_env(:jellyfish, :components_used, [ + Component.SIP, + Component.HLS, + Component.RTSP, + Component.File + ]) on_exit(fn -> - Application.put_env(:jellyfish, :sip_config, sip_used?: false, sip_external_ip: nil) + Application.put_env(:jellyfish, :sip_config, sip_external_ip: nil) + Application.put_env(:jellyfish, :components_used, []) end) assert {:ok, _pid} = Endpoint.start_link() From 7316dd0f1b0f78ff5bdcb1e4a3d7a542b6e5935d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Thu, 16 May 2024 17:06:31 +0200 Subject: [PATCH 33/51] Rename Jellyfish references in the code (#196) --- .github/workflows/deploy-image.yml | 2 +- .gitignore | 4 +- .gitmodules | 2 +- Dockerfile | 34 ++--- compile_proto.sh | 2 +- config/ci.exs | 4 +- config/config.exs | 12 +- config/dev.exs | 4 +- config/prod.exs | 4 +- config/runtime.exs | 42 ++--- config/test.exs | 4 +- docker-compose-dns.yaml | 32 ++-- docker-compose-epmd.yaml | 30 ++-- docker-entrypoint.sh | 6 +- docs/api_versioning.md | 14 +- docs/authentication_architecture.md | 40 ++--- docs/server_notifications.md | 6 +- docs/signaling_architecture.md | 44 +++--- examples/README.md | 10 +- examples/rtsp_to_hls.exs | 32 ++-- examples/rtsp_to_hls.py | 16 +- grafana/jellyfish-server-overiew.json | 28 ++-- lib/jellyfish.ex | 8 +- lib/jellyfish/application.ex | 58 +++---- lib/jellyfish/component.ex | 10 +- lib/jellyfish/component/file.ex | 14 +- lib/jellyfish/component/hls.ex | 14 +- lib/jellyfish/component/hls/ets_helper.ex | 4 +- lib/jellyfish/component/hls/httpoison.ex | 2 +- lib/jellyfish/component/hls/ll_storage.ex | 6 +- lib/jellyfish/component/hls/manager.ex | 8 +- lib/jellyfish/component/hls/recording.ex | 10 +- .../component/hls/request_handler.ex | 10 +- lib/jellyfish/component/hls/storage.ex | 4 +- lib/jellyfish/component/recording.ex | 12 +- lib/jellyfish/component/rtsp.ex | 8 +- lib/jellyfish/component/sip.ex | 10 +- lib/jellyfish/config_reader.ex | 106 +++++++------ lib/jellyfish/endpoint/config.ex | 2 +- lib/jellyfish/event.ex | 8 +- lib/jellyfish/metrics_scraper.ex | 6 +- lib/jellyfish/peer.ex | 6 +- lib/jellyfish/peer/webrtc.ex | 6 +- lib/jellyfish/resource_manager.ex | 6 +- lib/jellyfish/room.ex | 14 +- lib/jellyfish/room/config.ex | 2 +- lib/jellyfish/room/state.ex | 22 +-- lib/jellyfish/room_service.ex | 28 ++-- lib/jellyfish/track.ex | 2 +- lib/jellyfish/utils/parser_json.ex | 2 +- lib/jellyfish/utils/path_validation.ex | 6 +- lib/jellyfish/webhook_notifier.ex | 6 +- lib/jellyfish_web.ex | 10 +- lib/jellyfish_web/api_spec.ex | 10 +- lib/jellyfish_web/api_spec/component.ex | 4 +- lib/jellyfish_web/api_spec/component/file.ex | 4 +- lib/jellyfish_web/api_spec/component/hls.ex | 4 +- .../api_spec/component/recording.ex | 6 +- lib/jellyfish_web/api_spec/component/rtsp.ex | 4 +- lib/jellyfish_web/api_spec/component/sip.ex | 4 +- lib/jellyfish_web/api_spec/dial.ex | 2 +- lib/jellyfish_web/api_spec/error.ex | 2 +- lib/jellyfish_web/api_spec/health_report.ex | 16 +- lib/jellyfish_web/api_spec/hls.ex | 2 +- lib/jellyfish_web/api_spec/peer.ex | 10 +- lib/jellyfish_web/api_spec/peer/webrtc.ex | 2 +- lib/jellyfish_web/api_spec/responses.ex | 28 ++-- lib/jellyfish_web/api_spec/room.ex | 10 +- lib/jellyfish_web/api_spec/subscription.ex | 2 +- lib/jellyfish_web/api_spec/track.ex | 2 +- .../controllers/component_controller.ex | 16 +- .../controllers/component_json.ex | 6 +- lib/jellyfish_web/controllers/error_json.ex | 2 +- .../controllers/fallback_controller.ex | 4 +- .../controllers/healthcheck_controller.ex | 18 +-- .../controllers/healthcheck_json.ex | 2 +- .../controllers/hls_content_controller.ex | 12 +- .../controllers/peer_controller.ex | 20 +-- lib/jellyfish_web/controllers/peer_json.ex | 4 +- .../recording_content_controller.ex | 10 +- .../controllers/recording_controller.ex | 10 +- .../controllers/recording_json.ex | 2 +- .../controllers/room_controller.ex | 16 +- lib/jellyfish_web/controllers/room_json.ex | 16 +- .../controllers/sip_call_controller.ex | 12 +- .../controllers/subscription_controller.ex | 12 +- lib/jellyfish_web/endpoint.ex | 18 +-- lib/jellyfish_web/peer_socket.ex | 14 +- lib/jellyfish_web/peer_token.ex | 10 +- lib/jellyfish_web/router.ex | 16 +- lib/jellyfish_web/server_socket.ex | 10 +- lib/jellyfish_web/telemetry.ex | 48 +++--- lib/jellyfish_web/traffic_metrics_plug.ex | 6 +- .../peer_notifications.pb.ex | 14 +- .../server_notifications.pb.ex | 110 +++++++------ mix.exs | 24 +-- openapi.yaml | 140 ++++++++--------- protos | 2 +- rel/env.sh.eex | 20 +-- .../jellyfish/cluster/load_balancing_test.exs | 54 +++---- .../component/hls/ets_helper_test.exs | 4 +- .../component/hls/ll_storage_test.exs | 4 +- test/jellyfish/component/hls/manager_test.exs | 6 +- .../component/hls/request_handler_test.exs | 6 +- test/jellyfish/config_reader_test.exs | 144 +++++++++--------- test/jellyfish/resource_manager_test.exs | 8 +- .../component/file_component_test.exs | 18 +-- .../component/hls_component_test.exs | 20 +-- .../component/recording_component_test.exs | 16 +- .../component/rtsp_component_test.exs | 10 +- .../component/sip_component_test.exs | 14 +- .../controllers/component_controller_test.exs | 24 +-- .../controllers/dial_controller_test.exs | 18 +-- .../controllers/error_json_test.exs | 8 +- .../healthcheck_controller_test.exs | 10 +- .../controllers/hls_controller_test.exs | 10 +- .../controllers/peer_controller_test.exs | 20 +-- .../controllers/recording_controller_test.exs | 14 +- .../controllers/room_controller_test.exs | 26 ++-- .../subscription_controller_test.exs | 20 +-- .../integration/peer_socket_test.exs | 32 ++-- .../integration/server_notification_test.exs | 56 +++---- test/support/component_case.ex | 18 +-- test/support/conn_case.ex | 12 +- test/support/webhook_plug.ex | 4 +- test/support/ws.ex | 6 +- test/test_helper.exs | 2 +- 127 files changed, 1036 insertions(+), 1036 deletions(-) rename lib/protos/{jellyfish => fishjam}/peer_notifications.pb.ex (57%) rename lib/protos/{jellyfish => fishjam}/server_notifications.pb.ex (68%) diff --git a/.github/workflows/deploy-image.yml b/.github/workflows/deploy-image.yml index c224a5e1..6015f067 100644 --- a/.github/workflows/deploy-image.yml +++ b/.github/workflows/deploy-image.yml @@ -51,4 +51,4 @@ jobs: tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max - build-args: JF_GIT_COMMIT=${{ env.sha_short }} + build-args: FJ_GIT_COMMIT=${{ env.sha_short }} diff --git a/.gitignore b/.gitignore index 031ab898..b74d702c 100644 --- a/.gitignore +++ b/.gitignore @@ -183,8 +183,8 @@ $RECYCLE.BIN/ # End of https://www.gitignore.io/api/c,vim,linux,macos,elixir,windows,visualstudiocode -# Default Jellyfish directory for storing multimedia files -jellyfish_resources +# Default Fishjam directory for storing multimedia files +fishjam_resources # asdf diff --git a/.gitmodules b/.gitmodules index 9fc0cb9d..d97d52f1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "protos"] path = protos - url = https://github.com/jellyfish-dev/protos.git + url = https://github.com/fishjam-dev/protos.git branch = master diff --git a/Dockerfile b/Dockerfile index 969d8363..c6e494ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,9 +26,9 @@ ENV MIX_ENV=prod # The order of the following commands is important. # It ensures that: # * any changes in the `lib` directory will only trigger -# jellyfish compilation +# fishjam compilation # * any changes in the `config` directory will -# trigger both jellyfish and deps compilation +# trigger both fishjam and deps compilation # but not deps fetching # * any changes in the `config/runtime.exs` won't trigger # anything @@ -51,10 +51,10 @@ RUN mix release FROM alpine:3.17 AS app -ARG JF_GIT_COMMIT -ENV JF_GIT_COMMIT=$JF_GIT_COMMIT +ARG FJ_GIT_COMMIT +ENV FJ_GIT_COMMIT=$FJ_GIT_COMMIT -RUN addgroup -S jellyfish && adduser -S jellyfish -G jellyfish +RUN addgroup -S fishjam && adduser -S fishjam -G fishjam # We run the whole image as root, fix permissions in # the docker-entrypoint.sh and then use gosu to step-down @@ -104,33 +104,33 @@ RUN \ WORKDIR /app -# base path where jellyfish media files are stored -ENV JF_RESOURCES_BASE_PATH=./jellyfish_resources +# base path where fishjam media files are stored +ENV FJ_RESOURCES_BASE_PATH=./fishjam_resources # override default (127, 0, 0, 1) IP by 0.0.0.0 # as docker doesn't allow for connections outside the # container when we listen to 127.0.0.1 -ENV JF_IP=0.0.0.0 -ENV JF_METRICS_IP=0.0.0.0 +ENV FJ_IP=0.0.0.0 +ENV FJ_METRICS_IP=0.0.0.0 -ENV JF_DIST_MIN_PORT=9000 -ENV JF_DIST_MAX_PORT=9000 +ENV FJ_DIST_MIN_PORT=9000 +ENV FJ_DIST_MAX_PORT=9000 -RUN mkdir ${JF_RESOURCES_BASE_PATH} && chown jellyfish:jellyfish ${JF_RESOURCES_BASE_PATH} +RUN mkdir ${FJ_RESOURCES_BASE_PATH} && chown fishjam:fishjam ${FJ_RESOURCES_BASE_PATH} # Create directory for File Component sources -RUN mkdir ${JF_RESOURCES_BASE_PATH}/file_component_sources \ - && chown jellyfish:jellyfish ${JF_RESOURCES_BASE_PATH}/file_component_sources +RUN mkdir ${FJ_RESOURCES_BASE_PATH}/file_component_sources \ + && chown fishjam:fishjam ${FJ_RESOURCES_BASE_PATH}/file_component_sources -COPY --from=build /app/_build/prod/rel/jellyfish ./ +COPY --from=build /app/_build/prod/rel/fishjam ./ COPY docker-entrypoint.sh ./docker-entrypoint.sh RUN chmod +x docker-entrypoint.sh ENV HOME=/app -HEALTHCHECK CMD curl --fail -H "authorization: Bearer ${JF_SERVER_API_TOKEN}" http://localhost:${JF_PORT:-8080}/health || exit 1 +HEALTHCHECK CMD curl --fail -H "authorization: Bearer ${FJ_SERVER_API_TOKEN}" http://localhost:${FJ_PORT:-8080}/health || exit 1 ENTRYPOINT ["./docker-entrypoint.sh"] -CMD ["bin/jellyfish", "start"] +CMD ["bin/fishjam", "start"] diff --git a/compile_proto.sh b/compile_proto.sh index cad73037..419ee8f6 100755 --- a/compile_proto.sh +++ b/compile_proto.sh @@ -9,7 +9,7 @@ git submodule sync --recursive >> /dev/null git submodule update --recursive --remote --init >> /dev/null printf "DONE\n\n" -files=$(find protos/jellyfish -name "*.proto") +files=$(find protos/fishjam -name "*.proto") printf "Compiling:\n" count=1 diff --git a/config/ci.exs b/config/ci.exs index b664fff0..8c2f45d4 100644 --- a/config/ci.exs +++ b/config/ci.exs @@ -1,10 +1,10 @@ import Config -config :jellyfish, ip: {127, 0, 0, 1}, port: 4002, server_api_token: "development" +config :fishjam, ip: {127, 0, 0, 1}, port: 4002, server_api_token: "development" # We don't run a server during test. If one is required, # you can enable the server option below. -config :jellyfish, JellyfishWeb.Endpoint, server: false +config :fishjam, FishjamWeb.Endpoint, server: false # Initialize plugs at runtime for faster test compilation config :phoenix, :plug_init_mode, :runtime diff --git a/config/config.exs b/config/config.exs index e9a89f74..786c8080 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,15 +1,15 @@ import Config -config :jellyfish, JellyfishWeb.Endpoint, +config :fishjam, FishjamWeb.Endpoint, url: [host: "localhost"], render_errors: [ - formats: [json: JellyfishWeb.ErrorJSON], + formats: [json: FishjamWeb.ErrorJSON], layout: false ], - pubsub_server: Jellyfish.PubSub, + pubsub_server: Fishjam.PubSub, live_view: [signing_salt: "/Lo03qJT"] -config :jellyfish, +config :fishjam, webrtc_metrics_scrape_interval: 1000, room_metrics_scrape_interval: 10 @@ -37,12 +37,12 @@ config :logger, [application: :membrane_rtc_engine_sip, level_lower_than: :warning] ] -config :jellyfish, +config :fishjam, divo: "docker-compose.yaml", divo_wait: [dwell: 1_500, max_tries: 50] config :ex_aws, - http_client: Jellyfish.Component.HLS.HTTPoison, + http_client: Fishjam.Component.HLS.HTTPoison, normalize_path: false config :bundlex, :disable_precompiled_os_deps, apps: [:membrane_h264_ffmpeg_plugin, :ex_libsrtp] diff --git a/config/dev.exs b/config/dev.exs index 550fd318..3e6e61f0 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -2,7 +2,7 @@ import Config # Binding to loopback ipv4 address prevents access from other machines. # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. -config :jellyfish, +config :fishjam, ip: {127, 0, 0, 1}, port: 5002, server_api_token: "development", @@ -14,7 +14,7 @@ config :jellyfish, # The watchers configuration can be used to run external # watchers to your application. For example, we use it # with esbuild to bundle .js and .css sources. -config :jellyfish, JellyfishWeb.Endpoint, +config :fishjam, FishjamWeb.Endpoint, check_origin: false, code_reloader: true, debug_errors: true, diff --git a/config/prod.exs b/config/prod.exs index bf9b68aa..1499af52 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -7,12 +7,12 @@ import Config # Do not print debug messages in production config :logger, level: :info -config :jellyfish, +config :fishjam, ip: {127, 0, 0, 1}, port: 8080 # run the server automatically when using prod release -config :jellyfish, JellyfishWeb.Endpoint, server: true +config :fishjam, FishjamWeb.Endpoint, server: true # Runtime production configuration, including reading # of environment variables, is done on config/runtime.exs. diff --git a/config/runtime.exs b/config/runtime.exs index f00aedca..47b4e331 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -1,6 +1,6 @@ import Config -alias Jellyfish.ConfigReader +alias Fishjam.ConfigReader # config/runtime.exs is executed for all environments, including # during releases. It is executed after compilation and before the @@ -10,31 +10,31 @@ alias Jellyfish.ConfigReader # The block below contains prod specific runtime configuration. config :ex_dtls, impl: :nif -structured_logging? = ConfigReader.read_boolean("JF_STRUCTURED_LOGGING") || false +structured_logging? = ConfigReader.read_boolean("FJ_STRUCTURED_LOGGING") || false config :logger, backends: [if(structured_logging?, do: LoggerJSON, else: :console)] prod? = config_env() == :prod -ip = ConfigReader.read_ip("JF_IP") || Application.fetch_env!(:jellyfish, :ip) +ip = ConfigReader.read_ip("FJ_IP") || Application.fetch_env!(:fishjam, :ip) -port = ConfigReader.read_port("JF_PORT") || Application.fetch_env!(:jellyfish, :port) +port = ConfigReader.read_port("FJ_PORT") || Application.fetch_env!(:fishjam, :port) host = - case System.get_env("JF_HOST") do + case System.get_env("FJ_HOST") do nil -> "#{:inet.ntoa(ip)}:#{port}" other -> other end components_used = ConfigReader.read_components_used() -sip_used? = Jellyfish.Component.SIP in components_used +sip_used? = Fishjam.Component.SIP in components_used -config :jellyfish, +config :fishjam, jwt_max_age: 24 * 3600, media_files_path: - System.get_env("JF_RESOURCES_BASE_PATH", "jellyfish_resources") |> Path.expand(), + System.get_env("FJ_RESOURCES_BASE_PATH", "fishjam_resources") |> Path.expand(), address: host, - metrics_ip: ConfigReader.read_ip("JF_METRICS_IP") || {127, 0, 0, 1}, - metrics_port: ConfigReader.read_port("JF_METRICS_PORT") || 9568, + metrics_ip: ConfigReader.read_ip("FJ_METRICS_IP") || {127, 0, 0, 1}, + metrics_port: ConfigReader.read_port("FJ_METRICS_PORT") || 9568, dist_config: ConfigReader.read_dist_config(), webrtc_config: ConfigReader.read_webrtc_config(), components_used: components_used, @@ -42,11 +42,11 @@ config :jellyfish, s3_config: ConfigReader.read_s3_config(), git_commit: ConfigReader.read_git_commit() -case System.get_env("JF_SERVER_API_TOKEN") do +case System.get_env("FJ_SERVER_API_TOKEN") do nil when prod? == true -> raise """ - environment variable JF_SERVER_API_TOKEN is missing. - JF_SERVER_API_TOKEN is used for HTTP requests and + environment variable FJ_SERVER_API_TOKEN is missing. + FJ_SERVER_API_TOKEN is used for HTTP requests and server WebSocket authorization. """ @@ -54,14 +54,14 @@ case System.get_env("JF_SERVER_API_TOKEN") do :ok token -> - config :jellyfish, server_api_token: token + config :fishjam, server_api_token: token end external_uri = URI.parse("//" <> host) -config :jellyfish, JellyfishWeb.Endpoint, +config :fishjam, FishjamWeb.Endpoint, secret_key_base: - System.get_env("JF_SECRET_KEY_BASE") || Base.encode64(:crypto.strong_rand_bytes(48)), + System.get_env("FJ_SECRET_KEY_BASE") || Base.encode64(:crypto.strong_rand_bytes(48)), url: [ host: external_uri.host, port: external_uri.port || 443, @@ -73,7 +73,7 @@ config :jellyfish, JellyfishWeb.Endpoint, # Mix task: mix phx.gen.cert case ConfigReader.read_ssl_config() do {ssl_key_path, ssl_cert_path} -> - config :jellyfish, JellyfishWeb.Endpoint, + config :fishjam, FishjamWeb.Endpoint, https: [ ip: ip, port: port, @@ -83,15 +83,15 @@ case ConfigReader.read_ssl_config() do ] nil -> - config :jellyfish, JellyfishWeb.Endpoint, http: [ip: ip, port: port] + config :fishjam, FishjamWeb.Endpoint, http: [ip: ip, port: port] end -check_origin = ConfigReader.read_check_origin("JF_CHECK_ORIGIN") +check_origin = ConfigReader.read_check_origin("FJ_CHECK_ORIGIN") if check_origin != nil do - config :jellyfish, JellyfishWeb.Endpoint, check_origin: check_origin + config :fishjam, FishjamWeb.Endpoint, check_origin: check_origin end if prod? do - config :jellyfish, JellyfishWeb.Endpoint, url: [scheme: "https"] + config :fishjam, FishjamWeb.Endpoint, url: [scheme: "https"] end diff --git a/config/test.exs b/config/test.exs index f94f47f6..1d0de5e8 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,13 +1,13 @@ import Config -config :jellyfish, +config :fishjam, ip: {127, 0, 0, 1}, port: 4002, server_api_token: "development", webrtc_metrics_scrape_interval: 50, room_metrics_scrape_interval: 1 -config :jellyfish, JellyfishWeb.Endpoint, +config :fishjam, FishjamWeb.Endpoint, http: [ip: {127, 0, 0, 1}, port: 4002], server: true diff --git a/docker-compose-dns.yaml b/docker-compose-dns.yaml index abb90499..1b7af457 100644 --- a/docker-compose-dns.yaml +++ b/docker-compose-dns.yaml @@ -1,12 +1,12 @@ version: "3" -x-jellyfish-template: &jellyfish-template +x-fishjam-template: &fishjam-template build: . - environment: &jellyfish-environment - JF_SERVER_API_TOKEN: "development" - JF_DIST_ENABLED: "true" - JF_DIST_MODE: "name" - JF_DIST_STRATEGY_NAME: "DNS" + environment: &fishjam-environment + FJ_SERVER_API_TOKEN: "development" + FJ_DIST_ENABLED: "true" + FJ_DIST_MODE: "name" + FJ_DIST_STRATEGY_NAME: "DNS" restart: on-failure services: @@ -28,12 +28,12 @@ services: - app2 app1: - <<: *jellyfish-template + <<: *fishjam-template environment: - <<: *jellyfish-environment - JF_HOST: "localhost:4001" - JF_PORT: 4001 - JF_DIST_QUERY: app.dns-network + <<: *fishjam-environment + FJ_HOST: "localhost:4001" + FJ_PORT: 4001 + FJ_DIST_QUERY: app.dns-network ports: - 4001:4001 networks: @@ -42,12 +42,12 @@ services: - app.dns-network app2: - <<: *jellyfish-template + <<: *fishjam-template environment: - <<: *jellyfish-environment - JF_HOST: "localhost:4002" - JF_PORT: 4002 - JF_DIST_QUERY: app.dns-network + <<: *fishjam-environment + FJ_HOST: "localhost:4002" + FJ_PORT: 4002 + FJ_DIST_QUERY: app.dns-network ports: - 4002:4002 networks: diff --git a/docker-compose-epmd.yaml b/docker-compose-epmd.yaml index 3f99872e..e60c521f 100644 --- a/docker-compose-epmd.yaml +++ b/docker-compose-epmd.yaml @@ -1,11 +1,11 @@ version: "3" -x-jellyfish-template: &jellyfish-template +x-fishjam-template: &fishjam-template build: . - environment: &jellyfish-environment - JF_SERVER_API_TOKEN: "development" - JF_DIST_ENABLED: "true" - JF_DIST_NODES: "app@app1 app@app2" + environment: &fishjam-environment + FJ_SERVER_API_TOKEN: "development" + FJ_DIST_ENABLED: "true" + FJ_DIST_NODES: "app@app1 app@app2" restart: on-failure services: @@ -27,21 +27,21 @@ services: - app2 app1: - <<: *jellyfish-template + <<: *fishjam-template environment: - <<: *jellyfish-environment - JF_HOST: "localhost:4001" - JF_PORT: 4001 - JF_DIST_NODE_NAME: app@app1 + <<: *fishjam-environment + FJ_HOST: "localhost:4001" + FJ_PORT: 4001 + FJ_DIST_NODE_NAME: app@app1 ports: - 4001:4001 app2: - <<: *jellyfish-template + <<: *fishjam-template environment: - <<: *jellyfish-environment - JF_HOST: "localhost:4002" - JF_PORT: 4002 - JF_DIST_NODE_NAME: app@app2 + <<: *fishjam-environment + FJ_HOST: "localhost:4002" + FJ_PORT: 4002 + FJ_DIST_NODE_NAME: app@app2 ports: - 4002:4002 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 4338d5e8..c81c1db6 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -11,10 +11,10 @@ log_debug() { if [ "$(id -u)" = '0' ]; then log_debug "Running as root. Fixing permissions for: \ - $(find . \! -user jellyfish -exec echo '{} \n' \;)" + $(find . \! -user fishjam -exec echo '{} \n' \;)" - find . \! -user jellyfish -exec chown jellyfish '{}' + - exec gosu jellyfish "$0" "$@" + find . \! -user fishjam -exec chown fishjam '{}' + + exec gosu fishjam "$0" "$@" fi log_debug "Running as user with UID: $(id -u) GID: $(id -g)" diff --git a/docs/api_versioning.md b/docs/api_versioning.md index 1081b123..584dd450 100644 --- a/docs/api_versioning.md +++ b/docs/api_versioning.md @@ -2,11 +2,11 @@ ## Motivation -The Jellyfish API consists of two transports: http REST API +The Fishjam API consists of two transports: http REST API and WebSocket using protobuf. The two APIs can be found in -the [api-description repository](https://github.com/jellyfish-dev/protos). +the [api-description repository](https://github.com/fishjam-dev/protos). -In order to ensure ease of use we introduce versioning to the Jellyfish API. +In order to ensure ease of use we introduce versioning to the Fishjam API. The versioning of the API provides reference points when comparing changes and allows to name compatible (and incompatible) versions of the API. @@ -14,15 +14,15 @@ versions of the API. ## Workflow Whenever it is possible, we try to introduce backward-compatible changes -in the API, allowing for a smoother transition between Jellyfish versions. +in the API, allowing for a smoother transition between Fishjam versions. -The API is versioned separately from Jellyfish, so a Jellyfish release +The API is versioned separately from Fishjam, so a Fishjam release does not necessarily cause a change to the API. When we decide to remove a particular functionality, we first mark -it as `deprecated` and only then remove it in a later version of Jellyfish. +it as `deprecated` and only then remove it in a later version of Fishjam. This makes the older versions of the SDKs still compatible with -newer Jellyfish until they are upgraded as well. +newer Fishjam until they are upgraded as well. ## Testing diff --git a/docs/authentication_architecture.md b/docs/authentication_architecture.md index 0545a19b..cc215dd2 100644 --- a/docs/authentication_architecture.md +++ b/docs/authentication_architecture.md @@ -4,31 +4,31 @@ - **CL** - client - **BE** - business logic (backend) implemented by the user -- **JF** - Jellyfish +- **FJ** - Fishjam ## Approaches and connection with signaling architecture Authentication might be a big factor when deciding on signaling architecture (see the other document). We need to consider 2 situations: -- **CL** connect directly do **JF**, -- **CL** connect to **BE** which forwards signaling messages to **JF**. +- **CL** connect directly do **FJ**, +- **CL** connect to **BE** which forwards signaling messages to **FJ**. Let's start with the first approach. -### Direct connection between **CL** and **JF** +### Direct connection between **CL** and **FJ** Let's assume **CL** wants to join some multimedia room and is already authenticated (from the bussines logic perspective, e.g. he is logged into his account). -Also, **JF** and **BE** share common secret (more about that later). +Also, **FJ** and **BE** share common secret (more about that later). Scenario: 1) **CL** sends request to join the multimedia room to **BE**. -2) **BE** recieves the request and sends `add_peer` request to **JF**. -3) **JF** sends back created `peer_id`. -4) **BE** uses received `peer_id` and id of the **JF** instance to create JWT that is signed with the secret (or the **JF** creates the token in the previous step). +2) **BE** recieves the request and sends `add_peer` request to **FJ**. +3) **FJ** sends back created `peer_id`. +4) **BE** uses received `peer_id` and id of the **FJ** instance to create JWT that is signed with the secret (or the **FJ** creates the token in the previous step). The token can also contain permissions (which may differ between "normal" clients and administrators). 5) **BE** responds to **CL** with the token. -6) **CL** can now open WebSocket connection to **JF** using the token to authorize themselves. **JF** (thanks to informations included in the token) -can tell who opened the connection and that it was intended for this instance of **JF**. +6) **CL** can now open WebSocket connection to **FJ** using the token to authorize themselves. **FJ** (thanks to informations included in the token) +can tell who opened the connection and that it was intended for this instance of **FJ**. Problems: @@ -55,23 +55,23 @@ to think if we want to use the token to tag the signaling messages (seems unnece ### **BE** -If **BE** (server SDK) is responsible for creating tokens, then it has to know the secret that will be used in signing JWT. It also needs `peer_id`, which can be obtained from **JF**. That takes care of **CL** authorization, but we also need to authorize **BE** - **JF** connection. One possible solution is to use the very same secret as before to create tokens that will be used by **BE**. So **BE** will generate token and then, instead of sending them to client, will use it itself (which may seem wierd, but we came to conclusion that the approach is alright). +If **BE** (server SDK) is responsible for creating tokens, then it has to know the secret that will be used in signing JWT. It also needs `peer_id`, which can be obtained from **FJ**. That takes care of **CL** authorization, but we also need to authorize **BE** - **FJ** connection. One possible solution is to use the very same secret as before to create tokens that will be used by **BE**. So **BE** will generate token and then, instead of sending them to client, will use it itself (which may seem wierd, but we came to conclusion that the approach is alright). -This approach only makes sense when using direct signaling, otherwise tokens are not necessary at all (except for **BE** - **JF** connection). +This approach only makes sense when using direct signaling, otherwise tokens are not necessary at all (except for **BE** - **FJ** connection). -### **JF** +### **FJ** -In this approach **JF** creates the tokens (when using `add_peer`, token is send in the response), so user only needs to pass it to the **CL**. -You also might need to pass expected token presmissions to **JF**. -Despite that, we still need a way to authenticate **BE** - **JF** connection. Possible solutions: +In this approach **FJ** creates the tokens (when using `add_peer`, token is send in the response), so user only needs to pass it to the **CL**. +You also might need to pass expected token presmissions to **FJ**. +Despite that, we still need a way to authenticate **BE** - **FJ** connection. Possible solutions: -- create JWT on **BE** side anyway (in such case you might as well use the first approach in order not to split logic responsible for token generation between Server SDK and Jellyfish), +- create JWT on **BE** side anyway (in such case you might as well use the first approach in order not to split logic responsible for token generation between Server SDK and Fishjam), - create JWT (or some other token type) once and use it in configuration (makes it easier to change **BE** permissions, if that's ever necessary, but the token never expires, I'm not sure whether that's a problem, also **BE** doesn't need to know the secret). No matter who generates the tokens, effort from the user comes to passing the token to **CL** (in direct signaling, otherwise no need to do anything except for creating the signalling connection). ## Consclusion -We will be using **JF** to generate tokens, as it makes it easier to maintain (we don't need to implement token generation in all of our SDKs) and we keep all of the logic responsible for generation and validation together. -Also, **BE** authentication might not be very difficult: they may just share a common secret and use it directly to authenticate (e.g. via HTTP authorization request header), especially when **JF** and **BE** are in the same internal -network, which will be a common case. Using **BE** to generate tokens could also make it harder to make modifications related to tokens (need to redeploy both **JF** and **BE** in such case). +We will be using **FJ** to generate tokens, as it makes it easier to maintain (we don't need to implement token generation in all of our SDKs) and we keep all of the logic responsible for generation and validation together. +Also, **BE** authentication might not be very difficult: they may just share a common secret and use it directly to authenticate (e.g. via HTTP authorization request header), especially when **FJ** and **BE** are in the same internal +network, which will be a common case. Using **BE** to generate tokens could also make it harder to make modifications related to tokens (need to redeploy both **FJ** and **BE** in such case). diff --git a/docs/server_notifications.md b/docs/server_notifications.md index e453f4a7..041af857 100644 --- a/docs/server_notifications.md +++ b/docs/server_notifications.md @@ -1,6 +1,6 @@ # Server notifications -Client SDKs communicate with Jellyfish using so called Control Messages (CM), which are messages exchanged using Websockets. +Client SDKs communicate with Fishjam using so called Control Messages (CM), which are messages exchanged using Websockets. A few examples when Control Messages are sent: * `join` - sent when peer joins WebRTC room @@ -15,7 +15,7 @@ In general we have two major options here: ### JSON -JSON has been used for a long time in [membrane_rtc_engine](https://github.com/jellyfish-dev/membrane_rtc_engine). +JSON has been used for a long time in [membrane_rtc_engine](https://github.com/fishjam-dev/membrane_rtc_engine). This solution has several drawbacks: * there is no versioning so it’s hard to track which version of CM is used on the server side and which one on the client side @@ -136,7 +136,7 @@ AsyncAPI and JsonSchema would be quite difficult to implement since we would hav GraphQL in general could be useful in our case, however there are a couple issues which overall would probably cause us headaches: * No code generator for Elixir - we would have to implement schema using `Absinthe.Schema`. From Elixir code we could generate GraphQL Schema and use it for generating code in other languages. -* No single code generator for all languages. This means we would have to find and integrate a separate code generator for each SDK. This may not be a problem for 2-3 SDK but would also likely discourage external developers from using Jellyfish. +* No single code generator for all languages. This means we would have to find and integrate a separate code generator for each SDK. This may not be a problem for 2-3 SDK but would also likely discourage external developers from using Fishjam. * Issues with maintaining signalling connection - so far we haven't found simple or non-hacky way of monitoring whether the client-server websocket connection is alive. Absinthe doesn't allow for access to the tcp socket. Overall it feels like our requirements are very specific in the context of GraphQL use-case and we would be be using it on the edge of what it was designed for in the first place. diff --git a/docs/signaling_architecture.md b/docs/signaling_architecture.md index 719c6619..7d2b6c35 100644 --- a/docs/signaling_architecture.md +++ b/docs/signaling_architecture.md @@ -4,76 +4,76 @@ - **CL** - client - **BE** - business logic (backend) implemented by the user -- **JF** - Jellyfish +- **FJ** - Fishjam ## Approaches Currently we consider three approaches to the signaling implementation: -- direct connection between **CL** and **JF** bypassing **BE**, +- direct connection between **CL** and **FJ** bypassing **BE**, - using **BE** as a middleman to pass signaling messages, - ability to use either of the options above, but providing some default implementation (i.e provide default implementation of direct signaling, but let the user replace it with his own implementation). ### Direct connection -**CL** must be able to open connection to **JF** (e.g. WebSocket). For that **CL** requires **JF** address and some form of authentication. +**CL** must be able to open connection to **FJ** (e.g. WebSocket). For that **CL** requires **FJ** address and some form of authentication. **Example:** 1) **CL** sends request to **BE** to join some room (meeting, webinar etc.). -2) **BE** sends request to **JF** (*add_peer*). -3) **JF** responds positively. +2) **BE** sends request to **FJ** (*add_peer*). +3) **FJ** responds positively. 4) **BE** responds to client positively with generated token. -5) **CL** opens WebSocket connection to **JF**, flow of signaling messages begins. +5) **CL** opens WebSocket connection to **FJ**, flow of signaling messages begins. -**JF** can diferentiate between the clients by the opened WebSocket connection. It knows +**FJ** can diferentiate between the clients by the opened WebSocket connection. It knows who is the sender of incoming Media Events and where to send generated Media Events. **Advantages:** - Easier option, the user doesn't have to do much more than passing authentication token to **CL**, client SDK handles the rest. -- Both **CL** and **JF** can tell when signaling connection was broken. That prevents situation when signaling connection between **CL** and **BE** was broken, but **BE** -did not notify **JF** of such occurence, is that case media is still flowing with no signaling (when new peer joins, tracks are not renegotiated, etc.). +- Both **CL** and **FJ** can tell when signaling connection was broken. That prevents situation when signaling connection between **CL** and **BE** was broken, but **BE** +did not notify **FJ** of such occurence, is that case media is still flowing with no signaling (when new peer joins, tracks are not renegotiated, etc.). **Disadvantages:** - **CL** has to participate in the authentication process (so they probably need to be -authenticated by **BE** and then authenticated again to open connection to **JF**). -- Some events can get desynchronized. Imagine scenerio where **JF** sent notification to +authenticated by **BE** and then authenticated again to open connection to **FJ**). +- Some events can get desynchronized. Imagine scenerio where **FJ** sent notification to **BE** that recording has begun (it is **BE**'s responibility to propagate this information to clients so they can show some icon indicator). At the same time, media and signaling connections were broken for some reason. -For a brief moment (until **JF** passes that information to **BE** and **BE** passes +For a brief moment (until **FJ** passes that information to **BE** and **BE** passes it to **CL**) **CL** might think that their screen is being recorded even though that is not the case (situation in this example can be prevented, but I hope you get the gist). ### Using **BE** as a middleman -Client SDK generates signaling messages, but requires the user to implement logic that handles forwarding them to **JF** (using **BE** in described scenario). +Client SDK generates signaling messages, but requires the user to implement logic that handles forwarding them to **FJ** (using **BE** in described scenario). **Exmaple:** 1) **CL** sends request to **BE** to join some room (meeting, webinar etc.). -2) **BE** sends request to **JF** (*add_peer*). -3) **JF** responds positively. +2) **BE** sends request to **FJ** (*add_peer*). +3) **FJ** responds positively. 4) **BE** responds to client positively. -5) **CL** starts generating signaling messages which are forwarded to **JF** by **BE**. +5) **CL** starts generating signaling messages which are forwarded to **FJ** by **BE**. In that case we need a way to identify the sender of signaling messages, we thought of 2 approaches (you'r welcome to suggest a better one): -- make **BE** open WebSocket connection to **JF** for every client (here seems like a very bad idea, makes a bit more sense in the mixed approach described later), -- tag every signaling message with something like `peer_id`, requires only one WebSocket connection between **BE** and **JF** (should SDK do this, or should it be the users responsibility?). +- make **BE** open WebSocket connection to **FJ** for every client (here seems like a very bad idea, makes a bit more sense in the mixed approach described later), +- tag every signaling message with something like `peer_id`, requires only one WebSocket connection between **BE** and **FJ** (should SDK do this, or should it be the users responsibility?). **Advantages:** -- Easy to implement (from our, Jellyfish developers, perspective). +- Easy to implement (from our, Fishjam developers, perspective). - **CL** does not require additional connection and authentication, everything is handled by **BE**. **Disadvantages:** -- Harder to implement by the user, much more error-prone (isn't that the point of Jellyfish to make it as simple as possible?). +- Harder to implement by the user, much more error-prone (isn't that the point of Fishjam to make it as simple as possible?). - Encourages the user to implement logic that relies on content of signaling messages (at least while it's JSON) which they should treat as a "black box". ### Mixed approach @@ -83,7 +83,7 @@ We assume that only one approach is being used at once (there cannot be a situat Now the problem with identifying signaling messages becomes much more apparent. -If signaling messages are individually tagged, when using direct connection approach there's some redundancy (**JF** doesn't need the "tag", it can tell who +If signaling messages are individually tagged, when using direct connection approach there's some redundancy (**FJ** doesn't need the "tag", it can tell who is who by the WebSocket connection), but the tags are necessary when using **BE** as a middleman. Obviously, we can handle each situation differently and not tag messages passed in direct connection, but that makes the implementation much more complicated. @@ -105,5 +105,5 @@ On the other hand, when tags are not used, there's everything alright with direc The approach that we are going to use is the **direct connection**, mostly because it's a lot easier for the user to implement (which outweights benefits like the flexibility of the other approach). Also, the drawbacks seem not to be very severe: some of the information about the state of rooms and peers (like the fact that recording has begun) can be -kept and shared by **JF** via the signalling connection. +kept and shared by **FJ** via the signalling connection. Mixed approach is not considered at the moment (as it brings some difficulties) but can be added in the future if there's need. diff --git a/examples/README.md b/examples/README.md index a11af330..9eb9dfaf 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,17 +1,17 @@ -# Jellyfish examples +# Fishjam examples -This directory contains example Elixir scripts for the most common Jellyfish use cases, +This directory contains example Elixir scripts for the most common Fishjam use cases, as well as the instructions necessary to run them. ## RTSP to HLS ### Elixir -1. Start a local instance of Jellyfish (e.g. by running `mix phx.server`) +1. Start a local instance of Fishjam (e.g. by running `mix phx.server`) 2. Run `elixir ./rtsp_to_hls.exs "rtsp://your-rtsp-stream-source:port/stream"` ### Python -1. Install the Jellyfish Python Server SDK: `pip install jellyfish-server-sdk` -2. Start a local instance of Jellyfish (e.g. by running `mix phx.server`) +1. Install the Fishjam Python Server SDK: `pip install fishjam-server-sdk` +2. Start a local instance of Fishjam (e.g. by running `mix phx.server`) 3. Run `python3 ./rtsp_to_hls.py "rtsp://your-rtsp-stream-source:port/stream"` diff --git a/examples/rtsp_to_hls.exs b/examples/rtsp_to_hls.exs index cbb0a795..d6e5d10c 100644 --- a/examples/rtsp_to_hls.exs +++ b/examples/rtsp_to_hls.exs @@ -1,36 +1,36 @@ Mix.install([ - # Keep in mind that you should lock onto a specific version of Jellyfish - # and the Jellyfish Server SDK in production code - {:jellyfish_server_sdk, github: "jellyfish-dev/elixir_server_sdk"} + # Keep in mind that you should lock onto a specific version of Fishjam + # and the Fishjam Server SDK in production code + {:fishjam_server_sdk, github: "fishjam-dev/elixir_server_sdk"} ]) defmodule Example do require Logger - @jellyfish_hostname "localhost" - @jellyfish_port 5002 - @jellyfish_token "development" + @fishjam_hostname "localhost" + @fishjam_port 5002 + @fishjam_token "development" def run(stream_uri) do client = - Jellyfish.Client.new( - server_address: "#{@jellyfish_hostname}:#{@jellyfish_port}", - server_api_token: @jellyfish_token + Fishjam.Client.new( + server_address: "#{@fishjam_hostname}:#{@fishjam_port}", + server_api_token: @fishjam_token ) - with {:ok, %Jellyfish.Room{id: room_id}, _jellyfish_address} <- - Jellyfish.Room.create(client, video_codec: :h264), - {:ok, %Jellyfish.Component{id: _hls_component_id}} <- - Jellyfish.Room.add_component(client, room_id, Jellyfish.Component.HLS), - {:ok, %Jellyfish.Component{id: _rtsp_component_id}} <- - Jellyfish.Room.add_component(client, room_id, %Jellyfish.Component.RTSP{ + with {:ok, %Fishjam.Room{id: room_id}, _fishjam_address} <- + Fishjam.Room.create(client, video_codec: :h264), + {:ok, %Fishjam.Component{id: _hls_component_id}} <- + Fishjam.Room.add_component(client, room_id, Fishjam.Component.HLS), + {:ok, %Fishjam.Component{id: _rtsp_component_id}} <- + Fishjam.Room.add_component(client, room_id, %Fishjam.Component.RTSP{ source_uri: stream_uri }) do Logger.info("Components added successfully") else {:error, reason} -> Logger.error(""" - Error when attempting to communicate with Jellyfish: #{inspect(reason)} + Error when attempting to communicate with Fishjam: #{inspect(reason)} Make sure you have started it by running `mix phx.server` """) end diff --git a/examples/rtsp_to_hls.py b/examples/rtsp_to_hls.py index 6a362054..b903afc5 100644 --- a/examples/rtsp_to_hls.py +++ b/examples/rtsp_to_hls.py @@ -1,12 +1,12 @@ import sys -# You can install the Jellyfish Python Server SDK by running `pip install jellyfish-server-sdk` -from jellyfish import RoomApi, ComponentOptionsHLS, ComponentOptionsRTSP +# You can install the Fishjam Python Server SDK by running `pip install fishjam-server-sdk` +from fishjam import RoomApi, ComponentOptionsHLS, ComponentOptionsRTSP -JELLYFISH_HOSTNAME="localhost" -JELLYFISH_PORT=5002 -JELLYFISH_TOKEN="development" +FISHJAM_HOSTNAME="localhost" +FISHJAM_PORT=5002 +FISHJAM_TOKEN="development" if len(sys.argv) > 1: @@ -15,9 +15,9 @@ raise RuntimeError("No stream URI specified, make sure you pass it as the argument to this script") try: - room_api = RoomApi(server_address=f"{JELLYFISH_HOSTNAME}:{JELLYFISH_PORT}", server_api_token=JELLYFISH_TOKEN) + room_api = RoomApi(server_address=f"{FISHJAM_HOSTNAME}:{FISHJAM_PORT}", server_api_token=FISHJAM_TOKEN) - _jellyfish_address, room = room_api.create_room(video_codec="h264") + _fishjam_address, room = room_api.create_room(video_codec="h264") _component_hls = room_api.add_component(room.id, options=ComponentOptionsHLS()) _component_rtsp = room_api.add_component(room.id, options=ComponentOptionsRTSP(source_uri=stream_uri)) @@ -25,7 +25,7 @@ except Exception as e: print(f""" - Error when attempting to communicate with Jellyfish: {e} + Error when attempting to communicate with Fishjam: {e} Make sure you have started it by running `mix phx.server` """) sys.exit(1) diff --git a/grafana/jellyfish-server-overiew.json b/grafana/jellyfish-server-overiew.json index ad6a2a48..ffb97e1e 100644 --- a/grafana/jellyfish-server-overiew.json +++ b/grafana/jellyfish-server-overiew.json @@ -88,7 +88,7 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "sum(jellyfish_rooms{instance=~\"$instance\"}) by(instance)", + "expr": "sum(fishjam_rooms{instance=~\"$instance\"}) by(instance)", "instant": false, "legendFormat": "{{instance}}", "range": true, @@ -103,7 +103,7 @@ "type": "prometheus", "uid": "${ds}" }, - "description": "Present the current number of rooms by jellyfish instance.", + "description": "Present the current number of rooms by fishjam instance.", "fieldConfig": { "defaults": { "color": { @@ -186,7 +186,7 @@ "uid": "${ds}" }, "editorMode": "code", - "expr": "sum(jellyfish_rooms{instance=~\"$instance\"}) by(instance)", + "expr": "sum(fishjam_rooms{instance=~\"$instance\"}) by(instance)", "instant": false, "legendFormat": "{{instance}}", "range": true, @@ -211,7 +211,7 @@ "type": "prometheus", "uid": "${ds}" }, - "description": "Ingress throughput from each of the jellyfish instances", + "description": "Ingress throughput from each of the fishjam instances", "fieldConfig": { "defaults": { "color": { @@ -293,7 +293,7 @@ "uid": "${ds}" }, "editorMode": "code", - "expr": "irate(jellyfish_traffic_ingress_webrtc_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8\n+ \nirate(jellyfish_traffic_ingress_http_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8", + "expr": "irate(fishjam_traffic_ingress_webrtc_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8\n+ \nirate(fishjam_traffic_ingress_http_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8", "instant": false, "legendFormat": "{{instance}}", "range": true, @@ -308,7 +308,7 @@ "type": "prometheus", "uid": "${ds}" }, - "description": "Egress throughput from each of the jellyfish instances", + "description": "Egress throughput from each of the fishjam instances", "fieldConfig": { "defaults": { "color": { @@ -390,7 +390,7 @@ "uid": "${ds}" }, "editorMode": "code", - "expr": "irate(jellyfish_traffic_egress_webrtc_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8\n+\nirate(jellyfish_traffic_egress_http_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8", + "expr": "irate(fishjam_traffic_egress_webrtc_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8\n+\nirate(fishjam_traffic_egress_http_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8", "instant": false, "legendFormat": "{{instance}}", "range": true, @@ -405,7 +405,7 @@ "type": "prometheus", "uid": "${ds}" }, - "description": "Ingress and Egress throughput from each of the jellyfish instances", + "description": "Ingress and Egress throughput from each of the fishjam instances", "fieldConfig": { "defaults": { "color": { @@ -503,7 +503,7 @@ "uid": "${ds}" }, "editorMode": "code", - "expr": "irate(jellyfish_traffic_ingress_webrtc_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8\n+\nirate(jellyfish_traffic_ingress_http_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8", + "expr": "irate(fishjam_traffic_ingress_webrtc_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8\n+\nirate(fishjam_traffic_ingress_http_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8", "instant": false, "legendFormat": "{{instance}} ingress", "range": true, @@ -515,7 +515,7 @@ "uid": "${ds}" }, "editorMode": "code", - "expr": "irate(jellyfish_traffic_egress_webrtc_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8\n+\nirate(jellyfish_traffic_egress_http_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8", + "expr": "irate(fishjam_traffic_egress_webrtc_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8\n+\nirate(fishjam_traffic_egress_http_total_bytes{instance=~\"$instance\"}[$__rate_interval])*8", "hide": false, "instant": false, "legendFormat": "{{instance}} egress", @@ -545,7 +545,7 @@ "type": "prometheus", "uid": "${ds}" }, - "description": "VM usage from each of the jellyfish instances", + "description": "VM usage from each of the fishjam instances", "fieldConfig": { "defaults": { "color": { @@ -679,7 +679,7 @@ "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "definition": "label_values(jellyfish_rooms,instance)", + "definition": "label_values(fishjam_rooms,instance)", "description": "Allows you limit graphs only to selected instances", "hide": 0, "includeAll": true, @@ -688,7 +688,7 @@ "name": "instance", "options": [], "query": { - "query": "label_values(jellyfish_rooms,instance)", + "query": "label_values(fishjam_rooms,instance)", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 1, @@ -705,7 +705,7 @@ }, "timepicker": {}, "timezone": "", - "title": "Jellyfishes dashboard", + "title": "Fishjams dashboard", "uid": "ef7bdb94-c912-49ac-a273-e7a31598001a", "version": 48, "weekStart": "" diff --git a/lib/jellyfish.ex b/lib/jellyfish.ex index 144ed968..3dafd640 100644 --- a/lib/jellyfish.ex +++ b/lib/jellyfish.ex @@ -1,6 +1,6 @@ -defmodule Jellyfish do +defmodule Fishjam do @moduledoc """ - Jellyfish keeps the contexts that define your domain + Fishjam keeps the contexts that define your domain and business logic. Contexts are also responsible for managing your data, regardless @@ -13,11 +13,11 @@ defmodule Jellyfish do @spec address() :: binary() def address() do - Application.fetch_env!(:jellyfish, :address) + Application.fetch_env!(:fishjam, :address) end @spec peer_websocket_address() :: binary() def peer_websocket_address() do - Application.fetch_env!(:jellyfish, :address) <> "/socket/peer/websocket" + Application.fetch_env!(:fishjam, :address) <> "/socket/peer/websocket" end end diff --git a/lib/jellyfish/application.ex b/lib/jellyfish/application.ex index 1beba9d8..0092e9bd 100644 --- a/lib/jellyfish/application.ex +++ b/lib/jellyfish/application.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Application do +defmodule Fishjam.Application do # See https://hexdocs.pm/elixir/Application.html # for more information on OTP Applications @moduledoc false @@ -16,38 +16,38 @@ defmodule Jellyfish.Application do @impl true def start(_type, _args) do - scrape_interval = Application.fetch_env!(:jellyfish, :webrtc_metrics_scrape_interval) - dist_config = Application.fetch_env!(:jellyfish, :dist_config) - webrtc_config = Application.fetch_env!(:jellyfish, :webrtc_config) - git_commit = Application.get_env(:jellyfish, :git_commit) - components_used = Application.get_env(:jellyfish, :components_used) + scrape_interval = Application.fetch_env!(:fishjam, :webrtc_metrics_scrape_interval) + dist_config = Application.fetch_env!(:fishjam, :dist_config) + webrtc_config = Application.fetch_env!(:fishjam, :webrtc_config) + git_commit = Application.get_env(:fishjam, :git_commit) + components_used = Application.get_env(:fishjam, :components_used) - Logger.info("Starting Jellyfish v#{Jellyfish.version()} (#{git_commit})") + Logger.info("Starting Fishjam v#{Fishjam.version()} (#{git_commit})") Logger.info("Distribution config: #{inspect(Keyword.delete(dist_config, :cookie))}") Logger.info("WebRTC config: #{inspect(webrtc_config)}") Logger.info("Allowed components: #{inspect(components_used)}") children = [ - {Phoenix.PubSub, name: Jellyfish.PubSub}, + {Phoenix.PubSub, name: Fishjam.PubSub}, {Membrane.TelemetryMetrics.Reporter, [ metrics: Membrane.RTC.Engine.Endpoint.WebRTC.Metrics.metrics(), - name: JellyfishMetricsReporter + name: FishjamMetricsReporter ]}, - {Jellyfish.MetricsScraper, scrape_interval}, - JellyfishWeb.Endpoint, + {Fishjam.MetricsScraper, scrape_interval}, + FishjamWeb.Endpoint, # Start the RoomService - Jellyfish.RoomService, + Fishjam.RoomService, # Start the ResourceManager, responsible for cleaning old recordings - {Jellyfish.ResourceManager, @resource_manager_opts}, - Jellyfish.WebhookNotifier, - {Registry, keys: :unique, name: Jellyfish.RoomRegistry}, - {Registry, keys: :unique, name: Jellyfish.RequestHandlerRegistry}, - # Start the Telemetry supervisor (must be started after Jellyfish.RoomRegistry) - JellyfishWeb.Telemetry, - {Task.Supervisor, name: Jellyfish.TaskSupervisor}, - {DynamicSupervisor, name: Jellyfish.HLS.ManagerSupervisor, strategy: :one_for_one} + {Fishjam.ResourceManager, @resource_manager_opts}, + Fishjam.WebhookNotifier, + {Registry, keys: :unique, name: Fishjam.RoomRegistry}, + {Registry, keys: :unique, name: Fishjam.RequestHandlerRegistry}, + # Start the Telemetry supervisor (must be started after Fishjam.RoomRegistry) + FishjamWeb.Telemetry, + {Task.Supervisor, name: Fishjam.TaskSupervisor}, + {DynamicSupervisor, name: Fishjam.HLS.ManagerSupervisor, strategy: :one_for_one} ] ++ if dist_config[:enabled] do config_distribution(dist_config) @@ -60,15 +60,15 @@ defmodule Jellyfish.Application do # See https://hexdocs.pm/elixir/Supervisor.html # for other strategies and supported options - opts = [strategy: :one_for_one, name: Jellyfish.Supervisor] + opts = [strategy: :one_for_one, name: Fishjam.Supervisor] - Application.put_env(:jellyfish, :start_time, System.monotonic_time(:second)) + Application.put_env(:fishjam, :start_time, System.monotonic_time(:second)) result = Supervisor.start_link(children, opts) # If we do not set a default value for WebRTC metrics, # the metrics will be sent from the moment the first peer joins a room. - JellyfishWeb.Telemetry.default_webrtc_metrics() + FishjamWeb.Telemetry.default_webrtc_metrics() result end @@ -77,14 +77,14 @@ defmodule Jellyfish.Application do # whenever the application is updated. @impl true def config_change(changed, _new, removed) do - JellyfishWeb.Endpoint.config_change(changed, removed) + FishjamWeb.Endpoint.config_change(changed, removed) :ok end defp config_distribution(dist_config) do :ok = ensure_epmd_started!() - # When running JF not in a cluster and using + # When running FJ not in a cluster and using # mix release, it starts in the distributed mode # automatically unless Node.alive?() do @@ -93,7 +93,7 @@ defmodule Jellyfish.Application do :ok {:error, reason} -> - raise "Couldn't start Jellyfish node, reason: #{inspect(reason)}" + raise "Couldn't start Fishjam node, reason: #{inspect(reason)}" end Node.set_cookie(dist_config[:cookie]) @@ -106,7 +106,7 @@ defmodule Jellyfish.Application do ] ] - [{Cluster.Supervisor, [topologies, [name: Jellyfish.ClusterSupervisor]]}] + [{Cluster.Supervisor, [topologies, [name: Fishjam.ClusterSupervisor]]}] end defp ensure_epmd_started!() do @@ -119,12 +119,12 @@ defmodule Jellyfish.Application do _exit_or_error, _e -> raise """ Couldn't start epmd daemon. - Epmd is required to run Jellyfish in distributed mode. + Epmd is required to run Fishjam in distributed mode. You can try to start it manually with: epmd -daemon - and run Jellyfish again. + and run Fishjam again. """ end diff --git a/lib/jellyfish/component.ex b/lib/jellyfish/component.ex index a8bbc219..1cd2e663 100644 --- a/lib/jellyfish/component.ex +++ b/lib/jellyfish/component.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Component do +defmodule Fishjam.Component do @moduledoc """ Component is a server side entity that can publish a track, subscribe to tracks and process them. @@ -10,9 +10,9 @@ defmodule Jellyfish.Component do use Bunch.Access - alias Jellyfish.Room - alias Jellyfish.Component.{File, HLS, Recording, RTSP, SIP} - alias Jellyfish.Track + alias Fishjam.Room + alias Fishjam.Component.{File, HLS, Recording, RTSP, SIP} + alias Fishjam.Track @enforce_keys [ :id, @@ -67,7 +67,7 @@ defmodule Jellyfish.Component do defmacro __using__(_opts) do quote location: :keep do - @behaviour Jellyfish.Component + @behaviour Fishjam.Component @impl true def after_init(_room_state, _component, _component_options), do: :ok diff --git a/lib/jellyfish/component/file.ex b/lib/jellyfish/component/file.ex index 3c465aff..70aa6f04 100644 --- a/lib/jellyfish/component/file.ex +++ b/lib/jellyfish/component/file.ex @@ -1,16 +1,16 @@ -defmodule Jellyfish.Component.File do +defmodule Fishjam.Component.File do @moduledoc """ Module representing the File component. """ - @behaviour Jellyfish.Endpoint.Config - use Jellyfish.Component + @behaviour Fishjam.Endpoint.Config + use Fishjam.Component alias ExSDP.Attribute.FMTP alias Membrane.RTC.Engine.Endpoint.File, as: FileEndpoint - alias Jellyfish.Utils.PathValidation - alias JellyfishWeb.ApiSpec.Component.File.Options + alias Fishjam.Utils.PathValidation + alias FishjamWeb.ApiSpec.Component.File.Options @type properties :: %{ file_path: Path.t(), @@ -61,7 +61,7 @@ defmodule Jellyfish.Component.File do defp validate_file_path(file_path) do base_path = - Application.fetch_env!(:jellyfish, :media_files_path) + Application.fetch_env!(:fishjam, :media_files_path) |> Path.join(@files_location) |> Path.expand() @@ -75,7 +75,7 @@ defmodule Jellyfish.Component.File do end defp expand_file_path(file_path) do - media_files_path = Application.fetch_env!(:jellyfish, :media_files_path) + media_files_path = Application.fetch_env!(:fishjam, :media_files_path) [media_files_path, @files_location, file_path] |> Path.join() |> Path.expand() end diff --git a/lib/jellyfish/component/hls.ex b/lib/jellyfish/component/hls.ex index ace0bd2d..fe64aae1 100644 --- a/lib/jellyfish/component/hls.ex +++ b/lib/jellyfish/component/hls.ex @@ -1,12 +1,12 @@ -defmodule Jellyfish.Component.HLS do +defmodule Fishjam.Component.HLS do @moduledoc """ Module representing HLS component. """ - @behaviour Jellyfish.Endpoint.Config - use Jellyfish.Component + @behaviour Fishjam.Endpoint.Config + use Fishjam.Component - alias Jellyfish.Component.HLS.{ + alias Fishjam.Component.HLS.{ EtsHelper, LLStorage, Manager, @@ -15,9 +15,9 @@ defmodule Jellyfish.Component.HLS do Storage } - alias Jellyfish.Room + alias Fishjam.Room - alias JellyfishWeb.ApiSpec.Component.HLS.Options + alias FishjamWeb.ApiSpec.Component.HLS.Options alias Membrane.RTC.Engine.Endpoint.HLS alias Membrane.RTC.Engine.Endpoint.HLS.{CompositorConfig, HLSConfig, MixerConfig} @@ -97,7 +97,7 @@ defmodule Jellyfish.Component.HLS do end def output_dir(room_id, persistent: false) do - base_path = Application.fetch_env!(:jellyfish, :media_files_path) + base_path = Application.fetch_env!(:fishjam, :media_files_path) Path.join([base_path, "temporary_hls", "#{room_id}"]) end diff --git a/lib/jellyfish/component/hls/ets_helper.ex b/lib/jellyfish/component/hls/ets_helper.ex index f8d11232..3f27bc18 100644 --- a/lib/jellyfish/component/hls/ets_helper.ex +++ b/lib/jellyfish/component/hls/ets_helper.ex @@ -1,7 +1,7 @@ -defmodule Jellyfish.Component.HLS.EtsHelper do +defmodule Fishjam.Component.HLS.EtsHelper do @moduledoc false - alias Jellyfish.Room + alias Fishjam.Room @rooms_to_tables :rooms_to_tables diff --git a/lib/jellyfish/component/hls/httpoison.ex b/lib/jellyfish/component/hls/httpoison.ex index 4a506e0f..50f3d865 100644 --- a/lib/jellyfish/component/hls/httpoison.ex +++ b/lib/jellyfish/component/hls/httpoison.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Component.HLS.HTTPoison do +defmodule Fishjam.Component.HLS.HTTPoison do @moduledoc false @behaviour ExAws.Request.HttpClient diff --git a/lib/jellyfish/component/hls/ll_storage.ex b/lib/jellyfish/component/hls/ll_storage.ex index 20d5b5c7..ef3f5fef 100644 --- a/lib/jellyfish/component/hls/ll_storage.ex +++ b/lib/jellyfish/component/hls/ll_storage.ex @@ -1,10 +1,10 @@ -defmodule Jellyfish.Component.HLS.LLStorage do +defmodule Fishjam.Component.HLS.LLStorage do @moduledoc false @behaviour Membrane.HTTPAdaptiveStream.Storage - alias Jellyfish.Component.HLS.{EtsHelper, RequestHandler} - alias Jellyfish.Room + alias Fishjam.Component.HLS.{EtsHelper, RequestHandler} + alias Fishjam.Room @enforce_keys [:directory, :room_id] defstruct @enforce_keys ++ diff --git a/lib/jellyfish/component/hls/manager.ex b/lib/jellyfish/component/hls/manager.ex index 2812220d..f56f2ccf 100644 --- a/lib/jellyfish/component/hls/manager.ex +++ b/lib/jellyfish/component/hls/manager.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Component.HLS.Manager do +defmodule Fishjam.Component.HLS.Manager do @moduledoc """ Module responsible for HLS processing. It: @@ -11,8 +11,8 @@ defmodule Jellyfish.Component.HLS.Manager do require Logger - alias Jellyfish.Event - alias Jellyfish.Room + alias Fishjam.Event + alias Fishjam.Room @hls_extensions [".m4s", ".m3u8", ".mp4"] @playlist_content_type "application/vnd.apple.mpegurl" @@ -27,7 +27,7 @@ defmodule Jellyfish.Component.HLS.Manager do @spec start(Room.id(), pid(), String.t(), map()) :: {:ok, pid()} | {:error, term()} def start(room_id, engine_pid, hls_dir, hls_options) do DynamicSupervisor.start_child( - Jellyfish.HLS.ManagerSupervisor, + Fishjam.HLS.ManagerSupervisor, {__MODULE__, %{room_id: room_id, engine_pid: engine_pid, hls_dir: hls_dir, hls_options: hls_options}} ) diff --git a/lib/jellyfish/component/hls/recording.ex b/lib/jellyfish/component/hls/recording.ex index 95c636f1..b2e05e45 100644 --- a/lib/jellyfish/component/hls/recording.ex +++ b/lib/jellyfish/component/hls/recording.ex @@ -1,9 +1,9 @@ -defmodule Jellyfish.Component.HLS.Recording do +defmodule Fishjam.Component.HLS.Recording do @moduledoc false - alias Jellyfish.Component.HLS.EtsHelper - alias Jellyfish.Room - alias Jellyfish.Utils.PathValidation + alias Fishjam.Component.HLS.EtsHelper + alias Fishjam.Room + alias Fishjam.Utils.PathValidation @recordings_folder "recordings" @@ -45,7 +45,7 @@ defmodule Jellyfish.Component.HLS.Recording do end defp root_directory() do - Application.fetch_env!(:jellyfish, :media_files_path) + Application.fetch_env!(:fishjam, :media_files_path) |> Path.join(@recordings_folder) |> Path.expand() end diff --git a/lib/jellyfish/component/hls/request_handler.ex b/lib/jellyfish/component/hls/request_handler.ex index 36720fa1..e8bbd303 100644 --- a/lib/jellyfish/component/hls/request_handler.ex +++ b/lib/jellyfish/component/hls/request_handler.ex @@ -1,12 +1,12 @@ -defmodule Jellyfish.Component.HLS.RequestHandler do +defmodule Fishjam.Component.HLS.RequestHandler do @moduledoc false use GenServer use Bunch.Access - alias Jellyfish.Utils.PathValidation - alias Jellyfish.Component.HLS.{EtsHelper, Recording} - alias Jellyfish.Room + alias Fishjam.Utils.PathValidation + alias Fishjam.Component.HLS.{EtsHelper, Recording} + alias Fishjam.Room @enforce_keys [:room_id, :room_pid] defstruct @enforce_keys ++ @@ -300,7 +300,7 @@ defmodule Jellyfish.Component.HLS.RequestHandler do |> List.to_tuple() end - defp registry_id(room_id), do: {:via, Registry, {Jellyfish.RequestHandlerRegistry, room_id}} + defp registry_id(room_id), do: {:via, Registry, {Fishjam.RequestHandlerRegistry, room_id}} defp send_partial_ready(waiting_pids) do Enum.each(waiting_pids, fn pid -> send(pid, :manifest_ready) end) diff --git a/lib/jellyfish/component/hls/storage.ex b/lib/jellyfish/component/hls/storage.ex index d989218b..c8f23c1f 100644 --- a/lib/jellyfish/component/hls/storage.ex +++ b/lib/jellyfish/component/hls/storage.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Component.HLS.Storage do +defmodule Fishjam.Component.HLS.Storage do @moduledoc false @behaviour Membrane.HTTPAdaptiveStream.Storage @@ -27,7 +27,7 @@ defmodule Jellyfish.Component.HLS.Storage do write_to_file(directory, name, content, [:binary]) %{mode: :binary, type: :partial_segment} -> - raise "This storage doesn't support ll-hls. Use `Jellyfish.Component.HLS.LLStorage` instead" + raise "This storage doesn't support ll-hls. Use `Fishjam.Component.HLS.LLStorage` instead" %{mode: :binary, type: :header} -> write_to_file(directory, name, content, [:binary]) diff --git a/lib/jellyfish/component/recording.ex b/lib/jellyfish/component/recording.ex index b8036990..772159ca 100644 --- a/lib/jellyfish/component/recording.ex +++ b/lib/jellyfish/component/recording.ex @@ -1,19 +1,19 @@ -defmodule Jellyfish.Component.Recording do +defmodule Fishjam.Component.Recording do @moduledoc """ Module representing the Recording component. """ - @behaviour Jellyfish.Endpoint.Config - use Jellyfish.Component + @behaviour Fishjam.Endpoint.Config + use Fishjam.Component - alias JellyfishWeb.ApiSpec.Component.Recording.Options + alias FishjamWeb.ApiSpec.Component.Recording.Options alias Membrane.RTC.Engine.Endpoint.Recording @type properties :: %{path_prefix: Path.t()} @impl true def config(%{engine_pid: engine} = options) do - sink_config = Application.fetch_env!(:jellyfish, :s3_config) + sink_config = Application.fetch_env!(:fishjam, :s3_config) with {:ok, serialized_opts} <- serialize_options(options, Options.schema()), result_opts <- parse_subscribe_mode(serialized_opts), @@ -52,7 +52,7 @@ defmodule Jellyfish.Component.Recording do end def get_base_path(), - do: :jellyfish |> Application.fetch_env!(:media_files_path) |> Path.join("raw_recordings") + do: :fishjam |> Application.fetch_env!(:media_files_path) |> Path.join("raw_recordings") defp parse_subscribe_mode(opts) do Map.update!(opts, :subscribe_mode, &String.to_atom/1) diff --git a/lib/jellyfish/component/rtsp.ex b/lib/jellyfish/component/rtsp.ex index dae6bdc9..ab9e5fb1 100644 --- a/lib/jellyfish/component/rtsp.ex +++ b/lib/jellyfish/component/rtsp.ex @@ -1,14 +1,14 @@ -defmodule Jellyfish.Component.RTSP do +defmodule Fishjam.Component.RTSP do @moduledoc """ Module representing the RTSP component. """ - @behaviour Jellyfish.Endpoint.Config - use Jellyfish.Component + @behaviour Fishjam.Endpoint.Config + use Fishjam.Component alias Membrane.RTC.Engine.Endpoint.RTSP - alias JellyfishWeb.ApiSpec.Component.RTSP.Options + alias FishjamWeb.ApiSpec.Component.RTSP.Options @type properties :: %{ sourceUri: String.t(), diff --git a/lib/jellyfish/component/sip.ex b/lib/jellyfish/component/sip.ex index cf90c164..fb1cee12 100644 --- a/lib/jellyfish/component/sip.ex +++ b/lib/jellyfish/component/sip.ex @@ -1,15 +1,15 @@ -defmodule Jellyfish.Component.SIP do +defmodule Fishjam.Component.SIP do @moduledoc """ Module representing the SIP component. """ - @behaviour Jellyfish.Endpoint.Config - use Jellyfish.Component + @behaviour Fishjam.Endpoint.Config + use Fishjam.Component alias Membrane.RTC.Engine.Endpoint.SIP alias Membrane.RTC.Engine.Endpoint.SIP.RegistrarCredentials - alias JellyfishWeb.ApiSpec.Component.SIP.Options + alias FishjamWeb.ApiSpec.Component.SIP.Options @type properties :: %{ registrar_credentials: %{ @@ -21,7 +21,7 @@ defmodule Jellyfish.Component.SIP do @impl true def config(%{engine_pid: engine} = options) do - external_ip = Application.fetch_env!(:jellyfish, :sip_config)[:sip_external_ip] + external_ip = Application.fetch_env!(:fishjam, :sip_config)[:sip_external_ip] with {:ok, serialized_opts} <- serialize_options(options, Options.schema()) do endpoint_spec = %SIP{ diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex index 572abb5e..f22be44f 100644 --- a/lib/jellyfish/config_reader.ex +++ b/lib/jellyfish/config_reader.ex @@ -1,10 +1,10 @@ -defmodule Jellyfish.ConfigReader do +defmodule Fishjam.ConfigReader do @moduledoc false require Logger def read_port_range(env) do - if value = System.get_env(env) do + if value = get_env(env) do with [str1, str2] <- String.split(value, "-"), from when from in 0..65_535 <- String.to_integer(str1), to when to in from..65_535 and from <= to <- String.to_integer(str2) do @@ -21,7 +21,7 @@ defmodule Jellyfish.ConfigReader do end def read_ip(env) do - if value = System.get_env(env) do + if value = get_env(env) do value = value |> to_charlist() case :inet.parse_address(value) do @@ -37,7 +37,7 @@ defmodule Jellyfish.ConfigReader do end def read_and_resolve_hostname(env) do - if value = System.get_env(env) do + if value = get_env(env) do # resolve_hostname will raise if address is invalid/unresolvable {:ok, resolved_ip} = value |> resolve_hostname() |> to_charlist() |> :inet.parse_address() @@ -46,7 +46,7 @@ defmodule Jellyfish.ConfigReader do end def read_port(env) do - if value = System.get_env(env) do + if value = get_env(env) do case Integer.parse(value) do {port, _sufix} when port in 1..65_535 -> port @@ -70,7 +70,7 @@ defmodule Jellyfish.ConfigReader do end def read_boolean(env, fallback \\ nil) do - if value = System.get_env(env) do + if value = get_env(env) do case String.downcase(value) do "true" -> true @@ -88,18 +88,18 @@ defmodule Jellyfish.ConfigReader do end def read_ssl_config() do - ssl_key_path = System.get_env("JF_SSL_KEY_PATH") - ssl_cert_path = System.get_env("JF_SSL_CERT_PATH") + ssl_key_path = get_env("FJ_SSL_KEY_PATH") + ssl_cert_path = get_env("FJ_SSL_CERT_PATH") case {ssl_key_path, ssl_cert_path} do {nil, nil} -> nil {nil, ssl_cert_path} when ssl_cert_path != nil -> - raise "JF_SSL_CERT_PATH has been set but JF_SSL_KEY_PATH remains unset" + raise "FJ_SSL_CERT_PATH has been set but FJ_SSL_KEY_PATH remains unset" {ssl_key_path, nil} when ssl_key_path != nil -> - raise "JF_SSL_KEY_PATH has been set but JF_SSL_CERT_PATH remains unset" + raise "FJ_SSL_KEY_PATH has been set but FJ_SSL_CERT_PATH remains unset" other -> other @@ -107,15 +107,15 @@ defmodule Jellyfish.ConfigReader do end def read_webrtc_config() do - webrtc_used? = read_boolean("JF_WEBRTC_USED") + webrtc_used? = read_boolean("FJ_WEBRTC_USED") if webrtc_used? != false do [ webrtc_used?: true, - turn_ip: read_ip("JF_WEBRTC_TURN_IP") || {127, 0, 0, 1}, - turn_listen_ip: read_and_resolve_hostname("JF_WEBRTC_TURN_LISTEN_IP") || {127, 0, 0, 1}, - turn_port_range: read_port_range("JF_WEBRTC_TURN_PORT_RANGE") || {50_000, 59_999}, - turn_tcp_port: read_port("JF_WEBRTC_TURN_TCP_PORT") + turn_ip: read_ip("FJ_WEBRTC_TURN_IP") || {127, 0, 0, 1}, + turn_listen_ip: read_and_resolve_hostname("FJ_WEBRTC_TURN_LISTEN_IP") || {127, 0, 0, 1}, + turn_port_range: read_port_range("FJ_WEBRTC_TURN_PORT_RANGE") || {50_000, 59_999}, + turn_tcp_port: read_port("FJ_WEBRTC_TURN_TCP_PORT") ] else [ @@ -129,25 +129,25 @@ defmodule Jellyfish.ConfigReader do end def read_components_used() do - components_used = System.get_env("JF_COMPONENTS_USED") || "" + components_used = get_env("FJ_COMPONENTS_USED") || "" components_used |> String.split(" ", trim: true) |> Enum.map(fn type -> - case Jellyfish.Component.parse_type(type) do + case Fishjam.Component.parse_type(type) do {:ok, component} -> component {:error, :invalid_type} -> raise( - "Invalid value in JF_COMPONENTS_USED. Expected a lowercase component name, got: #{type}" + "Invalid value in FJ_COMPONENTS_USED. Expected a lowercase component name, got: #{type}" ) end end) end def read_sip_config(sip_used?) do - sip_ip = System.get_env("JF_SIP_IP") || "" + sip_ip = get_env("FJ_SIP_IP") || "" cond do sip_used? != true -> @@ -162,20 +162,20 @@ defmodule Jellyfish.ConfigReader do true -> raise """ - SIP components are allowed, but incorrect IP address was provided as `JF_SIP_IP` + SIP components are allowed, but incorrect IP address was provided as `FJ_SIP_IP` """ end end def read_s3_config() do credentials = [ - bucket: System.get_env("JF_S3_BUCKET"), - region: System.get_env("JF_S3_REGION"), - access_key_id: System.get_env("JF_S3_ACCESS_KEY_ID"), - secret_access_key: System.get_env("JF_S3_SECRET_ACCESS_KEY") + bucket: get_env("FJ_S3_BUCKET"), + region: get_env("FJ_S3_REGION"), + access_key_id: get_env("FJ_S3_ACCESS_KEY_ID"), + secret_access_key: get_env("FJ_S3_SECRET_ACCESS_KEY") ] - path_prefix = System.get_env("JF_S3_PATH_PREFIX") + path_prefix = get_env("FJ_S3_PATH_PREFIX") credentials = cond do @@ -190,11 +190,11 @@ defmodule Jellyfish.ConfigReader do credentials |> Enum.filter(fn {_key, val} -> val == nil end) |> Enum.map(fn {key, _val} -> - "JF_" <> (key |> Atom.to_string() |> String.upcase()) + "FJ_" <> (key |> Atom.to_string() |> String.upcase()) end) raise """ - Either all S3 credentials have to be set: `JF_S3_BUCKET`, `JF_S3_REGION`, `JF_S3_ACCESS_KEY_ID`, `JF_S3_SECRET_ACCESS_KEY`, or none must be set. + Either all S3 credentials have to be set: `FJ_S3_BUCKET`, `FJ_S3_REGION`, `FJ_S3_ACCESS_KEY_ID`, `FJ_S3_SECRET_ACCESS_KEY`, or none must be set. Currently, the following required credentials are missing: #{inspect(missing_envs)}. """ end @@ -206,13 +206,13 @@ defmodule Jellyfish.ConfigReader do end def read_dist_config() do - dist_enabled? = read_boolean("JF_DIST_ENABLED") - dist_strategy = System.get_env("JF_DIST_STRATEGY_NAME") - mode_value = System.get_env("JF_DIST_MODE", "sname") - cookie_value = System.get_env("JF_DIST_COOKIE", "jellyfish_cookie") + dist_enabled? = read_boolean("FJ_DIST_ENABLED") + dist_strategy = get_env("FJ_DIST_STRATEGY_NAME") + mode_value = get_env("FJ_DIST_MODE", "sname") + cookie_value = get_env("FJ_DIST_COOKIE", "fishjam_cookie") {:ok, hostname} = :inet.gethostname() - node_name_value = System.get_env("JF_DIST_NODE_NAME", "jellyfish@#{hostname}") + node_name_value = get_env("FJ_DIST_NODE_NAME", "fishjam@#{hostname}") cookie = parse_cookie(cookie_value) mode = parse_mode(mode_value) @@ -236,26 +236,26 @@ defmodule Jellyfish.ConfigReader do true -> raise """ - JF_DIST_ENABLED has been set but unknown JF_DIST_STRATEGY was provided. + FJ_DIST_ENABLED has been set but unknown FJ_DIST_STRATEGY was provided. Availabile strategies are EPMD or DNS, provided strategy name was: "#{dist_strategy}" """ end end def read_git_commit() do - System.get_env("JF_GIT_COMMIT", "dev") + get_env("FJ_GIT_COMMIT", "dev") end defp do_read_nodes_list_config(node_name_value, cookie, mode) do - nodes_value = System.get_env("JF_DIST_NODES", "") + nodes_value = get_env("FJ_DIST_NODES", "") node_name = parse_node_name(node_name_value) nodes = parse_nodes(nodes_value) if nodes == [] do Logger.warning(""" - NODES_LIST strategy requires JF_DIST_NODES to be set - by at least one Jellyfish instace. This instance has JF_DIST_NODES unset. + NODES_LIST strategy requires FJ_DIST_NODES to be set + by at least one Fishjam instace. This instance has FJ_DIST_NODES unset. """) end @@ -270,17 +270,17 @@ defmodule Jellyfish.ConfigReader do end defp do_read_dns_config(_node_name_value, _cookie, :shortnames) do - raise "DNS strategy requires `JF_DIST_MODE` to be `name`" + raise "DNS strategy requires `FJ_DIST_MODE` to be `name`" end defp do_read_dns_config(node_name_value, cookie, mode) do # Verify the node name is formatted correctly _node_name = parse_node_name(node_name_value) - query_value = System.get_env("JF_DIST_QUERY") + query_value = get_env("FJ_DIST_QUERY") unless query_value do - raise "JF_DIST_QUERY is required by DNS strategy" + raise "FJ_DIST_QUERY is required by DNS strategy" end [node_basename, hostname | []] = String.split(node_name_value, "@") @@ -309,7 +309,7 @@ defmodule Jellyfish.ConfigReader do String.to_atom(node_name) _other -> - raise "JF_DIST_NODE_NAME has to be in form of @. Got: #{node_name}" + raise "FJ_DIST_NODE_NAME has to be in form of @. Got: #{node_name}" end end @@ -322,20 +322,20 @@ defmodule Jellyfish.ConfigReader do end defp parse_polling_interval() do - env_value = System.get_env("JF_DIST_POLLING_INTERVAL", "5000") + env_value = get_env("FJ_DIST_POLLING_INTERVAL", "5000") case Integer.parse(env_value) do {polling_interval, ""} when polling_interval > 0 -> polling_interval _other -> - raise "`JF_DIST_POLLING_INTERVAL` must be a positivie integer. Got: #{env_value}" + raise "`FJ_DIST_POLLING_INTERVAL` must be a positivie integer. Got: #{env_value}" end end defp parse_mode("name"), do: :longnames defp parse_mode("sname"), do: :shortnames - defp parse_mode(other), do: raise("Invalid JF_DIST_MODE. Expected sname or name, got: #{other}") + defp parse_mode(other), do: raise("Invalid FJ_DIST_MODE. Expected sname or name, got: #{other}") defp ip_address?(hostname) do case :inet.parse_address(String.to_charlist(hostname)) do @@ -363,4 +363,22 @@ defmodule Jellyfish.ConfigReader do """ end end + + defp get_env("FJ_" <> rest = name, default \\ nil) do + fj_name = name + jf_name = "JF_" <> rest + + fj_var = System.get_env(fj_name) + jf_var = System.get_env(jf_name) + + if jf_var != nil do + Logger.warning(""" + It looks like you have still an env variable prefixed with JF_ set. + Support for those variables will be removed in version 0.8.0. + Variable: #{jf_name} + """) + end + + fj_var || jf_var || default + end end diff --git a/lib/jellyfish/endpoint/config.ex b/lib/jellyfish/endpoint/config.ex index e14bc2a3..5d0e2fa2 100644 --- a/lib/jellyfish/endpoint/config.ex +++ b/lib/jellyfish/endpoint/config.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Endpoint.Config do +defmodule Fishjam.Endpoint.Config do @moduledoc """ An interface for RTC Engine endpoint configuration. """ diff --git a/lib/jellyfish/event.ex b/lib/jellyfish/event.ex index 918fd843..e4def2a8 100644 --- a/lib/jellyfish/event.ex +++ b/lib/jellyfish/event.ex @@ -1,7 +1,7 @@ -defmodule Jellyfish.Event do +defmodule Fishjam.Event do @moduledoc false - alias Jellyfish.ServerMessage.{ + alias Fishjam.ServerMessage.{ ComponentCrashed, HlsPlayable, HlsUploadCrashed, @@ -24,7 +24,7 @@ defmodule Jellyfish.Event do alias Membrane.RTC.Engine.Message - @pubsub Jellyfish.PubSub + @pubsub Fishjam.PubSub @valid_topics [:server_notification, :metrics] def broadcast_metrics(message), do: broadcast(:metrics, message) @@ -115,7 +115,7 @@ defmodule Jellyfish.Event do defp to_proto_server_notification({:hls_upload_crashed, room_id}), do: {:hls_upload_crashed, %HlsUploadCrashed{room_id: room_id}} - defp to_proto_track(%Jellyfish.Track{} = track) do + defp to_proto_track(%Fishjam.Track{} = track) do %Track{ id: track.id, type: to_proto_track_type(track.type), diff --git a/lib/jellyfish/metrics_scraper.ex b/lib/jellyfish/metrics_scraper.ex index 75eaa7a0..5f50081d 100644 --- a/lib/jellyfish/metrics_scraper.ex +++ b/lib/jellyfish/metrics_scraper.ex @@ -1,9 +1,9 @@ -defmodule Jellyfish.MetricsScraper do +defmodule Fishjam.MetricsScraper do @moduledoc false use GenServer, restart: :temporary - alias Jellyfish.Event + alias Fishjam.Event alias Membrane.TelemetryMetrics.Reporter @metrics_to_derive [ @@ -41,7 +41,7 @@ defmodule Jellyfish.MetricsScraper do @impl true def handle_info(:scrape, state) do - report = Reporter.scrape(JellyfishMetricsReporter) + report = Reporter.scrape(FishjamMetricsReporter) report |> prepare_report(state) diff --git a/lib/jellyfish/peer.ex b/lib/jellyfish/peer.ex index ae00ef01..88251884 100644 --- a/lib/jellyfish/peer.ex +++ b/lib/jellyfish/peer.ex @@ -1,12 +1,12 @@ -defmodule Jellyfish.Peer do +defmodule Fishjam.Peer do @moduledoc """ Peer is an entity that connects to the server to publish, subscribe or publish and subscribe to tracks published by producers or other peers. Peer process is spawned after peer connects to the server. """ use Bunch.Access - alias Jellyfish.Peer.WebRTC - alias Jellyfish.Track + alias Fishjam.Peer.WebRTC + alias Fishjam.Track @enforce_keys [ :id, diff --git a/lib/jellyfish/peer/webrtc.ex b/lib/jellyfish/peer/webrtc.ex index aa34bec3..f9409b35 100644 --- a/lib/jellyfish/peer/webrtc.ex +++ b/lib/jellyfish/peer/webrtc.ex @@ -1,16 +1,16 @@ -defmodule Jellyfish.Peer.WebRTC do +defmodule Fishjam.Peer.WebRTC do @moduledoc """ Module representing WebRTC peer. """ - @behaviour Jellyfish.Endpoint.Config + @behaviour Fishjam.Endpoint.Config alias Membrane.RTC.Engine.Endpoint.WebRTC alias Membrane.RTC.Engine.Endpoint.WebRTC.SimulcastConfig alias Membrane.WebRTC.Extension.{Mid, RepairedRid, Rid, TWCC, VAD} alias Membrane.WebRTC.Track.Encoding - alias JellyfishWeb.ApiSpec + alias FishjamWeb.ApiSpec @impl true def config(options) do diff --git a/lib/jellyfish/resource_manager.ex b/lib/jellyfish/resource_manager.ex index e164c4d5..65310e47 100644 --- a/lib/jellyfish/resource_manager.ex +++ b/lib/jellyfish/resource_manager.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.ResourceManager do +defmodule Fishjam.ResourceManager do @moduledoc """ Module responsible for deleting outdated resources. Right now it only removes outdated resources created by recording component. @@ -8,8 +8,8 @@ defmodule Jellyfish.ResourceManager do require Logger - alias Jellyfish.Component.Recording - alias Jellyfish.RoomService + alias Fishjam.Component.Recording + alias Fishjam.RoomService @type seconds :: pos_integer() @type opts :: %{interval: seconds(), recording_timeout: seconds()} diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index 62de054c..ac41daa7 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Room do +defmodule Fishjam.Room do @moduledoc """ Module representing room. """ @@ -6,14 +6,14 @@ defmodule Jellyfish.Room do use Bunch.Access use GenServer - import Jellyfish.Room.State + import Fishjam.Room.State require Logger - alias Jellyfish.Component - alias Jellyfish.Component.{HLS, Recording, SIP} - alias Jellyfish.Peer - alias Jellyfish.Room.{Config, State} + alias Fishjam.Component + alias Fishjam.Component.{HLS, Recording, SIP} + alias Fishjam.Peer + alias Fishjam.Room.{Config, State} alias Membrane.RTC.Engine alias Membrane.RTC.Engine.Endpoint @@ -32,7 +32,7 @@ defmodule Jellyfish.Room do @type id :: String.t() @type t :: State.t() - def registry_id(room_id), do: {:via, Registry, {Jellyfish.RoomRegistry, room_id}} + def registry_id(room_id), do: {:via, Registry, {Fishjam.RoomRegistry, room_id}} @spec start(Config.t()) :: {:ok, pid(), id()} def start(%Config{room_id: id} = config) do diff --git a/lib/jellyfish/room/config.ex b/lib/jellyfish/room/config.ex index c8cb5053..92f9703e 100644 --- a/lib/jellyfish/room/config.ex +++ b/lib/jellyfish/room/config.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Room.Config do +defmodule Fishjam.Room.Config do @moduledoc """ Room configuration """ diff --git a/lib/jellyfish/room/state.ex b/lib/jellyfish/room/state.ex index 8cf9fe63..fd2ac547 100644 --- a/lib/jellyfish/room/state.ex +++ b/lib/jellyfish/room/state.ex @@ -1,13 +1,13 @@ -defmodule Jellyfish.Room.State do +defmodule Fishjam.Room.State do @moduledoc false use Bunch.Access require Logger - alias Jellyfish.{Component, Event, Peer, Room, Track} - alias Jellyfish.Component.{HLS, Recording, RTSP} - alias Jellyfish.Room.Config + alias Fishjam.{Component, Event, Peer, Room, Track} + alias Fishjam.Component.{HLS, Recording, RTSP} + alias Fishjam.Room.Config alias Membrane.ICE.TURNManager alias Membrane.RTC.Engine @@ -64,7 +64,7 @@ defmodule Jellyfish.Room.State do {:ok, pid} = Engine.start_link(rtc_engine_options, []) Engine.register(pid, self()) - webrtc_config = Application.fetch_env!(:jellyfish, :webrtc_config) + webrtc_config = Application.fetch_env!(:fishjam, :webrtc_config) turn_options = if webrtc_config[:webrtc_used?] do @@ -232,7 +232,7 @@ defmodule Jellyfish.Room.State do Logger.info("Peer #{inspect(peer.id)} connected") - :telemetry.execute([:jellyfish, :room], %{peer_connects: 1}, %{room_id: state.id}) + :telemetry.execute([:fishjam, :room], %{peer_connects: 1}, %{room_id: state.id}) state end @@ -247,7 +247,7 @@ defmodule Jellyfish.Room.State do :ok = Engine.remove_endpoint(state.engine_pid, peer_id) Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) - :telemetry.execute([:jellyfish, :room], %{peer_disconnects: 1}, %{room_id: state.id}) + :telemetry.execute([:fishjam, :room], %{peer_disconnects: 1}, %{room_id: state.id}) peer.tracks |> Map.values() @@ -289,13 +289,13 @@ defmodule Jellyfish.Room.State do if peer.status == :connected and reason == :peer_removed do Event.broadcast_server_notification({:peer_disconnected, state.id, peer_id}) - :telemetry.execute([:jellyfish, :room], %{peer_disconnects: 1}, %{room_id: state.id}) + :telemetry.execute([:fishjam, :room], %{peer_disconnects: 1}, %{room_id: state.id}) end case reason do {:peer_crashed, crash_reason} -> Event.broadcast_server_notification({:peer_crashed, state.id, peer_id, crash_reason}) - :telemetry.execute([:jellyfish, :room], %{peer_crashes: 1}, %{room_id: state.id}) + :telemetry.execute([:fishjam, :room], %{peer_crashes: 1}, %{room_id: state.id}) _other -> Event.broadcast_server_notification({:peer_deleted, state.id, peer.id}) @@ -386,7 +386,7 @@ defmodule Jellyfish.Room.State do :ok | {:error, :peer_disabled_globally | :reached_peers_limit} def check_peer_allowed(Peer.WebRTC, state) do cond do - not Application.fetch_env!(:jellyfish, :webrtc_config)[:webrtc_used?] -> + not Application.fetch_env!(:fishjam, :webrtc_config)[:webrtc_used?] -> {:error, :peer_disabled_globally} Enum.count(state.peers) >= state.config.max_peers -> @@ -402,7 +402,7 @@ defmodule Jellyfish.Room.State do | {:error, :component_disabled_globally | :incompatible_codec | :reached_components_limit} def check_component_allowed(type, state) do - if type in Application.fetch_env!(:jellyfish, :components_used) do + if type in Application.fetch_env!(:fishjam, :components_used) do check_component_allowed_in_room(type, state) else {:error, :component_disabled_globally} diff --git a/lib/jellyfish/room_service.ex b/lib/jellyfish/room_service.ex index 379f1fa5..1b6614b2 100644 --- a/lib/jellyfish/room_service.ex +++ b/lib/jellyfish/room_service.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.RoomService do +defmodule Fishjam.RoomService do @moduledoc """ Module responsible for managing rooms. """ @@ -7,9 +7,9 @@ defmodule Jellyfish.RoomService do require Logger - alias Jellyfish.{Event, Room, WebhookNotifier} + alias Fishjam.{Event, Room, WebhookNotifier} - @metric_interval_in_seconds Application.compile_env!(:jellyfish, :room_metrics_scrape_interval) + @metric_interval_in_seconds Application.compile_env!(:fishjam, :room_metrics_scrape_interval) @metric_interval_in_milliseconds @metric_interval_in_seconds * 1_000 def start_link(args) do @@ -18,7 +18,7 @@ defmodule Jellyfish.RoomService do @spec find_room(Room.id()) :: {:ok, pid()} | {:error, :room_not_found} def find_room(room_id) do - case Registry.lookup(Jellyfish.RoomRegistry, room_id) do + case Registry.lookup(Fishjam.RoomRegistry, room_id) do [{room_pid, _value}] -> {:ok, room_pid} @@ -59,7 +59,7 @@ defmodule Jellyfish.RoomService do @spec create_room(Room.Config.t()) :: {:ok, Room.t(), String.t()} | {:error, atom()} def create_room(config) do {node_resources, failed_nodes} = - :rpc.multicall(Jellyfish.RoomService, :get_resource_usage, []) + :rpc.multicall(Fishjam.RoomService, :get_resource_usage, []) if Enum.count(failed_nodes) > 0 do Logger.warning( @@ -102,7 +102,7 @@ defmodule Jellyfish.RoomService do room_ids |> Enum.map(fn room_id -> - Task.Supervisor.async_nolink(Jellyfish.TaskSupervisor, fn -> + Task.Supervisor.async_nolink(Fishjam.TaskSupervisor, fn -> Room.get_num_forwarded_tracks(room_id) end) end) @@ -128,7 +128,7 @@ defmodule Jellyfish.RoomService do @impl true def handle_continue(_continue_arg, state) do Process.send_after(self(), :rooms_metrics, @metric_interval_in_milliseconds) - :ok = Phoenix.PubSub.subscribe(Jellyfish.PubSub, "jellyfishes") + :ok = Phoenix.PubSub.subscribe(Fishjam.PubSub, "fishjams") {:noreply, state} end @@ -146,7 +146,7 @@ defmodule Jellyfish.RoomService do Event.broadcast_server_notification({:room_created, room_id}) - {:reply, {:ok, room, Jellyfish.address()}, state} + {:reply, {:ok, room, Fishjam.address()}, state} else {:error, :room_already_exists} = error -> {:reply, error, state} @@ -173,7 +173,7 @@ defmodule Jellyfish.RoomService do rooms = list_rooms() :telemetry.execute( - [:jellyfish], + [:fishjam], %{ rooms: Enum.count(rooms) } @@ -183,7 +183,7 @@ defmodule Jellyfish.RoomService do peer_count = room.peers |> Enum.count() :telemetry.execute( - [:jellyfish, :room], + [:fishjam, :room], %{ peers: peer_count, peer_time: peer_count * @metric_interval_in_seconds, @@ -204,7 +204,7 @@ defmodule Jellyfish.RoomService do Logger.debug("Room #{room_id} is down with reason: normal") - Phoenix.PubSub.broadcast(Jellyfish.PubSub, room_id, :room_stopped) + Phoenix.PubSub.broadcast(Fishjam.PubSub, room_id, :room_stopped) Event.broadcast_server_notification({:room_deleted, room_id}) clear_room_metrics(room_id) @@ -217,7 +217,7 @@ defmodule Jellyfish.RoomService do Logger.warning("Process #{room_id} is down with reason: #{inspect(reason)}") - Phoenix.PubSub.broadcast(Jellyfish.PubSub, room_id, :room_crashed) + Phoenix.PubSub.broadcast(Fishjam.PubSub, room_id, :room_crashed) Event.broadcast_server_notification({:room_crashed, room_id}) clear_room_metrics(room_id) @@ -225,7 +225,7 @@ defmodule Jellyfish.RoomService do end defp clear_room_metrics(room_id) do - :telemetry.execute([:jellyfish, :room], %{peers: 0}, %{room_id: room_id}) + :telemetry.execute([:fishjam, :room], %{peers: 0}, %{room_id: room_id}) end defp find_best_node(node_resources) do @@ -247,7 +247,7 @@ defmodule Jellyfish.RoomService do end defp get_rooms_ids() do - Jellyfish.RoomRegistry + Fishjam.RoomRegistry |> Registry.select([{{:"$1", :_, :_}, [], [:"$1"]}]) end diff --git a/lib/jellyfish/track.ex b/lib/jellyfish/track.ex index 418d0382..c48b43b6 100644 --- a/lib/jellyfish/track.ex +++ b/lib/jellyfish/track.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Track do +defmodule Fishjam.Track do @moduledoc """ Represents a media track send from Component or Peer. """ diff --git a/lib/jellyfish/utils/parser_json.ex b/lib/jellyfish/utils/parser_json.ex index 011a3c0e..28f1e4a1 100644 --- a/lib/jellyfish/utils/parser_json.ex +++ b/lib/jellyfish/utils/parser_json.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Utils.ParserJSON do +defmodule Fishjam.Utils.ParserJSON do @moduledoc """ A utility module for converting controller responses into JSON format. """ diff --git a/lib/jellyfish/utils/path_validation.ex b/lib/jellyfish/utils/path_validation.ex index 3add9865..d4cc37ca 100644 --- a/lib/jellyfish/utils/path_validation.ex +++ b/lib/jellyfish/utils/path_validation.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.Utils.PathValidation do +defmodule Fishjam.Utils.PathValidation do @moduledoc """ A helper module for validating file and directory paths. This module is mainly used to validate filenames and paths embedded in requests. @@ -14,10 +14,10 @@ defmodule Jellyfish.Utils.PathValidation do ## Example: - iex> Jellyfish.Utils.PathValidation.inside_directory?("relative/path/to/file", "relative/path") + iex> Fishjam.Utils.PathValidation.inside_directory?("relative/path/to/file", "relative/path") true - iex> Jellyfish.Utils.PathValidation.inside_directory?("/absolute/path/to/file", "relative/path") + iex> Fishjam.Utils.PathValidation.inside_directory?("/absolute/path/to/file", "relative/path") false """ diff --git a/lib/jellyfish/webhook_notifier.ex b/lib/jellyfish/webhook_notifier.ex index 2ac0cdf7..c68377f5 100644 --- a/lib/jellyfish/webhook_notifier.ex +++ b/lib/jellyfish/webhook_notifier.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.WebhookNotifier do +defmodule Fishjam.WebhookNotifier do @moduledoc """ Module responsible for sending notifications through webhooks. """ @@ -7,8 +7,8 @@ defmodule Jellyfish.WebhookNotifier do require Logger - alias Jellyfish.Event - alias Jellyfish.ServerMessage + alias Fishjam.Event + alias Fishjam.ServerMessage def start_link(args) do GenServer.start_link(__MODULE__, args, name: __MODULE__) diff --git a/lib/jellyfish_web.ex b/lib/jellyfish_web.ex index e50ea0f5..6977bb29 100644 --- a/lib/jellyfish_web.ex +++ b/lib/jellyfish_web.ex @@ -1,11 +1,11 @@ -defmodule JellyfishWeb do +defmodule FishjamWeb 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 JellyfishWeb, :controller + use FishjamWeb, :controller The definitions below will be executed for every controller, component, etc, so keep them short and clean, focused @@ -48,9 +48,9 @@ defmodule JellyfishWeb do def verified_routes do quote do use Phoenix.VerifiedRoutes, - endpoint: JellyfishWeb.Endpoint, - router: JellyfishWeb.Router, - statics: JellyfishWeb.static_paths() + endpoint: FishjamWeb.Endpoint, + router: FishjamWeb.Router, + statics: FishjamWeb.static_paths() end end diff --git a/lib/jellyfish_web/api_spec.ex b/lib/jellyfish_web/api_spec.ex index 194925b2..1d065132 100644 --- a/lib/jellyfish_web/api_spec.ex +++ b/lib/jellyfish_web/api_spec.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec do +defmodule FishjamWeb.ApiSpec do @moduledoc false @behaviour OpenApiSpex.OpenApi @@ -10,14 +10,14 @@ defmodule JellyfishWeb.ApiSpec do def spec() do %OpenApiSpex.OpenApi{ info: %Info{ - title: "Jellyfish Media Server", - version: Jellyfish.version(), + title: "Fishjam Media Server", + version: Fishjam.version(), license: %License{ name: "Apache 2.0", url: "https://www.apache.org/licenses/LICENSE-2.0" } }, - paths: Paths.from_router(JellyfishWeb.Router), + paths: Paths.from_router(FishjamWeb.Router), components: %Components{ securitySchemes: %{"authorization" => %SecurityScheme{type: "http", scheme: "bearer"}} } @@ -32,6 +32,6 @@ defmodule JellyfishWeb.ApiSpec do @spec error(String.t()) :: {String.t(), String.t(), module()} def error(description) do - {description, "application/json", JellyfishWeb.ApiSpec.Error} + {description, "application/json", FishjamWeb.ApiSpec.Error} end end diff --git a/lib/jellyfish_web/api_spec/component.ex b/lib/jellyfish_web/api_spec/component.ex index 119d2328..2f2e6ef1 100644 --- a/lib/jellyfish_web/api_spec/component.ex +++ b/lib/jellyfish_web/api_spec/component.ex @@ -1,9 +1,9 @@ -defmodule JellyfishWeb.ApiSpec.Component do +defmodule FishjamWeb.ApiSpec.Component do @moduledoc false require OpenApiSpex - alias JellyfishWeb.ApiSpec.Component.{File, HLS, Recording, RTSP, SIP} + alias FishjamWeb.ApiSpec.Component.{File, HLS, Recording, RTSP, SIP} defmodule Type do @moduledoc false diff --git a/lib/jellyfish_web/api_spec/component/file.ex b/lib/jellyfish_web/api_spec/component/file.ex index 059915e9..d4d0459b 100644 --- a/lib/jellyfish_web/api_spec/component/file.ex +++ b/lib/jellyfish_web/api_spec/component/file.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.Component.File do +defmodule FishjamWeb.ApiSpec.Component.File do @moduledoc false require OpenApiSpex @@ -68,7 +68,7 @@ defmodule JellyfishWeb.ApiSpec.Component.File do properties: Properties, tracks: %Schema{ type: :array, - items: JellyfishWeb.ApiSpec.Track, + items: FishjamWeb.ApiSpec.Track, description: "List of all component's tracks" } }, diff --git a/lib/jellyfish_web/api_spec/component/hls.ex b/lib/jellyfish_web/api_spec/component/hls.ex index 9eca9cb5..861eebe2 100644 --- a/lib/jellyfish_web/api_spec/component/hls.ex +++ b/lib/jellyfish_web/api_spec/component/hls.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.Component.HLS do +defmodule FishjamWeb.ApiSpec.Component.HLS do @moduledoc false require OpenApiSpex @@ -129,7 +129,7 @@ defmodule JellyfishWeb.ApiSpec.Component.HLS do properties: Properties, tracks: %Schema{ type: :array, - items: JellyfishWeb.ApiSpec.Track, + items: FishjamWeb.ApiSpec.Track, description: "List of all component's tracks" } }, diff --git a/lib/jellyfish_web/api_spec/component/recording.ex b/lib/jellyfish_web/api_spec/component/recording.ex index f1c6f7f3..c1c18751 100644 --- a/lib/jellyfish_web/api_spec/component/recording.ex +++ b/lib/jellyfish_web/api_spec/component/recording.ex @@ -1,9 +1,9 @@ -defmodule JellyfishWeb.ApiSpec.Component.Recording do +defmodule FishjamWeb.ApiSpec.Component.Recording do @moduledoc false require OpenApiSpex - alias JellyfishWeb.ApiSpec.Component.HLS.S3 + alias FishjamWeb.ApiSpec.Component.HLS.S3 alias OpenApiSpex.Schema defmodule Properties do @@ -73,7 +73,7 @@ defmodule JellyfishWeb.ApiSpec.Component.Recording do properties: Properties, tracks: %Schema{ type: :array, - items: JellyfishWeb.ApiSpec.Track, + items: FishjamWeb.ApiSpec.Track, description: "List of all component's tracks" } }, diff --git a/lib/jellyfish_web/api_spec/component/rtsp.ex b/lib/jellyfish_web/api_spec/component/rtsp.ex index 1ee98028..a32fb7a2 100644 --- a/lib/jellyfish_web/api_spec/component/rtsp.ex +++ b/lib/jellyfish_web/api_spec/component/rtsp.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.Component.RTSP do +defmodule FishjamWeb.ApiSpec.Component.RTSP do @moduledoc false require OpenApiSpex @@ -99,7 +99,7 @@ defmodule JellyfishWeb.ApiSpec.Component.RTSP do properties: Properties, tracks: %Schema{ type: :array, - items: JellyfishWeb.ApiSpec.Track, + items: FishjamWeb.ApiSpec.Track, description: "List of all component's tracks" } }, diff --git a/lib/jellyfish_web/api_spec/component/sip.ex b/lib/jellyfish_web/api_spec/component/sip.ex index eaa8b8ac..c41e9a93 100644 --- a/lib/jellyfish_web/api_spec/component/sip.ex +++ b/lib/jellyfish_web/api_spec/component/sip.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.Component.SIP do +defmodule FishjamWeb.ApiSpec.Component.SIP do @moduledoc false require OpenApiSpex @@ -70,7 +70,7 @@ defmodule JellyfishWeb.ApiSpec.Component.SIP do properties: Properties, tracks: %Schema{ type: :array, - items: JellyfishWeb.ApiSpec.Track, + items: FishjamWeb.ApiSpec.Track, description: "List of all component's tracks" } }, diff --git a/lib/jellyfish_web/api_spec/dial.ex b/lib/jellyfish_web/api_spec/dial.ex index 51ce1060..20999c9e 100644 --- a/lib/jellyfish_web/api_spec/dial.ex +++ b/lib/jellyfish_web/api_spec/dial.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.Dial do +defmodule FishjamWeb.ApiSpec.Dial do @moduledoc false require OpenApiSpex diff --git a/lib/jellyfish_web/api_spec/error.ex b/lib/jellyfish_web/api_spec/error.ex index 72619b8b..9252646a 100644 --- a/lib/jellyfish_web/api_spec/error.ex +++ b/lib/jellyfish_web/api_spec/error.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.Error do +defmodule FishjamWeb.ApiSpec.Error do @moduledoc false require OpenApiSpex diff --git a/lib/jellyfish_web/api_spec/health_report.ex b/lib/jellyfish_web/api_spec/health_report.ex index 682c12dd..014971ec 100644 --- a/lib/jellyfish_web/api_spec/health_report.ex +++ b/lib/jellyfish_web/api_spec/health_report.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.HealthReport do +defmodule FishjamWeb.ApiSpec.HealthReport do @moduledoc false require OpenApiSpex @@ -11,7 +11,7 @@ defmodule JellyfishWeb.ApiSpec.HealthReport do OpenApiSpex.schema(%{ title: "HealthReportStatus", - description: "Informs about the status of Jellyfish or a specific service", + description: "Informs about the status of Fishjam or a specific service", type: :string, enum: ["UP", "DOWN"], example: "UP" @@ -25,18 +25,18 @@ defmodule JellyfishWeb.ApiSpec.HealthReport do OpenApiSpex.schema(%{ title: "HealthReportDistribution", - description: "Informs about the status of Jellyfish distribution", + description: "Informs about the status of Fishjam distribution", type: :object, properties: %{ enabled: %Schema{ type: :boolean, - description: "Whether distribution is enabled on this Jellyfish" + description: "Whether distribution is enabled on this Fishjam" }, nodeStatus: Status, nodesInCluster: %Schema{ type: :integer, description: - "Amount of nodes (including this Jellyfish's node) in the distribution cluster" + "Amount of nodes (including this Fishjam's node) in the distribution cluster" } }, required: [:nodeStatus, :nodesInCluster] @@ -45,13 +45,13 @@ defmodule JellyfishWeb.ApiSpec.HealthReport do OpenApiSpex.schema(%{ title: "HealthReport", - description: "Describes overall Jellyfish health", + description: "Describes overall Fishjam health", type: :object, properties: %{ status: Status, - uptime: %Schema{type: :integer, description: "Uptime of Jellyfish (in seconds)"}, + uptime: %Schema{type: :integer, description: "Uptime of Fishjam (in seconds)"}, distribution: Distribution, - version: %Schema{type: :string, description: "Version of Jellyfish"}, + version: %Schema{type: :string, description: "Version of Fishjam"}, gitCommit: %Schema{type: :string, description: "Commit hash of the build"} }, required: [:status, :uptime, :distribution, :version, :gitCommit] diff --git a/lib/jellyfish_web/api_spec/hls.ex b/lib/jellyfish_web/api_spec/hls.ex index 4e365294..2ec3d394 100644 --- a/lib/jellyfish_web/api_spec/hls.ex +++ b/lib/jellyfish_web/api_spec/hls.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.HLS do +defmodule FishjamWeb.ApiSpec.HLS do require OpenApiSpex defmodule Params do diff --git a/lib/jellyfish_web/api_spec/peer.ex b/lib/jellyfish_web/api_spec/peer.ex index 3f2c1b6d..c953facc 100644 --- a/lib/jellyfish_web/api_spec/peer.ex +++ b/lib/jellyfish_web/api_spec/peer.ex @@ -1,10 +1,10 @@ -defmodule JellyfishWeb.ApiSpec.Peer do +defmodule FishjamWeb.ApiSpec.Peer do @moduledoc false require OpenApiSpex alias OpenApiSpex.Schema - alias JellyfishWeb.ApiSpec.Peer.WebRTC + alias FishjamWeb.ApiSpec.Peer.WebRTC defmodule Type do @moduledoc false @@ -70,7 +70,7 @@ defmodule JellyfishWeb.ApiSpec.Peer do title: "WebsocketURL", description: "Websocket URL to which peer has to connect", type: :string, - example: "www.jellyfish.org/socket/peer" + example: "www.fishjam.org/socket/peer" }) end @@ -82,7 +82,7 @@ defmodule JellyfishWeb.ApiSpec.Peer do OpenApiSpex.schema(%{ title: "PeerMetadata", description: "Custom metadata set by the peer", - example: %{name: "JellyfishUser"}, + example: %{name: "FishjamUser"}, nullable: true }) end @@ -97,7 +97,7 @@ defmodule JellyfishWeb.ApiSpec.Peer do status: Status, tracks: %Schema{ type: :array, - items: JellyfishWeb.ApiSpec.Track, + items: FishjamWeb.ApiSpec.Track, description: "List of all peer's tracks" }, metadata: PeerMetadata diff --git a/lib/jellyfish_web/api_spec/peer/webrtc.ex b/lib/jellyfish_web/api_spec/peer/webrtc.ex index 0207bd20..d1b28b4a 100644 --- a/lib/jellyfish_web/api_spec/peer/webrtc.ex +++ b/lib/jellyfish_web/api_spec/peer/webrtc.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.Peer.WebRTC do +defmodule FishjamWeb.ApiSpec.Peer.WebRTC do @moduledoc false require OpenApiSpex diff --git a/lib/jellyfish_web/api_spec/responses.ex b/lib/jellyfish_web/api_spec/responses.ex index e8237e17..dd7c3905 100644 --- a/lib/jellyfish_web/api_spec/responses.ex +++ b/lib/jellyfish_web/api_spec/responses.ex @@ -3,38 +3,38 @@ %OpenApiSpex.Schema{ type: :object, properties: %{ - peer: JellyfishWeb.ApiSpec.Peer, - token: JellyfishWeb.ApiSpec.Peer.Token, - peer_websocket_url: JellyfishWeb.ApiSpec.Peer.WebSocketUrl + peer: FishjamWeb.ApiSpec.Peer, + token: FishjamWeb.ApiSpec.Peer.Token, + peer_websocket_url: FishjamWeb.ApiSpec.Peer.WebSocketUrl }, required: [:peer, :token] }}, - {RoomDetailsResponse, "Response containing room details", JellyfishWeb.ApiSpec.Room}, + {RoomDetailsResponse, "Response containing room details", FishjamWeb.ApiSpec.Room}, {RoomCreateDetailsResponse, "Response containing room details", %OpenApiSpex.Schema{ type: :object, properties: %{ - room: JellyfishWeb.ApiSpec.Room, - jellyfish_address: %OpenApiSpex.Schema{ + room: FishjamWeb.ApiSpec.Room, + fishjam_address: %OpenApiSpex.Schema{ description: - "Jellyfish instance address where the room was created. This might be different than the address of Jellyfish where the request was sent only when running a cluster of Jellyfishes.", + "Fishjam instance address where the room was created. This might be different than the address of Fishjam where the request was sent only when running a cluster of Fishjams.", type: :string, - example: "jellyfish1:5003" + example: "fishjam1:5003" } }, - required: [:room, :jellyfish_address] + required: [:room, :fishjam_address] }}, {ComponentDetailsResponse, "Response containing component details", - JellyfishWeb.ApiSpec.Component}, + FishjamWeb.ApiSpec.Component}, {RoomsListingResponse, "Response containing list of all rooms", - %OpenApiSpex.Schema{type: :array, items: JellyfishWeb.ApiSpec.Room}}, + %OpenApiSpex.Schema{type: :array, items: FishjamWeb.ApiSpec.Room}}, {RecordingListResponse, "Response containing list of all recording", %OpenApiSpex.Schema{type: :array, items: %OpenApiSpex.Schema{type: :string}}}, - {HealthcheckResponse, "Response containing health report of Jellyfish", - JellyfishWeb.ApiSpec.HealthReport} + {HealthcheckResponse, "Response containing health report of Fishjam", + FishjamWeb.ApiSpec.HealthReport} ] |> Enum.map(fn {title, description, schema} -> - module = Module.concat(JellyfishWeb.ApiSpec, title) + module = Module.concat(FishjamWeb.ApiSpec, title) title_str = inspect(title) defmodule module do diff --git a/lib/jellyfish_web/api_spec/room.ex b/lib/jellyfish_web/api_spec/room.ex index c9ab507f..0c192723 100644 --- a/lib/jellyfish_web/api_spec/room.ex +++ b/lib/jellyfish_web/api_spec/room.ex @@ -1,7 +1,7 @@ -defmodule JellyfishWeb.ApiSpec.Room do +defmodule FishjamWeb.ApiSpec.Room do require OpenApiSpex - alias JellyfishWeb.ApiSpec.{Component, Peer} + alias FishjamWeb.ApiSpec.{Component, Peer} alias OpenApiSpex.Schema defmodule Config do @@ -17,7 +17,7 @@ defmodule JellyfishWeb.ApiSpec.Room do roomId: %Schema{ type: :string, description: - "Custom id used for identifying room within Jellyfish. Must be unique across all rooms. If not provided, random UUID is generated.", + "Custom id used for identifying room within Fishjam. Must be unique across all rooms. If not provided, random UUID is generated.", nullable: true }, maxPeers: %Schema{ @@ -34,9 +34,9 @@ defmodule JellyfishWeb.ApiSpec.Room do nullable: true }, webhookUrl: %Schema{ - description: "URL where Jellyfish notifications will be sent", + description: "URL where Fishjam notifications will be sent", type: :string, - example: "https://backend.address.com/jellyfish-notifications-endpoint", + example: "https://backend.address.com/fishjam-notifications-endpoint", nullable: true }, peerlessPurgeTimeout: %Schema{ diff --git a/lib/jellyfish_web/api_spec/subscription.ex b/lib/jellyfish_web/api_spec/subscription.ex index 293b16f8..2a0d0d3e 100644 --- a/lib/jellyfish_web/api_spec/subscription.ex +++ b/lib/jellyfish_web/api_spec/subscription.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.Subscription do +defmodule FishjamWeb.ApiSpec.Subscription do require OpenApiSpex alias OpenApiSpex.Schema diff --git a/lib/jellyfish_web/api_spec/track.ex b/lib/jellyfish_web/api_spec/track.ex index a12c4358..d6f3f4fd 100644 --- a/lib/jellyfish_web/api_spec/track.ex +++ b/lib/jellyfish_web/api_spec/track.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ApiSpec.Track do +defmodule FishjamWeb.ApiSpec.Track do @moduledoc false require OpenApiSpex diff --git a/lib/jellyfish_web/controllers/component_controller.ex b/lib/jellyfish_web/controllers/component_controller.ex index 443fdc3b..f50a8024 100644 --- a/lib/jellyfish_web/controllers/component_controller.ex +++ b/lib/jellyfish_web/controllers/component_controller.ex @@ -1,14 +1,14 @@ -defmodule JellyfishWeb.ComponentController do - use JellyfishWeb, :controller +defmodule FishjamWeb.ComponentController do + use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs - alias Jellyfish.Component - alias Jellyfish.Room - alias Jellyfish.RoomService - alias JellyfishWeb.ApiSpec + alias Fishjam.Component + alias Fishjam.Room + alias Fishjam.RoomService + alias FishjamWeb.ApiSpec alias OpenApiSpex.{Response, Schema} - action_fallback JellyfishWeb.FallbackController + action_fallback FishjamWeb.FallbackController tags [:room] @@ -83,7 +83,7 @@ defmodule JellyfishWeb.ComponentController do {:error, :bad_request, "Invalid component type"} {:error, {:component_disabled_globally, type}} -> - {:error, :bad_request, "Components of type #{type} are disabled on this Jellyfish"} + {:error, :bad_request, "Components of type #{type} are disabled on this Fishjam"} {:error, :incompatible_codec} -> {:error, :bad_request, "Incompatible video codec enforced in room #{room_id}"} diff --git a/lib/jellyfish_web/controllers/component_json.ex b/lib/jellyfish_web/controllers/component_json.ex index c7987d67..63746b19 100644 --- a/lib/jellyfish_web/controllers/component_json.ex +++ b/lib/jellyfish_web/controllers/component_json.ex @@ -1,8 +1,8 @@ -defmodule JellyfishWeb.ComponentJSON do +defmodule FishjamWeb.ComponentJSON do @moduledoc false - alias Jellyfish.Component.{File, HLS, Recording, RTSP, SIP} - alias Jellyfish.Utils.ParserJSON + alias Fishjam.Component.{File, HLS, Recording, RTSP, SIP} + alias Fishjam.Utils.ParserJSON def show(%{component: component}) do %{data: data(component)} diff --git a/lib/jellyfish_web/controllers/error_json.ex b/lib/jellyfish_web/controllers/error_json.ex index f5de1ac5..60e5f108 100644 --- a/lib/jellyfish_web/controllers/error_json.ex +++ b/lib/jellyfish_web/controllers/error_json.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ErrorJSON do +defmodule FishjamWeb.ErrorJSON do @moduledoc false # If you want to customize a particular status code, # you may add your own clauses, such as: diff --git a/lib/jellyfish_web/controllers/fallback_controller.ex b/lib/jellyfish_web/controllers/fallback_controller.ex index d3168591..00f424eb 100644 --- a/lib/jellyfish_web/controllers/fallback_controller.ex +++ b/lib/jellyfish_web/controllers/fallback_controller.ex @@ -1,5 +1,5 @@ -defmodule JellyfishWeb.FallbackController do - use JellyfishWeb, :controller +defmodule FishjamWeb.FallbackController do + use FishjamWeb, :controller def call(conn, {:error, status, reason}) do conn diff --git a/lib/jellyfish_web/controllers/healthcheck_controller.ex b/lib/jellyfish_web/controllers/healthcheck_controller.ex index de2ab21b..724eaef3 100644 --- a/lib/jellyfish_web/controllers/healthcheck_controller.ex +++ b/lib/jellyfish_web/controllers/healthcheck_controller.ex @@ -1,10 +1,10 @@ -defmodule JellyfishWeb.HealthcheckController do - use JellyfishWeb, :controller +defmodule FishjamWeb.HealthcheckController do + use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs - alias JellyfishWeb.ApiSpec + alias FishjamWeb.ApiSpec - action_fallback JellyfishWeb.FallbackController + action_fallback FishjamWeb.FallbackController tags [:health] @@ -12,7 +12,7 @@ defmodule JellyfishWeb.HealthcheckController do operation :show, operation_id: "healthcheck", - summary: "Describes the health of Jellyfish", + summary: "Describes the health of Fishjam", responses: [ ok: ApiSpec.data("Healthy", ApiSpec.HealthcheckResponse), unauthorized: ApiSpec.error("Unauthorized") @@ -31,13 +31,13 @@ defmodule JellyfishWeb.HealthcheckController do status: :up, uptime: get_uptime(), distribution: get_distribution_report(), - version: Jellyfish.version(), - gitCommit: Application.get_env(:jellyfish, :git_commit) + version: Fishjam.version(), + gitCommit: Application.get_env(:fishjam, :git_commit) } end defp get_uptime() do - System.monotonic_time(:second) - Application.fetch_env!(:jellyfish, :start_time) + System.monotonic_time(:second) - Application.fetch_env!(:fishjam, :start_time) end defp get_distribution_report() do @@ -45,7 +45,7 @@ defmodule JellyfishWeb.HealthcheckController do visible_nodes = Node.list() |> length() %{ - enabled: Application.fetch_env!(:jellyfish, :dist_config)[:enabled], + enabled: Application.fetch_env!(:fishjam, :dist_config)[:enabled], node_status: if(alive?, do: :up, else: :down), nodes_in_cluster: visible_nodes + if(alive?, do: 1, else: 0) } diff --git a/lib/jellyfish_web/controllers/healthcheck_json.ex b/lib/jellyfish_web/controllers/healthcheck_json.ex index 22d2dc31..3c5452d1 100644 --- a/lib/jellyfish_web/controllers/healthcheck_json.ex +++ b/lib/jellyfish_web/controllers/healthcheck_json.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.HealthcheckJSON do +defmodule FishjamWeb.HealthcheckJSON do @moduledoc false def show(%{report: report}) do diff --git a/lib/jellyfish_web/controllers/hls_content_controller.ex b/lib/jellyfish_web/controllers/hls_content_controller.ex index ba935346..940d2524 100644 --- a/lib/jellyfish_web/controllers/hls_content_controller.ex +++ b/lib/jellyfish_web/controllers/hls_content_controller.ex @@ -1,16 +1,16 @@ -defmodule JellyfishWeb.HLSContentController do - use JellyfishWeb, :controller +defmodule FishjamWeb.HLSContentController do + use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs require Logger - alias Jellyfish.Component.HLS.RequestHandler - alias JellyfishWeb.ApiSpec - alias JellyfishWeb.ApiSpec.HLS.{Params, Response} + alias Fishjam.Component.HLS.RequestHandler + alias FishjamWeb.ApiSpec + alias FishjamWeb.ApiSpec.HLS.{Params, Response} alias Plug.Conn - action_fallback JellyfishWeb.FallbackController + action_fallback FishjamWeb.FallbackController tags [:hls] diff --git a/lib/jellyfish_web/controllers/peer_controller.ex b/lib/jellyfish_web/controllers/peer_controller.ex index 39d846ba..39baa587 100644 --- a/lib/jellyfish_web/controllers/peer_controller.ex +++ b/lib/jellyfish_web/controllers/peer_controller.ex @@ -1,15 +1,15 @@ -defmodule JellyfishWeb.PeerController do - use JellyfishWeb, :controller +defmodule FishjamWeb.PeerController do + use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs - alias Jellyfish.Peer - alias Jellyfish.Room - alias Jellyfish.RoomService - alias JellyfishWeb.ApiSpec - alias JellyfishWeb.PeerToken + alias Fishjam.Peer + alias Fishjam.Room + alias Fishjam.RoomService + alias FishjamWeb.ApiSpec + alias FishjamWeb.PeerToken alias OpenApiSpex.{Response, Schema} - action_fallback JellyfishWeb.FallbackController + action_fallback FishjamWeb.FallbackController tags [:room] @@ -77,7 +77,7 @@ defmodule JellyfishWeb.PeerController do assigns = [ peer: peer, token: PeerToken.generate(%{peer_id: peer.id, room_id: room_id}), - peer_websocket_url: Jellyfish.peer_websocket_address() + peer_websocket_url: Fishjam.peer_websocket_address() ] conn @@ -95,7 +95,7 @@ defmodule JellyfishWeb.PeerController do {:error, :bad_request, "Invalid peer type"} {:error, {:peer_disabled_globally, type}} -> - {:error, :bad_request, "Peers of type #{type} are disabled on this Jellyfish"} + {:error, :bad_request, "Peers of type #{type} are disabled on this Fishjam"} {:error, {:reached_peers_limit, type}} -> {:error, :service_unavailable, "Reached #{type} peers limit in room #{room_id}"} diff --git a/lib/jellyfish_web/controllers/peer_json.ex b/lib/jellyfish_web/controllers/peer_json.ex index 974afee2..0d2fdb7e 100644 --- a/lib/jellyfish_web/controllers/peer_json.ex +++ b/lib/jellyfish_web/controllers/peer_json.ex @@ -1,6 +1,6 @@ -defmodule JellyfishWeb.PeerJSON do +defmodule FishjamWeb.PeerJSON do @moduledoc false - alias Jellyfish.Peer.WebRTC + alias Fishjam.Peer.WebRTC def show(%{peer: peer, token: token, peer_websocket_url: ws_url}) do %{data: %{peer: data(peer), token: token, peer_websocket_url: ws_url}} diff --git a/lib/jellyfish_web/controllers/recording_content_controller.ex b/lib/jellyfish_web/controllers/recording_content_controller.ex index 51d30db2..17facf1e 100644 --- a/lib/jellyfish_web/controllers/recording_content_controller.ex +++ b/lib/jellyfish_web/controllers/recording_content_controller.ex @@ -1,15 +1,15 @@ -defmodule JellyfishWeb.RecordingContentController do - use JellyfishWeb, :controller +defmodule FishjamWeb.RecordingContentController do + use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs require Logger - alias Jellyfish.Component.HLS.RequestHandler - alias JellyfishWeb.ApiSpec + alias Fishjam.Component.HLS.RequestHandler + alias FishjamWeb.ApiSpec alias Plug.Conn - action_fallback JellyfishWeb.FallbackController + action_fallback FishjamWeb.FallbackController @playlist_content_type "application/vnd.apple.mpegurl" @recording_id_spec [in: :path, description: "Recording id", type: :string] diff --git a/lib/jellyfish_web/controllers/recording_controller.ex b/lib/jellyfish_web/controllers/recording_controller.ex index 1678635c..d1059941 100644 --- a/lib/jellyfish_web/controllers/recording_controller.ex +++ b/lib/jellyfish_web/controllers/recording_controller.ex @@ -1,15 +1,15 @@ -defmodule JellyfishWeb.RecordingController do - use JellyfishWeb, :controller +defmodule FishjamWeb.RecordingController do + use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs require Logger - alias Jellyfish.Component.HLS.{Recording, RequestHandler} - alias JellyfishWeb.ApiSpec + alias Fishjam.Component.HLS.{Recording, RequestHandler} + alias FishjamWeb.ApiSpec alias Plug.Conn - action_fallback JellyfishWeb.FallbackController + action_fallback FishjamWeb.FallbackController @playlist_content_type "application/vnd.apple.mpegurl" @recording_id_spec [in: :path, description: "Recording id", type: :string] diff --git a/lib/jellyfish_web/controllers/recording_json.ex b/lib/jellyfish_web/controllers/recording_json.ex index 08201530..6a601f8a 100644 --- a/lib/jellyfish_web/controllers/recording_json.ex +++ b/lib/jellyfish_web/controllers/recording_json.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.RecordingJSON do +defmodule FishjamWeb.RecordingJSON do @moduledoc false def show(%{recordings: recordings}) do diff --git a/lib/jellyfish_web/controllers/room_controller.ex b/lib/jellyfish_web/controllers/room_controller.ex index 00e9e419..76b236a9 100644 --- a/lib/jellyfish_web/controllers/room_controller.ex +++ b/lib/jellyfish_web/controllers/room_controller.ex @@ -1,13 +1,13 @@ -defmodule JellyfishWeb.RoomController do - use JellyfishWeb, :controller +defmodule FishjamWeb.RoomController do + use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs - alias Jellyfish.Room - alias Jellyfish.RoomService - alias JellyfishWeb.ApiSpec + alias Fishjam.Room + alias Fishjam.RoomService + alias FishjamWeb.ApiSpec alias OpenApiSpex.Response - action_fallback JellyfishWeb.FallbackController + action_fallback FishjamWeb.FallbackController tags [:room] @@ -75,11 +75,11 @@ defmodule JellyfishWeb.RoomController do def create(conn, params) do with {:ok, config} <- Room.Config.from_params(params), - {:ok, room, jellyfish_address} <- RoomService.create_room(config) do + {:ok, room, fishjam_address} <- RoomService.create_room(config) do conn |> put_resp_content_type("application/json") |> put_status(:created) - |> render("show.json", room: room, jellyfish_address: jellyfish_address) + |> render("show.json", room: room, fishjam_address: fishjam_address) else {:error, :invalid_max_peers} -> max_peers = Map.get(params, "maxPeers") diff --git a/lib/jellyfish_web/controllers/room_json.ex b/lib/jellyfish_web/controllers/room_json.ex index b117dd03..880f9d72 100644 --- a/lib/jellyfish_web/controllers/room_json.ex +++ b/lib/jellyfish_web/controllers/room_json.ex @@ -1,17 +1,17 @@ -defmodule JellyfishWeb.RoomJSON do +defmodule FishjamWeb.RoomJSON do @moduledoc false - alias JellyfishWeb.ComponentJSON - alias JellyfishWeb.PeerJSON + alias FishjamWeb.ComponentJSON + alias FishjamWeb.PeerJSON - alias Jellyfish.Utils.ParserJSON + alias Fishjam.Utils.ParserJSON def index(%{rooms: rooms}) do %{data: rooms |> Enum.map(&data/1)} end - def show(%{room: room, jellyfish_address: jellyfish_address}) do - %{data: data(room, jellyfish_address)} + def show(%{room: room, fishjam_address: fishjam_address}) do + %{data: data(room, fishjam_address)} end def show(%{room: room}) do @@ -20,8 +20,8 @@ defmodule JellyfishWeb.RoomJSON do def data(room), do: room_data(room) - def data(room, jellyfish_address) do - %{room: room_data(room), jellyfish_address: jellyfish_address} + def data(room, fishjam_address) do + %{room: room_data(room), fishjam_address: fishjam_address} end defp room_data(room) do diff --git a/lib/jellyfish_web/controllers/sip_call_controller.ex b/lib/jellyfish_web/controllers/sip_call_controller.ex index 1654e850..a1ee43f0 100644 --- a/lib/jellyfish_web/controllers/sip_call_controller.ex +++ b/lib/jellyfish_web/controllers/sip_call_controller.ex @@ -1,13 +1,13 @@ -defmodule JellyfishWeb.SIPCallController do - use JellyfishWeb, :controller +defmodule FishjamWeb.SIPCallController do + use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs - alias Jellyfish.Room - alias Jellyfish.RoomService - alias JellyfishWeb.ApiSpec + alias Fishjam.Room + alias Fishjam.RoomService + alias FishjamWeb.ApiSpec alias OpenApiSpex.Response - action_fallback JellyfishWeb.FallbackController + action_fallback FishjamWeb.FallbackController tags [:sip] diff --git a/lib/jellyfish_web/controllers/subscription_controller.ex b/lib/jellyfish_web/controllers/subscription_controller.ex index 16261a8a..c62a5c35 100644 --- a/lib/jellyfish_web/controllers/subscription_controller.ex +++ b/lib/jellyfish_web/controllers/subscription_controller.ex @@ -1,13 +1,13 @@ -defmodule JellyfishWeb.SubscriptionController do - use JellyfishWeb, :controller +defmodule FishjamWeb.SubscriptionController do + use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs - alias Jellyfish.Room - alias Jellyfish.RoomService - alias JellyfishWeb.ApiSpec + alias Fishjam.Room + alias Fishjam.RoomService + alias FishjamWeb.ApiSpec alias OpenApiSpex.Response - action_fallback JellyfishWeb.FallbackController + action_fallback FishjamWeb.FallbackController tags [:room] diff --git a/lib/jellyfish_web/endpoint.ex b/lib/jellyfish_web/endpoint.ex index 18d0c75c..29dac23e 100644 --- a/lib/jellyfish_web/endpoint.ex +++ b/lib/jellyfish_web/endpoint.ex @@ -1,11 +1,11 @@ -defmodule JellyfishWeb.Endpoint do - use Phoenix.Endpoint, otp_app: :jellyfish +defmodule FishjamWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :fishjam - socket "/socket/peer", JellyfishWeb.PeerSocket, + socket "/socket/peer", FishjamWeb.PeerSocket, websocket: true, longpoll: false - socket "/socket/server", JellyfishWeb.ServerSocket, + socket "/socket/server", FishjamWeb.ServerSocket, websocket: true, longpoll: false @@ -14,7 +14,7 @@ defmodule JellyfishWeb.Endpoint do # Set :encryption_salt if you would also like to encrypt it. @session_options [ store: :cookie, - key: "_jellyfish_key", + key: "_fishjam_key", signing_salt: "ojOkSTjg", same_site: "Lax" ] @@ -27,9 +27,9 @@ defmodule JellyfishWeb.Endpoint do # when deploying your static files in production. plug Plug.Static, at: "/", - from: :jellyfish, + from: :fishjam, gzip: false, - only: JellyfishWeb.static_paths() + only: FishjamWeb.static_paths() # Code reloading can be explicitly enabled under the # :code_reloader configuration of your endpoint. @@ -53,6 +53,6 @@ defmodule JellyfishWeb.Endpoint do plug Plug.Head plug Plug.Session, @session_options plug CORSPlug, origin: ["*"] - plug JellyfishWeb.TrafficMetricsPlug - plug JellyfishWeb.Router + plug FishjamWeb.TrafficMetricsPlug + plug FishjamWeb.Router end diff --git a/lib/jellyfish_web/peer_socket.ex b/lib/jellyfish_web/peer_socket.ex index 1f1d605d..b00077a3 100644 --- a/lib/jellyfish_web/peer_socket.ex +++ b/lib/jellyfish_web/peer_socket.ex @@ -1,13 +1,13 @@ -defmodule JellyfishWeb.PeerSocket do +defmodule FishjamWeb.PeerSocket do @moduledoc false @behaviour Phoenix.Socket.Transport require Logger - alias Jellyfish.Event - alias Jellyfish.PeerMessage - alias Jellyfish.PeerMessage.{Authenticated, AuthRequest, MediaEvent} - alias Jellyfish.{Room, RoomService} - alias JellyfishWeb.PeerToken + alias Fishjam.Event + alias Fishjam.PeerMessage + alias Fishjam.PeerMessage.{Authenticated, AuthRequest, MediaEvent} + alias Fishjam.{Room, RoomService} + alias FishjamWeb.PeerToken @heartbeat_interval 30_000 @@ -33,7 +33,7 @@ defmodule JellyfishWeb.PeerSocket do with {:ok, %{peer_id: peer_id, room_id: room_id}} <- PeerToken.verify(token), {:ok, room_pid} <- RoomService.find_room(room_id), :ok <- Room.set_peer_connected(room_id, peer_id), - :ok <- Phoenix.PubSub.subscribe(Jellyfish.PubSub, room_id) do + :ok <- Phoenix.PubSub.subscribe(Fishjam.PubSub, room_id) do Process.send_after(self(), :send_ping, @heartbeat_interval) encoded_message = diff --git a/lib/jellyfish_web/peer_token.ex b/lib/jellyfish_web/peer_token.ex index 4f682ab9..d06e3ce3 100644 --- a/lib/jellyfish_web/peer_token.ex +++ b/lib/jellyfish_web/peer_token.ex @@ -1,12 +1,12 @@ -defmodule JellyfishWeb.PeerToken do +defmodule FishjamWeb.PeerToken do @moduledoc false - alias JellyfishWeb.Endpoint + alias FishjamWeb.Endpoint @spec generate(map()) :: nonempty_binary def generate(data) do Phoenix.Token.sign( Endpoint, - Application.fetch_env!(:jellyfish, Endpoint)[:secret_key_base], + Application.fetch_env!(:fishjam, Endpoint)[:secret_key_base], data ) end @@ -15,9 +15,9 @@ defmodule JellyfishWeb.PeerToken do def verify(token) do Phoenix.Token.verify( Endpoint, - Application.fetch_env!(:jellyfish, Endpoint)[:secret_key_base], + Application.fetch_env!(:fishjam, Endpoint)[:secret_key_base], token, - max_age: Application.fetch_env!(:jellyfish, :jwt_max_age) + max_age: Application.fetch_env!(:fishjam, :jwt_max_age) ) end end diff --git a/lib/jellyfish_web/router.ex b/lib/jellyfish_web/router.ex index 69df8212..930af3a4 100644 --- a/lib/jellyfish_web/router.ex +++ b/lib/jellyfish_web/router.ex @@ -1,12 +1,12 @@ -defmodule JellyfishWeb.Router do - use JellyfishWeb, :router +defmodule FishjamWeb.Router do + use FishjamWeb, :router pipeline :api do plug :accepts, ["json"] plug :bearer_auth end - scope "/", JellyfishWeb do + scope "/", FishjamWeb do pipe_through :api scope "/health" do @@ -38,13 +38,13 @@ defmodule JellyfishWeb.Router do end # Paths which DO NOT require auth - scope "/", JellyfishWeb do + scope "/", FishjamWeb do get "/hls/:room_id/:filename", HLSContentController, :index get "/recording/:recording_id/:filename", RecordingContentController, :index end # Enable LiveDashboard in development - if Application.compile_env(:jellyfish, :dev_routes) do + if Application.compile_env(:fishjam, :dev_routes) do # If you want to use the LiveDashboard in production, you should put # it behind authentication and allow only admins to access it. # If your application does not have an admins-only section yet, @@ -55,11 +55,11 @@ defmodule JellyfishWeb.Router do scope "/dev" do pipe_through [:fetch_session, :protect_from_forgery] - live_dashboard "/dashboard", metrics: JellyfishWeb.Telemetry + live_dashboard "/dashboard", metrics: FishjamWeb.Telemetry end pipeline :open_api_spec do - plug OpenApiSpex.Plug.PutApiSpec, module: JellyfishWeb.ApiSpec + plug OpenApiSpex.Plug.PutApiSpec, module: FishjamWeb.ApiSpec end scope "/" do @@ -71,7 +71,7 @@ defmodule JellyfishWeb.Router do def bearer_auth(conn, _opts) do with ["Bearer " <> token] <- get_req_header(conn, "authorization"), - true <- token == Application.fetch_env!(:jellyfish, :server_api_token) do + true <- token == Application.fetch_env!(:fishjam, :server_api_token) do conn else false -> diff --git a/lib/jellyfish_web/server_socket.ex b/lib/jellyfish_web/server_socket.ex index 1367a281..cfda7b24 100644 --- a/lib/jellyfish_web/server_socket.ex +++ b/lib/jellyfish_web/server_socket.ex @@ -1,18 +1,18 @@ -defmodule JellyfishWeb.ServerSocket do +defmodule FishjamWeb.ServerSocket do @moduledoc false @behaviour Phoenix.Socket.Transport require Logger - alias Jellyfish.ServerMessage + alias Fishjam.ServerMessage - alias Jellyfish.ServerMessage.{ + alias Fishjam.ServerMessage.{ Authenticated, AuthRequest, SubscribeRequest, SubscribeResponse } - alias Jellyfish.Event + alias Fishjam.Event @heartbeat_interval 30_000 @@ -42,7 +42,7 @@ defmodule JellyfishWeb.ServerSocket do def handle_in({encoded_message, [opcode: :binary]}, %{authenticated?: false} = state) do case ServerMessage.decode(encoded_message) do %ServerMessage{content: {:auth_request, %AuthRequest{token: token}}} -> - if token == Application.fetch_env!(:jellyfish, :server_api_token) do + if token == Application.fetch_env!(:fishjam, :server_api_token) do Process.send_after(self(), :send_ping, @heartbeat_interval) encoded_message = diff --git a/lib/jellyfish_web/telemetry.ex b/lib/jellyfish_web/telemetry.ex index 8cdef662..4cb3b9c0 100644 --- a/lib/jellyfish_web/telemetry.ex +++ b/lib/jellyfish_web/telemetry.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.Telemetry do +defmodule FishjamWeb.Telemetry do @moduledoc false use Supervisor @@ -7,8 +7,8 @@ defmodule JellyfishWeb.Telemetry do @ice_received_event [Membrane.ICE, :ice, :payload, :received] @ice_sent_event [Membrane.ICE, :ice, :payload, :sent] - @http_request_event [:jellyfish_web, :request] - @http_response_event [:jellyfish_web, :response] + @http_request_event [:fishjam_web, :request] + @http_response_event [:fishjam_web, :response] def start_link(arg) do Supervisor.start_link(__MODULE__, arg, name: __MODULE__) @@ -16,8 +16,8 @@ defmodule JellyfishWeb.Telemetry do @impl true def init(_arg) do - metrics_ip = Application.fetch_env!(:jellyfish, :metrics_ip) - metrics_port = Application.fetch_env!(:jellyfish, :metrics_port) + metrics_ip = Application.fetch_env!(:fishjam, :metrics_ip) + metrics_port = Application.fetch_env!(:fishjam, :metrics_port) Logger.info( "Starting prometheus metrics endpoint at: http://#{:inet.ntoa(metrics_ip)}:#{metrics_port}" @@ -98,70 +98,70 @@ defmodule JellyfishWeb.Telemetry do metric_type.("vm.total_run_queue_lengths.io", []) ] ++ [ - # Jellyfish Metrics + # Fishjam Metrics # FIXME: At the moment, the traffic metrics track: - # - Most HTTP traffic (Jellyfish API, HLS) + # - Most HTTP traffic (Fishjam API, HLS) # - ICE events (WebRTC) # # which means they don't count: # - WebSocket traffic # - RTP events (RTSP components don't use ICE) # - HTTP traffic related to metrics (not handled by Phoenix) - sum("jellyfish.traffic.ingress.webrtc.total.bytes", + sum("fishjam.traffic.ingress.webrtc.total.bytes", event_name: @ice_received_event, description: "Total WebRTC traffic received (bytes)" ), - sum("jellyfish.traffic.egress.webrtc.total.bytes", + sum("fishjam.traffic.egress.webrtc.total.bytes", event_name: @ice_sent_event, description: "Total WebRTC traffic sent (bytes)" ), - sum("jellyfish.traffic.ingress.http.total.bytes", + sum("fishjam.traffic.ingress.http.total.bytes", event_name: @http_request_event, description: "Total HTTP traffic received (bytes)" ), - sum("jellyfish.traffic.egress.http.total.bytes", + sum("fishjam.traffic.egress.http.total.bytes", event_name: @http_response_event, description: "Total HTTP traffic sent (bytes)" ), - last_value("jellyfish.rooms", - description: "Number of rooms currently present in Jellyfish" + last_value("fishjam.rooms", + description: "Number of rooms currently present in Fishjam" ), # FIXME: Prometheus warns about using labels to store dimensions with high cardinality, # such as UUIDs. For more information refer here: https://prometheus.io/docs/practices/naming/#labels - last_value("jellyfish.room.peers", + last_value("fishjam.room.peers", tags: [:room_id], description: "Number of peers currently present in a given room" ), - sum("jellyfish.room.peer_time.total.seconds", - event_name: [:jellyfish, :room], + sum("fishjam.room.peer_time.total.seconds", + event_name: [:fishjam, :room], measurement: :peer_time, tags: [:room_id], description: "Total peer time accumulated for a given room (seconds)" ), - sum("jellyfish.room.duration.seconds", - event_name: [:jellyfish, :room], + sum("fishjam.room.duration.seconds", + event_name: [:fishjam, :room], measurement: :duration, tags: [:room_id], description: "Duration of a given room (seconds)" ), - sum("jellyfish.room.peer_connects.total", - event_name: [:jellyfish, :room], + sum("fishjam.room.peer_connects.total", + event_name: [:fishjam, :room], measurement: :peer_connects, tags: [:room_id], description: "Number of PeerConnected events emitted during the lifetime of a given room" ), - sum("jellyfish.room.peer_disconnects.total", - event_name: [:jellyfish, :room], + sum("fishjam.room.peer_disconnects.total", + event_name: [:fishjam, :room], measurement: :peer_disconnects, tags: [:room_id], description: "Number of PeerDisconnected events emitted during the lifetime of a given room" ), - sum("jellyfish.room.peer_crashes.total", - event_name: [:jellyfish, :room], + sum("fishjam.room.peer_crashes.total", + event_name: [:fishjam, :room], measurement: :peer_crashes, tags: [:room_id], description: "Number of PeerCrashed events emitted during the lifetime of a given room" diff --git a/lib/jellyfish_web/traffic_metrics_plug.ex b/lib/jellyfish_web/traffic_metrics_plug.ex index 98de2898..edf8ad3b 100644 --- a/lib/jellyfish_web/traffic_metrics_plug.ex +++ b/lib/jellyfish_web/traffic_metrics_plug.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.TrafficMetricsPlug do +defmodule FishjamWeb.TrafficMetricsPlug do @moduledoc false import Plug.Conn @@ -24,13 +24,13 @@ defmodule JellyfishWeb.TrafficMetricsPlug do def call(conn, _opts) do :telemetry.execute( - [:jellyfish_web, :request], + [:fishjam_web, :request], %{bytes: request_size(conn)} ) register_before_send(conn, fn conn -> :telemetry.execute( - [:jellyfish_web, :response], + [:fishjam_web, :response], %{bytes: response_size(conn)} ) diff --git a/lib/protos/jellyfish/peer_notifications.pb.ex b/lib/protos/fishjam/peer_notifications.pb.ex similarity index 57% rename from lib/protos/jellyfish/peer_notifications.pb.ex rename to lib/protos/fishjam/peer_notifications.pb.ex index 452c83e9..33cb2c51 100644 --- a/lib/protos/jellyfish/peer_notifications.pb.ex +++ b/lib/protos/fishjam/peer_notifications.pb.ex @@ -1,10 +1,10 @@ -defmodule Jellyfish.PeerMessage.Authenticated do +defmodule Fishjam.PeerMessage.Authenticated do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" end -defmodule Jellyfish.PeerMessage.AuthRequest do +defmodule Fishjam.PeerMessage.AuthRequest do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -12,7 +12,7 @@ defmodule Jellyfish.PeerMessage.AuthRequest do field :token, 1, type: :string end -defmodule Jellyfish.PeerMessage.MediaEvent do +defmodule Fishjam.PeerMessage.MediaEvent do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -20,19 +20,19 @@ defmodule Jellyfish.PeerMessage.MediaEvent do field :data, 1, type: :string end -defmodule Jellyfish.PeerMessage do +defmodule Fishjam.PeerMessage do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" oneof :content, 0 - field :authenticated, 1, type: Jellyfish.PeerMessage.Authenticated, oneof: 0 + field :authenticated, 1, type: Fishjam.PeerMessage.Authenticated, oneof: 0 field :auth_request, 2, - type: Jellyfish.PeerMessage.AuthRequest, + type: Fishjam.PeerMessage.AuthRequest, json_name: "authRequest", oneof: 0 - field :media_event, 3, type: Jellyfish.PeerMessage.MediaEvent, json_name: "mediaEvent", oneof: 0 + field :media_event, 3, type: Fishjam.PeerMessage.MediaEvent, json_name: "mediaEvent", oneof: 0 end diff --git a/lib/protos/jellyfish/server_notifications.pb.ex b/lib/protos/fishjam/server_notifications.pb.ex similarity index 68% rename from lib/protos/jellyfish/server_notifications.pb.ex rename to lib/protos/fishjam/server_notifications.pb.ex index 5e599133..e44c8524 100644 --- a/lib/protos/jellyfish/server_notifications.pb.ex +++ b/lib/protos/fishjam/server_notifications.pb.ex @@ -1,4 +1,4 @@ -defmodule Jellyfish.ServerMessage.EventType do +defmodule Fishjam.ServerMessage.EventType do @moduledoc false use Protobuf, enum: true, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -8,7 +8,7 @@ defmodule Jellyfish.ServerMessage.EventType do field :EVENT_TYPE_METRICS, 2 end -defmodule Jellyfish.ServerMessage.TrackType do +defmodule Fishjam.ServerMessage.TrackType do @moduledoc false use Protobuf, enum: true, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -18,7 +18,7 @@ defmodule Jellyfish.ServerMessage.TrackType do field :TRACK_TYPE_AUDIO, 2 end -defmodule Jellyfish.ServerMessage.RoomCrashed do +defmodule Fishjam.ServerMessage.RoomCrashed do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -26,7 +26,7 @@ defmodule Jellyfish.ServerMessage.RoomCrashed do field :room_id, 1, type: :string, json_name: "roomId" end -defmodule Jellyfish.ServerMessage.PeerAdded do +defmodule Fishjam.ServerMessage.PeerAdded do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -35,7 +35,7 @@ defmodule Jellyfish.ServerMessage.PeerAdded do field :peer_id, 2, type: :string, json_name: "peerId" end -defmodule Jellyfish.ServerMessage.PeerDeleted do +defmodule Fishjam.ServerMessage.PeerDeleted do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -44,7 +44,7 @@ defmodule Jellyfish.ServerMessage.PeerDeleted do field :peer_id, 2, type: :string, json_name: "peerId" end -defmodule Jellyfish.ServerMessage.PeerConnected do +defmodule Fishjam.ServerMessage.PeerConnected do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -53,7 +53,7 @@ defmodule Jellyfish.ServerMessage.PeerConnected do field :peer_id, 2, type: :string, json_name: "peerId" end -defmodule Jellyfish.ServerMessage.PeerDisconnected do +defmodule Fishjam.ServerMessage.PeerDisconnected do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -62,7 +62,7 @@ defmodule Jellyfish.ServerMessage.PeerDisconnected do field :peer_id, 2, type: :string, json_name: "peerId" end -defmodule Jellyfish.ServerMessage.PeerCrashed do +defmodule Fishjam.ServerMessage.PeerCrashed do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -72,7 +72,7 @@ defmodule Jellyfish.ServerMessage.PeerCrashed do field :reason, 3, type: :string end -defmodule Jellyfish.ServerMessage.ComponentCrashed do +defmodule Fishjam.ServerMessage.ComponentCrashed do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -81,13 +81,13 @@ defmodule Jellyfish.ServerMessage.ComponentCrashed do field :component_id, 2, type: :string, json_name: "componentId" end -defmodule Jellyfish.ServerMessage.Authenticated do +defmodule Fishjam.ServerMessage.Authenticated do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" end -defmodule Jellyfish.ServerMessage.AuthRequest do +defmodule Fishjam.ServerMessage.AuthRequest do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -95,29 +95,23 @@ defmodule Jellyfish.ServerMessage.AuthRequest do field :token, 1, type: :string end -defmodule Jellyfish.ServerMessage.SubscribeRequest do +defmodule Fishjam.ServerMessage.SubscribeRequest do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" - field :event_type, 1, - type: Jellyfish.ServerMessage.EventType, - json_name: "eventType", - enum: true + field :event_type, 1, type: Fishjam.ServerMessage.EventType, json_name: "eventType", enum: true end -defmodule Jellyfish.ServerMessage.SubscribeResponse do +defmodule Fishjam.ServerMessage.SubscribeResponse do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" - field :event_type, 1, - type: Jellyfish.ServerMessage.EventType, - json_name: "eventType", - enum: true + field :event_type, 1, type: Fishjam.ServerMessage.EventType, json_name: "eventType", enum: true end -defmodule Jellyfish.ServerMessage.RoomCreated do +defmodule Fishjam.ServerMessage.RoomCreated do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -125,7 +119,7 @@ defmodule Jellyfish.ServerMessage.RoomCreated do field :room_id, 1, type: :string, json_name: "roomId" end -defmodule Jellyfish.ServerMessage.RoomDeleted do +defmodule Fishjam.ServerMessage.RoomDeleted do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -133,7 +127,7 @@ defmodule Jellyfish.ServerMessage.RoomDeleted do field :room_id, 1, type: :string, json_name: "roomId" end -defmodule Jellyfish.ServerMessage.MetricsReport do +defmodule Fishjam.ServerMessage.MetricsReport do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -141,7 +135,7 @@ defmodule Jellyfish.ServerMessage.MetricsReport do field :metrics, 1, type: :string end -defmodule Jellyfish.ServerMessage.HlsPlayable do +defmodule Fishjam.ServerMessage.HlsPlayable do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -150,7 +144,7 @@ defmodule Jellyfish.ServerMessage.HlsPlayable do field :component_id, 2, type: :string, json_name: "componentId" end -defmodule Jellyfish.ServerMessage.HlsUploaded do +defmodule Fishjam.ServerMessage.HlsUploaded do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -158,7 +152,7 @@ defmodule Jellyfish.ServerMessage.HlsUploaded do field :room_id, 1, type: :string, json_name: "roomId" end -defmodule Jellyfish.ServerMessage.HlsUploadCrashed do +defmodule Fishjam.ServerMessage.HlsUploadCrashed do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -166,7 +160,7 @@ defmodule Jellyfish.ServerMessage.HlsUploadCrashed do field :room_id, 1, type: :string, json_name: "roomId" end -defmodule Jellyfish.ServerMessage.PeerMetadataUpdated do +defmodule Fishjam.ServerMessage.PeerMetadataUpdated do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -176,17 +170,17 @@ defmodule Jellyfish.ServerMessage.PeerMetadataUpdated do field :metadata, 3, type: :string end -defmodule Jellyfish.ServerMessage.Track do +defmodule Fishjam.ServerMessage.Track do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" field :id, 1, type: :string - field :type, 2, type: Jellyfish.ServerMessage.TrackType, enum: true + field :type, 2, type: Fishjam.ServerMessage.TrackType, enum: true field :metadata, 3, type: :string end -defmodule Jellyfish.ServerMessage.TrackAdded do +defmodule Fishjam.ServerMessage.TrackAdded do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -196,10 +190,10 @@ defmodule Jellyfish.ServerMessage.TrackAdded do field :room_id, 1, type: :string, json_name: "roomId" field :peer_id, 2, type: :string, json_name: "peerId", oneof: 0 field :component_id, 3, type: :string, json_name: "componentId", oneof: 0 - field :track, 4, type: Jellyfish.ServerMessage.Track + field :track, 4, type: Fishjam.ServerMessage.Track end -defmodule Jellyfish.ServerMessage.TrackRemoved do +defmodule Fishjam.ServerMessage.TrackRemoved do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -209,10 +203,10 @@ defmodule Jellyfish.ServerMessage.TrackRemoved do field :room_id, 1, type: :string, json_name: "roomId" field :peer_id, 2, type: :string, json_name: "peerId", oneof: 0 field :component_id, 3, type: :string, json_name: "componentId", oneof: 0 - field :track, 4, type: Jellyfish.ServerMessage.Track + field :track, 4, type: Fishjam.ServerMessage.Track end -defmodule Jellyfish.ServerMessage.TrackMetadataUpdated do +defmodule Fishjam.ServerMessage.TrackMetadataUpdated do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -222,10 +216,10 @@ defmodule Jellyfish.ServerMessage.TrackMetadataUpdated do field :room_id, 1, type: :string, json_name: "roomId" field :peer_id, 2, type: :string, json_name: "peerId", oneof: 0 field :component_id, 3, type: :string, json_name: "componentId", oneof: 0 - field :track, 4, type: Jellyfish.ServerMessage.Track + field :track, 4, type: Fishjam.ServerMessage.Track end -defmodule Jellyfish.ServerMessage do +defmodule Fishjam.ServerMessage do @moduledoc false use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" @@ -233,101 +227,101 @@ defmodule Jellyfish.ServerMessage do oneof :content, 0 field :room_crashed, 1, - type: Jellyfish.ServerMessage.RoomCrashed, + type: Fishjam.ServerMessage.RoomCrashed, json_name: "roomCrashed", oneof: 0 field :peer_connected, 2, - type: Jellyfish.ServerMessage.PeerConnected, + type: Fishjam.ServerMessage.PeerConnected, json_name: "peerConnected", oneof: 0 field :peer_disconnected, 3, - type: Jellyfish.ServerMessage.PeerDisconnected, + type: Fishjam.ServerMessage.PeerDisconnected, json_name: "peerDisconnected", oneof: 0 field :peer_crashed, 4, - type: Jellyfish.ServerMessage.PeerCrashed, + type: Fishjam.ServerMessage.PeerCrashed, json_name: "peerCrashed", oneof: 0 field :component_crashed, 5, - type: Jellyfish.ServerMessage.ComponentCrashed, + type: Fishjam.ServerMessage.ComponentCrashed, json_name: "componentCrashed", oneof: 0 - field :authenticated, 6, type: Jellyfish.ServerMessage.Authenticated, oneof: 0 + field :authenticated, 6, type: Fishjam.ServerMessage.Authenticated, oneof: 0 field :auth_request, 7, - type: Jellyfish.ServerMessage.AuthRequest, + type: Fishjam.ServerMessage.AuthRequest, json_name: "authRequest", oneof: 0 field :subscribe_request, 8, - type: Jellyfish.ServerMessage.SubscribeRequest, + type: Fishjam.ServerMessage.SubscribeRequest, json_name: "subscribeRequest", oneof: 0 field :subscribe_response, 9, - type: Jellyfish.ServerMessage.SubscribeResponse, + type: Fishjam.ServerMessage.SubscribeResponse, json_name: "subscribeResponse", oneof: 0 field :room_created, 10, - type: Jellyfish.ServerMessage.RoomCreated, + type: Fishjam.ServerMessage.RoomCreated, json_name: "roomCreated", oneof: 0 field :room_deleted, 11, - type: Jellyfish.ServerMessage.RoomDeleted, + type: Fishjam.ServerMessage.RoomDeleted, json_name: "roomDeleted", oneof: 0 field :metrics_report, 12, - type: Jellyfish.ServerMessage.MetricsReport, + type: Fishjam.ServerMessage.MetricsReport, json_name: "metricsReport", oneof: 0 field :hls_playable, 13, - type: Jellyfish.ServerMessage.HlsPlayable, + type: Fishjam.ServerMessage.HlsPlayable, json_name: "hlsPlayable", oneof: 0 field :hls_uploaded, 14, - type: Jellyfish.ServerMessage.HlsUploaded, + type: Fishjam.ServerMessage.HlsUploaded, json_name: "hlsUploaded", oneof: 0 field :hls_upload_crashed, 15, - type: Jellyfish.ServerMessage.HlsUploadCrashed, + type: Fishjam.ServerMessage.HlsUploadCrashed, json_name: "hlsUploadCrashed", oneof: 0 field :peer_metadata_updated, 16, - type: Jellyfish.ServerMessage.PeerMetadataUpdated, + type: Fishjam.ServerMessage.PeerMetadataUpdated, json_name: "peerMetadataUpdated", oneof: 0 field :track_added, 17, - type: Jellyfish.ServerMessage.TrackAdded, + type: Fishjam.ServerMessage.TrackAdded, json_name: "trackAdded", oneof: 0 field :track_removed, 18, - type: Jellyfish.ServerMessage.TrackRemoved, + type: Fishjam.ServerMessage.TrackRemoved, json_name: "trackRemoved", oneof: 0 field :track_metadata_updated, 19, - type: Jellyfish.ServerMessage.TrackMetadataUpdated, + type: Fishjam.ServerMessage.TrackMetadataUpdated, json_name: "trackMetadataUpdated", oneof: 0 - field :peer_added, 20, type: Jellyfish.ServerMessage.PeerAdded, json_name: "peerAdded", oneof: 0 + field :peer_added, 20, type: Fishjam.ServerMessage.PeerAdded, json_name: "peerAdded", oneof: 0 field :peer_deleted, 21, - type: Jellyfish.ServerMessage.PeerDeleted, + type: Fishjam.ServerMessage.PeerDeleted, json_name: "peerDeleted", oneof: 0 end diff --git a/mix.exs b/mix.exs index fffd98d7..5e393486 100644 --- a/mix.exs +++ b/mix.exs @@ -1,10 +1,10 @@ -defmodule Jellyfish.MixProject do +defmodule Fishjam.MixProject do use Mix.Project def project do [ - app: :jellyfish, - version: "0.5.0", + app: :fishjam, + version: "0.6.0-dev", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, @@ -13,8 +13,7 @@ defmodule Jellyfish.MixProject do dialyzer: dialyzer(), # hex - description: "Jellyfish media server", - package: package(), + description: "Fishjam media server", # test coverage test_coverage: [tool: ExCoveralls], @@ -35,7 +34,7 @@ defmodule Jellyfish.MixProject do # Type `mix help compile.app` for more information. def application do [ - mod: {Jellyfish.Application, []}, + mod: {Fishjam.Application, []}, extra_applications: [:logger, :runtime_tools] ] end @@ -132,17 +131,6 @@ defmodule Jellyfish.MixProject do end end - defp package do - [ - maintainers: ["Membrane Team"], - licenses: ["Apache-2.0"], - links: %{ - "GitHub" => "https://github.com/jellyfish-dev/jellyfish", - "Membrane Framework Homepage" => "https://membrane.stream" - } - ] - end - defp generate_api_spec(_args) do output_filename = "openapi.yaml" generated_filename = "openapi-gen.yaml" @@ -156,7 +144,7 @@ defmodule Jellyfish.MixProject do "openapi.spec.yaml", "--start-app=false", "--spec", - "JellyfishWeb.ApiSpec", + "FishjamWeb.ApiSpec", generated_filename ], into: IO.stream() diff --git a/openapi.yaml b/openapi.yaml index fc5e1637..2a800621 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -13,16 +13,16 @@ components: example: disconnected title: PeerStatus type: string - x-struct: Elixir.JellyfishWeb.ApiSpec.Peer.Status + x-struct: Elixir.FishjamWeb.ApiSpec.Peer.Status HealthReportStatus: - description: Informs about the status of Jellyfish or a specific service + description: Informs about the status of Fishjam or a specific service enum: - UP - DOWN example: UP title: HealthReportStatus type: string - x-struct: Elixir.JellyfishWeb.ApiSpec.HealthReport.Status + x-struct: Elixir.FishjamWeb.ApiSpec.HealthReport.Status RoomDetailsResponse: description: Response containing room details properties: @@ -32,7 +32,7 @@ components: - data title: RoomDetailsResponse type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.RoomDetailsResponse + x-struct: Elixir.FishjamWeb.ApiSpec.RoomDetailsResponse ComponentOptionsRTSP: description: Options specific to the RTSP component properties: @@ -64,7 +64,7 @@ components: - sourceUri title: ComponentOptionsRTSP type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.RTSP.Options + x-struct: Elixir.FishjamWeb.ApiSpec.Component.RTSP.Options ComponentOptionsSIP: description: Options specific to the SIP component properties: @@ -87,12 +87,12 @@ components: - password title: SIPCredentials type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP.SIPCredentials + x-struct: Elixir.FishjamWeb.ApiSpec.Component.SIP.SIPCredentials required: - registrarCredentials title: ComponentOptionsSIP type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP.Options + x-struct: Elixir.FishjamWeb.ApiSpec.Component.SIP.Options ComponentPropertiesHLS: description: Properties specific to the HLS component properties: @@ -123,7 +123,7 @@ components: - subscribeMode title: ComponentPropertiesHLS type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.HLS.Properties + x-struct: Elixir.FishjamWeb.ApiSpec.Component.HLS.Properties Room: description: Description of the room state properties: @@ -150,7 +150,7 @@ components: - peers title: Room type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Room + x-struct: Elixir.FishjamWeb.ApiSpec.Room ComponentOptionsRecording: description: Options specific to the Recording component properties: @@ -173,7 +173,7 @@ components: type: string title: ComponentOptionsRecording type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Recording.Options + x-struct: Elixir.FishjamWeb.ApiSpec.Component.Recording.Options Component: description: Describes component discriminator: @@ -192,7 +192,7 @@ components: - $ref: '#/components/schemas/ComponentRecording' title: Component type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component + x-struct: Elixir.FishjamWeb.ApiSpec.Component PeerOptionsWebRTC: description: Options specific to the WebRTC peer properties: @@ -202,9 +202,9 @@ components: type: boolean title: PeerOptionsWebRTC type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Peer.WebRTC + x-struct: Elixir.FishjamWeb.ApiSpec.Peer.WebRTC HealthcheckResponse: - description: Response containing health report of Jellyfish + description: Response containing health report of Fishjam properties: data: $ref: '#/components/schemas/HealthReport' @@ -212,7 +212,7 @@ components: - data title: HealthcheckResponse type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.HealthcheckResponse + x-struct: Elixir.FishjamWeb.ApiSpec.HealthcheckResponse ComponentSIP: description: Describes the SIP component properties: @@ -238,7 +238,7 @@ components: - tracks title: ComponentSIP type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP + x-struct: Elixir.FishjamWeb.ApiSpec.Component.SIP ComponentHLS: description: Describes the HLS component properties: @@ -264,21 +264,21 @@ components: - tracks title: ComponentHLS type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.HLS + x-struct: Elixir.FishjamWeb.ApiSpec.Component.HLS HlsSkip: description: Set to "YES" if delta manifest should be requested example: YES nullable: true title: HlsSkip type: string - x-struct: Elixir.JellyfishWeb.ApiSpec.HLS.Params.HlsSkip + x-struct: Elixir.FishjamWeb.ApiSpec.HLS.Params.HlsSkip PeerMetadata: description: Custom metadata set by the peer example: - name: JellyfishUser + name: FishjamUser nullable: true title: PeerMetadata - x-struct: Elixir.JellyfishWeb.ApiSpec.Peer.PeerMetadata + x-struct: Elixir.FishjamWeb.ApiSpec.Peer.PeerMetadata ComponentPropertiesFile: description: Properties specific to the File component properties: @@ -295,7 +295,7 @@ components: - framerate title: ComponentPropertiesFile type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.File.Properties + x-struct: Elixir.FishjamWeb.ApiSpec.Component.File.Properties ComponentFile: description: Describes the File component properties: @@ -320,7 +320,7 @@ components: - tracks title: ComponentFile type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.File + x-struct: Elixir.FishjamWeb.ApiSpec.Component.File SubscriptionConfig: description: Subscription config properties: @@ -331,7 +331,7 @@ components: type: array title: SubscriptionConfig type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Subscription.Origins + x-struct: Elixir.FishjamWeb.ApiSpec.Subscription.Origins HlsMsn: description: Segment sequence number example: 10 @@ -339,7 +339,7 @@ components: nullable: true title: HlsMsn type: integer - x-struct: Elixir.JellyfishWeb.ApiSpec.HLS.Params.HlsMsn + x-struct: Elixir.FishjamWeb.ApiSpec.HLS.Params.HlsMsn ComponentPropertiesRecording: description: Properties specific to the Recording component properties: @@ -353,7 +353,7 @@ components: - subscribeMode title: ComponentPropertiesRecording type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Recording.Properties + x-struct: Elixir.FishjamWeb.ApiSpec.Component.Recording.Properties DialConfig: description: Dial config properties: @@ -362,7 +362,7 @@ components: type: string title: DialConfig type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Dial.PhoneNumber + x-struct: Elixir.FishjamWeb.ApiSpec.Dial.PhoneNumber ComponentRTSP: description: Describes the RTSP component properties: @@ -388,7 +388,7 @@ components: - tracks title: ComponentRTSP type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.RTSP + x-struct: Elixir.FishjamWeb.ApiSpec.Component.RTSP HlsPart: description: Partial segment sequence number example: 10 @@ -396,13 +396,13 @@ components: nullable: true title: HlsPart type: integer - x-struct: Elixir.JellyfishWeb.ApiSpec.HLS.Params.HlsPart + x-struct: Elixir.FishjamWeb.ApiSpec.HLS.Params.HlsPart WebsocketURL: description: Websocket URL to which peer has to connect - example: www.jellyfish.org/socket/peer + example: www.fishjam.org/socket/peer title: WebsocketURL type: string - x-struct: Elixir.JellyfishWeb.ApiSpec.Peer.WebSocketUrl + x-struct: Elixir.FishjamWeb.ApiSpec.Peer.WebSocketUrl ComponentDetailsResponse: description: Response containing component details properties: @@ -412,7 +412,7 @@ components: - data title: ComponentDetailsResponse type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.ComponentDetailsResponse + x-struct: Elixir.FishjamWeb.ApiSpec.ComponentDetailsResponse ComponentOptionsHLS: description: Options specific to the HLS component properties: @@ -443,7 +443,7 @@ components: type: integer title: ComponentOptionsHLS type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.HLS.Options + x-struct: Elixir.FishjamWeb.ApiSpec.Component.HLS.Options SIPCredentials: description: Credentials used to authorize in SIP Provider service properties: @@ -463,7 +463,7 @@ components: - password title: SIPCredentials type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP.SIPCredentials + x-struct: Elixir.FishjamWeb.ApiSpec.Component.SIP.SIPCredentials S3Credentials: description: An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided properties: @@ -486,7 +486,7 @@ components: - bucket title: S3Credentials type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.HLS.S3 + x-struct: Elixir.FishjamWeb.ApiSpec.Component.HLS.S3 RecordingListResponse: description: Response containing list of all recording properties: @@ -498,7 +498,7 @@ components: - data title: RecordingListResponse type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.RecordingListResponse + x-struct: Elixir.FishjamWeb.ApiSpec.RecordingListResponse ComponentOptions: description: Component-specific options oneOf: @@ -509,7 +509,7 @@ components: - $ref: '#/components/schemas/ComponentOptionsRecording' title: ComponentOptions type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Options + x-struct: Elixir.FishjamWeb.ApiSpec.Component.Options Track: description: Describes media track of a Peer or Component properties: @@ -524,24 +524,24 @@ components: type: string title: Track type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Track + x-struct: Elixir.FishjamWeb.ApiSpec.Track HealthReportDistribution: - description: Informs about the status of Jellyfish distribution + description: Informs about the status of Fishjam distribution properties: enabled: - description: Whether distribution is enabled on this Jellyfish + description: Whether distribution is enabled on this Fishjam type: boolean nodeStatus: $ref: '#/components/schemas/HealthReportStatus' nodesInCluster: - description: Amount of nodes (including this Jellyfish's node) in the distribution cluster + description: Amount of nodes (including this Fishjam's node) in the distribution cluster type: integer required: - nodeStatus - nodesInCluster title: HealthReportDistribution type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.HealthReport.Distribution + x-struct: Elixir.FishjamWeb.ApiSpec.HealthReport.Distribution ComponentPropertiesRTSP: description: Properties specific to the RTSP component properties: @@ -568,16 +568,16 @@ components: - pierceNat title: ComponentPropertiesRTSP type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.RTSP.Properties + x-struct: Elixir.FishjamWeb.ApiSpec.Component.RTSP.Properties PeerOptions: description: Peer-specific options oneOf: - $ref: '#/components/schemas/PeerOptionsWebRTC' title: PeerOptions type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Peer.Options + x-struct: Elixir.FishjamWeb.ApiSpec.Peer.Options HealthReport: - description: Describes overall Jellyfish health + description: Describes overall Fishjam health properties: distribution: $ref: '#/components/schemas/HealthReportDistribution' @@ -587,10 +587,10 @@ components: status: $ref: '#/components/schemas/HealthReportStatus' uptime: - description: Uptime of Jellyfish (in seconds) + description: Uptime of Fishjam (in seconds) type: integer version: - description: Version of Jellyfish + description: Version of Fishjam type: string required: - status @@ -600,7 +600,7 @@ components: - gitCommit title: HealthReport type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.HealthReport + x-struct: Elixir.FishjamWeb.ApiSpec.HealthReport Peer: description: Describes peer status properties: @@ -627,19 +627,19 @@ components: - metadata title: Peer type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Peer + x-struct: Elixir.FishjamWeb.ApiSpec.Peer ComponentType: description: Component type example: hls title: ComponentType type: string - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Type + x-struct: Elixir.FishjamWeb.ApiSpec.Component.Type AuthToken: description: Token for authorizing websocket connection example: 5cdac726-57a3-4ecb-b1d5-72a3d62ec242 title: AuthToken type: string - x-struct: Elixir.JellyfishWeb.ApiSpec.Peer.Token + x-struct: Elixir.FishjamWeb.ApiSpec.Peer.Token RoomsListingResponse: description: Response containing list of all rooms properties: @@ -651,7 +651,7 @@ components: - data title: RoomsListingResponse type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.RoomsListingResponse + x-struct: Elixir.FishjamWeb.ApiSpec.RoomsListingResponse ComponentPropertiesSIP: description: Properties specific to the SIP component properties: @@ -674,12 +674,12 @@ components: - password title: SIPCredentials type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP.SIPCredentials + x-struct: Elixir.FishjamWeb.ApiSpec.Component.SIP.SIPCredentials required: - registrarCredentials title: ComponentPropertiesSIP type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP.Properties + x-struct: Elixir.FishjamWeb.ApiSpec.Component.SIP.Properties PeerDetailsResponse: description: Response containing peer details and their token properties: @@ -699,12 +699,12 @@ components: - data title: PeerDetailsResponse type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.PeerDetailsResponse + x-struct: Elixir.FishjamWeb.ApiSpec.PeerDetailsResponse HlsResponse: description: Requested file title: HlsResponse type: string - x-struct: Elixir.JellyfishWeb.ApiSpec.HLS.Response + x-struct: Elixir.FishjamWeb.ApiSpec.HLS.Response ComponentRecording: description: Describes the Recording component properties: @@ -730,33 +730,33 @@ components: - tracks title: ComponentRecording type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Recording + x-struct: Elixir.FishjamWeb.ApiSpec.Component.Recording PeerType: description: Peer type example: webrtc title: PeerType type: string - x-struct: Elixir.JellyfishWeb.ApiSpec.Peer.Type + x-struct: Elixir.FishjamWeb.ApiSpec.Peer.Type RoomCreateDetailsResponse: description: Response containing room details properties: data: properties: - jellyfish_address: - description: Jellyfish instance address where the room was created. This might be different than the address of Jellyfish where the request was sent only when running a cluster of Jellyfishes. - example: jellyfish1:5003 + fishjam_address: + description: Fishjam instance address where the room was created. This might be different than the address of Fishjam where the request was sent only when running a cluster of Fishjams. + example: fishjam1:5003 type: string room: $ref: '#/components/schemas/Room' required: - room - - jellyfish_address + - fishjam_address type: object required: - data title: RoomCreateDetailsResponse type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.RoomCreateDetailsResponse + x-struct: Elixir.FishjamWeb.ApiSpec.RoomCreateDetailsResponse ComponentOptionsFile: description: Options specific to the File component properties: @@ -773,7 +773,7 @@ components: - filePath title: ComponentOptionsFile type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Component.File.Options + x-struct: Elixir.FishjamWeb.ApiSpec.Component.File.Options Error: description: Error message properties: @@ -785,7 +785,7 @@ components: - errors title: Error type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Error + x-struct: Elixir.FishjamWeb.ApiSpec.Error RoomConfig: description: Room configuration properties: @@ -808,7 +808,7 @@ components: nullable: true type: integer roomId: - description: Custom id used for identifying room within Jellyfish. Must be unique across all rooms. If not provided, random UUID is generated. + description: Custom id used for identifying room within Fishjam. Must be unique across all rooms. If not provided, random UUID is generated. nullable: true type: string videoCodec: @@ -819,13 +819,13 @@ components: nullable: true type: string webhookUrl: - description: URL where Jellyfish notifications will be sent - example: https://backend.address.com/jellyfish-notifications-endpoint + description: URL where Fishjam notifications will be sent + example: https://backend.address.com/fishjam-notifications-endpoint nullable: true type: string title: RoomConfig type: object - x-struct: Elixir.JellyfishWeb.ApiSpec.Room.Config + x-struct: Elixir.FishjamWeb.ApiSpec.Room.Config securitySchemes: authorization: scheme: bearer @@ -834,8 +834,8 @@ info: license: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 - title: Jellyfish Media Server - version: 0.5.0 + title: Fishjam Media Server + version: 0.6.0-dev openapi: 3.0.0 paths: /health: @@ -858,7 +858,7 @@ paths: description: Unauthorized security: - authorization: [] - summary: Describes the health of Jellyfish + summary: Describes the health of Fishjam tags: - health /hls/{room_id}/{filename}: diff --git a/protos b/protos index 7da5da12..83aa1514 160000 --- a/protos +++ b/protos @@ -1 +1 @@ -Subproject commit 7da5da127c8e018ee0c845c921f598b10209271c +Subproject commit 83aa151401f9cd03fe91029d4ef2f8175fcf58f2 diff --git a/rel/env.sh.eex b/rel/env.sh.eex index 8a30c779..936e04cf 100644 --- a/rel/env.sh.eex +++ b/rel/env.sh.eex @@ -1,37 +1,37 @@ # We introduce our own env vars and use them to override # those provided by mix release. -# This is to have a unified way of configuring Jellyfish distribution +# This is to have a unified way of configuring Fishjam distribution # in both development and production environments -if [ "$JF_DIST_ENABLED" == "true" ]; then - # If Jellyfish is meant to be run in a cluster, +if [ "$FJ_DIST_ENABLED" == "true" ]; then + # If Fishjam is meant to be run in a cluster, # leave node setup to the Elixir code where # we do extra steps for DNS strategy to determine actual node name. # # We also try to read RELEASE_DISTRIBUTION env var - # to allow for calling `remote` command with JF_DIST_ENABLED set. + # to allow for calling `remote` command with FJ_DIST_ENABLED set. # In other case, RELEASE_DISTRIBUTION will always be set to none. - # This is a little hack. + # This is a little hack. # We can get rid of it once we move to the reverse DNS strategy. export RELEASE_DISTRIBUTION=${RELEASE_DISTRIBUTION:-none} # We only set min and max ports for start and daemon commands. # In other case, when someone wants to expose only one port - # (JF_DIST_MIN_PORT==JF_DIST_MAX_PORT), we won't be able to + # (FJ_DIST_MIN_PORT==FJ_DIST_MAX_PORT), we won't be able to # connect to already running node with the `remote` command. - if [ "$JF_DIST_MIN_PORT" != "" ]; then + if [ "$FJ_DIST_MIN_PORT" != "" ]; then case $RELEASE_COMMAND in start* | daemon*) - ELIXIR_ERL_OPTIONS="$ELIXIR_ERL_OPTIONS -kernel inet_dist_listen_min $JF_DIST_MIN_PORT" + ELIXIR_ERL_OPTIONS="$ELIXIR_ERL_OPTIONS -kernel inet_dist_listen_min $FJ_DIST_MIN_PORT" export ELIXIR_ERL_OPTIONS ;; *) ;; esac fi - if [ "$JF_DIST_MAX_PORT" != "" ]; then + if [ "$FJ_DIST_MAX_PORT" != "" ]; then case $RELEASE_COMMAND in start* | daemon*) - ELIXIR_ERL_OPTIONS="$ELIXIR_ERL_OPTIONS -kernel inet_dist_listen_max $JF_DIST_MAX_PORT" + ELIXIR_ERL_OPTIONS="$ELIXIR_ERL_OPTIONS -kernel inet_dist_listen_max $FJ_DIST_MAX_PORT" export ELIXIR_ERL_OPTIONS ;; *) ;; diff --git a/test/jellyfish/cluster/load_balancing_test.exs b/test/jellyfish/cluster/load_balancing_test.exs index 53d9d9fc..8d5ed654 100644 --- a/test/jellyfish/cluster/load_balancing_test.exs +++ b/test/jellyfish/cluster/load_balancing_test.exs @@ -1,11 +1,11 @@ -defmodule Jellyfish.Cluster.LoadBalancingTest do +defmodule Fishjam.Cluster.LoadBalancingTest do @moduledoc false use ExUnit.Case, async: false @node1 "localhost:4001" @node2 "localhost:4002" - @token Application.compile_env(:jellyfish, :server_api_token) + @token Application.compile_env(:fishjam, :server_api_token) @headers [Authorization: "Bearer #{@token}", Accept: "Application/json; Charset=utf-8"] @moduletag :cluster @@ -28,70 +28,70 @@ defmodule Jellyfish.Cluster.LoadBalancingTest do response_body1 = add_room(node1) - jellyfish_instance1 = get_jellyfish_address(response_body1) + fishjam_instance1 = get_fishjam_address(response_body1) - assert_rooms_number_on_jellyfish(jellyfish_instance1, 1) + assert_rooms_number_on_fishjam(fishjam_instance1, 1) response_body2 = add_room(node1) - jellyfish_instance2 = get_jellyfish_address(response_body2) + fishjam_instance2 = get_fishjam_address(response_body2) - assert_rooms_number_on_jellyfish(jellyfish_instance2, 1) + assert_rooms_number_on_fishjam(fishjam_instance2, 1) - assert_rooms_number_on_jellyfish(node1, 1) - assert_rooms_number_on_jellyfish(node2, 1) + assert_rooms_number_on_fishjam(node1, 1) + assert_rooms_number_on_fishjam(node2, 1) room_id = response_body1 |> Jason.decode!() |> get_in(["data", "room", "id"]) - delete_room(jellyfish_instance1, room_id) + delete_room(fishjam_instance1, room_id) - assert_rooms_number_on_jellyfish(jellyfish_instance1, 0) - assert_rooms_number_on_jellyfish(jellyfish_instance2, 1) + assert_rooms_number_on_fishjam(fishjam_instance1, 0) + assert_rooms_number_on_fishjam(fishjam_instance2, 1) response_body3 = add_room(node1) - jellyfish_instance3 = get_jellyfish_address(response_body3) - assert_rooms_number_on_jellyfish(jellyfish_instance3, 1) + fishjam_instance3 = get_fishjam_address(response_body3) + assert_rooms_number_on_fishjam(fishjam_instance3, 1) - assert_rooms_number_on_jellyfish(node1, 1) - assert_rooms_number_on_jellyfish(node2, 1) + assert_rooms_number_on_fishjam(node1, 1) + assert_rooms_number_on_fishjam(node2, 1) end - defp add_room(jellyfish_instance) do + defp add_room(fishjam_instance) do assert {:ok, %HTTPoison.Response{status_code: 201, body: body}} = - HTTPoison.post("http://#{jellyfish_instance}/room", [], @headers) + HTTPoison.post("http://#{fishjam_instance}/room", [], @headers) body end - defp delete_room(jellyfish_instance, room_id) do + defp delete_room(fishjam_instance, room_id) do assert {:ok, %HTTPoison.Response{status_code: 204, body: body}} = - HTTPoison.delete("http://#{jellyfish_instance}/room/#{room_id}", @headers) + HTTPoison.delete("http://#{fishjam_instance}/room/#{room_id}", @headers) body end if Mix.env() == :test do - defp map_jellyfish_address(jellyfish), do: jellyfish + defp map_fishjam_address(fishjam), do: fishjam else - defp map_jellyfish_address(jellyfish) do + defp map_fishjam_address(fishjam) do %{ @node1 => "app1:4001", @node2 => "app2:4002" } - |> Map.get(jellyfish) + |> Map.get(fishjam) end end - defp get_jellyfish_address(response_body) do + defp get_fishjam_address(response_body) do response_body |> Jason.decode!() - |> get_in(["data", "jellyfish_address"]) - |> map_jellyfish_address() + |> get_in(["data", "fishjam_address"]) + |> map_fishjam_address() end - defp assert_rooms_number_on_jellyfish(jellyfish_instance, rooms) do + defp assert_rooms_number_on_fishjam(fishjam_instance, rooms) do assert {:ok, %HTTPoison.Response{status_code: 200, body: body}} = - HTTPoison.get("http://#{jellyfish_instance}/room", @headers) + HTTPoison.get("http://#{fishjam_instance}/room", @headers) assert ^rooms = body |> Jason.decode!() |> Map.get("data") |> Enum.count() end diff --git a/test/jellyfish/component/hls/ets_helper_test.exs b/test/jellyfish/component/hls/ets_helper_test.exs index 65863584..35b72fca 100644 --- a/test/jellyfish/component/hls/ets_helper_test.exs +++ b/test/jellyfish/component/hls/ets_helper_test.exs @@ -1,9 +1,9 @@ -defmodule Jellyfish.Component.HLS.EtsHelperTest do +defmodule Fishjam.Component.HLS.EtsHelperTest do @moduledoc false use ExUnit.Case, async: false - alias Jellyfish.Component.HLS.EtsHelper + alias Fishjam.Component.HLS.EtsHelper @partial <<1, 2, 3>> @partial_name "partial_1" diff --git a/test/jellyfish/component/hls/ll_storage_test.exs b/test/jellyfish/component/hls/ll_storage_test.exs index 956e7a4e..c0a23508 100644 --- a/test/jellyfish/component/hls/ll_storage_test.exs +++ b/test/jellyfish/component/hls/ll_storage_test.exs @@ -1,9 +1,9 @@ -defmodule Jellyfish.Component.HLS.LLStorageTest do +defmodule Fishjam.Component.HLS.LLStorageTest do @moduledoc false use ExUnit.Case, async: true - alias Jellyfish.Component.HLS.{EtsHelper, LLStorage, RequestHandler} + alias Fishjam.Component.HLS.{EtsHelper, LLStorage, RequestHandler} @segment_name "segment" @segment_content <<1, 2, 3>> diff --git a/test/jellyfish/component/hls/manager_test.exs b/test/jellyfish/component/hls/manager_test.exs index bbc47f23..10ccf482 100644 --- a/test/jellyfish/component/hls/manager_test.exs +++ b/test/jellyfish/component/hls/manager_test.exs @@ -1,12 +1,12 @@ -defmodule Jellyfish.Component.HLS.ManagerTest do +defmodule Fishjam.Component.HLS.ManagerTest do @moduledoc false use ExUnit.Case import Mox - alias Jellyfish.Component.HLS - alias Jellyfish.Component.HLS.Manager + alias Fishjam.Component.HLS + alias Fishjam.Component.HLS.Manager @files ["manifest.m3u8", "header.mp4", "segment_1.m3u8", "segment_2.m3u8"] @s3_credentials %{ diff --git a/test/jellyfish/component/hls/request_handler_test.exs b/test/jellyfish/component/hls/request_handler_test.exs index 35d8d8c4..d01516e4 100644 --- a/test/jellyfish/component/hls/request_handler_test.exs +++ b/test/jellyfish/component/hls/request_handler_test.exs @@ -1,10 +1,10 @@ -defmodule Jellyfish.Component.HLS.RequestHandlerTest do +defmodule Fishjam.Component.HLS.RequestHandlerTest do @moduledoc false use ExUnit.Case, async: true - alias Jellyfish.Component.HLS - alias Jellyfish.Component.HLS.{EtsHelper, RequestHandler} + alias Fishjam.Component.HLS + alias Fishjam.Component.HLS.{EtsHelper, RequestHandler} @wrong_room_id "321" diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs index 05c6eb92..4c94e254 100644 --- a/test/jellyfish/config_reader_test.exs +++ b/test/jellyfish/config_reader_test.exs @@ -1,10 +1,10 @@ -defmodule Jellyfish.ConfigReaderTest do +defmodule Fishjam.ConfigReaderTest do use ExUnit.Case # run this test synchronously as we use # official env vars in read_dist_config test - alias Jellyfish.ConfigReader + alias Fishjam.ConfigReader defmacrop with_env(env, do: body) do # get current env(s) value(s), @@ -31,7 +31,7 @@ defmodule Jellyfish.ConfigReaderTest do end test "read_ip/1" do - env_name = "JF_CONF_READER_TEST_IP" + env_name = "FJ_CONF_READER_TEST_IP" with_env env_name do System.put_env(env_name, "127.0.0.1") @@ -44,7 +44,7 @@ defmodule Jellyfish.ConfigReaderTest do end test "read_and_resolve_hostname/1" do - env_name = "JF_CONF_READER_TEST_HOSTNAME" + env_name = "FJ_CONF_READER_TEST_HOSTNAME" with_env env_name do System.put_env(env_name, "127.0.0.1") @@ -67,7 +67,7 @@ defmodule Jellyfish.ConfigReaderTest do end test "read_port/1" do - env_name = "JF_CONF_READER_TEST_PORT" + env_name = "FJ_CONF_READER_TEST_PORT" with_env env_name do System.put_env(env_name, "20000") @@ -82,7 +82,7 @@ defmodule Jellyfish.ConfigReaderTest do end test "read_boolean/1" do - env_name = "JF_CONF_READER_TEST_BOOL" + env_name = "FJ_CONF_READER_TEST_BOOL" with_env env_name do System.put_env(env_name, "false") @@ -95,19 +95,19 @@ defmodule Jellyfish.ConfigReaderTest do end test "read_check_origin/1" do - env_name = "JF_CHECK_ORIGIN" + env_name = "FJ_CHECK_ORIGIN" with_env env_name do System.put_env(env_name, "false") assert ConfigReader.read_check_origin(env_name) == false System.put_env(env_name, "true") assert ConfigReader.read_check_origin(env_name) == true - System.put_env(env_name, "jellyfish.ovh jellyfish2.ovh jellyfish3.ovh") + System.put_env(env_name, "fishjam.ovh fishjam2.ovh fishjam3.ovh") assert ConfigReader.read_check_origin(env_name) == [ - "jellyfish.ovh", - "jellyfish2.ovh", - "jellyfish3.ovh" + "fishjam.ovh", + "fishjam2.ovh", + "fishjam3.ovh" ] # Case from phoenix documentation @@ -123,7 +123,7 @@ defmodule Jellyfish.ConfigReaderTest do end test "read_port_range/1" do - env_name = "JF_CONF_READER_TEST_PORT_RANGE" + env_name = "FJ_CONF_READER_TEST_PORT_RANGE" with_env env_name do System.put_env(env_name, "50000-60000") @@ -136,72 +136,72 @@ defmodule Jellyfish.ConfigReaderTest do end test "read_ssl_config/0" do - with_env ["JF_SSL_KEY_PATH", "JF_SSL_CERT_PATH"] do + with_env ["FJ_SSL_KEY_PATH", "FJ_SSL_CERT_PATH"] do assert ConfigReader.read_ssl_config() == nil - System.put_env("JF_SSL_KEY_PATH", "/some/key/path") + System.put_env("FJ_SSL_KEY_PATH", "/some/key/path") assert_raise RuntimeError, fn -> ConfigReader.read_ssl_config() end - System.delete_env("JF_SSL_KEY_PATH") + System.delete_env("FJ_SSL_KEY_PATH") - System.put_env("JF_SSL_CERT_PATH", "/some/cert/path") + System.put_env("FJ_SSL_CERT_PATH", "/some/cert/path") assert_raise RuntimeError, fn -> ConfigReader.read_ssl_config() end - System.put_env("JF_SSL_KEY_PATH", "/some/key/path") + System.put_env("FJ_SSL_KEY_PATH", "/some/key/path") assert ConfigReader.read_ssl_config() == {"/some/key/path", "/some/cert/path"} end end test "read_components_used/0" do - with_env ["JF_COMPONENTS_USED"] do + with_env ["FJ_COMPONENTS_USED"] do assert ConfigReader.read_components_used() == [] - System.put_env("JF_COMPONENTS_USED", "hls") - assert ConfigReader.read_components_used() == [Jellyfish.Component.HLS] + System.put_env("FJ_COMPONENTS_USED", "hls") + assert ConfigReader.read_components_used() == [Fishjam.Component.HLS] - System.put_env("JF_COMPONENTS_USED", "recording rtsp sip ") + System.put_env("FJ_COMPONENTS_USED", "recording rtsp sip ") assert ConfigReader.read_components_used() |> Enum.sort() == - [Jellyfish.Component.Recording, Jellyfish.Component.RTSP, Jellyfish.Component.SIP] + [Fishjam.Component.Recording, Fishjam.Component.RTSP, Fishjam.Component.SIP] |> Enum.sort() - System.put_env("JF_COMPONENTS_USED", "file rtsp invalid_component") + System.put_env("FJ_COMPONENTS_USED", "file rtsp invalid_component") assert_raise RuntimeError, fn -> ConfigReader.read_components_used() end end end test "read_sip_config/1" do - with_env ["JF_SIP_IP"] do + with_env ["FJ_SIP_IP"] do assert ConfigReader.read_sip_config(false) == [sip_external_ip: nil] assert_raise RuntimeError, fn -> ConfigReader.read_sip_config(true) end - System.put_env("JF_SIP_IP", "abcdefg") + System.put_env("FJ_SIP_IP", "abcdefg") assert_raise RuntimeError, fn -> ConfigReader.read_sip_config(true) end - System.put_env("JF_SIP_IP", "127.0.0.1") + System.put_env("FJ_SIP_IP", "127.0.0.1") assert ConfigReader.read_sip_config(true) == [sip_external_ip: "127.0.0.1"] end end test "read_s3_config/0" do with_env [ - "JF_S3_BUCKET", - "JF_S3_ACCESS_KEY_ID", - "JF_S3_SECRET_ACCESS_KEY", - "JF_S3_REGION", - "JF_S3_PATH_PREFIX" + "FJ_S3_BUCKET", + "FJ_S3_ACCESS_KEY_ID", + "FJ_S3_SECRET_ACCESS_KEY", + "FJ_S3_REGION", + "FJ_S3_PATH_PREFIX" ] do assert ConfigReader.read_s3_config() == [path_prefix: nil, credentials: nil] - System.put_env("JF_S3_PATH_PREFIX", "path_prefix") + System.put_env("FJ_S3_PATH_PREFIX", "path_prefix") assert ConfigReader.read_s3_config() == [path_prefix: "path_prefix", credentials: nil] - System.put_env("JF_S3_BUCKET", "bucket") + System.put_env("FJ_S3_BUCKET", "bucket") assert_raise RuntimeError, fn -> ConfigReader.read_s3_config() end - System.put_env("JF_S3_ACCESS_KEY_ID", "id") - System.put_env("JF_S3_SECRET_ACCESS_KEY", "key") - System.put_env("JF_S3_REGION", "region") + System.put_env("FJ_S3_ACCESS_KEY_ID", "id") + System.put_env("FJ_S3_SECRET_ACCESS_KEY", "key") + System.put_env("FJ_S3_REGION", "region") assert ConfigReader.read_s3_config() == [ path_prefix: "path_prefix", @@ -217,12 +217,12 @@ defmodule Jellyfish.ConfigReaderTest do test "read_dist_config/0 NODES_LIST" do with_env [ - "JF_DIST_ENABLED", - "JF_DIST_MODE", - "JF_DIST_COOKIE", - "JF_DIST_NODE_NAME", - "JF_DIST_NODES", - "JF_DIST_POLLING_INTERVAL" + "FJ_DIST_ENABLED", + "FJ_DIST_MODE", + "FJ_DIST_COOKIE", + "FJ_DIST_NODE_NAME", + "FJ_DIST_NODES", + "FJ_DIST_POLLING_INTERVAL" ] do {:ok, hostname} = :inet.gethostname() @@ -235,30 +235,30 @@ defmodule Jellyfish.ConfigReaderTest do strategy_config: nil ] - System.put_env("JF_DIST_ENABLED", "true") + System.put_env("FJ_DIST_ENABLED", "true") assert ConfigReader.read_dist_config() == [ enabled: true, mode: :shortnames, strategy: Cluster.Strategy.Epmd, - node_name: :"jellyfish@#{hostname}", - cookie: :jellyfish_cookie, + node_name: :"fishjam@#{hostname}", + cookie: :fishjam_cookie, strategy_config: [hosts: []] ] - System.put_env("JF_DIST_NODE_NAME", "testnodename@abc@def") + System.put_env("FJ_DIST_NODE_NAME", "testnodename@abc@def") assert_raise RuntimeError, fn -> ConfigReader.read_dist_config() end - System.put_env("JF_DIST_NODE_NAME", "testnodename") + System.put_env("FJ_DIST_NODE_NAME", "testnodename") assert_raise RuntimeError, fn -> ConfigReader.read_dist_config() end - System.delete_env("JF_DIST_NODE_NAME") + System.delete_env("FJ_DIST_NODE_NAME") assert ConfigReader.read_dist_config() - System.put_env("JF_DIST_MODE", "invalid") + System.put_env("FJ_DIST_MODE", "invalid") assert_raise RuntimeError, fn -> ConfigReader.read_dist_config() end - System.put_env("JF_DIST_MODE", "name") - System.put_env("JF_DIST_COOKIE", "testcookie") - System.put_env("JF_DIST_NODE_NAME", "testnodename@127.0.0.1") - System.put_env("JF_DIST_NODES", "testnodename1@127.0.0.1 testnodename2@127.0.0.1") + System.put_env("FJ_DIST_MODE", "name") + System.put_env("FJ_DIST_COOKIE", "testcookie") + System.put_env("FJ_DIST_NODE_NAME", "testnodename@127.0.0.1") + System.put_env("FJ_DIST_NODES", "testnodename1@127.0.0.1 testnodename2@127.0.0.1") assert ConfigReader.read_dist_config() == [ enabled: true, @@ -273,13 +273,13 @@ defmodule Jellyfish.ConfigReaderTest do test "read_dist_config/0 DNS" do with_env [ - "JF_DIST_ENABLED", - "JF_DIST_MODE", - "JF_DIST_COOKIE", - "JF_DIST_NODE_NAME", - "JF_DIST_NODES", - "JF_DIST_STRATEGY_NAME", - "JF_DIST_POLLING_INTERVAL" + "FJ_DIST_ENABLED", + "FJ_DIST_MODE", + "FJ_DIST_COOKIE", + "FJ_DIST_NODE_NAME", + "FJ_DIST_NODES", + "FJ_DIST_STRATEGY_NAME", + "FJ_DIST_POLLING_INTERVAL" ] do assert ConfigReader.read_dist_config() == [ enabled: false, @@ -290,27 +290,27 @@ defmodule Jellyfish.ConfigReaderTest do strategy_config: nil ] - System.put_env("JF_DIST_ENABLED", "true") - System.put_env("JF_DIST_MODE", "name") - System.put_env("JF_DIST_STRATEGY_NAME", "DNS") + System.put_env("FJ_DIST_ENABLED", "true") + System.put_env("FJ_DIST_MODE", "name") + System.put_env("FJ_DIST_STRATEGY_NAME", "DNS") assert_raise RuntimeError, fn -> ConfigReader.read_dist_config() end - System.put_env("JF_DIST_QUERY", "my-app.example.com") + System.put_env("FJ_DIST_QUERY", "my-app.example.com") assert [ enabled: true, mode: :longnames, strategy: Cluster.Strategy.DNSPoll, node_name: _node_name, - cookie: :jellyfish_cookie, + cookie: :fishjam_cookie, strategy_config: [ polling_interval: 5_000, query: "my-app.example.com", - node_basename: "jellyfish" + node_basename: "fishjam" ] ] = ConfigReader.read_dist_config() - System.put_env("JF_DIST_COOKIE", "testcookie") - System.put_env("JF_DIST_NODE_NAME", "testnodename@127.0.0.1") + System.put_env("FJ_DIST_COOKIE", "testcookie") + System.put_env("FJ_DIST_NODE_NAME", "testnodename@127.0.0.1") assert ConfigReader.read_dist_config() == [ enabled: true, @@ -326,12 +326,12 @@ defmodule Jellyfish.ConfigReaderTest do ] System.put_env( - "JF_DIST_POLLING_INTERVAL", + "FJ_DIST_POLLING_INTERVAL", "10000" ) # check if hostname is resolved correctly - System.put_env("JF_DIST_NODE_NAME", "testnodename@localhost") + System.put_env("FJ_DIST_NODE_NAME", "testnodename@localhost") assert ConfigReader.read_dist_config() == [ enabled: true, @@ -346,9 +346,9 @@ defmodule Jellyfish.ConfigReaderTest do ] ] - System.put_env("JF_DIST_POLLING_INTERVAL", "abcd") + System.put_env("FJ_DIST_POLLING_INTERVAL", "abcd") assert_raise RuntimeError, fn -> ConfigReader.read_dist_config() end - System.put_env("JF_DIST_POLLING_INTERVAL", "-25") + System.put_env("FJ_DIST_POLLING_INTERVAL", "-25") assert_raise RuntimeError, fn -> ConfigReader.read_dist_config() end end end diff --git a/test/jellyfish/resource_manager_test.exs b/test/jellyfish/resource_manager_test.exs index d5f760ab..c78a53f8 100644 --- a/test/jellyfish/resource_manager_test.exs +++ b/test/jellyfish/resource_manager_test.exs @@ -1,8 +1,8 @@ -defmodule Jellyfish.ResourceManagerTest do - use JellyfishWeb.ComponentCase, async: true +defmodule Fishjam.ResourceManagerTest do + use FishjamWeb.ComponentCase, async: true - alias Jellyfish.Component.Recording - alias Jellyfish.ResourceManager + alias Fishjam.Component.Recording + alias Fishjam.ResourceManager @hour 3_600 diff --git a/test/jellyfish_web/controllers/component/file_component_test.exs b/test/jellyfish_web/controllers/component/file_component_test.exs index 6055a8e2..2639c72f 100644 --- a/test/jellyfish_web/controllers/component/file_component_test.exs +++ b/test/jellyfish_web/controllers/component/file_component_test.exs @@ -1,10 +1,10 @@ -defmodule JellyfishWeb.Component.FileComponentTest do - use JellyfishWeb.ConnCase - use JellyfishWeb.ComponentCase +defmodule FishjamWeb.Component.FileComponentTest do + use FishjamWeb.ConnCase + use FishjamWeb.ComponentCase - alias JellyfishWeb.WS + alias FishjamWeb.WS - alias Jellyfish.ServerMessage.{ + alias Fishjam.ServerMessage.{ Authenticated, Track, TrackAdded, @@ -21,10 +21,10 @@ defmodule JellyfishWeb.Component.FileComponentTest do @auth_response %Authenticated{} setup_all _tags do - Application.put_env(:jellyfish, :components_used, [Jellyfish.Component.File]) + Application.put_env(:fishjam, :components_used, [Fishjam.Component.File]) media_sources_directory = - Application.fetch_env!(:jellyfish, :media_files_path) + Application.fetch_env!(:fishjam, :media_files_path) |> Path.join(@file_component_directory) |> Path.expand() @@ -34,7 +34,7 @@ defmodule JellyfishWeb.Component.FileComponentTest do on_exit(fn -> :file.del_dir_r(media_sources_directory) - Application.put_env(:jellyfish, :components_used, []) + Application.put_env(:fishjam, :components_used, []) end) {:ok, %{media_sources_directory: media_sources_directory}} @@ -307,7 +307,7 @@ defmodule JellyfishWeb.Component.FileComponentTest do end defp start_notifier() do - token = Application.fetch_env!(:jellyfish, :server_api_token) + token = Application.fetch_env!(:fishjam, :server_api_token) {:ok, ws} = WS.start_link(@ws_url, :server) WS.send_auth_request(ws, token) diff --git a/test/jellyfish_web/controllers/component/hls_component_test.exs b/test/jellyfish_web/controllers/component/hls_component_test.exs index aafe0f21..58bd3628 100644 --- a/test/jellyfish_web/controllers/component/hls_component_test.exs +++ b/test/jellyfish_web/controllers/component/hls_component_test.exs @@ -1,12 +1,12 @@ -defmodule JellyfishWeb.Component.HlsComponentTest do - use JellyfishWeb.ConnCase - use JellyfishWeb.ComponentCase +defmodule FishjamWeb.Component.HlsComponentTest do + use FishjamWeb.ConnCase + use FishjamWeb.ComponentCase import Mox - alias Jellyfish.RoomService + alias Fishjam.RoomService - alias Jellyfish.Component.HLS + alias Fishjam.Component.HLS @files ["manifest.m3u8", "header.mp4", "segment_1.m3u8", "segment_2.m3u8"] @body <<1, 2, 3, 4>> @@ -22,10 +22,10 @@ defmodule JellyfishWeb.Component.HlsComponentTest do |> map_keys_to_string() setup_all do - Application.put_env(:jellyfish, :components_used, [HLS]) + Application.put_env(:fishjam, :components_used, [HLS]) on_exit(fn -> - Application.put_env(:jellyfish, :components_used, []) + Application.put_env(:fishjam, :components_used, []) end) end @@ -154,7 +154,7 @@ defmodule JellyfishWeb.Component.HlsComponentTest do end test "renders component with ll-hls enabled", %{conn: conn, room_id: room_id} do - assert Registry.lookup(Jellyfish.RequestHandlerRegistry, room_id) |> Enum.empty?() + assert Registry.lookup(Fishjam.RequestHandlerRegistry, room_id) |> Enum.empty?() conn = post(conn, ~p"/room/#{room_id}/component", type: "hls", options: %{lowLatency: true}) @@ -165,7 +165,7 @@ defmodule JellyfishWeb.Component.HlsComponentTest do assert_component_created(conn, room_id, id, "hls") - [{request_handler, _value}] = Registry.lookup(Jellyfish.RequestHandlerRegistry, room_id) + [{request_handler, _value}] = Registry.lookup(Fishjam.RequestHandlerRegistry, room_id) assert Process.alive?(request_handler) Process.monitor(request_handler) @@ -183,7 +183,7 @@ defmodule JellyfishWeb.Component.HlsComponentTest do assert_receive {:DOWN, _ref, :process, ^engine_pid, :normal}, 10_000 assert_receive {:DOWN, _ref, :process, ^request_handler, :normal} - assert Registry.lookup(Jellyfish.RequestHandlerRegistry, room_id) |> Enum.empty?() + assert Registry.lookup(Fishjam.RequestHandlerRegistry, room_id) |> Enum.empty?() end test "renders errors when video codec is different than h264 - vp8", %{conn: conn} do diff --git a/test/jellyfish_web/controllers/component/recording_component_test.exs b/test/jellyfish_web/controllers/component/recording_component_test.exs index 1dfaa3b0..590229ff 100644 --- a/test/jellyfish_web/controllers/component/recording_component_test.exs +++ b/test/jellyfish_web/controllers/component/recording_component_test.exs @@ -1,10 +1,10 @@ -defmodule JellyfishWeb.Component.RecordingComponentTest do - use JellyfishWeb.ConnCase - use JellyfishWeb.ComponentCase +defmodule FishjamWeb.Component.RecordingComponentTest do + use FishjamWeb.ConnCase + use FishjamWeb.ComponentCase import Mox - alias Jellyfish.RoomService + alias Fishjam.RoomService @s3_credentials %{ accessKeyId: "access_key_id", @@ -16,10 +16,10 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do @path_prefix "path_prefix" setup_all do - Application.put_env(:jellyfish, :components_used, [Jellyfish.Component.Recording]) + Application.put_env(:fishjam, :components_used, [Fishjam.Component.Recording]) on_exit(fn -> - Application.put_env(:jellyfish, :components_used, []) + Application.put_env(:fishjam, :components_used, []) end) end @@ -265,14 +265,14 @@ defmodule JellyfishWeb.Component.RecordingComponentTest do end defp put_s3_envs(path_prefix: path_prefix, credentials: credentials) do - Application.put_env(:jellyfish, :s3_config, + Application.put_env(:fishjam, :s3_config, path_prefix: path_prefix, credentials: credentials ) end defp clean_s3_envs() do - Application.put_env(:jellyfish, :s3_config, path_prefix: nil, credentials: nil) + Application.put_env(:fishjam, :s3_config, path_prefix: nil, credentials: nil) end defp get_recording_path_prefix(room_id, component_id) do diff --git a/test/jellyfish_web/controllers/component/rtsp_component_test.exs b/test/jellyfish_web/controllers/component/rtsp_component_test.exs index 9372db6b..24dc5e79 100644 --- a/test/jellyfish_web/controllers/component/rtsp_component_test.exs +++ b/test/jellyfish_web/controllers/component/rtsp_component_test.exs @@ -1,6 +1,6 @@ -defmodule JellyfishWeb.Component.RTSPComponentTest do - use JellyfishWeb.ConnCase - use JellyfishWeb.ComponentCase +defmodule FishjamWeb.Component.RTSPComponentTest do + use FishjamWeb.ConnCase + use FishjamWeb.ComponentCase @source_uri "rtsp://placeholder-19inrifjbsjb.it:12345/afwefae" @@ -23,10 +23,10 @@ defmodule JellyfishWeb.Component.RTSPComponentTest do @rtsp_custom_properties @rtsp_custom_options |> map_keys_to_string() setup_all do - Application.put_env(:jellyfish, :components_used, [Jellyfish.Component.RTSP]) + Application.put_env(:fishjam, :components_used, [Fishjam.Component.RTSP]) on_exit(fn -> - Application.put_env(:jellyfish, :components_used, []) + Application.put_env(:fishjam, :components_used, []) end) end diff --git a/test/jellyfish_web/controllers/component/sip_component_test.exs b/test/jellyfish_web/controllers/component/sip_component_test.exs index 9a3a4826..43ba4e68 100644 --- a/test/jellyfish_web/controllers/component/sip_component_test.exs +++ b/test/jellyfish_web/controllers/component/sip_component_test.exs @@ -1,6 +1,6 @@ -defmodule JellyfishWeb.Component.SIPComponentTest do - use JellyfishWeb.ConnCase - use JellyfishWeb.ComponentCase +defmodule FishjamWeb.Component.SIPComponentTest do + use FishjamWeb.ConnCase + use FishjamWeb.ComponentCase @sip_credentials %{ address: "my-sip-registrar.net", @@ -14,12 +14,12 @@ defmodule JellyfishWeb.Component.SIPComponentTest do |> map_keys_to_string() setup_all do - Application.put_env(:jellyfish, :sip_config, sip_external_ip: "127.0.0.1") - Application.put_env(:jellyfish, :components_used, [Jellyfish.Component.SIP]) + Application.put_env(:fishjam, :sip_config, sip_external_ip: "127.0.0.1") + Application.put_env(:fishjam, :components_used, [Fishjam.Component.SIP]) on_exit(fn -> - Application.put_env(:jellyfish, :sip_config, sip_external_ip: nil) - Application.put_env(:jellyfish, :components_used, []) + Application.put_env(:fishjam, :sip_config, sip_external_ip: nil) + Application.put_env(:fishjam, :components_used, []) end) end diff --git a/test/jellyfish_web/controllers/component_controller_test.exs b/test/jellyfish_web/controllers/component_controller_test.exs index cb73719a..5f1a3bc2 100644 --- a/test/jellyfish_web/controllers/component_controller_test.exs +++ b/test/jellyfish_web/controllers/component_controller_test.exs @@ -1,17 +1,17 @@ -defmodule JellyfishWeb.ComponentControllerTest do - use JellyfishWeb.ConnCase - use JellyfishWeb.ComponentCase +defmodule FishjamWeb.ComponentControllerTest do + use FishjamWeb.ConnCase + use FishjamWeb.ComponentCase @source_uri "rtsp://placeholder-19inrifjbsjb.it:12345/afwefae" setup_all do - Application.put_env(:jellyfish, :components_used, [ - Jellyfish.Component.RTSP, - Jellyfish.Component.HLS + Application.put_env(:fishjam, :components_used, [ + Fishjam.Component.RTSP, + Fishjam.Component.HLS ]) on_exit(fn -> - Application.put_env(:jellyfish, :components_used, []) + Application.put_env(:fishjam, :components_used, []) end) end @@ -32,19 +32,19 @@ defmodule JellyfishWeb.ComponentControllerTest do end test "renders errors when component isn't allowed globally", %{conn: conn, room_id: room_id} do - Application.put_env(:jellyfish, :components_used, []) + Application.put_env(:fishjam, :components_used, []) on_exit(fn -> - Application.put_env(:jellyfish, :components_used, [ - Jellyfish.Component.RTSP, - Jellyfish.Component.HLS + Application.put_env(:fishjam, :components_used, [ + Fishjam.Component.RTSP, + Fishjam.Component.HLS ]) end) conn = post(conn, ~p"/room/#{room_id}/component", type: "hls") response = model_response(conn, :bad_request, "Error") - assert response["errors"] == "Components of type hls are disabled on this Jellyfish" + assert response["errors"] == "Components of type hls are disabled on this Fishjam" end end diff --git a/test/jellyfish_web/controllers/dial_controller_test.exs b/test/jellyfish_web/controllers/dial_controller_test.exs index 17c4c09f..03c3f62f 100644 --- a/test/jellyfish_web/controllers/dial_controller_test.exs +++ b/test/jellyfish_web/controllers/dial_controller_test.exs @@ -1,5 +1,5 @@ -defmodule JellyfishWeb.DialControllerTest do - use JellyfishWeb.ConnCase +defmodule FishjamWeb.DialControllerTest do + use FishjamWeb.ConnCase @source_uri "rtsp://placeholder-19inrifjbsjb.it:12345/afwefae" @@ -10,21 +10,21 @@ defmodule JellyfishWeb.DialControllerTest do } setup_all do - Application.put_env(:jellyfish, :sip_config, sip_external_ip: "127.0.0.1") + Application.put_env(:fishjam, :sip_config, sip_external_ip: "127.0.0.1") - Application.put_env(:jellyfish, :components_used, [ - Jellyfish.Component.SIP, - Jellyfish.Component.RTSP + Application.put_env(:fishjam, :components_used, [ + Fishjam.Component.SIP, + Fishjam.Component.RTSP ]) on_exit(fn -> - Application.put_env(:jellyfish, :sip_config, sip_external_ip: nil) - Application.put_env(:jellyfish, :components_used, []) + Application.put_env(:fishjam, :sip_config, sip_external_ip: nil) + Application.put_env(:fishjam, :components_used, []) end) end setup %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) conn = put_req_header(conn, "accept", "application/json") diff --git a/test/jellyfish_web/controllers/error_json_test.exs b/test/jellyfish_web/controllers/error_json_test.exs index e25f28e6..ae9640a6 100644 --- a/test/jellyfish_web/controllers/error_json_test.exs +++ b/test/jellyfish_web/controllers/error_json_test.exs @@ -1,12 +1,12 @@ -defmodule JellyfishWeb.ErrorJSONTest do - use JellyfishWeb.ConnCase, async: true +defmodule FishjamWeb.ErrorJSONTest do + use FishjamWeb.ConnCase, async: true test "renders 404" do - assert JellyfishWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} + assert FishjamWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} end test "renders 500" do - assert JellyfishWeb.ErrorJSON.render("500.json", %{}) == + assert FishjamWeb.ErrorJSON.render("500.json", %{}) == %{errors: %{detail: "Internal Server Error"}} end end diff --git a/test/jellyfish_web/controllers/healthcheck_controller_test.exs b/test/jellyfish_web/controllers/healthcheck_controller_test.exs index 8ab06b8f..46dae84a 100644 --- a/test/jellyfish_web/controllers/healthcheck_controller_test.exs +++ b/test/jellyfish_web/controllers/healthcheck_controller_test.exs @@ -1,14 +1,14 @@ -defmodule JellyfishWeb.HealthcheckControllerTest do - use JellyfishWeb.ConnCase, async: true +defmodule FishjamWeb.HealthcheckControllerTest do + use FishjamWeb.ConnCase, async: true import OpenApiSpex.TestAssertions - @schema JellyfishWeb.ApiSpec.spec() + @schema FishjamWeb.ApiSpec.spec() @commit_hash_length 7 setup %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) [conn: conn] @@ -19,7 +19,7 @@ defmodule JellyfishWeb.HealthcheckControllerTest do response = json_response(conn, :ok) assert_response_schema(response, "HealthcheckResponse", @schema) - version = Jellyfish.version() + version = Fishjam.version() assert %{ "status" => "UP", diff --git a/test/jellyfish_web/controllers/hls_controller_test.exs b/test/jellyfish_web/controllers/hls_controller_test.exs index 7a0a820b..bab35ecd 100644 --- a/test/jellyfish_web/controllers/hls_controller_test.exs +++ b/test/jellyfish_web/controllers/hls_controller_test.exs @@ -1,10 +1,10 @@ -defmodule JellyfishWeb.HLSControllerTest do - use JellyfishWeb.ConnCase, async: true +defmodule FishjamWeb.HLSControllerTest do + use FishjamWeb.ConnCase, async: true import OpenApiSpex.TestAssertions - alias Jellyfish.Component.HLS - alias Jellyfish.Component.HLS.{EtsHelper, RequestHandler} + alias Fishjam.Component.HLS + alias Fishjam.Component.HLS.{EtsHelper, RequestHandler} @room_id "hls_controller_test" @wrong_room_id "wrong_id" @@ -28,7 +28,7 @@ defmodule JellyfishWeb.HLSControllerTest do @outside_manifest "../outside_manifest.m3u8" - @schema JellyfishWeb.ApiSpec.spec() + @schema FishjamWeb.ApiSpec.spec() setup_all do output_path = HLS.output_dir(@room_id, persistent: false) diff --git a/test/jellyfish_web/controllers/peer_controller_test.exs b/test/jellyfish_web/controllers/peer_controller_test.exs index 646397c5..9ef1878c 100644 --- a/test/jellyfish_web/controllers/peer_controller_test.exs +++ b/test/jellyfish_web/controllers/peer_controller_test.exs @@ -1,13 +1,13 @@ -defmodule JellyfishWeb.PeerControllerTest do - use JellyfishWeb.ConnCase +defmodule FishjamWeb.PeerControllerTest do + use FishjamWeb.ConnCase import OpenApiSpex.TestAssertions - @schema JellyfishWeb.ApiSpec.spec() + @schema FishjamWeb.ApiSpec.spec() @peer_type "webrtc" setup %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) conn = conn @@ -22,7 +22,7 @@ defmodule JellyfishWeb.PeerControllerTest do assert response(conn, :no_content) end) - peer_ws_url = Jellyfish.peer_websocket_address() + peer_ws_url = Fishjam.peer_websocket_address() {:ok, %{conn: conn, room_id: id, peer_ws_url: peer_ws_url}} end @@ -58,8 +58,8 @@ defmodule JellyfishWeb.PeerControllerTest do assert {:ok, %{peer_id: ^peer_id, room_id: ^room_id}} = Phoenix.Token.verify( - JellyfishWeb.Endpoint, - Application.fetch_env!(:jellyfish, JellyfishWeb.Endpoint)[:secret_key_base], + FishjamWeb.Endpoint, + Application.fetch_env!(:fishjam, FishjamWeb.Endpoint)[:secret_key_base], token ) end @@ -91,16 +91,16 @@ defmodule JellyfishWeb.PeerControllerTest do end test "renders errors when peer isn't allowed globally", %{conn: conn, room_id: room_id} do - Application.put_env(:jellyfish, :webrtc_config, webrtc_used?: false) + Application.put_env(:fishjam, :webrtc_config, webrtc_used?: false) on_exit(fn -> - Application.put_env(:jellyfish, :webrtc_config, webrtc_used?: true) + Application.put_env(:fishjam, :webrtc_config, webrtc_used?: true) end) conn = post(conn, ~p"/room/#{room_id}/peer", type: @peer_type) assert json_response(conn, :bad_request)["errors"] == - "Peers of type webrtc are disabled on this Jellyfish" + "Peers of type webrtc are disabled on this Fishjam" end end diff --git a/test/jellyfish_web/controllers/recording_controller_test.exs b/test/jellyfish_web/controllers/recording_controller_test.exs index 835779c1..a76f8b85 100644 --- a/test/jellyfish_web/controllers/recording_controller_test.exs +++ b/test/jellyfish_web/controllers/recording_controller_test.exs @@ -1,12 +1,12 @@ -defmodule JellyfishWeb.RecordingControllerTest do - use JellyfishWeb.ConnCase, async: true +defmodule FishjamWeb.RecordingControllerTest do + use FishjamWeb.ConnCase, async: true import OpenApiSpex.TestAssertions - doctest Jellyfish.Utils.PathValidation + doctest Fishjam.Utils.PathValidation - alias Jellyfish.Component.HLS - alias Jellyfish.Component.HLS.{EtsHelper, Recording} + alias Fishjam.Component.HLS + alias Fishjam.Component.HLS.{EtsHelper, Recording} @recording_id "recording_id" @live_stream_id "live_stream_id" @@ -21,7 +21,7 @@ defmodule JellyfishWeb.RecordingControllerTest do @wrong_manifest_name "wrong_manifest_name.m3u8" @manifest_content <<3>> - @schema JellyfishWeb.ApiSpec.spec() + @schema FishjamWeb.ApiSpec.spec() setup_all do recording_path = Recording.directory(@recording_id) @@ -127,7 +127,7 @@ defmodule JellyfishWeb.RecordingControllerTest do end defp inject_auth_headers(conn) do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) conn |> put_req_header("authorization", "Bearer " <> server_api_token) diff --git a/test/jellyfish_web/controllers/room_controller_test.exs b/test/jellyfish_web/controllers/room_controller_test.exs index 55bc2503..99132e3f 100644 --- a/test/jellyfish_web/controllers/room_controller_test.exs +++ b/test/jellyfish_web/controllers/room_controller_test.exs @@ -1,15 +1,15 @@ -defmodule JellyfishWeb.RoomControllerTest do - use JellyfishWeb.ConnCase, async: false +defmodule FishjamWeb.RoomControllerTest do + use FishjamWeb.ConnCase, async: false import OpenApiSpex.TestAssertions alias __MODULE__.Endpoint - alias Jellyfish.PeerMessage.Authenticated - alias Jellyfish.RoomService - alias JellyfishWeb.{PeerSocket, WS} + alias Fishjam.PeerMessage.Authenticated + alias Fishjam.RoomService + alias FishjamWeb.{PeerSocket, WS} - @schema JellyfishWeb.ApiSpec.spec() + @schema FishjamWeb.ApiSpec.spec() @purge_timeout_s 3 @purge_timeout_ms @purge_timeout_s * 1000 @@ -19,7 +19,7 @@ defmodule JellyfishWeb.RoomControllerTest do @auth_response %Authenticated{} Application.put_env( - :jellyfish, + :fishjam, Endpoint, https: false, http: [port: @port], @@ -27,9 +27,9 @@ defmodule JellyfishWeb.RoomControllerTest do ) defmodule Endpoint do - use Phoenix.Endpoint, otp_app: :jellyfish + use Phoenix.Endpoint, otp_app: :fishjam - alias JellyfishWeb.PeerSocket + alias FishjamWeb.PeerSocket socket "/socket/peer", PeerSocket, websocket: true, @@ -43,7 +43,7 @@ defmodule JellyfishWeb.RoomControllerTest do end setup %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) Klotho.Mock.reset() @@ -62,7 +62,7 @@ defmodule JellyfishWeb.RoomControllerTest do test "invalid token", %{conn: conn} do invalid_server_api_token = - "invalid" <> Application.fetch_env!(:jellyfish, :server_api_token) + "invalid" <> Application.fetch_env!(:fishjam, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> invalid_server_api_token) @@ -82,7 +82,7 @@ defmodule JellyfishWeb.RoomControllerTest do end test "correct token", %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) conn = post(conn, ~p"/room", maxPeers: 10) @@ -506,7 +506,7 @@ defmodule JellyfishWeb.RoomControllerTest do end defp delete_all_rooms() do - token = Application.fetch_env!(:jellyfish, :server_api_token) + token = Application.fetch_env!(:fishjam, :server_api_token) headers = [Authorization: "Bearer #{token}", Accept: "Application/json; Charset=utf-8"] assert {:ok, %HTTPoison.Response{status_code: 200, body: body}} = diff --git a/test/jellyfish_web/controllers/subscription_controller_test.exs b/test/jellyfish_web/controllers/subscription_controller_test.exs index 6fe03d3d..799263ef 100644 --- a/test/jellyfish_web/controllers/subscription_controller_test.exs +++ b/test/jellyfish_web/controllers/subscription_controller_test.exs @@ -1,5 +1,5 @@ -defmodule JellyfishWeb.SubscriptionControllerTest do - use JellyfishWeb.ConnCase +defmodule FishjamWeb.SubscriptionControllerTest do + use FishjamWeb.ConnCase @s3_credentials %{ accessKeyId: "access_key_id", @@ -15,22 +15,22 @@ defmodule JellyfishWeb.SubscriptionControllerTest do } setup_all do - Application.put_env(:jellyfish, :sip_config, sip_external_ip: "127.0.0.1") + Application.put_env(:fishjam, :sip_config, sip_external_ip: "127.0.0.1") - Application.put_env(:jellyfish, :components_used, [ - Jellyfish.Component.SIP, - Jellyfish.Component.HLS, - Jellyfish.Component.Recording + Application.put_env(:fishjam, :components_used, [ + Fishjam.Component.SIP, + Fishjam.Component.HLS, + Fishjam.Component.Recording ]) on_exit(fn -> - Application.put_env(:jellyfish, :sip_config, sip_external_ip: nil) - Application.put_env(:jellyfish, :components_used, []) + Application.put_env(:fishjam, :sip_config, sip_external_ip: nil) + Application.put_env(:fishjam, :components_used, []) end) end setup %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) conn = put_req_header(conn, "accept", "application/json") diff --git a/test/jellyfish_web/integration/peer_socket_test.exs b/test/jellyfish_web/integration/peer_socket_test.exs index 4fe2038d..684c1546 100644 --- a/test/jellyfish_web/integration/peer_socket_test.exs +++ b/test/jellyfish_web/integration/peer_socket_test.exs @@ -1,18 +1,18 @@ -defmodule JellyfishWeb.Integration.PeerSocketTest do - use JellyfishWeb.ConnCase +defmodule FishjamWeb.Integration.PeerSocketTest do + use FishjamWeb.ConnCase alias __MODULE__.Endpoint - alias Jellyfish.PeerMessage - alias Jellyfish.PeerMessage.{Authenticated, MediaEvent} - alias Jellyfish.RoomService - alias JellyfishWeb.{PeerSocket, WS} + alias Fishjam.PeerMessage + alias Fishjam.PeerMessage.{Authenticated, MediaEvent} + alias Fishjam.RoomService + alias FishjamWeb.{PeerSocket, WS} @port 5908 @path "ws://127.0.0.1:#{@port}/socket/peer/websocket" @auth_response %Authenticated{} Application.put_env( - :jellyfish, + :fishjam, Endpoint, https: false, http: [port: @port], @@ -20,9 +20,9 @@ defmodule JellyfishWeb.Integration.PeerSocketTest do ) defmodule Endpoint do - use Phoenix.Endpoint, otp_app: :jellyfish + use Phoenix.Endpoint, otp_app: :fishjam - alias JellyfishWeb.PeerSocket + alias FishjamWeb.PeerSocket socket "/socket/peer", PeerSocket, websocket: true, @@ -35,7 +35,7 @@ defmodule JellyfishWeb.Integration.PeerSocketTest do end setup %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) conn = post(conn, ~p"/room", maxPeers: 1) @@ -74,7 +74,7 @@ defmodule JellyfishWeb.Integration.PeerSocketTest do test "valid token but peer doesn't exist", %{room_id: room_id} do {:ok, ws} = WS.start_link(@path, :peer) - unadded_peer_token = JellyfishWeb.PeerToken.generate(%{peer_id: "peer_id", room_id: room_id}) + unadded_peer_token = FishjamWeb.PeerToken.generate(%{peer_id: "peer_id", room_id: room_id}) WS.send_auth_request(ws, unadded_peer_token) assert_receive {:disconnected, {:remote, 1000, "peer not found"}}, 1000 @@ -169,13 +169,13 @@ defmodule JellyfishWeb.Integration.PeerSocketTest do assert %{} = get_peers_room_metrics() create_and_authenticate(token) - peers_in_room_key = "jellyfish_room_peers{room_id=\"#{room_id}\"}" - peers_in_room_time_key = "jellyfish_room_peer_time_total_seconds{room_id=\"#{room_id}\"}" + peers_in_room_key = "fishjam_room_peers{room_id=\"#{room_id}\"}" + peers_in_room_time_key = "fishjam_room_peer_time_total_seconds{room_id=\"#{room_id}\"}" metrics_after_one_tick = %{ peers_in_room_key => "1", peers_in_room_time_key => "1", - "jellyfish_rooms" => "1" + "fishjam_rooms" => "1" } Process.sleep(1_000) @@ -194,7 +194,7 @@ defmodule JellyfishWeb.Integration.PeerSocketTest do metrics_after_removal = %{ peers_in_room_key => "0", peers_in_room_time_key => "1", - "jellyfish_rooms" => "0" + "fishjam_rooms" => "0" } metrics_to_compare = get_peers_room_metrics() @@ -227,7 +227,7 @@ defmodule JellyfishWeb.Integration.PeerSocketTest do end end) |> Enum.filter(fn {key, _value} -> - String.starts_with?(key, "jellyfish_room") + String.starts_with?(key, "fishjam_room") end) |> Map.new() end diff --git a/test/jellyfish_web/integration/server_notification_test.exs b/test/jellyfish_web/integration/server_notification_test.exs index efdae952..6308b5e5 100644 --- a/test/jellyfish_web/integration/server_notification_test.exs +++ b/test/jellyfish_web/integration/server_notification_test.exs @@ -1,20 +1,20 @@ -defmodule JellyfishWeb.Integration.ServerNotificationTest do - use JellyfishWeb.ConnCase +defmodule FishjamWeb.Integration.ServerNotificationTest do + use FishjamWeb.ConnCase import Mox - import JellyfishWeb.WS, only: [subscribe: 2] + import FishjamWeb.WS, only: [subscribe: 2] alias __MODULE__.Endpoint alias Membrane.RTC.Engine - alias Jellyfish.Component - alias Jellyfish.Component.HLS - alias Jellyfish.Component.HLS.Manager - alias Jellyfish.{PeerMessage, Room, RoomService, ServerMessage} + alias Fishjam.Component + alias Fishjam.Component.HLS + alias Fishjam.Component.HLS.Manager + alias Fishjam.{PeerMessage, Room, RoomService, ServerMessage} - alias Jellyfish.ServerMessage.{ + alias Fishjam.ServerMessage.{ Authenticated, ComponentCrashed, HlsPlayable, @@ -34,7 +34,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do TrackRemoved } - alias JellyfishWeb.{PeerSocket, ServerSocket, WS} + alias FishjamWeb.{PeerSocket, ServerSocket, WS} alias Phoenix.PubSub @port 5907 @@ -42,7 +42,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do @webhook_url "http://127.0.0.1:#{@webhook_port}/" @path "ws://127.0.0.1:#{@port}/socket/server/websocket" @auth_response %Authenticated{} - @pubsub Jellyfish.PubSub + @pubsub Fishjam.PubSub @file_component_directory "file_component_sources" @fixtures_directory "test/fixtures" @@ -70,7 +70,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do @purge_timeout_ms @purge_timeout_s * 1000 Application.put_env( - :jellyfish, + :fishjam, Endpoint, https: false, http: [port: @port], @@ -78,9 +78,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do ) defmodule Endpoint do - use Phoenix.Endpoint, otp_app: :jellyfish + use Phoenix.Endpoint, otp_app: :fishjam - alias JellyfishWeb.ServerSocket + alias FishjamWeb.ServerSocket socket("/socket/server", ServerSocket, websocket: true, @@ -94,9 +94,9 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do end setup_all do - Application.put_env(:jellyfish, :sip_config, sip_external_ip: "127.0.0.1") + Application.put_env(:fishjam, :sip_config, sip_external_ip: "127.0.0.1") - Application.put_env(:jellyfish, :components_used, [ + Application.put_env(:fishjam, :components_used, [ Component.SIP, Component.HLS, Component.RTSP, @@ -104,8 +104,8 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do ]) on_exit(fn -> - Application.put_env(:jellyfish, :sip_config, sip_external_ip: nil) - Application.put_env(:jellyfish, :components_used, []) + Application.put_env(:fishjam, :sip_config, sip_external_ip: nil) + Application.put_env(:fishjam, :components_used, []) end) assert {:ok, _pid} = Endpoint.start_link() @@ -120,7 +120,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do setup(%{conn: conn}) do :ok = PubSub.subscribe(@pubsub, "webhook") - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) Klotho.Mock.reset() @@ -145,7 +145,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do describe "establishing connection" do test "invalid token" do {:ok, ws} = WS.start_link(@path, :server) - server_api_token = "invalid" <> Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = "invalid" <> Application.fetch_env!(:fishjam, :server_api_token) WS.send_auth_request(ws, server_api_token) assert_receive {:disconnected, {:remote, 1000, "invalid token"}}, 1000 @@ -190,7 +190,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do end test "sends a message when room gets created and deleted", %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) ws = create_and_authenticate() subscribe(ws, :server_notification) @@ -211,7 +211,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do end test "sends a message when room gets created and deleted by peerless purge", %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) ws = create_and_authenticate() subscribe(ws, :server_notification) @@ -287,7 +287,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do test "sends a message when peer connects and room crashes", %{conn: conn} do {room_id, peer_id, _conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) - {:ok, room_pid} = Jellyfish.RoomService.find_room(room_id) + {:ok, room_pid} = Fishjam.RoomService.find_room(room_id) Process.exit(room_pid, :kill) @@ -305,7 +305,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do test "sends a message when peer connects and it crashes", %{conn: conn} do {room_id, peer_id, conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) - {:ok, room_pid} = Jellyfish.RoomService.find_room(room_id) + {:ok, room_pid} = Fishjam.RoomService.find_room(room_id) state = :sys.get_state(room_pid) @@ -328,7 +328,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do test "sends message when peer metadata is updated", %{conn: conn} do {room_id, peer_id, _conn, peer_ws} = subscribe_on_notifications_and_connect_peer(conn) - metadata = %{name: "Jellyuser"} + metadata = %{name: "FishjamUser"} metadata_encoded = Jason.encode!(metadata) media_event = %PeerMessage.MediaEvent{ @@ -357,7 +357,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do {:ok, config} = Room.Config.from_params(%{"webhookUrl" => @webhook_url}) {:ok, room_pid, room_id} = Room.start(config) - Jellyfish.WebhookNotifier.add_webhook(room_id, config.webhook_url) + Fishjam.WebhookNotifier.add_webhook(room_id, config.webhook_url) {peer_id, token, _conn} = add_peer(conn, room_id) {:ok, peer_ws} = WS.start("ws://127.0.0.1:#{@port}/socket/peer/websocket", :peer) @@ -418,7 +418,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do end test "sends a message when peer gets created and deleted by disconnected purge", %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) ws = create_and_authenticate() subscribe(ws, :server_notification) @@ -469,7 +469,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do test "sends message when File adds or removes tracks", %{conn: conn} do media_sources_directory = - Application.fetch_env!(:jellyfish, :media_files_path) + Application.fetch_env!(:fishjam, :media_files_path) |> Path.join(@file_component_directory) |> Path.expand() @@ -607,7 +607,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do end def create_and_authenticate() do - token = Application.fetch_env!(:jellyfish, :server_api_token) + token = Application.fetch_env!(:fishjam, :server_api_token) {:ok, ws} = WS.start_link(@path, :server) WS.send_auth_request(ws, token) diff --git a/test/support/component_case.ex b/test/support/component_case.ex index bbf72e6f..66dbfa04 100644 --- a/test/support/component_case.ex +++ b/test/support/component_case.ex @@ -1,22 +1,22 @@ -defmodule JellyfishWeb.ComponentCase do +defmodule FishjamWeb.ComponentCase do @moduledoc """ This module defines the test case to be used by - Jellyfish Component tests. + Fishjam Component tests. """ use ExUnit.CaseTemplate - use JellyfishWeb.ConnCase + use FishjamWeb.ConnCase - alias Jellyfish.RoomService + alias Fishjam.RoomService using do quote do - import JellyfishWeb.ComponentCase + import FishjamWeb.ComponentCase end end setup %{conn: conn} do - server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) + server_api_token = Application.fetch_env!(:fishjam, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> server_api_token) conn = put_req_header(conn, "accept", "application/json") @@ -44,8 +44,8 @@ defmodule JellyfishWeb.ComponentCase do @spec assert_component_created( Plug.Conn.t(), - Jellyfish.Room.id(), - Jellyfish.Component.id(), + Fishjam.Room.id(), + Fishjam.Component.id(), String.t() ) :: map() def assert_component_created(conn, room_id, component_id, component_type) do @@ -70,7 +70,7 @@ defmodule JellyfishWeb.ComponentCase do OpenApiSpex.TestAssertions.assert_response_schema( response, model, - JellyfishWeb.ApiSpec.spec() + FishjamWeb.ApiSpec.spec() ) end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index cf524cf7..85b270c7 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -1,4 +1,4 @@ -defmodule JellyfishWeb.ConnCase do +defmodule FishjamWeb.ConnCase do @moduledoc """ This module defines the test case to be used by tests that require setting up a connection. @@ -11,7 +11,7 @@ defmodule JellyfishWeb.ConnCase do 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 JellyfishWeb.ConnCase, async: true`, although + by setting `use FishjamWeb.ConnCase, async: true`, although this option is not recommended for other databases. """ @@ -19,17 +19,17 @@ defmodule JellyfishWeb.ConnCase do using do quote do - use JellyfishWeb, :verified_routes + use FishjamWeb, :verified_routes # Import conveniences for testing with connections import Plug.Conn import Phoenix.ConnTest - import JellyfishWeb.ConnCase + import FishjamWeb.ConnCase - alias JellyfishWeb.Router.Helpers, as: Routes + alias FishjamWeb.Router.Helpers, as: Routes # The default endpoint for testing - @endpoint JellyfishWeb.Endpoint + @endpoint FishjamWeb.Endpoint end end diff --git a/test/support/webhook_plug.ex b/test/support/webhook_plug.ex index 497c72da..79d55c5b 100644 --- a/test/support/webhook_plug.ex +++ b/test/support/webhook_plug.ex @@ -1,10 +1,10 @@ defmodule WebHookPlug do @moduledoc false import Plug.Conn - alias Jellyfish.ServerMessage + alias Fishjam.ServerMessage alias Phoenix.PubSub - @pubsub Jellyfish.PubSub + @pubsub Fishjam.PubSub def init(opts) do # initialize options diff --git a/test/support/ws.ex b/test/support/ws.ex index cd004922..42d46876 100644 --- a/test/support/ws.ex +++ b/test/support/ws.ex @@ -1,10 +1,10 @@ -defmodule JellyfishWeb.WS do +defmodule FishjamWeb.WS do @moduledoc false use WebSockex - alias Jellyfish.PeerMessage - alias Jellyfish.ServerMessage + alias Fishjam.PeerMessage + alias Fishjam.ServerMessage @spec start(String.t(), :server | :peer) :: {:ok, pid()} | {:error, term()} def start(url, type) do diff --git a/test/test_helper.exs b/test/test_helper.exs index 4818be81..9325c4b9 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,4 @@ -Application.put_env(:jellyfish, :media_files_path, "tmp/jellyfish_resources/") +Application.put_env(:fishjam, :media_files_path, "tmp/fishjam_resources/") Mox.defmock(ExAws.Request.HttpMock, for: ExAws.Request.HttpClient) From 7a311a479ba1bb81c350b11f5175808ce597a3fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Thu, 16 May 2024 17:23:37 +0200 Subject: [PATCH 34/51] Bump version to 0.6.0 (#198) --- mix.exs | 2 +- openapi.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 5e393486..d28181eb 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Fishjam.MixProject do def project do [ app: :fishjam, - version: "0.6.0-dev", + version: "0.6.0", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, diff --git a/openapi.yaml b/openapi.yaml index 2a800621..9ad53ddf 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -835,7 +835,7 @@ info: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 title: Fishjam Media Server - version: 0.6.0-dev + version: 0.6.0 openapi: 3.0.0 paths: /health: From 465ae81bba32186bfaf50657f5be1b46de579ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= <48837433+roznawsk@users.noreply.github.com> Date: Fri, 17 May 2024 12:00:17 +0200 Subject: [PATCH 35/51] Fallback to JF_ env vars when FJ_ are missing in all cases (#199) * Fix parsing api token * Fix other variables * bump version --- config/runtime.exs | 8 ++++---- lib/jellyfish/config_reader.ex | 4 ++++ mix.exs | 2 +- openapi.yaml | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/config/runtime.exs b/config/runtime.exs index 47b4e331..5608f942 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -20,7 +20,7 @@ ip = ConfigReader.read_ip("FJ_IP") || Application.fetch_env!(:fishjam, :ip) port = ConfigReader.read_port("FJ_PORT") || Application.fetch_env!(:fishjam, :port) host = - case System.get_env("FJ_HOST") do + case ConfigReader.read_string("FJ_HOST") do nil -> "#{:inet.ntoa(ip)}:#{port}" other -> other end @@ -31,7 +31,7 @@ sip_used? = Fishjam.Component.SIP in components_used config :fishjam, jwt_max_age: 24 * 3600, media_files_path: - System.get_env("FJ_RESOURCES_BASE_PATH", "fishjam_resources") |> Path.expand(), + ConfigReader.read_string("FJ_RESOURCES_BASE_PATH", "fishjam_resources") |> Path.expand(), address: host, metrics_ip: ConfigReader.read_ip("FJ_METRICS_IP") || {127, 0, 0, 1}, metrics_port: ConfigReader.read_port("FJ_METRICS_PORT") || 9568, @@ -42,7 +42,7 @@ config :fishjam, s3_config: ConfigReader.read_s3_config(), git_commit: ConfigReader.read_git_commit() -case System.get_env("FJ_SERVER_API_TOKEN") do +case ConfigReader.read_string("FJ_SERVER_API_TOKEN") do nil when prod? == true -> raise """ environment variable FJ_SERVER_API_TOKEN is missing. @@ -61,7 +61,7 @@ external_uri = URI.parse("//" <> host) config :fishjam, FishjamWeb.Endpoint, secret_key_base: - System.get_env("FJ_SECRET_KEY_BASE") || Base.encode64(:crypto.strong_rand_bytes(48)), + ConfigReader.read_string("FJ_SECRET_KEY_BASE") || Base.encode64(:crypto.strong_rand_bytes(48)), url: [ host: external_uri.host, port: external_uri.port || 443, diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex index f22be44f..f4a23671 100644 --- a/lib/jellyfish/config_reader.ex +++ b/lib/jellyfish/config_reader.ex @@ -246,6 +246,10 @@ defmodule Fishjam.ConfigReader do get_env("FJ_GIT_COMMIT", "dev") end + def read_string(string, default \\ nil) do + get_env(string, default) + end + defp do_read_nodes_list_config(node_name_value, cookie, mode) do nodes_value = get_env("FJ_DIST_NODES", "") diff --git a/mix.exs b/mix.exs index d28181eb..d9a3372c 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Fishjam.MixProject do def project do [ app: :fishjam, - version: "0.6.0", + version: "0.6.1", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, diff --git a/openapi.yaml b/openapi.yaml index 9ad53ddf..c67ac562 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -835,7 +835,7 @@ info: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 title: Fishjam Media Server - version: 0.6.0 + version: 0.6.1 openapi: 3.0.0 paths: /health: From 64f7e385d6a1f4a38bd81dfa1a0fa2d80d9366f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Mon, 20 May 2024 15:01:52 +0200 Subject: [PATCH 36/51] Configure log level (#197) * Fix logger in hls manager * Add test * Modify possible log levels * Changes after review --- config/dev.exs | 2 -- config/prod.exs | 3 --- config/runtime.exs | 2 ++ lib/jellyfish/component/hls/manager.ex | 3 ++- lib/jellyfish/config_reader.ex | 17 +++++++++++++++++ test/jellyfish/config_reader_test.exs | 17 +++++++++++++++++ 6 files changed, 38 insertions(+), 6 deletions(-) diff --git a/config/dev.exs b/config/dev.exs index 3e6e61f0..84a86de7 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -26,5 +26,3 @@ config :phoenix, :stacktrace_depth, 20 # Initialize plugs at runtime for faster development compilation config :phoenix, :plug_init_mode, :runtime - -config :logger, level: :info diff --git a/config/prod.exs b/config/prod.exs index 1499af52..3e571170 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -4,9 +4,6 @@ import Config # to something meaningful, Phoenix uses this information # when generating URLs. -# Do not print debug messages in production -config :logger, level: :info - config :fishjam, ip: {127, 0, 0, 1}, port: 8080 diff --git a/config/runtime.exs b/config/runtime.exs index 5608f942..00178356 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -13,6 +13,8 @@ config :ex_dtls, impl: :nif structured_logging? = ConfigReader.read_boolean("FJ_STRUCTURED_LOGGING") || false config :logger, backends: [if(structured_logging?, do: LoggerJSON, else: :console)] +config :logger, level: ConfigReader.read_logger_level() + prod? = config_env() == :prod ip = ConfigReader.read_ip("FJ_IP") || Application.fetch_env!(:fishjam, :ip) diff --git a/lib/jellyfish/component/hls/manager.ex b/lib/jellyfish/component/hls/manager.ex index f56f2ccf..06a000d5 100644 --- a/lib/jellyfish/component/hls/manager.ex +++ b/lib/jellyfish/component/hls/manager.ex @@ -75,7 +75,8 @@ defmodule Fishjam.Component.HLS.Manager do end) broadcast_notification(result, room_id) - Logger.info("Finished uploading to s3 with result: #{result}, room: #{room_id}") + + Logger.info("Finished uploading to s3 with result: #{inspect(result)}, room: #{room_id}") end defp upload_file_to_s3(content, s3_path, opts, config, credentials) do diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex index f4a23671..07b5e519 100644 --- a/lib/jellyfish/config_reader.ex +++ b/lib/jellyfish/config_reader.ex @@ -250,6 +250,23 @@ defmodule Fishjam.ConfigReader do get_env(string, default) end + def read_logger_level() do + log_level = get_env("FJ_LOG_LEVEL", "info") + + log_levels_string = ["info", "debug", "warning", "error"] + + if log_level in log_levels_string do + String.to_existing_atom(log_level) + else + Logger.warning(""" + Provided unknown level of logs: #{log_level}. Valid values are #{Enum.join(log_levels_string, ", ")}. + Set value to default - info. + """) + + :info + end + end + defp do_read_nodes_list_config(node_name_value, cookie, mode) do nodes_value = get_env("FJ_DIST_NODES", "") diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs index 4c94e254..aab6ac63 100644 --- a/test/jellyfish/config_reader_test.exs +++ b/test/jellyfish/config_reader_test.exs @@ -352,4 +352,21 @@ defmodule Fishjam.ConfigReaderTest do assert_raise RuntimeError, fn -> ConfigReader.read_dist_config() end end end + + test "read_logger_level/0" do + with_env ["FJ_LOG_LEVEL"] do + env_value_to_log_level = %{ + "info" => :info, + "debug" => :debug, + "warning" => :warning, + "error" => :error, + "other_env_value" => :info + } + + for {env_value, log_level} <- env_value_to_log_level do + System.put_env("FJ_LOG_LEVEL", env_value) + assert ConfigReader.read_logger_level() == log_level + end + end + end end From 0f2646533a9bbbece3652c8640b908f45359178f Mon Sep 17 00:00:00 2001 From: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> Date: Wed, 22 May 2024 12:45:47 +0200 Subject: [PATCH 37/51] [RTC-548] Allow underscore in room IDs, rename leftover files (#201) * [RTC-548] Allow underscore in room IDs, rename leftover files * Update error msg * Update test --- ...-server-overiew.json => fishjam-server-overview.json} | 0 lib/{jellyfish.ex => fishjam.ex} | 0 lib/{jellyfish => fishjam}/application.ex | 0 lib/{jellyfish => fishjam}/component.ex | 0 lib/{jellyfish => fishjam}/component/file.ex | 0 lib/{jellyfish => fishjam}/component/hls.ex | 0 lib/{jellyfish => fishjam}/component/hls/ets_helper.ex | 0 lib/{jellyfish => fishjam}/component/hls/httpoison.ex | 0 lib/{jellyfish => fishjam}/component/hls/ll_storage.ex | 0 lib/{jellyfish => fishjam}/component/hls/manager.ex | 0 lib/{jellyfish => fishjam}/component/hls/recording.ex | 0 .../component/hls/request_handler.ex | 0 lib/{jellyfish => fishjam}/component/hls/storage.ex | 0 lib/{jellyfish => fishjam}/component/recording.ex | 0 lib/{jellyfish => fishjam}/component/rtsp.ex | 0 lib/{jellyfish => fishjam}/component/sip.ex | 0 lib/{jellyfish => fishjam}/config_reader.ex | 0 lib/{jellyfish => fishjam}/endpoint/config.ex | 0 lib/{jellyfish => fishjam}/event.ex | 0 lib/{jellyfish => fishjam}/metrics_scraper.ex | 0 lib/{jellyfish => fishjam}/peer.ex | 0 lib/{jellyfish => fishjam}/peer/webrtc.ex | 0 lib/{jellyfish => fishjam}/resource_manager.ex | 0 lib/{jellyfish => fishjam}/room.ex | 0 lib/{jellyfish => fishjam}/room/config.ex | 2 +- lib/{jellyfish => fishjam}/room/state.ex | 0 lib/{jellyfish => fishjam}/room_service.ex | 3 +-- lib/{jellyfish => fishjam}/track.ex | 0 lib/{jellyfish => fishjam}/utils/parser_json.ex | 0 lib/{jellyfish => fishjam}/utils/path_validation.ex | 0 lib/{jellyfish => fishjam}/webhook_notifier.ex | 0 lib/{jellyfish_web.ex => fishjam_web.ex} | 0 lib/{jellyfish_web => fishjam_web}/api_spec.ex | 0 lib/{jellyfish_web => fishjam_web}/api_spec/component.ex | 0 .../api_spec/component/file.ex | 0 .../api_spec/component/hls.ex | 0 .../api_spec/component/recording.ex | 0 .../api_spec/component/rtsp.ex | 0 .../api_spec/component/sip.ex | 0 lib/{jellyfish_web => fishjam_web}/api_spec/dial.ex | 0 lib/{jellyfish_web => fishjam_web}/api_spec/error.ex | 0 .../api_spec/health_report.ex | 0 lib/{jellyfish_web => fishjam_web}/api_spec/hls.ex | 0 lib/{jellyfish_web => fishjam_web}/api_spec/peer.ex | 0 .../api_spec/peer/webrtc.ex | 0 lib/{jellyfish_web => fishjam_web}/api_spec/responses.ex | 0 lib/{jellyfish_web => fishjam_web}/api_spec/room.ex | 0 .../api_spec/subscription.ex | 0 lib/{jellyfish_web => fishjam_web}/api_spec/track.ex | 0 .../controllers/component_controller.ex | 0 .../controllers/component_json.ex | 0 .../controllers/error_json.ex | 0 .../controllers/fallback_controller.ex | 0 .../controllers/healthcheck_controller.ex | 0 .../controllers/healthcheck_json.ex | 0 .../controllers/hls_content_controller.ex | 0 .../controllers/peer_controller.ex | 0 .../controllers/peer_json.ex | 0 .../controllers/recording_content_controller.ex | 0 .../controllers/recording_controller.ex | 0 .../controllers/recording_json.ex | 0 .../controllers/room_controller.ex | 2 +- .../controllers/room_json.ex | 0 .../controllers/sip_call_controller.ex | 0 .../controllers/subscription_controller.ex | 0 lib/{jellyfish_web => fishjam_web}/endpoint.ex | 0 lib/{jellyfish_web => fishjam_web}/peer_socket.ex | 0 lib/{jellyfish_web => fishjam_web}/peer_token.ex | 0 lib/{jellyfish_web => fishjam_web}/router.ex | 0 lib/{jellyfish_web => fishjam_web}/server_socket.ex | 0 lib/{jellyfish_web => fishjam_web}/telemetry.ex | 0 .../traffic_metrics_plug.ex | 0 .../cluster/load_balancing_test.exs | 0 .../component/hls/ets_helper_test.exs | 0 .../component/hls/ll_storage_test.exs | 0 .../component/hls/manager_test.exs | 0 .../component/hls/request_handler_test.exs | 0 test/{jellyfish => fishjam}/config_reader_test.exs | 0 test/{jellyfish => fishjam}/resource_manager_test.exs | 0 .../controllers/component/file_component_test.exs | 0 .../controllers/component/hls_component_test.exs | 0 .../controllers/component/recording_component_test.exs | 0 .../controllers/component/rtsp_component_test.exs | 0 .../controllers/component/sip_component_test.exs | 0 .../controllers/component_controller_test.exs | 0 .../controllers/dial_controller_test.exs | 0 .../controllers/error_json_test.exs | 0 .../controllers/healthcheck_controller_test.exs | 0 .../controllers/hls_controller_test.exs | 0 .../controllers/peer_controller_test.exs | 0 .../controllers/recording_controller_test.exs | 0 .../controllers/room_controller_test.exs | 9 ++++----- .../controllers/subscription_controller_test.exs | 0 .../integration/peer_socket_test.exs | 0 .../integration/server_notification_test.exs | 0 95 files changed, 7 insertions(+), 9 deletions(-) rename grafana/{jellyfish-server-overiew.json => fishjam-server-overview.json} (100%) rename lib/{jellyfish.ex => fishjam.ex} (100%) rename lib/{jellyfish => fishjam}/application.ex (100%) rename lib/{jellyfish => fishjam}/component.ex (100%) rename lib/{jellyfish => fishjam}/component/file.ex (100%) rename lib/{jellyfish => fishjam}/component/hls.ex (100%) rename lib/{jellyfish => fishjam}/component/hls/ets_helper.ex (100%) rename lib/{jellyfish => fishjam}/component/hls/httpoison.ex (100%) rename lib/{jellyfish => fishjam}/component/hls/ll_storage.ex (100%) rename lib/{jellyfish => fishjam}/component/hls/manager.ex (100%) rename lib/{jellyfish => fishjam}/component/hls/recording.ex (100%) rename lib/{jellyfish => fishjam}/component/hls/request_handler.ex (100%) rename lib/{jellyfish => fishjam}/component/hls/storage.ex (100%) rename lib/{jellyfish => fishjam}/component/recording.ex (100%) rename lib/{jellyfish => fishjam}/component/rtsp.ex (100%) rename lib/{jellyfish => fishjam}/component/sip.ex (100%) rename lib/{jellyfish => fishjam}/config_reader.ex (100%) rename lib/{jellyfish => fishjam}/endpoint/config.ex (100%) rename lib/{jellyfish => fishjam}/event.ex (100%) rename lib/{jellyfish => fishjam}/metrics_scraper.ex (100%) rename lib/{jellyfish => fishjam}/peer.ex (100%) rename lib/{jellyfish => fishjam}/peer/webrtc.ex (100%) rename lib/{jellyfish => fishjam}/resource_manager.ex (100%) rename lib/{jellyfish => fishjam}/room.ex (100%) rename lib/{jellyfish => fishjam}/room/config.ex (98%) rename lib/{jellyfish => fishjam}/room/state.ex (100%) rename lib/{jellyfish => fishjam}/room_service.ex (98%) rename lib/{jellyfish => fishjam}/track.ex (100%) rename lib/{jellyfish => fishjam}/utils/parser_json.ex (100%) rename lib/{jellyfish => fishjam}/utils/path_validation.ex (100%) rename lib/{jellyfish => fishjam}/webhook_notifier.ex (100%) rename lib/{jellyfish_web.ex => fishjam_web.ex} (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/component.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/component/file.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/component/hls.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/component/recording.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/component/rtsp.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/component/sip.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/dial.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/error.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/health_report.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/hls.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/peer.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/peer/webrtc.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/responses.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/room.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/subscription.ex (100%) rename lib/{jellyfish_web => fishjam_web}/api_spec/track.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/component_controller.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/component_json.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/error_json.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/fallback_controller.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/healthcheck_controller.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/healthcheck_json.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/hls_content_controller.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/peer_controller.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/peer_json.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/recording_content_controller.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/recording_controller.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/recording_json.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/room_controller.ex (98%) rename lib/{jellyfish_web => fishjam_web}/controllers/room_json.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/sip_call_controller.ex (100%) rename lib/{jellyfish_web => fishjam_web}/controllers/subscription_controller.ex (100%) rename lib/{jellyfish_web => fishjam_web}/endpoint.ex (100%) rename lib/{jellyfish_web => fishjam_web}/peer_socket.ex (100%) rename lib/{jellyfish_web => fishjam_web}/peer_token.ex (100%) rename lib/{jellyfish_web => fishjam_web}/router.ex (100%) rename lib/{jellyfish_web => fishjam_web}/server_socket.ex (100%) rename lib/{jellyfish_web => fishjam_web}/telemetry.ex (100%) rename lib/{jellyfish_web => fishjam_web}/traffic_metrics_plug.ex (100%) rename test/{jellyfish => fishjam}/cluster/load_balancing_test.exs (100%) rename test/{jellyfish => fishjam}/component/hls/ets_helper_test.exs (100%) rename test/{jellyfish => fishjam}/component/hls/ll_storage_test.exs (100%) rename test/{jellyfish => fishjam}/component/hls/manager_test.exs (100%) rename test/{jellyfish => fishjam}/component/hls/request_handler_test.exs (100%) rename test/{jellyfish => fishjam}/config_reader_test.exs (100%) rename test/{jellyfish => fishjam}/resource_manager_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/component/file_component_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/component/hls_component_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/component/recording_component_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/component/rtsp_component_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/component/sip_component_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/component_controller_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/dial_controller_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/error_json_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/healthcheck_controller_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/hls_controller_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/peer_controller_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/recording_controller_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/controllers/room_controller_test.exs (98%) rename test/{jellyfish_web => fishjam_web}/controllers/subscription_controller_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/integration/peer_socket_test.exs (100%) rename test/{jellyfish_web => fishjam_web}/integration/server_notification_test.exs (100%) diff --git a/grafana/jellyfish-server-overiew.json b/grafana/fishjam-server-overview.json similarity index 100% rename from grafana/jellyfish-server-overiew.json rename to grafana/fishjam-server-overview.json diff --git a/lib/jellyfish.ex b/lib/fishjam.ex similarity index 100% rename from lib/jellyfish.ex rename to lib/fishjam.ex diff --git a/lib/jellyfish/application.ex b/lib/fishjam/application.ex similarity index 100% rename from lib/jellyfish/application.ex rename to lib/fishjam/application.ex diff --git a/lib/jellyfish/component.ex b/lib/fishjam/component.ex similarity index 100% rename from lib/jellyfish/component.ex rename to lib/fishjam/component.ex diff --git a/lib/jellyfish/component/file.ex b/lib/fishjam/component/file.ex similarity index 100% rename from lib/jellyfish/component/file.ex rename to lib/fishjam/component/file.ex diff --git a/lib/jellyfish/component/hls.ex b/lib/fishjam/component/hls.ex similarity index 100% rename from lib/jellyfish/component/hls.ex rename to lib/fishjam/component/hls.ex diff --git a/lib/jellyfish/component/hls/ets_helper.ex b/lib/fishjam/component/hls/ets_helper.ex similarity index 100% rename from lib/jellyfish/component/hls/ets_helper.ex rename to lib/fishjam/component/hls/ets_helper.ex diff --git a/lib/jellyfish/component/hls/httpoison.ex b/lib/fishjam/component/hls/httpoison.ex similarity index 100% rename from lib/jellyfish/component/hls/httpoison.ex rename to lib/fishjam/component/hls/httpoison.ex diff --git a/lib/jellyfish/component/hls/ll_storage.ex b/lib/fishjam/component/hls/ll_storage.ex similarity index 100% rename from lib/jellyfish/component/hls/ll_storage.ex rename to lib/fishjam/component/hls/ll_storage.ex diff --git a/lib/jellyfish/component/hls/manager.ex b/lib/fishjam/component/hls/manager.ex similarity index 100% rename from lib/jellyfish/component/hls/manager.ex rename to lib/fishjam/component/hls/manager.ex diff --git a/lib/jellyfish/component/hls/recording.ex b/lib/fishjam/component/hls/recording.ex similarity index 100% rename from lib/jellyfish/component/hls/recording.ex rename to lib/fishjam/component/hls/recording.ex diff --git a/lib/jellyfish/component/hls/request_handler.ex b/lib/fishjam/component/hls/request_handler.ex similarity index 100% rename from lib/jellyfish/component/hls/request_handler.ex rename to lib/fishjam/component/hls/request_handler.ex diff --git a/lib/jellyfish/component/hls/storage.ex b/lib/fishjam/component/hls/storage.ex similarity index 100% rename from lib/jellyfish/component/hls/storage.ex rename to lib/fishjam/component/hls/storage.ex diff --git a/lib/jellyfish/component/recording.ex b/lib/fishjam/component/recording.ex similarity index 100% rename from lib/jellyfish/component/recording.ex rename to lib/fishjam/component/recording.ex diff --git a/lib/jellyfish/component/rtsp.ex b/lib/fishjam/component/rtsp.ex similarity index 100% rename from lib/jellyfish/component/rtsp.ex rename to lib/fishjam/component/rtsp.ex diff --git a/lib/jellyfish/component/sip.ex b/lib/fishjam/component/sip.ex similarity index 100% rename from lib/jellyfish/component/sip.ex rename to lib/fishjam/component/sip.ex diff --git a/lib/jellyfish/config_reader.ex b/lib/fishjam/config_reader.ex similarity index 100% rename from lib/jellyfish/config_reader.ex rename to lib/fishjam/config_reader.ex diff --git a/lib/jellyfish/endpoint/config.ex b/lib/fishjam/endpoint/config.ex similarity index 100% rename from lib/jellyfish/endpoint/config.ex rename to lib/fishjam/endpoint/config.ex diff --git a/lib/jellyfish/event.ex b/lib/fishjam/event.ex similarity index 100% rename from lib/jellyfish/event.ex rename to lib/fishjam/event.ex diff --git a/lib/jellyfish/metrics_scraper.ex b/lib/fishjam/metrics_scraper.ex similarity index 100% rename from lib/jellyfish/metrics_scraper.ex rename to lib/fishjam/metrics_scraper.ex diff --git a/lib/jellyfish/peer.ex b/lib/fishjam/peer.ex similarity index 100% rename from lib/jellyfish/peer.ex rename to lib/fishjam/peer.ex diff --git a/lib/jellyfish/peer/webrtc.ex b/lib/fishjam/peer/webrtc.ex similarity index 100% rename from lib/jellyfish/peer/webrtc.ex rename to lib/fishjam/peer/webrtc.ex diff --git a/lib/jellyfish/resource_manager.ex b/lib/fishjam/resource_manager.ex similarity index 100% rename from lib/jellyfish/resource_manager.ex rename to lib/fishjam/resource_manager.ex diff --git a/lib/jellyfish/room.ex b/lib/fishjam/room.ex similarity index 100% rename from lib/jellyfish/room.ex rename to lib/fishjam/room.ex diff --git a/lib/jellyfish/room/config.ex b/lib/fishjam/room/config.ex similarity index 98% rename from lib/jellyfish/room/config.ex rename to lib/fishjam/room/config.ex index 92f9703e..1f2cf028 100644 --- a/lib/jellyfish/room/config.ex +++ b/lib/fishjam/room/config.ex @@ -58,7 +58,7 @@ defmodule Fishjam.Room.Config do defp parse_room_id(nil), do: {:ok, UUID.uuid4()} defp parse_room_id(room_id) when is_binary(room_id) do - if Regex.match?(~r/^[a-zA-Z0-9-]+$/, room_id) do + if Regex.match?(~r/^[a-zA-Z0-9-_]+$/, room_id) do {:ok, room_id} else {:error, :invalid_room_id} diff --git a/lib/jellyfish/room/state.ex b/lib/fishjam/room/state.ex similarity index 100% rename from lib/jellyfish/room/state.ex rename to lib/fishjam/room/state.ex diff --git a/lib/jellyfish/room_service.ex b/lib/fishjam/room_service.ex similarity index 98% rename from lib/jellyfish/room_service.ex rename to lib/fishjam/room_service.ex index 1b6614b2..81f117de 100644 --- a/lib/jellyfish/room_service.ex +++ b/lib/fishjam/room_service.ex @@ -58,8 +58,7 @@ defmodule Fishjam.RoomService do @spec create_room(Room.Config.t()) :: {:ok, Room.t(), String.t()} | {:error, atom()} def create_room(config) do - {node_resources, failed_nodes} = - :rpc.multicall(Fishjam.RoomService, :get_resource_usage, []) + {node_resources, failed_nodes} = :rpc.multicall(Fishjam.RoomService, :get_resource_usage, []) if Enum.count(failed_nodes) > 0 do Logger.warning( diff --git a/lib/jellyfish/track.ex b/lib/fishjam/track.ex similarity index 100% rename from lib/jellyfish/track.ex rename to lib/fishjam/track.ex diff --git a/lib/jellyfish/utils/parser_json.ex b/lib/fishjam/utils/parser_json.ex similarity index 100% rename from lib/jellyfish/utils/parser_json.ex rename to lib/fishjam/utils/parser_json.ex diff --git a/lib/jellyfish/utils/path_validation.ex b/lib/fishjam/utils/path_validation.ex similarity index 100% rename from lib/jellyfish/utils/path_validation.ex rename to lib/fishjam/utils/path_validation.ex diff --git a/lib/jellyfish/webhook_notifier.ex b/lib/fishjam/webhook_notifier.ex similarity index 100% rename from lib/jellyfish/webhook_notifier.ex rename to lib/fishjam/webhook_notifier.ex diff --git a/lib/jellyfish_web.ex b/lib/fishjam_web.ex similarity index 100% rename from lib/jellyfish_web.ex rename to lib/fishjam_web.ex diff --git a/lib/jellyfish_web/api_spec.ex b/lib/fishjam_web/api_spec.ex similarity index 100% rename from lib/jellyfish_web/api_spec.ex rename to lib/fishjam_web/api_spec.ex diff --git a/lib/jellyfish_web/api_spec/component.ex b/lib/fishjam_web/api_spec/component.ex similarity index 100% rename from lib/jellyfish_web/api_spec/component.ex rename to lib/fishjam_web/api_spec/component.ex diff --git a/lib/jellyfish_web/api_spec/component/file.ex b/lib/fishjam_web/api_spec/component/file.ex similarity index 100% rename from lib/jellyfish_web/api_spec/component/file.ex rename to lib/fishjam_web/api_spec/component/file.ex diff --git a/lib/jellyfish_web/api_spec/component/hls.ex b/lib/fishjam_web/api_spec/component/hls.ex similarity index 100% rename from lib/jellyfish_web/api_spec/component/hls.ex rename to lib/fishjam_web/api_spec/component/hls.ex diff --git a/lib/jellyfish_web/api_spec/component/recording.ex b/lib/fishjam_web/api_spec/component/recording.ex similarity index 100% rename from lib/jellyfish_web/api_spec/component/recording.ex rename to lib/fishjam_web/api_spec/component/recording.ex diff --git a/lib/jellyfish_web/api_spec/component/rtsp.ex b/lib/fishjam_web/api_spec/component/rtsp.ex similarity index 100% rename from lib/jellyfish_web/api_spec/component/rtsp.ex rename to lib/fishjam_web/api_spec/component/rtsp.ex diff --git a/lib/jellyfish_web/api_spec/component/sip.ex b/lib/fishjam_web/api_spec/component/sip.ex similarity index 100% rename from lib/jellyfish_web/api_spec/component/sip.ex rename to lib/fishjam_web/api_spec/component/sip.ex diff --git a/lib/jellyfish_web/api_spec/dial.ex b/lib/fishjam_web/api_spec/dial.ex similarity index 100% rename from lib/jellyfish_web/api_spec/dial.ex rename to lib/fishjam_web/api_spec/dial.ex diff --git a/lib/jellyfish_web/api_spec/error.ex b/lib/fishjam_web/api_spec/error.ex similarity index 100% rename from lib/jellyfish_web/api_spec/error.ex rename to lib/fishjam_web/api_spec/error.ex diff --git a/lib/jellyfish_web/api_spec/health_report.ex b/lib/fishjam_web/api_spec/health_report.ex similarity index 100% rename from lib/jellyfish_web/api_spec/health_report.ex rename to lib/fishjam_web/api_spec/health_report.ex diff --git a/lib/jellyfish_web/api_spec/hls.ex b/lib/fishjam_web/api_spec/hls.ex similarity index 100% rename from lib/jellyfish_web/api_spec/hls.ex rename to lib/fishjam_web/api_spec/hls.ex diff --git a/lib/jellyfish_web/api_spec/peer.ex b/lib/fishjam_web/api_spec/peer.ex similarity index 100% rename from lib/jellyfish_web/api_spec/peer.ex rename to lib/fishjam_web/api_spec/peer.ex diff --git a/lib/jellyfish_web/api_spec/peer/webrtc.ex b/lib/fishjam_web/api_spec/peer/webrtc.ex similarity index 100% rename from lib/jellyfish_web/api_spec/peer/webrtc.ex rename to lib/fishjam_web/api_spec/peer/webrtc.ex diff --git a/lib/jellyfish_web/api_spec/responses.ex b/lib/fishjam_web/api_spec/responses.ex similarity index 100% rename from lib/jellyfish_web/api_spec/responses.ex rename to lib/fishjam_web/api_spec/responses.ex diff --git a/lib/jellyfish_web/api_spec/room.ex b/lib/fishjam_web/api_spec/room.ex similarity index 100% rename from lib/jellyfish_web/api_spec/room.ex rename to lib/fishjam_web/api_spec/room.ex diff --git a/lib/jellyfish_web/api_spec/subscription.ex b/lib/fishjam_web/api_spec/subscription.ex similarity index 100% rename from lib/jellyfish_web/api_spec/subscription.ex rename to lib/fishjam_web/api_spec/subscription.ex diff --git a/lib/jellyfish_web/api_spec/track.ex b/lib/fishjam_web/api_spec/track.ex similarity index 100% rename from lib/jellyfish_web/api_spec/track.ex rename to lib/fishjam_web/api_spec/track.ex diff --git a/lib/jellyfish_web/controllers/component_controller.ex b/lib/fishjam_web/controllers/component_controller.ex similarity index 100% rename from lib/jellyfish_web/controllers/component_controller.ex rename to lib/fishjam_web/controllers/component_controller.ex diff --git a/lib/jellyfish_web/controllers/component_json.ex b/lib/fishjam_web/controllers/component_json.ex similarity index 100% rename from lib/jellyfish_web/controllers/component_json.ex rename to lib/fishjam_web/controllers/component_json.ex diff --git a/lib/jellyfish_web/controllers/error_json.ex b/lib/fishjam_web/controllers/error_json.ex similarity index 100% rename from lib/jellyfish_web/controllers/error_json.ex rename to lib/fishjam_web/controllers/error_json.ex diff --git a/lib/jellyfish_web/controllers/fallback_controller.ex b/lib/fishjam_web/controllers/fallback_controller.ex similarity index 100% rename from lib/jellyfish_web/controllers/fallback_controller.ex rename to lib/fishjam_web/controllers/fallback_controller.ex diff --git a/lib/jellyfish_web/controllers/healthcheck_controller.ex b/lib/fishjam_web/controllers/healthcheck_controller.ex similarity index 100% rename from lib/jellyfish_web/controllers/healthcheck_controller.ex rename to lib/fishjam_web/controllers/healthcheck_controller.ex diff --git a/lib/jellyfish_web/controllers/healthcheck_json.ex b/lib/fishjam_web/controllers/healthcheck_json.ex similarity index 100% rename from lib/jellyfish_web/controllers/healthcheck_json.ex rename to lib/fishjam_web/controllers/healthcheck_json.ex diff --git a/lib/jellyfish_web/controllers/hls_content_controller.ex b/lib/fishjam_web/controllers/hls_content_controller.ex similarity index 100% rename from lib/jellyfish_web/controllers/hls_content_controller.ex rename to lib/fishjam_web/controllers/hls_content_controller.ex diff --git a/lib/jellyfish_web/controllers/peer_controller.ex b/lib/fishjam_web/controllers/peer_controller.ex similarity index 100% rename from lib/jellyfish_web/controllers/peer_controller.ex rename to lib/fishjam_web/controllers/peer_controller.ex diff --git a/lib/jellyfish_web/controllers/peer_json.ex b/lib/fishjam_web/controllers/peer_json.ex similarity index 100% rename from lib/jellyfish_web/controllers/peer_json.ex rename to lib/fishjam_web/controllers/peer_json.ex diff --git a/lib/jellyfish_web/controllers/recording_content_controller.ex b/lib/fishjam_web/controllers/recording_content_controller.ex similarity index 100% rename from lib/jellyfish_web/controllers/recording_content_controller.ex rename to lib/fishjam_web/controllers/recording_content_controller.ex diff --git a/lib/jellyfish_web/controllers/recording_controller.ex b/lib/fishjam_web/controllers/recording_controller.ex similarity index 100% rename from lib/jellyfish_web/controllers/recording_controller.ex rename to lib/fishjam_web/controllers/recording_controller.ex diff --git a/lib/jellyfish_web/controllers/recording_json.ex b/lib/fishjam_web/controllers/recording_json.ex similarity index 100% rename from lib/jellyfish_web/controllers/recording_json.ex rename to lib/fishjam_web/controllers/recording_json.ex diff --git a/lib/jellyfish_web/controllers/room_controller.ex b/lib/fishjam_web/controllers/room_controller.ex similarity index 98% rename from lib/jellyfish_web/controllers/room_controller.ex rename to lib/fishjam_web/controllers/room_controller.ex index 76b236a9..a28130f6 100644 --- a/lib/jellyfish_web/controllers/room_controller.ex +++ b/lib/fishjam_web/controllers/room_controller.ex @@ -108,7 +108,7 @@ defmodule FishjamWeb.RoomController do room_id = Map.get(params, "roomId") {:error, :bad_request, - "Cannot add room with id \"#{room_id}\" - roomId may contain only alphanumeric characters and hyphens"} + "Cannot add room with id \"#{room_id}\" - roomId may contain only alphanumeric characters, hyphens and underscores"} end end diff --git a/lib/jellyfish_web/controllers/room_json.ex b/lib/fishjam_web/controllers/room_json.ex similarity index 100% rename from lib/jellyfish_web/controllers/room_json.ex rename to lib/fishjam_web/controllers/room_json.ex diff --git a/lib/jellyfish_web/controllers/sip_call_controller.ex b/lib/fishjam_web/controllers/sip_call_controller.ex similarity index 100% rename from lib/jellyfish_web/controllers/sip_call_controller.ex rename to lib/fishjam_web/controllers/sip_call_controller.ex diff --git a/lib/jellyfish_web/controllers/subscription_controller.ex b/lib/fishjam_web/controllers/subscription_controller.ex similarity index 100% rename from lib/jellyfish_web/controllers/subscription_controller.ex rename to lib/fishjam_web/controllers/subscription_controller.ex diff --git a/lib/jellyfish_web/endpoint.ex b/lib/fishjam_web/endpoint.ex similarity index 100% rename from lib/jellyfish_web/endpoint.ex rename to lib/fishjam_web/endpoint.ex diff --git a/lib/jellyfish_web/peer_socket.ex b/lib/fishjam_web/peer_socket.ex similarity index 100% rename from lib/jellyfish_web/peer_socket.ex rename to lib/fishjam_web/peer_socket.ex diff --git a/lib/jellyfish_web/peer_token.ex b/lib/fishjam_web/peer_token.ex similarity index 100% rename from lib/jellyfish_web/peer_token.ex rename to lib/fishjam_web/peer_token.ex diff --git a/lib/jellyfish_web/router.ex b/lib/fishjam_web/router.ex similarity index 100% rename from lib/jellyfish_web/router.ex rename to lib/fishjam_web/router.ex diff --git a/lib/jellyfish_web/server_socket.ex b/lib/fishjam_web/server_socket.ex similarity index 100% rename from lib/jellyfish_web/server_socket.ex rename to lib/fishjam_web/server_socket.ex diff --git a/lib/jellyfish_web/telemetry.ex b/lib/fishjam_web/telemetry.ex similarity index 100% rename from lib/jellyfish_web/telemetry.ex rename to lib/fishjam_web/telemetry.ex diff --git a/lib/jellyfish_web/traffic_metrics_plug.ex b/lib/fishjam_web/traffic_metrics_plug.ex similarity index 100% rename from lib/jellyfish_web/traffic_metrics_plug.ex rename to lib/fishjam_web/traffic_metrics_plug.ex diff --git a/test/jellyfish/cluster/load_balancing_test.exs b/test/fishjam/cluster/load_balancing_test.exs similarity index 100% rename from test/jellyfish/cluster/load_balancing_test.exs rename to test/fishjam/cluster/load_balancing_test.exs diff --git a/test/jellyfish/component/hls/ets_helper_test.exs b/test/fishjam/component/hls/ets_helper_test.exs similarity index 100% rename from test/jellyfish/component/hls/ets_helper_test.exs rename to test/fishjam/component/hls/ets_helper_test.exs diff --git a/test/jellyfish/component/hls/ll_storage_test.exs b/test/fishjam/component/hls/ll_storage_test.exs similarity index 100% rename from test/jellyfish/component/hls/ll_storage_test.exs rename to test/fishjam/component/hls/ll_storage_test.exs diff --git a/test/jellyfish/component/hls/manager_test.exs b/test/fishjam/component/hls/manager_test.exs similarity index 100% rename from test/jellyfish/component/hls/manager_test.exs rename to test/fishjam/component/hls/manager_test.exs diff --git a/test/jellyfish/component/hls/request_handler_test.exs b/test/fishjam/component/hls/request_handler_test.exs similarity index 100% rename from test/jellyfish/component/hls/request_handler_test.exs rename to test/fishjam/component/hls/request_handler_test.exs diff --git a/test/jellyfish/config_reader_test.exs b/test/fishjam/config_reader_test.exs similarity index 100% rename from test/jellyfish/config_reader_test.exs rename to test/fishjam/config_reader_test.exs diff --git a/test/jellyfish/resource_manager_test.exs b/test/fishjam/resource_manager_test.exs similarity index 100% rename from test/jellyfish/resource_manager_test.exs rename to test/fishjam/resource_manager_test.exs diff --git a/test/jellyfish_web/controllers/component/file_component_test.exs b/test/fishjam_web/controllers/component/file_component_test.exs similarity index 100% rename from test/jellyfish_web/controllers/component/file_component_test.exs rename to test/fishjam_web/controllers/component/file_component_test.exs diff --git a/test/jellyfish_web/controllers/component/hls_component_test.exs b/test/fishjam_web/controllers/component/hls_component_test.exs similarity index 100% rename from test/jellyfish_web/controllers/component/hls_component_test.exs rename to test/fishjam_web/controllers/component/hls_component_test.exs diff --git a/test/jellyfish_web/controllers/component/recording_component_test.exs b/test/fishjam_web/controllers/component/recording_component_test.exs similarity index 100% rename from test/jellyfish_web/controllers/component/recording_component_test.exs rename to test/fishjam_web/controllers/component/recording_component_test.exs diff --git a/test/jellyfish_web/controllers/component/rtsp_component_test.exs b/test/fishjam_web/controllers/component/rtsp_component_test.exs similarity index 100% rename from test/jellyfish_web/controllers/component/rtsp_component_test.exs rename to test/fishjam_web/controllers/component/rtsp_component_test.exs diff --git a/test/jellyfish_web/controllers/component/sip_component_test.exs b/test/fishjam_web/controllers/component/sip_component_test.exs similarity index 100% rename from test/jellyfish_web/controllers/component/sip_component_test.exs rename to test/fishjam_web/controllers/component/sip_component_test.exs diff --git a/test/jellyfish_web/controllers/component_controller_test.exs b/test/fishjam_web/controllers/component_controller_test.exs similarity index 100% rename from test/jellyfish_web/controllers/component_controller_test.exs rename to test/fishjam_web/controllers/component_controller_test.exs diff --git a/test/jellyfish_web/controllers/dial_controller_test.exs b/test/fishjam_web/controllers/dial_controller_test.exs similarity index 100% rename from test/jellyfish_web/controllers/dial_controller_test.exs rename to test/fishjam_web/controllers/dial_controller_test.exs diff --git a/test/jellyfish_web/controllers/error_json_test.exs b/test/fishjam_web/controllers/error_json_test.exs similarity index 100% rename from test/jellyfish_web/controllers/error_json_test.exs rename to test/fishjam_web/controllers/error_json_test.exs diff --git a/test/jellyfish_web/controllers/healthcheck_controller_test.exs b/test/fishjam_web/controllers/healthcheck_controller_test.exs similarity index 100% rename from test/jellyfish_web/controllers/healthcheck_controller_test.exs rename to test/fishjam_web/controllers/healthcheck_controller_test.exs diff --git a/test/jellyfish_web/controllers/hls_controller_test.exs b/test/fishjam_web/controllers/hls_controller_test.exs similarity index 100% rename from test/jellyfish_web/controllers/hls_controller_test.exs rename to test/fishjam_web/controllers/hls_controller_test.exs diff --git a/test/jellyfish_web/controllers/peer_controller_test.exs b/test/fishjam_web/controllers/peer_controller_test.exs similarity index 100% rename from test/jellyfish_web/controllers/peer_controller_test.exs rename to test/fishjam_web/controllers/peer_controller_test.exs diff --git a/test/jellyfish_web/controllers/recording_controller_test.exs b/test/fishjam_web/controllers/recording_controller_test.exs similarity index 100% rename from test/jellyfish_web/controllers/recording_controller_test.exs rename to test/fishjam_web/controllers/recording_controller_test.exs diff --git a/test/jellyfish_web/controllers/room_controller_test.exs b/test/fishjam_web/controllers/room_controller_test.exs similarity index 98% rename from test/jellyfish_web/controllers/room_controller_test.exs rename to test/fishjam_web/controllers/room_controller_test.exs index 99132e3f..35fda7eb 100644 --- a/test/jellyfish_web/controllers/room_controller_test.exs +++ b/test/fishjam_web/controllers/room_controller_test.exs @@ -61,8 +61,7 @@ defmodule FishjamWeb.RoomControllerTest do end test "invalid token", %{conn: conn} do - invalid_server_api_token = - "invalid" <> Application.fetch_env!(:fishjam, :server_api_token) + invalid_server_api_token = "invalid" <> Application.fetch_env!(:fishjam, :server_api_token) conn = put_req_header(conn, "authorization", "Bearer " <> invalid_server_api_token) @@ -125,7 +124,7 @@ defmodule FishjamWeb.RoomControllerTest do test "renders room when data is valid, custom room_id + max_peers and peerless_purge_timeout not present", %{conn: conn} do - room_id = UUID.uuid4() + room_id = UUID.uuid4() <> "_ABCD-123_xyz" conn = post(conn, ~p"/room", roomId: room_id) json_response(conn, :created) @@ -183,12 +182,12 @@ defmodule FishjamWeb.RoomControllerTest do conn = post(conn, ~p"/room", roomId: "test/path") assert json_response(conn, :bad_request)["errors"] == - "Cannot add room with id \"test/path\" - roomId may contain only alphanumeric characters and hyphens" + "Cannot add room with id \"test/path\" - roomId may contain only alphanumeric characters, hyphens and underscores" conn = post(conn, ~p"/room", roomId: "") assert json_response(conn, :bad_request)["errors"] == - "Cannot add room with id \"\" - roomId may contain only alphanumeric characters and hyphens" + "Cannot add room with id \"\" - roomId may contain only alphanumeric characters, hyphens and underscores" end end diff --git a/test/jellyfish_web/controllers/subscription_controller_test.exs b/test/fishjam_web/controllers/subscription_controller_test.exs similarity index 100% rename from test/jellyfish_web/controllers/subscription_controller_test.exs rename to test/fishjam_web/controllers/subscription_controller_test.exs diff --git a/test/jellyfish_web/integration/peer_socket_test.exs b/test/fishjam_web/integration/peer_socket_test.exs similarity index 100% rename from test/jellyfish_web/integration/peer_socket_test.exs rename to test/fishjam_web/integration/peer_socket_test.exs diff --git a/test/jellyfish_web/integration/server_notification_test.exs b/test/fishjam_web/integration/server_notification_test.exs similarity index 100% rename from test/jellyfish_web/integration/server_notification_test.exs rename to test/fishjam_web/integration/server_notification_test.exs From fadf344ccae659756ef6c87757e8b2cf4c57a974 Mon Sep 17 00:00:00 2001 From: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> Date: Wed, 22 May 2024 14:01:50 +0200 Subject: [PATCH 38/51] Release v0.6.2 (#204) --- mix.exs | 2 +- openapi.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index d9a3372c..1f4d44b8 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Fishjam.MixProject do def project do [ app: :fishjam, - version: "0.6.1", + version: "0.6.2", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, diff --git a/openapi.yaml b/openapi.yaml index c67ac562..dacfe1f2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -835,7 +835,7 @@ info: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 title: Fishjam Media Server - version: 0.6.1 + version: 0.6.2 openapi: 3.0.0 paths: /health: From 6e669902556843d774ef2a51b041771fdf40cfea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Thu, 23 May 2024 14:58:19 +0200 Subject: [PATCH 39/51] Read JF_DIST_* env vars. Remove divo. (#205) --- config/config.exs | 4 --- config/{ci.exs => test_cluster.exs} | 0 docker-compose-dns.yaml | 2 +- docker-compose-epmd.yaml | 2 +- lib/fishjam/application.ex | 7 ++++- mix.exs | 20 +++++------- mix.lock | 2 -- openapi.yaml | 2 +- rel/env.sh.eex | 22 ++++++++++--- test/fishjam/cluster/load_balancing_test.exs | 33 ++++++++------------ 10 files changed, 46 insertions(+), 48 deletions(-) rename config/{ci.exs => test_cluster.exs} (100%) diff --git a/config/config.exs b/config/config.exs index 786c8080..1db072c3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -37,10 +37,6 @@ config :logger, [application: :membrane_rtc_engine_sip, level_lower_than: :warning] ] -config :fishjam, - divo: "docker-compose.yaml", - divo_wait: [dwell: 1_500, max_tries: 50] - config :ex_aws, http_client: Fishjam.Component.HLS.HTTPoison, normalize_path: false diff --git a/config/ci.exs b/config/test_cluster.exs similarity index 100% rename from config/ci.exs rename to config/test_cluster.exs diff --git a/docker-compose-dns.yaml b/docker-compose-dns.yaml index 1b7af457..97ed1f14 100644 --- a/docker-compose-dns.yaml +++ b/docker-compose-dns.yaml @@ -18,7 +18,7 @@ services: - | cd app/ mix deps.get - MIX_ENV=ci mix test --only cluster + MIX_ENV=test_cluster mix test --only cluster volumes: - .:/app - /app/_build diff --git a/docker-compose-epmd.yaml b/docker-compose-epmd.yaml index e60c521f..12f8e028 100644 --- a/docker-compose-epmd.yaml +++ b/docker-compose-epmd.yaml @@ -17,7 +17,7 @@ services: - | cd app/ mix deps.get - MIX_ENV=ci mix test --only cluster + MIX_ENV=test_cluster mix test --only cluster volumes: - .:/app - /app/_build diff --git a/lib/fishjam/application.ex b/lib/fishjam/application.ex index 0092e9bd..4560ac4c 100644 --- a/lib/fishjam/application.ex +++ b/lib/fishjam/application.ex @@ -87,7 +87,12 @@ defmodule Fishjam.Application do # When running FJ not in a cluster and using # mix release, it starts in the distributed mode # automatically - unless Node.alive?() do + if Node.alive?() do + Logger.info(""" + Not starting Fishjam node as it is already alive. \ + Node name: #{Node.self()}.\ + """) + else case Node.start(dist_config[:node_name], dist_config[:mode]) do {:ok, _} -> :ok diff --git a/mix.exs b/mix.exs index 1f4d44b8..ba15795a 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Fishjam.MixProject do def project do [ app: :fishjam, - version: "0.6.2", + version: "0.6.3", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, @@ -22,9 +22,7 @@ defmodule Fishjam.MixProject do "coveralls.detail": :test, "coveralls.post": :test, "coveralls.html": :test, - "coveralls.json": :test, - "test.cluster": :test, - "test.cluster.ci": :test + "coveralls.json": :test ] ] end @@ -40,7 +38,7 @@ defmodule Fishjam.MixProject do end # Specifies which paths to compile per environment. - defp elixirc_paths(env) when env in [:test, :ci], do: ["lib", "test/support"] + defp elixirc_paths(env) when env in [:test, :test_cluster], do: ["lib", "test/support"] defp elixirc_paths(_env), do: ["lib"] defp deps do @@ -94,12 +92,9 @@ defmodule Fishjam.MixProject do {:klotho, "~> 0.1.0"}, # Test deps - {:websockex, "~> 0.4.3", only: [:test, :ci], runtime: false}, + {:websockex, "~> 0.4.3", only: [:test, :test_cluster], runtime: false}, {:excoveralls, "~> 0.15.0", only: :test, runtime: false}, - {:mox, "~> 1.0", only: [:test, :ci]}, - - # Load balancing tests - {:divo, "~> 1.3.1", only: [:test, :ci]} + {:mox, "~> 1.0", only: [:test, :test_cluster]} ] end @@ -108,11 +103,10 @@ defmodule Fishjam.MixProject do setup: ["deps.get"], "api.spec": &generate_api_spec/1, test: ["test --exclude cluster"], - "test.cluster": ["test --only cluster"], - "test.cluster.ci": [ + "test.cluster.epmd": [ "cmd docker compose -f docker-compose-epmd.yaml up test; docker compose -f docker-compose-epmd.yaml down" ], - "test.cluster.dns.ci": [ + "test.cluster.dns": [ "cmd docker compose -f docker-compose-dns.yaml up test; docker compose -f docker-compose-dns.yaml down" ] ] diff --git a/mix.lock b/mix.lock index e8ef3e49..0856c6d3 100644 --- a/mix.lock +++ b/mix.lock @@ -15,7 +15,6 @@ "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.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [: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", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "divo": {:hex, :divo, "1.3.2", "3a5ce880a1fe930ea804361d1b57b5144129e79e1c856623d923a6fab6d539a1", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:patiently, "~> 0.2", [hex: :patiently, repo: "hexpm", optional: false]}], "hexpm", "4bd035510838959709db2cacd28edd2eda7948d0e7f1b0dfa810a134c913a88a"}, "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, @@ -101,7 +100,6 @@ "open_api_spex": {:hex, :open_api_spex, "3.18.3", "fefb84fe323cacfc92afdd0ecb9e89bc0261ae00b7e3167ffc2028ce3944de42", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "c0cfc31570199ce7e7520b494a591027da609af45f6bf9adce51e2469b1609fb"}, "p1_utils": {:hex, :p1_utils, "1.0.23", "7f94466ada69bd982ea7bb80fbca18e7053e7d0b82c9d9e37621fa508587069b", [:rebar3], [], "hexpm", "47f21618694eeee5006af1c88731ad86b757161e7823c29b6f73921b571c8502"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "patiently": {:hex, :patiently, "0.2.0", "67eb139591e10c4b363ae0198e832552f191c58894731efd3bf124ec4722267a", [:mix], [], "hexpm", "c08cc5edc27def565647a9b55a0bea8025a5f81a4472e57692f28f2292c44c94"}, "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [: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", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"}, "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [: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]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"}, diff --git a/openapi.yaml b/openapi.yaml index dacfe1f2..5d0a784f 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -835,7 +835,7 @@ info: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0 title: Fishjam Media Server - version: 0.6.2 + version: 0.6.3 openapi: 3.0.0 paths: /health: diff --git a/rel/env.sh.eex b/rel/env.sh.eex index 936e04cf..73a7c4b2 100644 --- a/rel/env.sh.eex +++ b/rel/env.sh.eex @@ -2,7 +2,7 @@ # those provided by mix release. # This is to have a unified way of configuring Fishjam distribution # in both development and production environments -if [ "$FJ_DIST_ENABLED" == "true" ]; then +if [[ "$FJ_DIST_ENABLED" == "true" || "$JF_DIST_ENABLED" == "true" ]]; then # If Fishjam is meant to be run in a cluster, # leave node setup to the Elixir code where # we do extra steps for DNS strategy to determine actual node name. @@ -18,20 +18,32 @@ if [ "$FJ_DIST_ENABLED" == "true" ]; then # In other case, when someone wants to expose only one port # (FJ_DIST_MIN_PORT==FJ_DIST_MAX_PORT), we won't be able to # connect to already running node with the `remote` command. - if [ "$FJ_DIST_MIN_PORT" != "" ]; then + if [[ "$FJ_DIST_MIN_PORT" != "" || "$JF_DIST_MIN_PORT" != "" ]]; then + if [ "$FJ_DIST_MIN_PORT" != "" ]; then + DIST_MIN_PORT="$FJ_DIST_MIN_PORT" + else + DIST_MIN_PORT="$JF_DIST_MIN_PORT" + fi + case $RELEASE_COMMAND in start* | daemon*) - ELIXIR_ERL_OPTIONS="$ELIXIR_ERL_OPTIONS -kernel inet_dist_listen_min $FJ_DIST_MIN_PORT" + ELIXIR_ERL_OPTIONS="$ELIXIR_ERL_OPTIONS -kernel inet_dist_listen_min $DIST_MIN_PORT" export ELIXIR_ERL_OPTIONS ;; *) ;; esac fi - if [ "$FJ_DIST_MAX_PORT" != "" ]; then + if [[ "$FJ_DIST_MAX_PORT" != "" || "$JF_DIST_MAX_PORT" != "" ]]; then + if [ "$FJ_DIST_MAX_PORT" != "" ]; then + DIST_MAX_PORT="$FJ_DIST_MAX_PORT" + else + DIST_MAX_PORT="$JF_DIST_MAX_PORT" + fi + case $RELEASE_COMMAND in start* | daemon*) - ELIXIR_ERL_OPTIONS="$ELIXIR_ERL_OPTIONS -kernel inet_dist_listen_max $FJ_DIST_MAX_PORT" + ELIXIR_ERL_OPTIONS="$ELIXIR_ERL_OPTIONS -kernel inet_dist_listen_max $DIST_MAX_PORT" export ELIXIR_ERL_OPTIONS ;; *) ;; diff --git a/test/fishjam/cluster/load_balancing_test.exs b/test/fishjam/cluster/load_balancing_test.exs index 8d5ed654..0f9bdcd4 100644 --- a/test/fishjam/cluster/load_balancing_test.exs +++ b/test/fishjam/cluster/load_balancing_test.exs @@ -1,10 +1,10 @@ defmodule Fishjam.Cluster.LoadBalancingTest do @moduledoc false + # These tests can only be run with `mix test.cluster.epmd` or `mix test.cluster.dns`. + use ExUnit.Case, async: false - @node1 "localhost:4001" - @node2 "localhost:4002" @token Application.compile_env(:fishjam, :server_api_token) @headers [Authorization: "Bearer #{@token}", Accept: "Application/json; Charset=utf-8"] @@ -17,14 +17,11 @@ defmodule Fishjam.Cluster.LoadBalancingTest do @tag timeout: @max_test_duration test "spawning tasks on a cluster" do - [node1, node2] = - if Mix.env() == :ci do - # On CI we don't use Divo, because we don't want to run Docker in Docker - ["app1:4001", "app2:4002"] - else - Divo.Suite.start(services: [:app1, :app2]) |> on_exit() - [@node1, @node2] - end + if Mix.env() != :test_cluster do + raise "Load balancing tests can only be run with MIX_ENV=test_cluster" + end + + [node1, node2] = ["app1:4001", "app2:4002"] response_body1 = add_room(node1) @@ -70,16 +67,12 @@ defmodule Fishjam.Cluster.LoadBalancingTest do body end - if Mix.env() == :test do - defp map_fishjam_address(fishjam), do: fishjam - else - defp map_fishjam_address(fishjam) do - %{ - @node1 => "app1:4001", - @node2 => "app2:4002" - } - |> Map.get(fishjam) - end + defp map_fishjam_address(fishjam) do + %{ + "localhost:4001" => "app1:4001", + "localhost:4002" => "app2:4002" + } + |> Map.get(fishjam) end defp get_fishjam_address(response_body) do From 0b3270f142673dabe01678d88d95c01c91616e7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Ko=C5=82odziej?= Date: Fri, 24 May 2024 13:16:18 +0200 Subject: [PATCH 40/51] Feat/introduce ADRs (#200) * feat: introduce ADRs to the codebase * docs: decision about release strategy --- docs/adrs/001-start-using-adrs.md | 43 ++++++++++ .../002-use-rolling-update-with-eviction.md | 82 +++++++++++++++++++ docs/adrs/template.md | 72 ++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 docs/adrs/001-start-using-adrs.md create mode 100644 docs/adrs/002-use-rolling-update-with-eviction.md create mode 100644 docs/adrs/template.md diff --git a/docs/adrs/001-start-using-adrs.md b/docs/adrs/001-start-using-adrs.md new file mode 100644 index 00000000..a41996aa --- /dev/null +++ b/docs/adrs/001-start-using-adrs.md @@ -0,0 +1,43 @@ +--- +status: accepted +date: 2024-05-17 +deciders: Fishjam team +--- +# Start using ADRs to document any major (non)architectural decisions for the project + +## Context and Problem Statement + +- Right now, we don't write down architecture decisions in any standardized format. +- We would like to keep a log of any major decisions made which affect the application. + +## Decision Drivers + +* One place to keep all the decisions made +* One format to follow +* Easy to read and write +* Close to the code + +## Considered Options + +* ADRs inside the repo +* Google Docs +* Confluence + +## Decision Outcome + +Chosen option: "ADRs inside the repo", because: + +- Documentation is near the code and is easily accessible for anyone (contributors) +- No need to lock on to other providers +- Support for Markdown + +### Consequences + +From now on, when a major decision is made, we are going to write an ADR for it. +Anything that changes the architecture, provider, configuration, etc. should have an ADR. For other cases, the contributor shall decide if one is necessary. +If a request for an ADR arises on PR, it's recommended to provide one with clear explanation for the decision. +We write this to have a record and also to be able to come back to it later on in the future. Because of that, we are going to keep the standard described in the template. + +## More Information + +We won't write ADRs for decisions that occurred in the past. This is a change that's going to have effect from now on. diff --git a/docs/adrs/002-use-rolling-update-with-eviction.md b/docs/adrs/002-use-rolling-update-with-eviction.md new file mode 100644 index 00000000..6d94b21c --- /dev/null +++ b/docs/adrs/002-use-rolling-update-with-eviction.md @@ -0,0 +1,82 @@ +--- +status: accepted +date: 2024-05-17 +deciders: Kamil Kołodziej, Radosław Szuma, Jakub Pisarek +informed: Fishjam Team, Cloud Team +--- +# Change deployment strategy to eliminate downtime + +## Context and Problem Statement + +In the current situation when we do a deployment we are shuting down the server and every room with that. +This is an awful user experience. Another issue is the fact that any ongoing recordings are lost. + +## Decision Drivers + +* No downtime while deployment happens +* 0 lost recordings during deployment + +## Considered Options + +* Rolling update with eviction +* Active room migration + +## Decision Outcome + +Chosen option: "Rolling update with eviction", because +while second option sounds great, we acknowledge that solution is not trivial and we want to fix downtimes ASAP. First solution is a good starting point to remove existing problems and allow us to move forward. + +### Consequences + +Every new deployment will need to trigger fishjam process which will handle the shutdown. +What that effectively means is that we are leaving the responsibility of triggering deployment to external orchestration tool (what is usually the case) but we handle the process inside the app. + +External process will trigger fishjam shutdown process by a SIGTERM. +This is gonna mark fishjam as one that no longer accepts new rooms. +Once every room is closed and all of the recordings are computed we are going to shutdown that instance of fishjam. + +This is applicable one by one for every instance in cluster, although it may take some time to deployment a new version and we may have 2 different versions on the cluster at the same time, we are accepting that tradeoff. +We must also consider `force` option to deploy a version no matter the state of fishjam (which may result in downtime). + +## Pros and Cons of the Options + +### Rolling update with eviction + +1. External orchestrator process triggers new deployment +2. Starts new instance of fishjam with new version +3. One of the fishjam instances receives SIGTERM +4. Fishjam shutdown process start +5. We mark that fishjam as one that no longer allows to create new rooms +6. Wait till the last room is closed and all recordings are completed +7. Once process is completed we shutdown the instance +8. (Possibly) Repeat for rest of the remaining instances + +* Good, because we will eliminate downtimes with deployments +* Good, because we won't lose any recordings +* Good, because we trap the SIGTERM and handle shutdown gracefully +* Bad, because deployment process may take some time (effectively as long as the longest conversation/stream) +* Bad, because we may end up with different versions on cluster at the same time + +### Active room migration + +This solution wasn't researched much so we supposed the flow should be like that: + +1. External orchestrator process triggers new deployment +2. Starts new instance of fishjam with new version +3. One of the old fishjam instances receives SIGTERM +4. We mark that fishjam as one that no longer allows to create new rooms +5. Fishjam starts migrating rooms to new instance +6. Somehow handles the recordings (?) +7. Once peers/rooms/streams are migrated, app is gonna shutdown +8. (Possibly) Repeat for rest of the remaining instances + +* Good, because we will eliminate downtimes with deployments +* Good, because we won't lose any recordings +* Good, because it happens fast, we don't have to wait for rooms to close/streams to end +* Good, because we will have 2 different versions on cluster for a short amount of time +* Bad, because we don't have a clue how to handle the active peer migration to new instance right now +* Bad, because we don't know how to handle the meetings with recording enabled during the migration + +## More Information + +This decision is heavily dependent on the Cloud Team and may be changed soon to meet their requirements. \ No newline at end of file diff --git a/docs/adrs/template.md b/docs/adrs/template.md new file mode 100644 index 00000000..eb374e52 --- /dev/null +++ b/docs/adrs/template.md @@ -0,0 +1,72 @@ +--- +# These are optional elements. Feel free to remove any of them. +status: "{proposed | rejected | accepted | deprecated | … | superseded by [ADR-0005](0005-example.md)}" +date: {YYYY-MM-DD when the decision was last updated} +deciders: {list everyone involved in the decision} +consulted: {list everyone whose opinions are sought (typically subject-matter experts); and with whom there is a two-way communication} +informed: {list everyone who is kept up-to-date on progress; and with whom there is a one-way communication} +--- +# {short title of solved problem and solution} + +## Context and Problem Statement + +{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. + You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.} + + +## Decision Drivers + +* {decision driver 1, e.g., a force, facing concern, …} +* {decision driver 2, e.g., a force, facing concern, …} +* … + +## Considered Options + +* {title of option 1} +* {title of option 2} +* {title of option 3} +* … + +## Decision Outcome + +Chosen option: "{title of option 1}", because +{justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}. + + +### Consequences + +* {Try to describe positive and negative consequences, every solution has some tradeoffs.} +* … + + +## Pros and Cons of the Options + +### {title of option 1} + + +{example | description | pointer to more information | …} + +* Good, because {argument a} +* Good, because {argument b} + +* Neutral, because {argument c} +* Bad, because {argument d} +* … + +### {title of other option} + +{example | description | pointer to more information | …} + +* Good, because {argument a} +* Good, because {argument b} +* Neutral, because {argument c} +* Bad, because {argument d} +* … + + +## More Information + +{You might want to provide additional evidence/confidence for the decision outcome here and/or + document the team agreement on the decision and/or + define when/how this decision the decision should be realized and if/when it should be re-visited. +Links to other decisions and resources might appear here as well.} \ No newline at end of file From 9b57b35d1f922aa445474e8af448f33be54b699c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= <48837433+roznawsk@users.noreply.github.com> Date: Mon, 27 May 2024 14:49:20 +0200 Subject: [PATCH 41/51] Bump deps (#192) --- mix.exs | 12 ++++++++---- mix.lock | 29 +++++++++++++++-------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/mix.exs b/mix.exs index ba15795a..d9212bb1 100644 --- a/mix.exs +++ b/mix.exs @@ -67,13 +67,17 @@ defmodule Fishjam.MixProject do # Membrane deps {:membrane_core, "1.1.0-rc0", override: true}, - {:membrane_rtc_engine, "~> 0.22.0"}, - {:membrane_rtc_engine_webrtc, "~> 0.8.0"}, + {:membrane_rtc_engine, + github: "fishjam-dev/membrane_rtc_engine", sparse: "engine", override: true}, + {:membrane_rtc_engine_webrtc, + github: "fishjam-dev/membrane_rtc_engine", sparse: "webrtc", override: true}, {:membrane_rtc_engine_hls, "~> 0.7.0"}, {:membrane_rtc_engine_recording, "~> 0.1.0"}, - {:membrane_rtc_engine_rtsp, "~> 0.7.0"}, + {:membrane_rtc_engine_rtsp, + github: "fishjam-dev/membrane_rtc_engine", sparse: "rtsp", override: true}, {:membrane_rtc_engine_file, "~> 0.5.0"}, - {:membrane_rtc_engine_sip, "~> 0.3.0"}, + {:membrane_rtc_engine_sip, + github: "fishjam-dev/membrane_rtc_engine", sparse: "sip", override: true}, {:membrane_telemetry_metrics, "~> 0.1.0"}, # HLS endpoints deps diff --git a/mix.lock b/mix.lock index 0856c6d3..0681f48e 100644 --- a/mix.lock +++ b/mix.lock @@ -4,7 +4,7 @@ "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.4.6", "6d93c4ca3bfb2282445e1e76ea263cedae49ba8524bf4cce94eb8124421c81fc", [: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", "549115f64f55e29b32f566ae054caa5557b334aceab279e0b820055ad0bfc8b6"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"}, + "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, @@ -22,7 +22,7 @@ "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"}, "ex_dtls": {:hex, :ex_dtls, "0.12.0", "648522f53340b42301eae57627bb8276555be508ec1010561e606b1621d9d2e9", [:mix], [{:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "0bc2d0de146e7cf9d85eb8d2c0a6a518479a66a2ded8a79c0960eced23fe73a9"}, "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_sdp": {:hex, :ex_sdp, "0.13.1", "8f8ea458694660fae5e687444b484f34ff2981401c5d11a64a8a3e004c4d53f1", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "a3d66221e2aed4ea4efff98a9a6b52613b4d2eb10937d46bd2d1cae99a8f183b"}, + "ex_sdp": {:hex, :ex_sdp, "0.15.0", "53815fb5b5e4fae0f3b26de90f372446bb8e0eed62a3cc20394d3c29519698be", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "d3f23596b73e7057521ff0f0d55b1189c6320a2f04388aa3a80a0aa97ffb379f"}, "excoveralls": {:hex, :excoveralls, "0.15.3", "54bb54043e1cf5fe431eb3db36b25e8fd62cf3976666bafe491e3fa5e29eba47", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f8eb5d8134d84c327685f7bb8f1db4147f1363c3c9533928234e496e3070114e"}, "fake_turn": {:hex, :fake_turn, "0.4.2", "8cd6c29d7ef8d2b42078ab2347c781ff90bd30e62d2e36f8a493ebe026947476", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d260a3b487c732f44ef7989c685e586dc51974076eb524504639c7f0080c14ac"}, "fast_tls": {:hex, :fast_tls, "1.1.13", "828cdc75e1e8fce8158846d2b971d8b4fe2b2ddcc75b759e88d751079bf78afd", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d1f422af40c7777fe534496f508ee86515cb929ad10f7d1d56aa94ce899b44a0"}, @@ -31,7 +31,7 @@ "gen_state_machine": {:hex, :gen_state_machine, "3.0.0", "1e57f86a494e5c6b14137ebef26a7eb342b3b0070c7135f2d6768ed3f6b6cdff", [:mix], [], "hexpm", "0a59652574bebceb7309f6b749d2a41b45fdeda8dbb4da0791e355dd19f0ed15"}, "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, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, @@ -54,7 +54,7 @@ "membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.31.6", "95574b1e2fea79f17c57db4295364ed82e2d57ab4ce229734421fde37e9bc632", [: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.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "30a45d79317c27c1402ab871e3e42b0244e6ecd72cbf1145aea337726cff7ac2"}, "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, "membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"}, - "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.1", "d7aeb166da55c6573b2178e18caeea290b09fd6f3cca428454085223e81476a0", [: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", "9cd63a67ffed0654a932efff34395ded04a05e48d08ea996c93daebf889dac08"}, + "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_http_adaptive_stream_plugin": {:hex, :membrane_http_adaptive_stream_plugin, "0.18.3", "202d409bda5ff9e611d482b61c5fcc893c59905d92f3b8dd5d0288561449535c", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.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.33.1", [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", "adb36192eba89e81d458c54eeccfee4da8d11ca78ee3488c57da4893203ed80d"}, "membrane_ice_plugin": {:hex, :membrane_ice_plugin, "0.18.0", "beecb741b641b0c8b4efea0569fa68a3564051294e3ed10a10a1e29028e1d474", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.12.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:fake_turn, "~> 0.4.0", [hex: :fake_turn, 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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "fff74d447d42902bb014bc8cdc78d6da3f797b59ed8fd622bfc7c6854323a287"}, "membrane_mp4_format": {:hex, :membrane_mp4_format, "0.8.0", "8c6e7d68829228117d333b4fbb030e7be829aab49dd8cb047fdc664db1812e6a", [:mix], [], "hexpm", "148dea678a1f82ccfd44dbde6f936d2f21255f496cb45a22cc6eec427f025522"}, @@ -67,20 +67,20 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:hex, :membrane_rtc_engine, "0.22.0", "7c7cb477ccf17f8a4604e61e55bb484995a77701c0db4066ce5fdc63755a1b57", [:mix], [{:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.1", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:statistics, "~> 0.6.0", [hex: :statistics, repo: "hexpm", optional: false]}], "hexpm", "064cd03961b29a2972477574c2df53ed4b6c33fe3cb2bf538bb6e316f35b7e04"}, + "membrane_rtc_engine": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "1732bdda81af4bbb7d4a239b904b9f073388dcd0", [sparse: "engine"]}, "membrane_rtc_engine_file": {:hex, :membrane_rtc_engine_file, "0.5.0", "6936d803e9ffd01bddc4f7bbc5d2a5893bd8e60117f3ab7c0ec66ec7568cf1f4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_ogg_plugin, "~> 0.3.0", [hex: :membrane_ogg_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_realtimer_plugin, "~> 0.9.0", [hex: :membrane_realtimer_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}], "hexpm", "150e42a34baa6971d341b64d2ada06ee066d7fcca9aee9a73a5d6a09fe192c0e"}, "membrane_rtc_engine_hls": {:hex, :membrane_rtc_engine_hls, "0.7.0", "03a7797389c493c824c1262afaf8fe4e1314406ec93d5d99de11e5528c5094b1", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.5", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_audio_mix_plugin, "~> 0.16.0", [hex: :membrane_audio_mix_plugin, repo: "hexpm", optional: true]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.31.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_http_adaptive_stream_plugin, "~> 0.18.2", [hex: :membrane_http_adaptive_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_video_compositor_plugin, "~> 0.7.0", [hex: :membrane_video_compositor_plugin, repo: "hexpm", optional: true]}], "hexpm", "88b39725792f1fa41439ef1e8260d016b4eb9173eb8568a307769d3ddd721f52"}, "membrane_rtc_engine_recording": {:hex, :membrane_rtc_engine_recording, "0.1.0", "49f01747a92755ee9a37925873ac4a54a36e6d7946685ae6260bd714bb9874b0", [:mix], [{:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: false]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_stream_plugin, "~> 0.4.0", [hex: :membrane_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: false]}], "hexpm", "1ba142c25757c1727f5d8f3588b40349c0b1cd227011cc1411a50bdb2fd79cb1"}, - "membrane_rtc_engine_rtsp": {:hex, :membrane_rtc_engine_rtsp, "0.7.0", "b57a685afef887c6011ba446e35ff25ee0048bfa003b4822c513ae66ca32b35d", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, 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_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.19.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.1", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.5.1", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.13.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}], "hexpm", "ee49ed47b952e5be056a5a6be01668c953bf9a021601b47f0ecf8d71dcd5d1af"}, - "membrane_rtc_engine_sip": {:hex, :membrane_rtc_engine_sip, "0.3.0", "eea1402db258bcc95d0ac88fc42755ec0c277eb4f9542dcba77575c966277b21", [:mix], [{:ex_sdp, "~> 0.11", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:membrane_audio_mix_plugin, "~> 0.16.0", [hex: :membrane_audio_mix_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.19.1", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_g711_plugin, "~> 0.1.0", [hex: :membrane_g711_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:membrane_raw_audio_parser_plugin, "~> 0.4.0", [hex: :membrane_raw_audio_parser_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_g711_plugin, "~> 0.2.0", [hex: :membrane_rtp_g711_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.0", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:membrane_udp_plugin, "~> 0.13.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}, {:sippet, "~> 1.0.11", [hex: :sippet, repo: "hexpm", optional: false]}], "hexpm", "9bd665fd8ca11cbe9c5e9348ae37f48025bbca97e2d75d4b624feb91d3b664bb"}, - "membrane_rtc_engine_webrtc": {:hex, :membrane_rtc_engine_webrtc, "0.8.0", "66138f2b39276e699b1f831cf93f7bfe22dedb97675a09a7b8cb9cf5f249d2dc", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "1.4.6", [hex: :bundlex, repo: "hexpm", optional: false]}, {:ex_libsrtp, ">= 0.0.0", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_ice_plugin, "~> 0.18.0", [hex: :membrane_ice_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtp_h264_plugin, "~> 0.19.0", [hex: :membrane_rtp_h264_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_opus_plugin, ">= 0.9.0", [hex: :membrane_rtp_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_plugin, "~> 0.24.1", [hex: :membrane_rtp_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_vp8_plugin, "~> 0.9.0", [hex: :membrane_rtp_vp8_plugin, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:unifex, "1.1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "84ecd82cb6d74be719cc7c8e78f3a36b2cfa52949e871df4ecf62a456a89566a"}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "1732bdda81af4bbb7d4a239b904b9f073388dcd0", [sparse: "rtsp"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "1732bdda81af4bbb7d4a239b904b9f073388dcd0", [sparse: "sip"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "1732bdda81af4bbb7d4a239b904b9f073388dcd0", [sparse: "webrtc"]}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, - "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.0", "112bfedc14fb83bdb549ef1a03da23908feedeb165fd3e4512a549f1af532ae7", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "76fd159e7406cadbef15124cba30eca3fffcf71a7420964f26e23d4cffd9b29d"}, + "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.1", "8c61b3d2968e54e1b459a42f070ea71f597056eba4059df780eaa8da8aee6035", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "fb2bb5d4ed18f38523851cb449a2c78ed533e58028dc058342b8cfd659f812d5"}, "membrane_rtp_opus_plugin": {:hex, :membrane_rtp_opus_plugin, "0.9.0", "ae76421faa04697a4af76a55b6c5e675dea61b611d29d8201098783d42863af7", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "58f095d2978daf999d87c1c016007cb7d99434208486331ab5045e77f5be9dcc"}, - "membrane_rtp_plugin": {:hex, :membrane_rtp_plugin, "0.24.1", "56238f31b28e66da8b22cacb1aa54aa607f0522d4ad4709aa0718512f10dd808", [: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]}, {: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.8.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", "04827ab3d16ebe980b88e2c40ddfbfb828d5d029365867669fd28862d8c3a559"}, + "membrane_rtp_plugin": {:hex, :membrane_rtp_plugin, "0.28.0", "46654f458033a2c5b0c44f528cd6723df4f78705ae64ad0d98bcd58d93b2feda", [: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]}, {: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.7.0", [hex: :membrane_rtsp, 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", "6413aebfc6942c38ba71bd19f3fbf6eaa2ba320555d724183350d96ecc153a16"}, "membrane_rtp_vp8_plugin": {:hex, :membrane_rtp_vp8_plugin, "0.9.0", "b45d597d3612f62faf734e9bc96c8fb4f4dc480ae76efb86ef099fa81036d7b5", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.4.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}], "hexpm", "2e8a48170d296678426e2a3a0bd45a27308aa4608ffc374f3968495187e8666b"}, - "membrane_rtsp": {:hex, :membrane_rtsp, "0.5.1", "3176333ce321081087579b9d9c087262980a368de51be38b7af26fb2a0c0ac74", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.13.1", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "ef7d90446fe036a46afdca3076d00c47093e46c9e2a49957d9734c247b03c1a3"}, + "membrane_rtsp": {:hex, :membrane_rtsp, "0.7.0", "92b145cd7c18e897c6e3e3f2e8c5b95bd98d9eec41f45755875eadd222c42933", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.15.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", "debf4fa612ea6b084bce1926745c660e404bf647ed975cf22f01925084080cbe"}, "membrane_stream_plugin": {:hex, :membrane_stream_plugin, "0.4.0", "0c4ab72a4e13bf0faa0f1166fbaf68d2e34167dbec345aedb74ce1eb7497bdda", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "5a9a9c17783e18ad740e6ddfed364581bdb7ebdab8e61ba2c19a1830356f7eb8"}, "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.0", "cb93d28356b436b0597736c3e4153738d82d2a14ff547f831df7e9051e54fc06", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "aba28dc8311f70ced95d984509be930fac55857d2d18bffcf768815e627be3f0"}, @@ -90,11 +90,12 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"}, + "mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"}, "mockery": {:hex, :mockery, "2.3.1", "a02fd60b10ac9ed37a7a2ecf6786c1f1dd5c75d2b079a60594b089fba32dc087", [:mix], [], "hexpm", "1d0971d88ebf084e962da3f2cfee16f0ea8e04ff73a7710428500d4500b947fa"}, "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "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"}, "open_api_spex": {:hex, :open_api_spex, "3.18.3", "fefb84fe323cacfc92afdd0ecb9e89bc0261ae00b7e3167ffc2028ce3944de42", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "c0cfc31570199ce7e7520b494a591027da609af45f6bf9adce51e2469b1609fb"}, @@ -106,9 +107,9 @@ "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{: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", [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]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"}, "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.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [: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", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, + "plug": {:hex, :plug, "1.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [: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", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [: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", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, - "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "protobuf": {:hex, :protobuf, "0.12.0", "58c0dfea5f929b96b5aa54ec02b7130688f09d2de5ddc521d696eec2a015b223", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "75fa6cbf262062073dd51be44dd0ab940500e18386a6c4e87d5819a58964dc45"}, "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, From 07a89a9c75515f29e9d29f1e07492e098bf407c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Ko=C5=82odziej?= Date: Fri, 31 May 2024 10:01:55 +0200 Subject: [PATCH 42/51] feat: introduce room_id generator (#206) --- .credo.exs | 2 +- config/runtime.exs | 6 ++ lib/fishjam/application.ex | 1 + lib/fishjam/component/file.ex | 3 +- lib/fishjam/component/hls/recording.ex | 8 +- lib/fishjam/component/hls/request_handler.ex | 6 +- lib/fishjam/feature_flags.ex | 15 +++ lib/fishjam/room/config.ex | 17 +--- lib/fishjam/room/id.ex | 91 +++++++++++++++++++ lib/fishjam/room_service.ex | 37 +++----- lib/fishjam/rpc_client.ex | 54 +++++++++++ test/fishjam/room/id_test.exs | 56 ++++++++++++ .../component/file_component_test.exs | 3 +- .../component/hls_component_test.exs | 4 +- .../integration/server_notification_test.exs | 3 +- 15 files changed, 258 insertions(+), 48 deletions(-) create mode 100644 lib/fishjam/feature_flags.ex create mode 100644 lib/fishjam/room/id.ex create mode 100644 lib/fishjam/rpc_client.ex create mode 100644 test/fishjam/room/id_test.exs diff --git a/.credo.exs b/.credo.exs index f9cc8d32..85a588e9 100644 --- a/.credo.exs +++ b/.credo.exs @@ -176,7 +176,7 @@ {Credo.Check.Refactor.DoubleBooleanNegation, false}, {Credo.Check.Refactor.ModuleDependencies, false}, {Credo.Check.Refactor.NegatedIsNil, false}, - {Credo.Check.Refactor.PipeChainStart, false}, + {Credo.Check.Refactor.PipeChainStart, []}, {Credo.Check.Refactor.VariableRebinding, false}, {Credo.Check.Warning.LeakyEnvironment, false}, {Credo.Check.Warning.MapGetUnsafePass, false}, diff --git a/config/runtime.exs b/config/runtime.exs index 00178356..16fe659c 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -88,6 +88,12 @@ case ConfigReader.read_ssl_config() do config :fishjam, FishjamWeb.Endpoint, http: [ip: ip, port: port] end +config :fishjam, + feature_flags: [ + custom_room_name_disabled: + ConfigReader.read_boolean("FJ_FEATURE_FLAG_CUSTOM_ROOM_NAME_DISABLED") || false + ] + check_origin = ConfigReader.read_check_origin("FJ_CHECK_ORIGIN") if check_origin != nil do diff --git a/lib/fishjam/application.ex b/lib/fishjam/application.ex index 4560ac4c..966f755b 100644 --- a/lib/fishjam/application.ex +++ b/lib/fishjam/application.ex @@ -117,6 +117,7 @@ defmodule Fishjam.Application do defp ensure_epmd_started!() do try do {_output, 0} = System.cmd("epmd", ["-daemon"]) + # credo:disable-for-next-line :ok = Task.async(&ensure_epmd_running/0) |> Task.await(@epmd_timeout) :ok diff --git a/lib/fishjam/component/file.ex b/lib/fishjam/component/file.ex index 70aa6f04..e16f3123 100644 --- a/lib/fishjam/component/file.ex +++ b/lib/fishjam/component/file.ex @@ -61,7 +61,8 @@ defmodule Fishjam.Component.File do defp validate_file_path(file_path) do base_path = - Application.fetch_env!(:fishjam, :media_files_path) + :fishjam + |> Application.fetch_env!(:media_files_path) |> Path.join(@files_location) |> Path.expand() diff --git a/lib/fishjam/component/hls/recording.ex b/lib/fishjam/component/hls/recording.ex index b2e05e45..3e55189c 100644 --- a/lib/fishjam/component/hls/recording.ex +++ b/lib/fishjam/component/hls/recording.ex @@ -45,7 +45,8 @@ defmodule Fishjam.Component.HLS.Recording do end defp root_directory() do - Application.fetch_env!(:fishjam, :media_files_path) + :fishjam + |> Application.fetch_env!(:media_files_path) |> Path.join(@recordings_folder) |> Path.expand() end @@ -58,7 +59,10 @@ defmodule Fishjam.Component.HLS.Recording do end defp do_delete(id) do - directory(id) |> File.rm_rf!() + id + |> directory() + |> File.rm_rf!() + :ok end end diff --git a/lib/fishjam/component/hls/request_handler.ex b/lib/fishjam/component/hls/request_handler.ex index e8bbd303..796b2659 100644 --- a/lib/fishjam/component/hls/request_handler.ex +++ b/lib/fishjam/component/hls/request_handler.ex @@ -64,7 +64,11 @@ defmodule Fishjam.Component.HLS.RequestHandler do def handle_recording_request(recording_id, filename) do with :ok <- Recording.validate_recording(recording_id) do recording_path = Recording.directory(recording_id) - file_path = Path.join(recording_path, filename) |> Path.expand() + + file_path = + recording_path + |> Path.join(filename) + |> Path.expand() if PathValidation.inside_directory?(file_path, recording_path), do: File.read(file_path), diff --git a/lib/fishjam/feature_flags.ex b/lib/fishjam/feature_flags.ex new file mode 100644 index 00000000..4d11286c --- /dev/null +++ b/lib/fishjam/feature_flags.ex @@ -0,0 +1,15 @@ +defmodule Fishjam.FeatureFlags do + @moduledoc """ + Module to resolve any feature flags, since we are not using database we can't use fun_with_flags. + Because of that we base feature flags on the environment variables mainly. + """ + + @doc """ + Flag for disabling custom room names, which will be replaced by the generated based on the node name. + + Introduced: 28/05/2024 + Removal: Once we move on to generated room_ids permanently. + """ + def custom_room_name_disabled?, + do: Application.get_env(:fishjam, :feature_flags)[:custom_room_name_disabled] +end diff --git a/lib/fishjam/room/config.ex b/lib/fishjam/room/config.ex index 1f2cf028..5cf1fb4b 100644 --- a/lib/fishjam/room/config.ex +++ b/lib/fishjam/room/config.ex @@ -2,6 +2,9 @@ defmodule Fishjam.Room.Config do @moduledoc """ Room configuration """ + + alias Fishjam.Room.ID + @enforce_keys [ :room_id, :max_peers, @@ -37,7 +40,7 @@ defmodule Fishjam.Room.Config do peerless_purge_timeout = Map.get(params, "peerlessPurgeTimeout") peer_disconnected_timeout = Map.get(params, "peerDisconnectedTimeout") - with {:ok, room_id} <- parse_room_id(room_id), + with {:ok, room_id} <- ID.generate(room_id), :ok <- validate_max_peers(max_peers), {:ok, video_codec} <- codec_to_atom(video_codec), :ok <- validate_webhook_url(webhook_url), @@ -55,18 +58,6 @@ defmodule Fishjam.Room.Config do end end - defp parse_room_id(nil), do: {:ok, UUID.uuid4()} - - defp parse_room_id(room_id) when is_binary(room_id) do - if Regex.match?(~r/^[a-zA-Z0-9-_]+$/, room_id) do - {:ok, room_id} - else - {:error, :invalid_room_id} - end - end - - defp parse_room_id(_room_id), do: {:error, :invalid_room_id} - defp validate_max_peers(nil), do: :ok defp validate_max_peers(max_peers) when is_integer(max_peers) and max_peers >= 0, do: :ok defp validate_max_peers(_max_peers), do: {:error, :invalid_max_peers} diff --git a/lib/fishjam/room/id.ex b/lib/fishjam/room/id.ex new file mode 100644 index 00000000..5c16f521 --- /dev/null +++ b/lib/fishjam/room/id.ex @@ -0,0 +1,91 @@ +defmodule Fishjam.Room.ID do + @moduledoc """ + This module allows to generate room_id with the node name in it. + """ + + @type id :: String.t() + + @doc """ + Based on the Room ID determines to which node it belongs to. + Returns an error if the node isn't present in the cluster. + + DISCLAIMER: + It should be used only with room_ids generated by generate/0, otherwise it can raise. + """ + @spec determine_node(id()) :: + {:ok, node()} | {:error, :invalid_room_id} | {:error, :invalid_node} + def determine_node(room_id) do + with {:ok, room_id} <- validate_room_id(room_id), + node_name <- decode_node_name(room_id), + true <- node_present_in_cluster?(node_name) do + {:ok, node_name} + else + {:error, :invalid_room_id} -> {:error, :invalid_room_id} + false -> {:error, :invalid_node} + end + end + + @doc """ + Room ID structure resembles the one of the UUID, although the last part is replaced by encoded node name. + + ## Example: + For node_name: "fishjam@10.0.0.1" + + iex> Fishjam.Room.ID.generate() + "da2e-4a75-95ff-776bad2caf04-666973686a616d4031302e302e302e31" + """ + @spec generate() :: id() + def generate do + UUID.uuid4() + |> String.split("-") + |> Enum.take(-4) + |> Enum.concat([encoded_node_name()]) + |> Enum.join("-") + end + + @doc """ + Depending on feature flag "custom_room_name_disabled" + - uses `generate/0` to generate room_id + or + - parses the `room_id` provided by the client + """ + @spec generate(nil | String.t()) :: {:ok, id()} | {:error, :invalid_room_id} + def generate(nil), do: generate(UUID.uuid4()) + + def generate(room_id) do + if Fishjam.FeatureFlags.custom_room_name_disabled?() do + {:ok, generate()} + else + validate_room_id(room_id) + end + end + + defp decode_node_name(room_id) do + room_id + |> String.split("-") + |> Enum.take(-1) + |> Enum.at(0) + |> Base.decode16!(case: :lower) + |> String.to_existing_atom() + end + + defp encoded_node_name do + Node.self() + |> Atom.to_string() + |> Base.encode16(case: :lower) + end + + defp node_present_in_cluster?(node) do + node in [Node.self() | Node.list()] + end + + defp validate_room_id(room_id) when is_binary(room_id) do + if Regex.match?(~r/^[a-zA-Z0-9-_]+$/, room_id) do + {:ok, room_id} + else + {:error, :invalid_room_id} + end + end + + defp validate_room_id(_room_id), do: {:error, :invalid_room_id} +end diff --git a/lib/fishjam/room_service.ex b/lib/fishjam/room_service.ex index 81f117de..b1ad68b0 100644 --- a/lib/fishjam/room_service.ex +++ b/lib/fishjam/room_service.ex @@ -7,7 +7,9 @@ defmodule Fishjam.RoomService do require Logger - alias Fishjam.{Event, Room, WebhookNotifier} + alias Fishjam.Event + alias Fishjam.Room + alias Fishjam.WebhookNotifier @metric_interval_in_seconds Application.compile_env!(:fishjam, :room_metrics_scrape_interval) @metric_interval_in_milliseconds @metric_interval_in_seconds * 1_000 @@ -58,31 +60,14 @@ defmodule Fishjam.RoomService do @spec create_room(Room.Config.t()) :: {:ok, Room.t(), String.t()} | {:error, atom()} def create_room(config) do - {node_resources, failed_nodes} = :rpc.multicall(Fishjam.RoomService, :get_resource_usage, []) - - if Enum.count(failed_nodes) > 0 do - Logger.warning( - "Couldn't get resource usage of the following nodes. Reason: nodes don't exist. Nodes: #{inspect(failed_nodes)}" - ) - end - - {failed_rpcs, node_resources} = - Enum.split_with(node_resources, fn - {:badrpc, _info} -> true - _other -> false - end) - - unless Enum.empty?(failed_rpcs) do - Logger.warning("These RPC calls fail: #{inspect(failed_rpcs)}") - end - - min_node = find_best_node(node_resources) - - if Enum.count(node_resources) > 1 do - Logger.info("Node with least used resources is #{inspect(min_node)}") - GenServer.call({__MODULE__, min_node}, {:create_room, config}) - else - GenServer.call(__MODULE__, {:create_room, config}) + case Fishjam.RPCClient.multicall(Fishjam.RoomService, :get_resource_usage, []) do + [_only_self_resources] -> + GenServer.call(__MODULE__, {:create_room, config}) + + nodes_resources -> + min_node = find_best_node(nodes_resources) + Logger.info("Node with least used resources is #{inspect(min_node)}") + GenServer.call({__MODULE__, min_node}, {:create_room, config}) end end diff --git a/lib/fishjam/rpc_client.ex b/lib/fishjam/rpc_client.ex new file mode 100644 index 00000000..047973de --- /dev/null +++ b/lib/fishjam/rpc_client.ex @@ -0,0 +1,54 @@ +defmodule Fishjam.RPCClient do + @moduledoc """ + This modules serves as simple RPC client to communicate with other nodes in cluster. + It utilizes the Enhanced version of Erlang `rpc` called `erpc`. + + Enhanced version allows to distinguish between returned value, raised exceptions, and other errors. + `erpc` also has better performance and scalability than the original rpc implementation. + """ + require Logger + + @doc """ + Executes mfa on a remote node. + Function returns {:ok, result} tuple only if the execution succeeded. + In case of any exceptions we are catching them logging and returning simple :error atom. + """ + @spec call(node(), module(), atom(), term(), timeout()) :: {:ok, term()} | :error + def call(node, module, function, args, timeout \\ :infinity) do + try do + {:ok, :erpc.call(node, module, function, args, timeout)} + rescue + e -> + Logger.warning("RPC call to node #{node} failed with exception: #{inspect(e)}") + :error + end + end + + @doc """ + Multicall to all nodes in the cluster, including this node. + It filters out any errors or exceptions from return so you may end up with empty list. + """ + @spec multicall(module(), atom(), term(), timeout()) :: list(term) + def multicall(module, function, args, timeout \\ :infinity) do + nodes() + |> :erpc.multicall(module, function, args, timeout) + |> handle_result() + end + + defp handle_result(result) when is_list(result) do + result + |> Enum.reduce([], fn + {:ok, res}, acc -> + [res | acc] + + {status, res}, acc -> + Logger.warning( + "RPC multicall to one of the nodes failed with status: #{inspect(status)} because of: #{inspect(res)}" + ) + + acc + end) + end + + defp nodes, do: [Node.self() | Node.list()] +end diff --git a/test/fishjam/room/id_test.exs b/test/fishjam/room/id_test.exs new file mode 100644 index 00000000..10fa92c5 --- /dev/null +++ b/test/fishjam/room/id_test.exs @@ -0,0 +1,56 @@ +defmodule Fishjam.Room.IDTest do + use ExUnit.Case + + alias Fishjam.Room.ID, as: Subject + + describe "determine_node/1" do + test "resolves node name from the provided room_id" do + node_name = Node.self() + room_id = Subject.generate() + + assert {:ok, node_name} == Subject.determine_node(room_id) + end + + test "returns error if node is not detected in cluster" do + invalid_node = :invalid_node |> Atom.to_string() |> Base.encode16(case: :lower) + invalid_room_id = "room-id-#{invalid_node}" + assert {:error, :invalid_node} == Subject.determine_node(invalid_room_id) + end + end + + describe "generate/0" do + test "room_id last part is based on the node name" do + room1_id = Subject.generate() + room2_id = Subject.generate() + + node_part_from_room1 = room1_id |> String.split("-") |> Enum.take(-1) + node_part_from_room2 = room2_id |> String.split("-") |> Enum.take(-1) + + assert node_part_from_room1 == node_part_from_room2 + end + + test "generated room_id has 5 parts" do + room_id = Subject.generate() + assert room_id |> String.split("-") |> length() == 5 + end + end + + describe "generate/1" do + setup do + Application.delete_env(:fishjam, :feature_flags) + end + + test "executes generate/0 when feature flag is enabled and generates random id" do + Application.put_env(:fishjam, :feature_flags, custom_room_name_disabled: true) + refute {:ok, "custom_room_name"} == Subject.generate("custom_room_name") + end + + test "parses custom room name when feature flag is disabled" do + assert {:ok, "custom_room_name"} == Subject.generate("custom_room_name") + end + + test "returns error when custom room doesn't meet naming criteria" do + assert {:error, :invalid_room_id} = Subject.generate("invalid_characters//??$@!") + end + end +end diff --git a/test/fishjam_web/controllers/component/file_component_test.exs b/test/fishjam_web/controllers/component/file_component_test.exs index 2639c72f..f3080031 100644 --- a/test/fishjam_web/controllers/component/file_component_test.exs +++ b/test/fishjam_web/controllers/component/file_component_test.exs @@ -24,7 +24,8 @@ defmodule FishjamWeb.Component.FileComponentTest do Application.put_env(:fishjam, :components_used, [Fishjam.Component.File]) media_sources_directory = - Application.fetch_env!(:fishjam, :media_files_path) + :fishjam + |> Application.fetch_env!(:media_files_path) |> Path.join(@file_component_directory) |> Path.expand() diff --git a/test/fishjam_web/controllers/component/hls_component_test.exs b/test/fishjam_web/controllers/component/hls_component_test.exs index 58bd3628..032564a1 100644 --- a/test/fishjam_web/controllers/component/hls_component_test.exs +++ b/test/fishjam_web/controllers/component/hls_component_test.exs @@ -154,7 +154,7 @@ defmodule FishjamWeb.Component.HlsComponentTest do end test "renders component with ll-hls enabled", %{conn: conn, room_id: room_id} do - assert Registry.lookup(Fishjam.RequestHandlerRegistry, room_id) |> Enum.empty?() + assert Fishjam.RequestHandlerRegistry |> Registry.lookup(room_id) |> Enum.empty?() conn = post(conn, ~p"/room/#{room_id}/component", type: "hls", options: %{lowLatency: true}) @@ -183,7 +183,7 @@ defmodule FishjamWeb.Component.HlsComponentTest do assert_receive {:DOWN, _ref, :process, ^engine_pid, :normal}, 10_000 assert_receive {:DOWN, _ref, :process, ^request_handler, :normal} - assert Registry.lookup(Fishjam.RequestHandlerRegistry, room_id) |> Enum.empty?() + assert Fishjam.RequestHandlerRegistry |> Registry.lookup(room_id) |> Enum.empty?() end test "renders errors when video codec is different than h264 - vp8", %{conn: conn} do diff --git a/test/fishjam_web/integration/server_notification_test.exs b/test/fishjam_web/integration/server_notification_test.exs index 6308b5e5..6f5505ad 100644 --- a/test/fishjam_web/integration/server_notification_test.exs +++ b/test/fishjam_web/integration/server_notification_test.exs @@ -469,7 +469,8 @@ defmodule FishjamWeb.Integration.ServerNotificationTest do test "sends message when File adds or removes tracks", %{conn: conn} do media_sources_directory = - Application.fetch_env!(:fishjam, :media_files_path) + :fishjam + |> Application.fetch_env!(:media_files_path) |> Path.join(@file_component_directory) |> Path.expand() From 453531f95cefacdb69d64b5432daf5ddc7666632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Mon, 3 Jun 2024 10:15:42 +0200 Subject: [PATCH 43/51] Add logs about errors during adding peer (#191) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add logs about errors during adding peer * Add generic error logger handler * More error logs * Apply TODOs * Fix formating * Update peer_controller * Changes after review * Update lib/fishjam_web/controllers/peer_controller.ex Co-authored-by: Michał Śledź * Update log messages * Update lib/fishjam/room_service.ex Co-authored-by: Michał Śledź * Move Logger.debug --------- Co-authored-by: Michał Śledź --- lib/fishjam/room_service.ex | 9 ++++++ .../controllers/fallback_controller.ex | 4 +++ .../controllers/peer_controller.ex | 28 +++++++++++++++---- .../controllers/room_controller.ex | 7 +++++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/lib/fishjam/room_service.ex b/lib/fishjam/room_service.ex index b1ad68b0..c4389db5 100644 --- a/lib/fishjam/room_service.ex +++ b/lib/fishjam/room_service.ex @@ -118,7 +118,10 @@ defmodule Fishjam.RoomService do @impl true def handle_call({:create_room, config}, _from, state) do + Logger.debug("Creating a new room") + with {:ok, room_pid, room_id} <- Room.start(config) do + Logger.debug("Room created successfully") room = Room.get_state(room_id) Process.monitor(room_pid) @@ -133,7 +136,13 @@ defmodule Fishjam.RoomService do {:reply, {:ok, room, Fishjam.address()}, state} else {:error, :room_already_exists} = error -> + Logger.warning("Room creation failed, because it already exists") + {:reply, error, state} + + reason -> + Logger.warning("Room creation failed with reason: #{inspect(reason)}") + {:reply, {:error, :room_doesnt_start}, state} end end diff --git a/lib/fishjam_web/controllers/fallback_controller.ex b/lib/fishjam_web/controllers/fallback_controller.ex index 00f424eb..0388e4dc 100644 --- a/lib/fishjam_web/controllers/fallback_controller.ex +++ b/lib/fishjam_web/controllers/fallback_controller.ex @@ -1,7 +1,11 @@ defmodule FishjamWeb.FallbackController do use FishjamWeb, :controller + require Logger + def call(conn, {:error, status, reason}) do + Logger.debug("Generic error handler status: #{status}, reason: #{reason}") + conn |> put_resp_content_type("application/json") |> put_status(status) diff --git a/lib/fishjam_web/controllers/peer_controller.ex b/lib/fishjam_web/controllers/peer_controller.ex index 39baa587..9b26d2b0 100644 --- a/lib/fishjam_web/controllers/peer_controller.ex +++ b/lib/fishjam_web/controllers/peer_controller.ex @@ -2,6 +2,7 @@ defmodule FishjamWeb.PeerController do use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs + require Logger alias Fishjam.Peer alias Fishjam.Room alias Fishjam.RoomService @@ -74,6 +75,8 @@ defmodule FishjamWeb.PeerController do {:ok, peer_type} <- Peer.parse_type(peer_type_string), {:ok, _room_pid} <- RoomService.find_room(room_id), {:ok, peer} <- Room.add_peer(room_id, peer_type, peer_options) do + Logger.debug("Successfully added peer to room: #{room_id}") + assigns = [ peer: peer, token: PeerToken.generate(%{peer_id: peer.id, room_id: room_id}), @@ -86,19 +89,30 @@ defmodule FishjamWeb.PeerController do |> render("show.json", assigns) else :error -> - {:error, :bad_request, "Invalid request body structure"} + msg = "Invalid request body structure" + log_warning(room_id, msg) + + {:error, :bad_request, msg} {:error, :room_not_found} -> - {:error, :not_found, "Room #{room_id} does not exist"} + msg = "Room #{room_id} does not exist" + log_warning(room_id, msg) + {:error, :not_found, msg} {:error, :invalid_type} -> - {:error, :bad_request, "Invalid peer type"} + msg = "Invalid peer type" + log_warning(room_id, msg) + {:error, :bad_request, msg} {:error, {:peer_disabled_globally, type}} -> - {:error, :bad_request, "Peers of type #{type} are disabled on this Fishjam"} + msg = "Peers of type #{type} are disabled on this Fishjam" + log_warning(room_id, msg) + {:error, :bad_request, msg} {:error, {:reached_peers_limit, type}} -> - {:error, :service_unavailable, "Reached #{type} peers limit in room #{room_id}"} + msg = "Reached #{type} peers limit in room #{room_id}" + log_warning(room_id, msg) + {:error, :service_unavailable, msg} end end @@ -111,4 +125,8 @@ defmodule FishjamWeb.PeerController do {:error, :peer_not_found} -> {:error, :not_found, "Peer #{id} does not exist"} end end + + defp log_warning(room_id, msg) do + Logger.warning("Unable to add peer to room #{room_id}, reason: #{msg}") + end end diff --git a/lib/fishjam_web/controllers/room_controller.ex b/lib/fishjam_web/controllers/room_controller.ex index a28130f6..62db8b78 100644 --- a/lib/fishjam_web/controllers/room_controller.ex +++ b/lib/fishjam_web/controllers/room_controller.ex @@ -2,6 +2,7 @@ defmodule FishjamWeb.RoomController do use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs + require Logger alias Fishjam.Room alias Fishjam.RoomService alias FishjamWeb.ApiSpec @@ -74,6 +75,8 @@ defmodule FishjamWeb.RoomController do end def create(conn, params) do + Logger.debug("Start creating room") + with {:ok, config} <- Room.Config.from_params(params), {:ok, room, fishjam_address} <- RoomService.create_room(config) do conn @@ -104,6 +107,10 @@ defmodule FishjamWeb.RoomController do room_id = Map.get(params, "roomId") {:error, :bad_request, "Cannot add room with id \"#{room_id}\" - room already exists"} + {:error, :room_doesnt_start} -> + room_id = Map.get(params, "roomId") + {:error, :bad_request, "Cannot add room with id \"#{room_id}\" - unexpected error"} + {:error, :invalid_room_id} -> room_id = Map.get(params, "roomId") From f20d6b9792842bbfb49793ec91462acbcb9e9fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:31:29 +0200 Subject: [PATCH 44/51] Fix HLS OpenAPI warning (#202) * Try fix S3 openapi issue * Fix credo issue * Run only test ci * Experimental fix of auth cache error * Return circleci to previous version * Modify test * WiP * Remove IO.inspect * Update circleci config * Apply review changes * Update deps * Update lib/fishjam/component/hls/manager.ex Co-authored-by: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> * Return purging logs from engine * Add TODO * Update deps * Add purging SRTP warnings --------- Co-authored-by: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> --- .circleci/config.yml | 2 +- config/config.exs | 5 +- config/test.exs | 2 + lib/fishjam/component/hls/manager.ex | 2 + lib/fishjam/room_service.ex | 2 +- lib/fishjam_web/api_spec/component/HLS/S3.ex | 32 ++++++++++++ lib/fishjam_web/api_spec/component/hls.ex | 41 ++------------- .../api_spec/component/recording.ex | 7 +-- lib/fishjam_web/peer_socket.ex | 2 +- mix.exs | 32 +++++++++--- mix.lock | 22 ++++---- openapi.yaml | 50 ++++++++++++++++--- test/fishjam/component/hls/manager_test.exs | 8 +-- .../component/recording_component_test.exs | 31 ++++++------ .../controllers/room_controller_test.exs | 12 +++-- .../subscription_controller_test.exs | 8 ++- .../integration/peer_socket_test.exs | 27 +++++++--- .../integration/server_notification_test.exs | 8 +-- test/support/adapter.ex | 16 ++++++ test/support/mock_manager.ex | 6 +++ 20 files changed, 206 insertions(+), 109 deletions(-) create mode 100644 lib/fishjam_web/api_spec/component/HLS/S3.ex create mode 100644 test/support/adapter.ex diff --git a/.circleci/config.yml b/.circleci/config.yml index 748b4c7d..2663aea9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ orbs: executors: machine_executor_amd64: machine: - image: ubuntu-2204:2022.04.2 + image: default environment: architecture: "amd64" platform: "linux/amd64" diff --git a/config/config.exs b/config/config.exs index 1db072c3..a6c2d523 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,5 +1,7 @@ import Config +config :membrane_core, :enable_metrics, false + config :fishjam, FishjamWeb.Endpoint, url: [host: "localhost"], render_errors: [ @@ -34,7 +36,8 @@ config :logger, [application: :membrane_rtc_engine_hls, level_lower_than: :warning], [application: :membrane_rtc_engine_rtsp, level_lower_than: :warning], [application: :membrane_rtc_engine_file, level_lower_than: :warning], - [application: :membrane_rtc_engine_sip, level_lower_than: :warning] + [application: :membrane_rtc_engine_sip, level_lower_than: :warning], + [module: Membrane.SRTP.Encryptor, level_lower_than: :error] ] config :ex_aws, diff --git a/config/test.exs b/config/test.exs index 1d0de5e8..029b6529 100644 --- a/config/test.exs +++ b/config/test.exs @@ -18,3 +18,5 @@ config :logger, level: :warning config :phoenix, :plug_init_mode, :runtime config :ex_aws, :http_client, ExAws.Request.HttpMock + +config :ex_aws, :awscli_auth_adapter, Fishjam.Adapter diff --git a/lib/fishjam/component/hls/manager.ex b/lib/fishjam/component/hls/manager.ex index 06a000d5..42d2d42f 100644 --- a/lib/fishjam/component/hls/manager.ex +++ b/lib/fishjam/component/hls/manager.ex @@ -55,6 +55,8 @@ defmodule Fishjam.Component.HLS.Manager do unless is_nil(hls_options.s3), do: upload_to_s3(hls_dir, room_id, hls_options.s3) unless hls_options.persistent, do: remove_hls(hls_dir, room_id) + Logger.info("Engine is down and HLS manager finished uploading stream") + {:stop, :normal, state} end diff --git a/lib/fishjam/room_service.ex b/lib/fishjam/room_service.ex index c4389db5..13f159d8 100644 --- a/lib/fishjam/room_service.ex +++ b/lib/fishjam/room_service.ex @@ -195,7 +195,7 @@ defmodule Fishjam.RoomService do def handle_info({:DOWN, _ref, :process, pid, :normal}, state) do {room_id, state} = pop_in(state, [:rooms, pid]) - Logger.debug("Room #{room_id} is down with reason: normal") + Logger.debug("Room #{inspect(room_id)} is down with reason: normal") Phoenix.PubSub.broadcast(Fishjam.PubSub, room_id, :room_stopped) Event.broadcast_server_notification({:room_deleted, room_id}) diff --git a/lib/fishjam_web/api_spec/component/HLS/S3.ex b/lib/fishjam_web/api_spec/component/HLS/S3.ex new file mode 100644 index 00000000..204c1b0d --- /dev/null +++ b/lib/fishjam_web/api_spec/component/HLS/S3.ex @@ -0,0 +1,32 @@ +defmodule FishjamWeb.ApiSpec.Component.HLS.S3 do + @moduledoc false + + require OpenApiSpex + alias OpenApiSpex.Schema + + OpenApiSpex.schema(%{ + title: "S3Credentials", + description: + "An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided", + type: :object, + properties: %{ + accessKeyId: %Schema{ + type: :string, + description: "An AWS access key identifier, linked to your AWS account." + }, + secretAccessKey: %Schema{ + type: :string, + description: "The secret key that is linked to the Access Key ID." + }, + region: %Schema{ + type: :string, + description: "The AWS region where your bucket is located." + }, + bucket: %Schema{ + type: :string, + description: "The name of the S3 bucket where your data will be stored." + } + }, + required: [:accessKeyId, :secretAccessKey, :region, :bucket] + }) +end diff --git a/lib/fishjam_web/api_spec/component/hls.ex b/lib/fishjam_web/api_spec/component/hls.ex index 861eebe2..fe75f28b 100644 --- a/lib/fishjam_web/api_spec/component/hls.ex +++ b/lib/fishjam_web/api_spec/component/hls.ex @@ -43,42 +43,12 @@ defmodule FishjamWeb.ApiSpec.Component.HLS do }) end - defmodule S3 do - @moduledoc false - - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "S3Credentials", - description: - "An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided", - type: :object, - properties: %{ - accessKeyId: %Schema{ - type: :string, - description: "An AWS access key identifier, linked to your AWS account." - }, - secretAccessKey: %Schema{ - type: :string, - description: "The secret key that is linked to the Access Key ID." - }, - region: %Schema{ - type: :string, - description: "The AWS region where your bucket is located." - }, - bucket: %Schema{ - type: :string, - description: "The name of the S3 bucket where your data will be stored." - } - }, - required: [:accessKeyId, :secretAccessKey, :region, :bucket] - }) - end - defmodule Options do @moduledoc false require OpenApiSpex + alias FishjamWeb.ApiSpec.Component.HLS.S3 + alias OpenApiSpex.Schema OpenApiSpex.schema(%{ title: "ComponentOptionsHLS", @@ -100,12 +70,7 @@ defmodule FishjamWeb.ApiSpec.Component.HLS do description: "Whether the video is stored after end of stream", default: false }, - s3: %Schema{ - type: :object, - description: "Credentials to AWS S3 bucket.", - oneOf: [S3], - nullable: true - }, + s3: S3.schema(), subscribeMode: %Schema{ type: :string, description: diff --git a/lib/fishjam_web/api_spec/component/recording.ex b/lib/fishjam_web/api_spec/component/recording.ex index c1c18751..b2b41978 100644 --- a/lib/fishjam_web/api_spec/component/recording.ex +++ b/lib/fishjam_web/api_spec/component/recording.ex @@ -44,12 +44,7 @@ defmodule FishjamWeb.ApiSpec.Component.Recording do default: nil, nullable: true }, - credentials: %Schema{ - type: :object, - description: "Credentials to AWS S3 bucket.", - oneOf: [S3], - nullable: true - }, + credentials: S3.schema(), subscribeMode: %Schema{ type: :string, description: diff --git a/lib/fishjam_web/peer_socket.ex b/lib/fishjam_web/peer_socket.ex index b00077a3..ed129b77 100644 --- a/lib/fishjam_web/peer_socket.ex +++ b/lib/fishjam_web/peer_socket.ex @@ -147,7 +147,7 @@ defmodule FishjamWeb.PeerSocket do @impl true def terminate(reason, _state) do - Logger.info("Peer socket terminates with reason #{reason}") + Logger.info("Peer socket terminates with reason #{inspect(reason)}") :ok end diff --git a/mix.exs b/mix.exs index d9212bb1..cf08b219 100644 --- a/mix.exs +++ b/mix.exs @@ -11,6 +11,8 @@ defmodule Fishjam.MixProject do aliases: aliases(), deps: deps(), dialyzer: dialyzer(), + # TODO: Remove once core fix bug + consolidate_protocols: false, # hex description: "Fishjam media server", @@ -52,7 +54,7 @@ defmodule Fishjam.MixProject do {:plug_cowboy, "~> 2.5"}, {:elixir_uuid, "~> 1.2"}, {:cors_plug, "~> 3.0"}, - {:open_api_spex, "~> 3.16"}, + {:open_api_spex, "~> 3.19"}, {:ymlr, "~> 3.0"}, {:bunch, "~> 1.6"}, {:logger_json, "~> 5.1"}, @@ -66,18 +68,34 @@ defmodule Fishjam.MixProject do {:protobuf, "~> 0.12.0"}, # Membrane deps - {:membrane_core, "1.1.0-rc0", override: true}, + {:membrane_core, "~> 1.1.0-rc1", override: true}, {:membrane_rtc_engine, - github: "fishjam-dev/membrane_rtc_engine", sparse: "engine", override: true}, + github: "fishjam-dev/membrane_rtc_engine", + sparse: "engine", + ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", + override: "true"}, {:membrane_rtc_engine_webrtc, - github: "fishjam-dev/membrane_rtc_engine", sparse: "webrtc", override: true}, + github: "fishjam-dev/membrane_rtc_engine", + sparse: "webrtc", + ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", + override: "true"}, {:membrane_rtc_engine_hls, "~> 0.7.0"}, - {:membrane_rtc_engine_recording, "~> 0.1.0"}, + {:membrane_rtc_engine_recording, + github: "fishjam-dev/membrane_rtc_engine", + sparse: "recording", + ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", + override: "true"}, {:membrane_rtc_engine_rtsp, - github: "fishjam-dev/membrane_rtc_engine", sparse: "rtsp", override: true}, + github: "fishjam-dev/membrane_rtc_engine", + sparse: "rtsp", + ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", + override: "true"}, {:membrane_rtc_engine_file, "~> 0.5.0"}, {:membrane_rtc_engine_sip, - github: "fishjam-dev/membrane_rtc_engine", sparse: "sip", override: true}, + github: "fishjam-dev/membrane_rtc_engine", + sparse: "sip", + ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", + override: "true"}, {:membrane_telemetry_metrics, "~> 0.1.0"}, # HLS endpoints deps diff --git a/mix.lock b/mix.lock index 0681f48e..02b6a61f 100644 --- a/mix.lock +++ b/mix.lock @@ -13,7 +13,7 @@ "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.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [: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", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, + "credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [: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", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, @@ -44,7 +44,7 @@ "membrane_audio_mix_plugin": {:hex, :membrane_audio_mix_plugin, "0.16.0", "34997707ee186683c6d7bd87572944e5e37c0249235cc44915d181d653c5c40e", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, 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_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", "a4a8c723f0da8d9cf9ac11bf657a732770ea0b8db4eff2efc16caa3a1819f435"}, "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.0-rc0", "ff24f9aa86c02014a778ce971d8b5ac25099351a71e444d2a7daa2ce8b9ddc68", [: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", "e552e6c0fcb8632a7f3dddca1f8250b68262ad536426adc641e029cc77a012ae"}, + "membrane_core": {:hex, :membrane_core, "1.1.0-rc1", "9492d890a718bbe104c3bbf142ad1b10d0ff51a7f68d1d3f617016688d25251e", [: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", "b438e08b76c025ed82a13e7a78861f185eab0e25eac35a3eccb0cb24301d6ee8"}, "membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.19.2", "c5e23f124f0b06283c1119525bf5d1fe595808fd6aeddf3b751c337499204910", [: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", "3c66264ee1d94c4d076f4e431014553a886730a909ad96a07714167656e0b8f2"}, "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.16.0", "7917f6682c22b9bcfc2ca20ed960eee0f7d03ad31fd5f59ed850f1fe3ddd545a", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "b0727998f75a9b4dab8a2baefdfc13c3eac00a04e061ab1b0e61dc5566927acc"}, "membrane_framerate_converter_plugin": {:hex, :membrane_framerate_converter_plugin, "0.8.0", "a6f89dc89bc41e23c70f6af96e447c0577d54c0f0457a8383b652650088cb864", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}], "hexpm", "15faf8267f4d0ea3f511085bec14b90e93b6140ea4047f419e5cfbdef1543c3c"}, @@ -67,13 +67,13 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "1732bdda81af4bbb7d4a239b904b9f073388dcd0", [sparse: "engine"]}, + "membrane_rtc_engine": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", [sparse: "engine", ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45"]}, "membrane_rtc_engine_file": {:hex, :membrane_rtc_engine_file, "0.5.0", "6936d803e9ffd01bddc4f7bbc5d2a5893bd8e60117f3ab7c0ec66ec7568cf1f4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_ogg_plugin, "~> 0.3.0", [hex: :membrane_ogg_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_realtimer_plugin, "~> 0.9.0", [hex: :membrane_realtimer_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}], "hexpm", "150e42a34baa6971d341b64d2ada06ee066d7fcca9aee9a73a5d6a09fe192c0e"}, "membrane_rtc_engine_hls": {:hex, :membrane_rtc_engine_hls, "0.7.0", "03a7797389c493c824c1262afaf8fe4e1314406ec93d5d99de11e5528c5094b1", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.5", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_audio_mix_plugin, "~> 0.16.0", [hex: :membrane_audio_mix_plugin, repo: "hexpm", optional: true]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.31.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_http_adaptive_stream_plugin, "~> 0.18.2", [hex: :membrane_http_adaptive_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_video_compositor_plugin, "~> 0.7.0", [hex: :membrane_video_compositor_plugin, repo: "hexpm", optional: true]}], "hexpm", "88b39725792f1fa41439ef1e8260d016b4eb9173eb8568a307769d3ddd721f52"}, - "membrane_rtc_engine_recording": {:hex, :membrane_rtc_engine_recording, "0.1.0", "49f01747a92755ee9a37925873ac4a54a36e6d7946685ae6260bd714bb9874b0", [:mix], [{:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: false]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_stream_plugin, "~> 0.4.0", [hex: :membrane_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.12.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: false]}], "hexpm", "1ba142c25757c1727f5d8f3588b40349c0b1cd227011cc1411a50bdb2fd79cb1"}, - "membrane_rtc_engine_rtsp": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "1732bdda81af4bbb7d4a239b904b9f073388dcd0", [sparse: "rtsp"]}, - "membrane_rtc_engine_sip": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "1732bdda81af4bbb7d4a239b904b9f073388dcd0", [sparse: "sip"]}, - "membrane_rtc_engine_webrtc": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "1732bdda81af4bbb7d4a239b904b9f073388dcd0", [sparse: "webrtc"]}, + "membrane_rtc_engine_recording": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", [sparse: "recording", ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45"]}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", [sparse: "rtsp", ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", [sparse: "sip", ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", [sparse: "webrtc", ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45"]}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.1", "8c61b3d2968e54e1b459a42f070ea71f597056eba4059df780eaa8da8aee6035", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "fb2bb5d4ed18f38523851cb449a2c78ed533e58028dc058342b8cfd659f812d5"}, @@ -89,20 +89,20 @@ "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.4.0", "6c29ec67479edfbab27b11266dc92f18f3baf4421262c5c31af348c33e5b92c7", [:mix], [], "hexpm", "8bb005ede61db8fcb3535a883f32168b251c2dfd1109197c8c3b39ce28ed08e2"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"}, "mockery": {:hex, :mockery, "2.3.1", "a02fd60b10ac9ed37a7a2ecf6786c1f1dd5c75d2b079a60594b089fba32dc087", [:mix], [], "hexpm", "1d0971d88ebf084e962da3f2cfee16f0ea8e04ff73a7710428500d4500b947fa"}, "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, - "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "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"}, - "open_api_spex": {:hex, :open_api_spex, "3.18.3", "fefb84fe323cacfc92afdd0ecb9e89bc0261ae00b7e3167ffc2028ce3944de42", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "c0cfc31570199ce7e7520b494a591027da609af45f6bf9adce51e2469b1609fb"}, + "open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"}, "p1_utils": {:hex, :p1_utils, "1.0.23", "7f94466ada69bd982ea7bb80fbca18e7053e7d0b82c9d9e37621fa508587069b", [:rebar3], [], "hexpm", "47f21618694eeee5006af1c88731ad86b757161e7823c29b6f73921b571c8502"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [: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", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"}, - "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [: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]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{: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", [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]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, diff --git a/openapi.yaml b/openapi.yaml index 5d0a784f..81eff8d8 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -155,11 +155,28 @@ components: description: Options specific to the Recording component properties: credentials: - description: Credentials to AWS S3 bucket. - nullable: true - oneOf: - - $ref: '#/components/schemas/S3Credentials' + description: An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided + properties: + accessKeyId: + description: An AWS access key identifier, linked to your AWS account. + type: string + bucket: + description: The name of the S3 bucket where your data will be stored. + type: string + region: + description: The AWS region where your bucket is located. + type: string + secretAccessKey: + description: The secret key that is linked to the Access Key ID. + type: string + required: + - accessKeyId + - secretAccessKey + - region + - bucket + title: S3Credentials type: object + x-struct: Elixir.FishjamWeb.ApiSpec.Component.HLS.S3 pathPrefix: description: Path prefix under which all recording are stored nullable: true @@ -425,11 +442,28 @@ components: description: Whether the video is stored after end of stream type: boolean s3: - description: Credentials to AWS S3 bucket. - nullable: true - oneOf: - - $ref: '#/components/schemas/S3Credentials' + description: An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided + properties: + accessKeyId: + description: An AWS access key identifier, linked to your AWS account. + type: string + bucket: + description: The name of the S3 bucket where your data will be stored. + type: string + region: + description: The AWS region where your bucket is located. + type: string + secretAccessKey: + description: The secret key that is linked to the Access Key ID. + type: string + required: + - accessKeyId + - secretAccessKey + - region + - bucket + title: S3Credentials type: object + x-struct: Elixir.FishjamWeb.ApiSpec.Component.HLS.S3 subscribeMode: default: auto description: Whether the HLS component should subscribe to tracks automatically or manually. diff --git a/test/fishjam/component/hls/manager_test.exs b/test/fishjam/component/hls/manager_test.exs index 10ccf482..e2cf4432 100644 --- a/test/fishjam/component/hls/manager_test.exs +++ b/test/fishjam/component/hls/manager_test.exs @@ -24,7 +24,9 @@ defmodule Fishjam.Component.HLS.ManagerTest do File.mkdir_p!(hls_dir) for filename <- @files, do: :ok = hls_dir |> Path.join(filename) |> File.touch!() - on_exit(fn -> File.rm_rf!(hls_dir) end) + on_exit(fn -> + File.rm_rf!(hls_dir) + end) {:ok, %{room_id: room_id, hls_dir: hls_dir, options: options}} end @@ -37,7 +39,6 @@ defmodule Fishjam.Component.HLS.ManagerTest do hls_dir: hls_dir, options: options } do - MockManager.http_mock_expect(0, status_code: 200) pid = MockManager.start_mock_engine() {:ok, manager} = Manager.start(room_id, pid, hls_dir, options) @@ -50,12 +51,11 @@ defmodule Fishjam.Component.HLS.ManagerTest do end test "Spawn manager with credentials", %{room_id: room_id, hls_dir: hls_dir, options: options} do - MockManager.http_mock_expect(4, status_code: 200) + MockManager.http_mock_stub(status_code: 200) pid = MockManager.start_mock_engine() {:ok, manager} = Manager.start(room_id, pid, hls_dir, %{options | s3: @s3_credentials}) ref = Process.monitor(manager) - MockManager.kill_mock_engine(pid) assert_receive {:DOWN, ^ref, :process, ^manager, :normal} diff --git a/test/fishjam_web/controllers/component/recording_component_test.exs b/test/fishjam_web/controllers/component/recording_component_test.exs index 590229ff..ebc82777 100644 --- a/test/fishjam_web/controllers/component/recording_component_test.exs +++ b/test/fishjam_web/controllers/component/recording_component_test.exs @@ -18,6 +18,8 @@ defmodule FishjamWeb.Component.RecordingComponentTest do setup_all do Application.put_env(:fishjam, :components_used, [Fishjam.Component.Recording]) + mock_http_request() + on_exit(fn -> Application.put_env(:fishjam, :components_used, []) end) @@ -28,8 +30,6 @@ defmodule FishjamWeb.Component.RecordingComponentTest do setup :set_mox_from_context test "renders component with required options", %{conn: conn, room_id: room_id} do - mock_http_request() - conn = post(conn, ~p"/room/#{room_id}/component", type: "recording", @@ -58,6 +58,8 @@ defmodule FishjamWeb.Component.RecordingComponentTest do assert model_response(conn, :bad_request, "Error")["errors"] == "Reached recording components limit in room #{room_id}" + + assert_component_removed(conn, room_id, id) end setup :set_mox_from_context @@ -66,7 +68,6 @@ defmodule FishjamWeb.Component.RecordingComponentTest do conn: conn, room_id: room_id } do - mock_http_request() put_s3_envs(path_prefix: nil, credentials: @s3_credentials) conn = @@ -91,6 +92,7 @@ defmodule FishjamWeb.Component.RecordingComponentTest do assert String.starts_with?(path_prefix, prefix) assert_component_created(conn, room_id, id, "recording") + assert_component_removed(conn, room_id, id) clean_s3_envs() end @@ -100,7 +102,6 @@ defmodule FishjamWeb.Component.RecordingComponentTest do conn: conn, room_id: room_id } do - mock_http_request() put_s3_envs(path_prefix: nil, credentials: @s3_credentials) conn = @@ -125,9 +126,7 @@ defmodule FishjamWeb.Component.RecordingComponentTest do assert String.starts_with?(path_prefix1, prefix) assert_component_created(conn, room_id, id, "recording") - - conn = delete(conn, ~p"/room/#{room_id}/component/#{id}") - assert response(conn, :no_content) + assert_component_removed(conn, room_id, id) # Second recording conn = @@ -153,7 +152,7 @@ defmodule FishjamWeb.Component.RecordingComponentTest do assert String.starts_with?(path_prefix2, prefix) assert path_prefix1 != path_prefix2 - + assert_component_removed(conn, room_id, id) clean_s3_envs() end @@ -161,7 +160,6 @@ defmodule FishjamWeb.Component.RecordingComponentTest do conn: conn, room_id: room_id } do - mock_http_request() put_s3_envs(path_prefix: @path_prefix, credentials: nil) conn = @@ -185,7 +183,7 @@ defmodule FishjamWeb.Component.RecordingComponentTest do assert String.starts_with?(path_prefix, @path_prefix) assert_component_created(conn, room_id, id, "recording") - + assert_component_removed(conn, room_id, id) clean_s3_envs() end @@ -255,11 +253,7 @@ defmodule FishjamWeb.Component.RecordingComponentTest do end defp mock_http_request() do - expect(ExAws.Request.HttpMock, :request, 4, fn _method, - _url, - _req_body, - _headers, - _http_opts -> + stub(ExAws.Request.HttpMock, :request, fn _method, _url, _req_body, _headers, _http_opts -> {:ok, %{status_code: 200, headers: %{}}} end) end @@ -289,4 +283,11 @@ defmodule FishjamWeb.Component.RecordingComponentTest do path_prefix end + + defp assert_component_removed(conn, room_id, component_id) do + conn = delete(conn, ~p"/room/#{room_id}/component/#{component_id}") + assert response(conn, :no_content) + + conn + end end diff --git a/test/fishjam_web/controllers/room_controller_test.exs b/test/fishjam_web/controllers/room_controller_test.exs index 35fda7eb..16a0fa6a 100644 --- a/test/fishjam_web/controllers/room_controller_test.exs +++ b/test/fishjam_web/controllers/room_controller_test.exs @@ -49,7 +49,9 @@ defmodule FishjamWeb.RoomControllerTest do Klotho.Mock.reset() Klotho.Mock.freeze() - on_exit(fn -> delete_all_rooms() end) + on_exit(fn -> + delete_all_rooms() + end) [conn: conn] end @@ -253,17 +255,18 @@ defmodule FishjamWeb.RoomControllerTest do end test "does not happen if peers rejoined quickly", %{conn: conn, id: id} do + timeout = 250 conn = post(conn, ~p"/room/#{id}/peer", type: "webrtc") assert %{"token" => token} = json_response(conn, :created)["data"] ws = connect_peer(token) - Klotho.Mock.warp_by(@purge_timeout_ms + 10) + Klotho.Mock.warp_by(@purge_timeout_ms + timeout) conn = get(conn, ~p"/room/#{id}") assert response(conn, :ok) GenServer.stop(ws) - Process.sleep(10) + Process.sleep(timeout) conn = get(conn, ~p"/room/#{id}") assert %{"status" => "disconnected"} = @@ -274,7 +277,7 @@ defmodule FishjamWeb.RoomControllerTest do assert response(conn, :ok) connect_peer(token) - Klotho.Mock.warp_by(@purge_timeout_ms + 10) + Klotho.Mock.warp_by(@purge_timeout_ms + timeout) conn = get(conn, ~p"/room/#{id}") assert response(conn, :ok) end @@ -466,6 +469,7 @@ defmodule FishjamWeb.RoomControllerTest do :erlang.trace(Process.whereis(RoomService), true, [:receive]) + assert Process.alive?(engine_pid) assert true = Process.exit(room_pid, :error) assert_receive({:trace, _pid, :receive, {:DOWN, _ref, :process, ^room_pid, :error}}) diff --git a/test/fishjam_web/controllers/subscription_controller_test.exs b/test/fishjam_web/controllers/subscription_controller_test.exs index 799263ef..8c2af05b 100644 --- a/test/fishjam_web/controllers/subscription_controller_test.exs +++ b/test/fishjam_web/controllers/subscription_controller_test.exs @@ -137,10 +137,12 @@ defmodule FishjamWeb.SubscriptionControllerTest do describe "recording endpoint tests" do test "returns error when subscribe mode is :auto", %{conn: conn, room_id: room_id} do + MockManager.http_mock_stub(status_code: 200) + conn = post(conn, ~p"/room/#{room_id}/component", type: "recording", - options: %{credentials: Enum.into(@s3_credentials, %{}), subscribeMode: "auto"} + options: %{credentials: @s3_credentials, subscribeMode: "auto"} ) assert %{ @@ -159,10 +161,12 @@ defmodule FishjamWeb.SubscriptionControllerTest do end test "return success when subscribe mode is :manual", %{conn: conn, room_id: room_id} do + MockManager.http_mock_stub(status_code: 200) + conn = post(conn, ~p"/room/#{room_id}/component", type: "recording", - options: %{credentials: Enum.into(@s3_credentials, %{}), subscribeMode: "manual"} + options: %{credentials: @s3_credentials, subscribeMode: "manual"} ) assert %{ diff --git a/test/fishjam_web/integration/peer_socket_test.exs b/test/fishjam_web/integration/peer_socket_test.exs index 684c1546..634a4267 100644 --- a/test/fishjam_web/integration/peer_socket_test.exs +++ b/test/fishjam_web/integration/peer_socket_test.exs @@ -178,13 +178,26 @@ defmodule FishjamWeb.Integration.PeerSocketTest do "fishjam_rooms" => "1" } - Process.sleep(1_000) - - metrics_to_compare = get_peers_room_metrics() - - for {k, v} <- metrics_after_one_tick do - assert Map.fetch!(metrics_to_compare, k) == v - end + assert Enum.reduce_while(0..15, false, fn _num, _acc -> + Process.sleep(100) + metrics_to_compare = get_peers_room_metrics() + + all_metrics_present? = + Enum.all?(metrics_after_one_tick, fn {k, _v} -> + is_map_key(metrics_to_compare, k) + end) + + if all_metrics_present? do + for {k, v} <- metrics_after_one_tick do + assert Map.fetch!(metrics_to_compare, k) == v + end + + {:halt, true} + else + {:cont, false} + end + end), + "Metrics aren't present after 1.5 seconds" conn = delete(conn, ~p"/room/#{room_id}/") response(conn, :no_content) diff --git a/test/fishjam_web/integration/server_notification_test.exs b/test/fishjam_web/integration/server_notification_test.exs index 6f5505ad..726c118d 100644 --- a/test/fishjam_web/integration/server_notification_test.exs +++ b/test/fishjam_web/integration/server_notification_test.exs @@ -345,7 +345,8 @@ defmodule FishjamWeb.Integration.ServerNotificationTest do room_id: ^room_id, peer_id: ^peer_id, metadata: ^metadata_encoded - } = peer_metadata_updated + } = peer_metadata_updated, + 1_000 assert_receive {:webhook_notification, ^peer_metadata_updated}, 1_000 end @@ -384,9 +385,10 @@ defmodule FishjamWeb.Integration.ServerNotificationTest do type: :TRACK_TYPE_VIDEO, metadata: "\"myvideo\"" } = track_info - } = track_added + } = track_added, + 1_000 - assert_receive {:webhook_notification, ^track_added}, 1000 + assert_receive {:webhook_notification, ^track_added} GenServer.stop(peer_ws) diff --git a/test/support/adapter.ex b/test/support/adapter.ex new file mode 100644 index 00000000..9640a053 --- /dev/null +++ b/test/support/adapter.ex @@ -0,0 +1,16 @@ +defmodule Fishjam.Adapter do + @moduledoc false + + @behaviour ExAws.Config.AuthCache.AuthConfigAdapter + + @config %{ + access_key_id: "AKIAIOSFODNN7EXAMPLE", + secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + region: "us-east-1" + } + + @impl true + def adapt_auth_config(_config, _profile, _expiration) do + @config + end +end diff --git a/test/support/mock_manager.ex b/test/support/mock_manager.ex index 5e86f335..29150459 100644 --- a/test/support/mock_manager.ex +++ b/test/support/mock_manager.ex @@ -13,6 +13,12 @@ defmodule MockManager do end) end + def http_mock_stub(status_code: status_code) do + stub(ExAws.Request.HttpMock, :request, fn _method, _url, _req_body, _headers, _http_opts -> + {:ok, %{status_code: status_code, body: %{}}} + end) + end + def start_mock_engine(), do: spawn(fn -> From b76e35b77f1c295b6ebd428ab8b2dcd99d3bebaa Mon Sep 17 00:00:00 2001 From: Karol Konkol <56369082+Karolk99@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:39:50 +0200 Subject: [PATCH 45/51] Update rtc engine (#208) --- mix.exs | 16 +++++----------- mix.lock | 10 +++++----- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/mix.exs b/mix.exs index cf08b219..ad7a08f7 100644 --- a/mix.exs +++ b/mix.exs @@ -72,30 +72,24 @@ defmodule Fishjam.MixProject do {:membrane_rtc_engine, github: "fishjam-dev/membrane_rtc_engine", sparse: "engine", - ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", + ref: "12e0c1c", override: "true"}, {:membrane_rtc_engine_webrtc, github: "fishjam-dev/membrane_rtc_engine", sparse: "webrtc", - ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", + ref: "12e0c1c", override: "true"}, {:membrane_rtc_engine_hls, "~> 0.7.0"}, {:membrane_rtc_engine_recording, github: "fishjam-dev/membrane_rtc_engine", sparse: "recording", - ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", + ref: "12e0c1c", override: "true"}, {:membrane_rtc_engine_rtsp, - github: "fishjam-dev/membrane_rtc_engine", - sparse: "rtsp", - ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", - override: "true"}, + github: "fishjam-dev/membrane_rtc_engine", sparse: "rtsp", ref: "12e0c1c", override: "true"}, {:membrane_rtc_engine_file, "~> 0.5.0"}, {:membrane_rtc_engine_sip, - github: "fishjam-dev/membrane_rtc_engine", - sparse: "sip", - ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", - override: "true"}, + github: "fishjam-dev/membrane_rtc_engine", sparse: "sip", ref: "12e0c1c", override: "true"}, {:membrane_telemetry_metrics, "~> 0.1.0"}, # HLS endpoints deps diff --git a/mix.lock b/mix.lock index 02b6a61f..c14599c3 100644 --- a/mix.lock +++ b/mix.lock @@ -67,13 +67,13 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", [sparse: "engine", ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45"]}, + "membrane_rtc_engine": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "12e0c1c4264a05a15fec6369d7e471fb2f3013b9", [sparse: "engine", ref: "12e0c1c"]}, "membrane_rtc_engine_file": {:hex, :membrane_rtc_engine_file, "0.5.0", "6936d803e9ffd01bddc4f7bbc5d2a5893bd8e60117f3ab7c0ec66ec7568cf1f4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_ogg_plugin, "~> 0.3.0", [hex: :membrane_ogg_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_realtimer_plugin, "~> 0.9.0", [hex: :membrane_realtimer_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}], "hexpm", "150e42a34baa6971d341b64d2ada06ee066d7fcca9aee9a73a5d6a09fe192c0e"}, "membrane_rtc_engine_hls": {:hex, :membrane_rtc_engine_hls, "0.7.0", "03a7797389c493c824c1262afaf8fe4e1314406ec93d5d99de11e5528c5094b1", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.5", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_audio_mix_plugin, "~> 0.16.0", [hex: :membrane_audio_mix_plugin, repo: "hexpm", optional: true]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.31.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_http_adaptive_stream_plugin, "~> 0.18.2", [hex: :membrane_http_adaptive_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_video_compositor_plugin, "~> 0.7.0", [hex: :membrane_video_compositor_plugin, repo: "hexpm", optional: true]}], "hexpm", "88b39725792f1fa41439ef1e8260d016b4eb9173eb8568a307769d3ddd721f52"}, - "membrane_rtc_engine_recording": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", [sparse: "recording", ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45"]}, - "membrane_rtc_engine_rtsp": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", [sparse: "rtsp", ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45"]}, - "membrane_rtc_engine_sip": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", [sparse: "sip", ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45"]}, - "membrane_rtc_engine_webrtc": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "a3786d2f4a2b4171bfaf7ffcfd79528845762b45", [sparse: "webrtc", ref: "a3786d2f4a2b4171bfaf7ffcfd79528845762b45"]}, + "membrane_rtc_engine_recording": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "12e0c1c4264a05a15fec6369d7e471fb2f3013b9", [sparse: "recording", ref: "12e0c1c"]}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "12e0c1c4264a05a15fec6369d7e471fb2f3013b9", [sparse: "rtsp", ref: "12e0c1c"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "12e0c1c4264a05a15fec6369d7e471fb2f3013b9", [sparse: "sip", ref: "12e0c1c"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "12e0c1c4264a05a15fec6369d7e471fb2f3013b9", [sparse: "webrtc", ref: "12e0c1c"]}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.1", "8c61b3d2968e54e1b459a42f070ea71f597056eba4059df780eaa8da8aee6035", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "fb2bb5d4ed18f38523851cb449a2c78ed533e58028dc058342b8cfd659f812d5"}, From e38b88b8c6bb19df2c37d284183f17884bfbf1bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szuma?= <56085570+Rados13@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:40:48 +0200 Subject: [PATCH 46/51] Allow S3 openapi to be nullable (#209) --- lib/fishjam_web/api_spec/component/HLS/S3.ex | 3 ++- openapi.yaml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/fishjam_web/api_spec/component/HLS/S3.ex b/lib/fishjam_web/api_spec/component/HLS/S3.ex index 204c1b0d..00478775 100644 --- a/lib/fishjam_web/api_spec/component/HLS/S3.ex +++ b/lib/fishjam_web/api_spec/component/HLS/S3.ex @@ -27,6 +27,7 @@ defmodule FishjamWeb.ApiSpec.Component.HLS.S3 do description: "The name of the S3 bucket where your data will be stored." } }, - required: [:accessKeyId, :secretAccessKey, :region, :bucket] + required: [:accessKeyId, :secretAccessKey, :region, :bucket], + nullable: true }) end diff --git a/openapi.yaml b/openapi.yaml index 81eff8d8..3d8c9cc2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -156,6 +156,7 @@ components: properties: credentials: description: An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided + nullable: true properties: accessKeyId: description: An AWS access key identifier, linked to your AWS account. @@ -443,6 +444,7 @@ components: type: boolean s3: description: An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided + nullable: true properties: accessKeyId: description: An AWS access key identifier, linked to your AWS account. @@ -500,6 +502,7 @@ components: x-struct: Elixir.FishjamWeb.ApiSpec.Component.SIP.SIPCredentials S3Credentials: description: An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided + nullable: true properties: accessKeyId: description: An AWS access key identifier, linked to your AWS account. From c79cd28b7f642881c3d837d19aa809950b53793c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Ko=C5=82odziej?= Date: Mon, 10 Jun 2024 09:22:29 +0200 Subject: [PATCH 47/51] chore: purge logs emitted from RCTP about sending FIR (#207) * chore: purge logs emitted from RCTP about sending FIR --- config/config.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index a6c2d523..fb589118 100644 --- a/config/config.exs +++ b/config/config.exs @@ -37,7 +37,8 @@ config :logger, [application: :membrane_rtc_engine_rtsp, level_lower_than: :warning], [application: :membrane_rtc_engine_file, level_lower_than: :warning], [application: :membrane_rtc_engine_sip, level_lower_than: :warning], - [module: Membrane.SRTP.Encryptor, level_lower_than: :error] + [module: Membrane.SRTP.Encryptor, level_lower_than: :error], + [module: Membrane.RTCP.Receiver, level_lower_than: :warning] ] config :ex_aws, From c8b4fdd6da9a4da0a8699c3f80fee8fe41c9f819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Ko=C5=82odziej?= Date: Mon, 10 Jun 2024 09:54:28 +0200 Subject: [PATCH 48/51] chore: update membrane rtc engine (#210) --- mix.exs | 16 +++++++++++----- mix.lock | 10 +++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/mix.exs b/mix.exs index ad7a08f7..7d13c10d 100644 --- a/mix.exs +++ b/mix.exs @@ -72,24 +72,30 @@ defmodule Fishjam.MixProject do {:membrane_rtc_engine, github: "fishjam-dev/membrane_rtc_engine", sparse: "engine", - ref: "12e0c1c", + ref: "dc3ffd0051dd3aec6de142076ac01a5f79fe846a", override: "true"}, {:membrane_rtc_engine_webrtc, github: "fishjam-dev/membrane_rtc_engine", sparse: "webrtc", - ref: "12e0c1c", + ref: "dc3ffd0051dd3aec6de142076ac01a5f79fe846a", override: "true"}, {:membrane_rtc_engine_hls, "~> 0.7.0"}, {:membrane_rtc_engine_recording, github: "fishjam-dev/membrane_rtc_engine", sparse: "recording", - ref: "12e0c1c", + ref: "dc3ffd0051dd3aec6de142076ac01a5f79fe846a", override: "true"}, {:membrane_rtc_engine_rtsp, - github: "fishjam-dev/membrane_rtc_engine", sparse: "rtsp", ref: "12e0c1c", override: "true"}, + github: "fishjam-dev/membrane_rtc_engine", + sparse: "rtsp", + ref: "dc3ffd0051dd3aec6de142076ac01a5f79fe846a", + override: "true"}, {:membrane_rtc_engine_file, "~> 0.5.0"}, {:membrane_rtc_engine_sip, - github: "fishjam-dev/membrane_rtc_engine", sparse: "sip", ref: "12e0c1c", override: "true"}, + github: "fishjam-dev/membrane_rtc_engine", + sparse: "sip", + ref: "dc3ffd0051dd3aec6de142076ac01a5f79fe846a", + override: "true"}, {:membrane_telemetry_metrics, "~> 0.1.0"}, # HLS endpoints deps diff --git a/mix.lock b/mix.lock index c14599c3..88a7787e 100644 --- a/mix.lock +++ b/mix.lock @@ -67,13 +67,13 @@ "membrane_raw_audio_parser_plugin": {:hex, :membrane_raw_audio_parser_plugin, "0.4.0", "7a1e53b68a221d00e47fb5d3c7e29200dfe8f7bc0862e69000b61c6562093acc", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}], "hexpm", "ff8d3fba45b1c2814b68d49878f19d2c1ad1147b53f606b48b6b67068435dcd0"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.3.0", "ba10f475e0814a6fe79602a74536b796047577c7ef5b0e33def27cd344229699", [:mix], [], "hexpm", "2f08760061c8a5386ecf04273480f10e48d25a1a40aa99476302b0bcd34ccb1c"}, "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_rtc_engine": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "12e0c1c4264a05a15fec6369d7e471fb2f3013b9", [sparse: "engine", ref: "12e0c1c"]}, + "membrane_rtc_engine": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "dc3ffd0051dd3aec6de142076ac01a5f79fe846a", [sparse: "engine", ref: "dc3ffd0051dd3aec6de142076ac01a5f79fe846a"]}, "membrane_rtc_engine_file": {:hex, :membrane_rtc_engine_file, "0.5.0", "6936d803e9ffd01bddc4f7bbc5d2a5893bd8e60117f3ab7c0ec66ec7568cf1f4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.16.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_ogg_plugin, "~> 0.3.0", [hex: :membrane_ogg_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_realtimer_plugin, "~> 0.9.0", [hex: :membrane_realtimer_plugin, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}], "hexpm", "150e42a34baa6971d341b64d2ada06ee066d7fcca9aee9a73a5d6a09fe192c0e"}, "membrane_rtc_engine_hls": {:hex, :membrane_rtc_engine_hls, "0.7.0", "03a7797389c493c824c1262afaf8fe4e1314406ec93d5d99de11e5528c5094b1", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.5", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.18.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_audio_mix_plugin, "~> 0.16.0", [hex: :membrane_audio_mix_plugin, repo: "hexpm", optional: true]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.31.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_http_adaptive_stream_plugin, "~> 0.18.2", [hex: :membrane_http_adaptive_stream_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.19.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:membrane_rtc_engine, "~> 0.22.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.8.0", [hex: :membrane_rtc_engine_webrtc, repo: "hexpm", optional: false]}, {:membrane_video_compositor_plugin, "~> 0.7.0", [hex: :membrane_video_compositor_plugin, repo: "hexpm", optional: true]}], "hexpm", "88b39725792f1fa41439ef1e8260d016b4eb9173eb8568a307769d3ddd721f52"}, - "membrane_rtc_engine_recording": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "12e0c1c4264a05a15fec6369d7e471fb2f3013b9", [sparse: "recording", ref: "12e0c1c"]}, - "membrane_rtc_engine_rtsp": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "12e0c1c4264a05a15fec6369d7e471fb2f3013b9", [sparse: "rtsp", ref: "12e0c1c"]}, - "membrane_rtc_engine_sip": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "12e0c1c4264a05a15fec6369d7e471fb2f3013b9", [sparse: "sip", ref: "12e0c1c"]}, - "membrane_rtc_engine_webrtc": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "12e0c1c4264a05a15fec6369d7e471fb2f3013b9", [sparse: "webrtc", ref: "12e0c1c"]}, + "membrane_rtc_engine_recording": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "dc3ffd0051dd3aec6de142076ac01a5f79fe846a", [sparse: "recording", ref: "dc3ffd0051dd3aec6de142076ac01a5f79fe846a"]}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "dc3ffd0051dd3aec6de142076ac01a5f79fe846a", [sparse: "rtsp", ref: "dc3ffd0051dd3aec6de142076ac01a5f79fe846a"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "dc3ffd0051dd3aec6de142076ac01a5f79fe846a", [sparse: "sip", ref: "dc3ffd0051dd3aec6de142076ac01a5f79fe846a"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/fishjam-dev/membrane_rtc_engine.git", "dc3ffd0051dd3aec6de142076ac01a5f79fe846a", [sparse: "webrtc", ref: "dc3ffd0051dd3aec6de142076ac01a5f79fe846a"]}, "membrane_rtp_format": {:hex, :membrane_rtp_format, "0.8.0", "828924bbd27efcf85b2015ae781e824c4a9928f0a7dc132abc66817b2c6edfc4", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bc75d2a649dfaef6df563212fbb9f9f62eebc871393692f9dae8d289bd4f94bb"}, "membrane_rtp_g711_plugin": {:hex, :membrane_rtp_g711_plugin, "0.2.0", "ce8dcd5c7dedec60f02778dec06a94fbcdc2be1c527d1ec99abebd904e39a8b3", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_g711_format, "~> 0.1.0", [hex: :membrane_g711_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "a852ff1e2619d832a0a6ffd3829853a44367c02398b67eca9bfa00b649ae005e"}, "membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.19.1", "8c61b3d2968e54e1b459a42f070ea71f597056eba4059df780eaa8da8aee6035", [: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.8.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "fb2bb5d4ed18f38523851cb449a2c78ed533e58028dc058342b8cfd659f812d5"}, From 09dd6a1436ac8af8b909a1d52f9ad613067f0c93 Mon Sep 17 00:00:00 2001 From: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> Date: Fri, 14 Jun 2024 16:03:50 +0200 Subject: [PATCH 49/51] [RTC-543] Request routing first iteration (#212) * [RTC-543] Request routing first iteration * Add integration tests, review fixes * fix types * fix more types * Increase timeout * Review fixes vol.2 * Review fixes vol.3 * Review fixes vol.4 --- .circleci/config.yml | 1 + Dockerfile | 8 +- config/runtime.exs | 4 +- config/test.exs | 3 +- docker-compose-dns.yaml | 35 +- docker-compose-epmd.yaml | 35 +- lib/fishjam/application.ex | 3 +- lib/fishjam/cluster/room.ex | 58 ++ lib/fishjam/cluster/room_service.ex | 69 ++ lib/fishjam/component.ex | 2 +- lib/fishjam/component/hls.ex | 7 +- .../component/hls/cluster/request_handler.ex | 39 ++ lib/fishjam/component/hls/ets_helper.ex | 22 +- lib/fishjam/component/hls/ll_storage.ex | 7 +- .../component/hls/local/request_handler.ex | 315 +++++++++ lib/fishjam/component/hls/manager.ex | 6 +- lib/fishjam/component/hls/recording.ex | 10 +- lib/fishjam/component/hls/request_handler.ex | 320 +-------- lib/fishjam/feature_flags.ex | 19 +- lib/fishjam/local/room.ex | 587 ++++++++++++++++ lib/fishjam/local/room_service.ex | 236 +++++++ lib/fishjam/resource_manager.ex | 2 +- lib/fishjam/room.ex | 628 ++---------------- lib/fishjam/room/id.ex | 50 +- lib/fishjam/room/state.ex | 10 +- lib/fishjam/room_service.ex | 258 +------ lib/fishjam/rpc_client.ex | 10 +- .../controllers/component_controller.ex | 24 +- .../controllers/fallback_controller.ex | 17 + .../controllers/hls_content_controller.ex | 26 +- .../controllers/local_room_controller.ex | 32 + .../controllers/peer_controller.ex | 32 +- .../recording_content_controller.ex | 2 +- .../controllers/recording_controller.ex | 3 +- .../controllers/room_controller.ex | 32 +- .../controllers/sip_call_controller.ex | 21 +- .../controllers/subscription_controller.ex | 12 +- lib/fishjam_web/peer_socket.ex | 2 +- lib/fishjam_web/router.ex | 6 + openapi.yaml | 88 ++- test/fishjam/cluster/api_test.exs | 241 +++++++ test/fishjam/cluster/load_balancing_test.exs | 91 --- .../fishjam/component/hls/ll_storage_test.exs | 3 +- .../component/hls/request_handler_test.exs | 3 +- test/fishjam/room/id_test.exs | 21 +- .../component/hls_component_test.exs | 2 +- .../component/recording_component_test.exs | 2 +- .../controllers/component_controller_test.exs | 6 +- .../controllers/dial_controller_test.exs | 7 +- .../controllers/hls_controller_test.exs | 8 +- .../controllers/peer_controller_test.exs | 6 +- .../controllers/room_controller_test.exs | 56 +- .../subscription_controller_test.exs | 8 +- .../integration/peer_socket_test.exs | 2 +- .../integration/server_notification_test.exs | 11 +- test/support/component_case.ex | 2 +- 56 files changed, 2089 insertions(+), 1421 deletions(-) create mode 100644 lib/fishjam/cluster/room.ex create mode 100644 lib/fishjam/cluster/room_service.ex create mode 100644 lib/fishjam/component/hls/cluster/request_handler.ex create mode 100644 lib/fishjam/component/hls/local/request_handler.ex create mode 100644 lib/fishjam/local/room.ex create mode 100644 lib/fishjam/local/room_service.ex create mode 100644 lib/fishjam_web/controllers/local_room_controller.ex create mode 100644 test/fishjam/cluster/api_test.exs delete mode 100644 test/fishjam/cluster/load_balancing_test.exs diff --git a/.circleci/config.yml b/.circleci/config.yml index 2663aea9..7bed151c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,6 +30,7 @@ jobs: - checkout - run: mix deps.get - run: mix coveralls.json --warnings-as-errors + - run: FJ_FEATURE_FLAG_REQUEST_ROUTING_ENABLED=true mix coveralls.json --warnings-as-errors - codecov/upload check_api_update: diff --git a/Dockerfile b/Dockerfile index c6e494ad..19e53aa8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,8 @@ ENV PATH="/root/.cargo/bin:${PATH}" RUN mix local.hex --force && \ mix local.rebar --force -ENV MIX_ENV=prod +ARG MIX_ENV=prod +ENV MIX_ENV=$MIX_ENV # The order of the following commands is important. # It ensures that: @@ -51,6 +52,9 @@ RUN mix release FROM alpine:3.17 AS app +ARG MIX_ENV=prod +ENV MIX_ENV=$MIX_ENV + ARG FJ_GIT_COMMIT ENV FJ_GIT_COMMIT=$FJ_GIT_COMMIT @@ -122,7 +126,7 @@ RUN mkdir ${FJ_RESOURCES_BASE_PATH} && chown fishjam:fishjam ${FJ_RESOURCES_BASE RUN mkdir ${FJ_RESOURCES_BASE_PATH}/file_component_sources \ && chown fishjam:fishjam ${FJ_RESOURCES_BASE_PATH}/file_component_sources -COPY --from=build /app/_build/prod/rel/fishjam ./ +COPY --from=build /app/_build/${MIX_ENV}/rel/fishjam ./ COPY docker-entrypoint.sh ./docker-entrypoint.sh RUN chmod +x docker-entrypoint.sh diff --git a/config/runtime.exs b/config/runtime.exs index 16fe659c..b439a73f 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -90,8 +90,8 @@ end config :fishjam, feature_flags: [ - custom_room_name_disabled: - ConfigReader.read_boolean("FJ_FEATURE_FLAG_CUSTOM_ROOM_NAME_DISABLED") || false + request_routing_enabled?: + ConfigReader.read_boolean("FJ_FEATURE_FLAG_REQUEST_ROUTING_ENABLED") || false ] check_origin = ConfigReader.read_check_origin("FJ_CHECK_ORIGIN") diff --git a/config/test.exs b/config/test.exs index 029b6529..6a52c3cc 100644 --- a/config/test.exs +++ b/config/test.exs @@ -5,7 +5,8 @@ config :fishjam, port: 4002, server_api_token: "development", webrtc_metrics_scrape_interval: 50, - room_metrics_scrape_interval: 1 + room_metrics_scrape_interval: 1, + test_routes: true config :fishjam, FishjamWeb.Endpoint, http: [ip: {127, 0, 0, 1}, port: 4002], diff --git a/docker-compose-dns.yaml b/docker-compose-dns.yaml index 97ed1f14..71809b09 100644 --- a/docker-compose-dns.yaml +++ b/docker-compose-dns.yaml @@ -1,31 +1,44 @@ version: "3" x-fishjam-template: &fishjam-template - build: . + build: + context: . + args: + - MIX_ENV=test environment: &fishjam-environment FJ_SERVER_API_TOKEN: "development" FJ_DIST_ENABLED: "true" FJ_DIST_MODE: "name" FJ_DIST_STRATEGY_NAME: "DNS" + MIX_ENV: "test" + FJ_FEATURE_FLAG_REQUEST_ROUTING_ENABLED: "true" + FJ_COMPONENTS_USED: "hls file" + volumes: + - ./test/fixtures:/app/fishjam_resources/file_component_sources restart: on-failure + healthcheck: + interval: 1s + timeout: 8s + retries: 16 services: test: - image: membraneframeworklabs/docker_membrane:latest + build: + context: . + target: build + args: + - MIX_ENV=test_cluster command: - sh - -c - - | - cd app/ - mix deps.get - MIX_ENV=test_cluster mix test --only cluster + - mix test --only cluster volumes: - - .:/app - - /app/_build - - /app/deps + - ./test:/app/test depends_on: - - app1 - - app2 + app1: + condition: service_healthy + app2: + condition: service_healthy app1: <<: *fishjam-template diff --git a/docker-compose-epmd.yaml b/docker-compose-epmd.yaml index 12f8e028..5775a70d 100644 --- a/docker-compose-epmd.yaml +++ b/docker-compose-epmd.yaml @@ -1,30 +1,43 @@ version: "3" x-fishjam-template: &fishjam-template - build: . + build: + context: . + args: + - MIX_ENV=test environment: &fishjam-environment FJ_SERVER_API_TOKEN: "development" FJ_DIST_ENABLED: "true" FJ_DIST_NODES: "app@app1 app@app2" + MIX_ENV: "test" + FJ_FEATURE_FLAG_REQUEST_ROUTING_ENABLED: "true" + FJ_COMPONENTS_USED: "hls file" + volumes: + - ./test/fixtures:/app/fishjam_resources/file_component_sources restart: on-failure + healthcheck: + interval: 1s + timeout: 8s + retries: 16 services: test: - image: membraneframeworklabs/docker_membrane:latest + build: + context: . + target: build + args: + - MIX_ENV=test_cluster command: - sh - -c - - | - cd app/ - mix deps.get - MIX_ENV=test_cluster mix test --only cluster + - mix test --only cluster volumes: - - .:/app - - /app/_build - - /app/deps + - ./test:/app/test depends_on: - - app1 - - app2 + app1: + condition: service_healthy + app2: + condition: service_healthy app1: <<: *fishjam-template diff --git a/lib/fishjam/application.ex b/lib/fishjam/application.ex index 966f755b..66e66c37 100644 --- a/lib/fishjam/application.ex +++ b/lib/fishjam/application.ex @@ -26,6 +26,7 @@ defmodule Fishjam.Application do Logger.info("Distribution config: #{inspect(Keyword.delete(dist_config, :cookie))}") Logger.info("WebRTC config: #{inspect(webrtc_config)}") Logger.info("Allowed components: #{inspect(components_used)}") + Logger.info(Fishjam.FeatureFlags.info()) children = [ @@ -38,7 +39,7 @@ defmodule Fishjam.Application do {Fishjam.MetricsScraper, scrape_interval}, FishjamWeb.Endpoint, # Start the RoomService - Fishjam.RoomService, + Fishjam.Local.RoomService, # Start the ResourceManager, responsible for cleaning old recordings {Fishjam.ResourceManager, @resource_manager_opts}, Fishjam.WebhookNotifier, diff --git a/lib/fishjam/cluster/room.ex b/lib/fishjam/cluster/room.ex new file mode 100644 index 00000000..8aa3cfa5 --- /dev/null +++ b/lib/fishjam/cluster/room.ex @@ -0,0 +1,58 @@ +defmodule Fishjam.Cluster.Room do + @moduledoc """ + Module responsible for managing a room present anywhere in the cluster. + """ + + @behaviour Fishjam.Room + + alias Fishjam.{Room, RPCClient} + + @local_module Fishjam.Local.Room + + @impl true + def add_peer(room_id, peer_type, options \\ %{}), + do: route_request(room_id, :add_peer, [room_id, peer_type, options]) + + @impl true + def set_peer_connected(room_id, peer_id), + do: route_request(room_id, :set_peer_connected, [room_id, peer_id]) + + @impl true + def get_peer_connection_status(room_id, peer_id), + do: route_request(room_id, :get_peer_connection_status, [room_id, peer_id]) + + @impl true + def remove_peer(room_id, peer_id), + do: route_request(room_id, :remove_peer, [room_id, peer_id]) + + @impl true + def add_component(room_id, component_type, options \\ %{}), + do: route_request(room_id, :add_component, [room_id, component_type, options]) + + @impl true + def remove_component(room_id, component_id), + do: route_request(room_id, :remove_component, [room_id, component_id]) + + @impl true + def subscribe(room_id, component_id, origins), + do: route_request(room_id, :subscribe, [room_id, component_id, origins]) + + @impl true + def dial(room_id, component_id, phone_number), + do: route_request(room_id, :dial, [room_id, component_id, phone_number]) + + @impl true + def end_call(room_id, component_id), + do: route_request(room_id, :end_call, [room_id, component_id]) + + @impl true + def receive_media_event(room_id, peer_id, event), + do: route_request(room_id, :receive_media_event, [room_id, peer_id, event]) + + defp route_request(room_id, fun, args) do + with {:ok, node} <- Room.ID.determine_node(room_id), + {:ok, result} <- RPCClient.call(node, @local_module, fun, args) do + result + end + end +end diff --git a/lib/fishjam/cluster/room_service.ex b/lib/fishjam/cluster/room_service.ex new file mode 100644 index 00000000..b86f516d --- /dev/null +++ b/lib/fishjam/cluster/room_service.ex @@ -0,0 +1,69 @@ +defmodule Fishjam.Cluster.RoomService do + @moduledoc """ + Module responsible for managing rooms in the entire cluster. + """ + + @behaviour Fishjam.RoomService + + require Logger + + alias Fishjam.{FeatureFlags, Room, RPCClient} + + @local_module Fishjam.Local.RoomService + + @impl true + def create_room(config) do + node_resources = RPCClient.multicall(@local_module, :get_resource_usage) + min_node = find_best_node(node_resources) + + if length(node_resources) > 1, + do: Logger.info("Node with least used resources is #{inspect(min_node)}") + + with {:ok, result} <- RPCClient.call(min_node, @local_module, :create_room, [config]) do + result + end + end + + @impl true + def list_rooms() do + if FeatureFlags.request_routing_enabled?() do + @local_module |> RPCClient.multicall(:list_rooms) |> List.flatten() + else + apply(@local_module, :list_rooms, []) + end + end + + @impl true + def find_room(room_id), do: route_request(room_id, :find_room, [room_id]) + + @impl true + def get_room(room_id), do: route_request(room_id, :get_room, [room_id]) + + @impl true + def delete_room(room_id), do: route_request(room_id, :delete_room, [room_id]) + + defp find_best_node(node_resources) do + %{node: min_node} = + Enum.min( + node_resources, + fn + %{forwarded_tracks_number: forwarded_tracks, rooms_number: rooms_num1}, + %{forwarded_tracks_number: forwarded_tracks, rooms_number: rooms_num2} -> + rooms_num1 < rooms_num2 + + %{forwarded_tracks_number: forwarded_tracks1}, + %{forwarded_tracks_number: forwarded_tracks2} -> + forwarded_tracks1 < forwarded_tracks2 + end + ) + + min_node + end + + defp route_request(room_id, fun, args) do + with {:ok, node} <- Room.ID.determine_node(room_id), + {:ok, result} <- RPCClient.call(node, @local_module, fun, args) do + result + end + end +end diff --git a/lib/fishjam/component.ex b/lib/fishjam/component.ex index 1cd2e663..642fa752 100644 --- a/lib/fishjam/component.ex +++ b/lib/fishjam/component.ex @@ -10,7 +10,7 @@ defmodule Fishjam.Component do use Bunch.Access - alias Fishjam.Room + alias Fishjam.Local.Room alias Fishjam.Component.{File, HLS, Recording, RTSP, SIP} alias Fishjam.Track diff --git a/lib/fishjam/component/hls.ex b/lib/fishjam/component/hls.ex index fe64aae1..47286b83 100644 --- a/lib/fishjam/component/hls.ex +++ b/lib/fishjam/component/hls.ex @@ -11,11 +11,12 @@ defmodule Fishjam.Component.HLS do LLStorage, Manager, Recording, - RequestHandler, Storage } - alias Fishjam.Room + alias Fishjam.Component.HLS.Local.RequestHandler + + alias Fishjam.Room.ID alias FishjamWeb.ApiSpec.Component.HLS.Options @@ -91,7 +92,7 @@ defmodule Fishjam.Component.HLS do if low_latency, do: remove_request_handler(room_id) end - @spec output_dir(Room.id(), persistent: boolean()) :: String.t() + @spec output_dir(ID.id(), persistent: boolean()) :: String.t() def output_dir(room_id, persistent: true) do Recording.directory(room_id) end diff --git a/lib/fishjam/component/hls/cluster/request_handler.ex b/lib/fishjam/component/hls/cluster/request_handler.ex new file mode 100644 index 00000000..e93ef15f --- /dev/null +++ b/lib/fishjam/component/hls/cluster/request_handler.ex @@ -0,0 +1,39 @@ +defmodule Fishjam.Component.HLS.Cluster.RequestHandler do + @moduledoc """ + Module responsible for handling HLS Retrieve Content requests in the cluster. + """ + + @behaviour Fishjam.Component.HLS.RequestHandler + + alias Fishjam.{Room, RPCClient} + + @local_module Fishjam.Component.HLS.Local.RequestHandler + + @impl true + def handle_file_request(room_id, filename), + do: route_request(room_id, :handle_file_request, [room_id, filename]) + + @impl true + def handle_partial_request(room_id, filename), + do: route_request(room_id, :handle_partial_request, [room_id, filename]) + + @impl true + def handle_manifest_request(room_id, partial), + do: route_request(room_id, :handle_manifest_request, [room_id, partial]) + + @impl true + def handle_delta_manifest_request(room_id, partial), + do: route_request(room_id, :handle_delta_manifest_request, [room_id, partial]) + + defp route_request(room_id, fun, args) do + with {:ok, node} <- Room.ID.determine_node(room_id), + {:here?, false} <- {:here?, node == Node.self()}, + # FIXME: Fishjam addresses could easily be cached + {:ok, address} <- RPCClient.call(node, Fishjam, :address) do + {:redirect, address} + else + {:here?, true} -> apply(@local_module, fun, args) + {:error, _reason} = error -> error + end + end +end diff --git a/lib/fishjam/component/hls/ets_helper.ex b/lib/fishjam/component/hls/ets_helper.ex index 3f27bc18..88e81052 100644 --- a/lib/fishjam/component/hls/ets_helper.ex +++ b/lib/fishjam/component/hls/ets_helper.ex @@ -1,7 +1,7 @@ defmodule Fishjam.Component.HLS.EtsHelper do @moduledoc false - alias Fishjam.Room + alias Fishjam.Room.ID @rooms_to_tables :rooms_to_tables @@ -19,7 +19,7 @@ defmodule Fishjam.Component.HLS.EtsHelper do ### ROOM MANAGMENT ### - @spec add_room(Room.id()) :: {:ok, reference()} | {:error, :already_exists} + @spec add_room(ID.id()) :: {:ok, reference()} | {:error, :already_exists} def add_room(room_id) do if room_exists?(room_id) do {:error, :already_exists} @@ -37,7 +37,7 @@ defmodule Fishjam.Component.HLS.EtsHelper do end end - @spec remove_room(Room.id()) :: :ok | {:error, String.t()} + @spec remove_room(ID.id()) :: :ok | {:error, String.t()} def remove_room(room_id) do case :ets.lookup(@rooms_to_tables, room_id) do [{^room_id, _table}] -> @@ -84,12 +84,12 @@ defmodule Fishjam.Component.HLS.EtsHelper do :ets.delete(table, filename) end - @spec add_hls_folder_path(Room.id(), String.t()) :: true + @spec add_hls_folder_path(ID.id(), String.t()) :: true def add_hls_folder_path(room_id, path) do :ets.insert(@hls_folder_path, {room_id, path}) end - @spec delete_hls_folder_path(Room.id()) :: true + @spec delete_hls_folder_path(ID.id()) :: true def delete_hls_folder_path(room_id) do :ets.delete(@hls_folder_path, room_id) end @@ -98,35 +98,35 @@ defmodule Fishjam.Component.HLS.EtsHelper do ### ETS GETTERS ### - @spec get_partial(Room.id(), String.t()) :: + @spec get_partial(ID.id(), String.t()) :: {:ok, binary()} | {:error, atom()} def get_partial(room_id, filename) do get_from_ets(room_id, filename) end - @spec get_recent_partial(Room.id()) :: + @spec get_recent_partial(ID.id()) :: {:ok, {non_neg_integer(), non_neg_integer()}} | {:error, atom()} def get_recent_partial(room_id) do get_from_ets(room_id, @recent_partial_key) end - @spec get_delta_recent_partial(Room.id()) :: + @spec get_delta_recent_partial(ID.id()) :: {:ok, {non_neg_integer(), non_neg_integer()}} | {:error, atom()} def get_delta_recent_partial(room_id) do get_from_ets(room_id, @delta_recent_partial_key) end - @spec get_manifest(Room.id()) :: {:ok, String.t()} | {:error, atom()} + @spec get_manifest(ID.id()) :: {:ok, String.t()} | {:error, atom()} def get_manifest(room_id) do get_from_ets(room_id, @manifest_key) end - @spec get_delta_manifest(Room.id()) :: {:ok, String.t()} | {:error, atom()} + @spec get_delta_manifest(ID.id()) :: {:ok, String.t()} | {:error, atom()} def get_delta_manifest(room_id) do get_from_ets(room_id, @delta_manifest_key) end - @spec get_hls_folder_path(Room.id()) :: {:ok, String.t()} | {:error, :room_not_found} + @spec get_hls_folder_path(ID.id()) :: {:ok, String.t()} | {:error, :room_not_found} def get_hls_folder_path(room_id) do get_path(room_id) end diff --git a/lib/fishjam/component/hls/ll_storage.ex b/lib/fishjam/component/hls/ll_storage.ex index ef3f5fef..03ffd5fa 100644 --- a/lib/fishjam/component/hls/ll_storage.ex +++ b/lib/fishjam/component/hls/ll_storage.ex @@ -3,8 +3,9 @@ defmodule Fishjam.Component.HLS.LLStorage do @behaviour Membrane.HTTPAdaptiveStream.Storage - alias Fishjam.Component.HLS.{EtsHelper, RequestHandler} - alias Fishjam.Room + alias Fishjam.Component.HLS.EtsHelper + alias Fishjam.Component.HLS.Local.RequestHandler + alias Fishjam.Room.ID @enforce_keys [:directory, :room_id] defstruct @enforce_keys ++ @@ -17,7 +18,7 @@ defmodule Fishjam.Component.HLS.LLStorage do @type t :: %__MODULE__{ directory: Path.t(), - room_id: Room.id(), + room_id: ID.id(), table: :ets.table() | nil, partial_sn: sequence_number(), segment_sn: sequence_number(), diff --git a/lib/fishjam/component/hls/local/request_handler.ex b/lib/fishjam/component/hls/local/request_handler.ex new file mode 100644 index 00000000..e0af1a72 --- /dev/null +++ b/lib/fishjam/component/hls/local/request_handler.ex @@ -0,0 +1,315 @@ +defmodule Fishjam.Component.HLS.Local.RequestHandler do + @moduledoc false + + @behaviour Fishjam.Component.HLS.RequestHandler + + use GenServer + use Bunch.Access + + alias Fishjam.Utils.PathValidation + alias Fishjam.Component.HLS.{EtsHelper, Recording, RequestHandler} + alias Fishjam.Room.ID + + @enforce_keys [:room_id, :room_pid] + defstruct @enforce_keys ++ + [ + preload_hints: [], + manifest: %{waiting_pids: %{}, last_partial: nil}, + delta_manifest: %{waiting_pids: %{}, last_partial: nil} + ] + + @type status :: %{ + waiting_pids: %{RequestHandler.partial() => [pid()]}, + last_partial: RequestHandler.partial() | nil + } + + @type t :: %__MODULE__{ + room_id: ID.id(), + room_pid: pid(), + manifest: status(), + delta_manifest: status(), + preload_hints: [pid()] + } + + ### + ### HLS Controller API + ### + + # FIXME: Opportunity for Improvement + # + # During stress test simulations involving 500 clients (at a rate of 1 Gb/s) + # it has been observed that RAM usage can surge up to 1 GB due solely to HLS requests. + # This spike is primarily caused by the current strategy of reading files individually for each request, rather than caching them in memory. + # + # Recommendation: + # To mitigate this issue, consider implementing a cache storage mechanism that maintains the last six segments. + # This way, whenever possible, these segments are retrieved from the cache instead of being repeatedly read from the file. + + @impl Fishjam.Component.HLS.RequestHandler + def handle_file_request(room_id, filename) do + with {:ok, room_path} <- EtsHelper.get_hls_folder_path(room_id) do + file_path = room_path |> Path.join(filename) |> Path.expand() + + if PathValidation.inside_directory?(file_path, Path.expand(room_path)), + do: File.read(file_path), + else: {:error, :invalid_path} + end + end + + @doc """ + Handles VOD requests: master playlist, headers, regular segments + """ + @spec handle_recording_request(ID.id(), String.t()) :: {:ok, binary()} | {:error, atom()} + def handle_recording_request(recording_id, filename) do + with :ok <- Recording.validate_recording(recording_id) do + recording_path = Recording.directory(recording_id) + + file_path = + recording_path + |> Path.join(filename) + |> Path.expand() + + if PathValidation.inside_directory?(file_path, recording_path), + do: File.read(file_path), + else: {:error, :invalid_path} + end + end + + @impl Fishjam.Component.HLS.RequestHandler + def handle_partial_request(room_id, filename) do + with {:ok, partial} <- EtsHelper.get_partial(room_id, filename) do + {:ok, partial} + else + {:error, :file_not_found} -> + case preload_hint?(room_id, filename) do + {:ok, true} -> + wait_for_partial_ready(room_id, filename) + + _other -> + {:error, :file_not_found} + end + + error -> + error + end + end + + @impl Fishjam.Component.HLS.RequestHandler + def handle_manifest_request(room_id, partial) do + with {:ok, last_partial} <- EtsHelper.get_recent_partial(room_id) do + unless partial_ready?(partial, last_partial) do + wait_for_manifest_ready(room_id, partial, :manifest) + end + + EtsHelper.get_manifest(room_id) + end + end + + @impl Fishjam.Component.HLS.RequestHandler + def handle_delta_manifest_request(room_id, partial) do + with {:ok, last_partial} <- EtsHelper.get_delta_recent_partial(room_id) do + unless partial_ready?(partial, last_partial) do + wait_for_manifest_ready(room_id, partial, :delta_manifest) + end + + EtsHelper.get_delta_manifest(room_id) + end + end + + ### + ### STORAGE API + ### + + @spec update_recent_partial(ID.id(), RequestHandler.partial()) :: :ok + def update_recent_partial(room_id, partial) do + GenServer.cast(registry_id(room_id), {:update_recent_partial, partial, :manifest}) + end + + @spec update_delta_recent_partial(ID.id(), RequestHandler.partial()) :: :ok + def update_delta_recent_partial(room_id, partial) do + GenServer.cast(registry_id(room_id), {:update_recent_partial, partial, :delta_manifest}) + end + + ### + ### MANAGMENT API + ### + + def start(room_id) do + # Request handler monitors the room process. + # This ensures that it will be killed if room crashes. + # In case of different use of this module it has to be refactored + GenServer.start(__MODULE__, %{room_id: room_id, room_pid: self()}, name: registry_id(room_id)) + end + + def stop(room_id) do + GenServer.cast(registry_id(room_id), :shutdown) + end + + @impl true + def init(%{room_id: room_id, room_pid: room_pid}) do + Process.monitor(room_pid) + {:ok, %__MODULE__{room_id: room_id, room_pid: room_pid}} + end + + @impl true + def handle_cast( + {:update_recent_partial, last_partial, manifest}, + %{preload_hints: preload_hints} = state + ) do + status = Map.fetch!(state, manifest) + + state = + state + |> Map.put(manifest, update_and_notify_manifest_ready(status, last_partial)) + |> Map.put(:preload_hints, update_and_notify_preload_hint_ready(preload_hints)) + + {:noreply, state} + end + + @impl true + def handle_cast({:partial_ready?, partial, from, manifest}, state) do + state = + state + |> Map.fetch!(manifest) + |> handle_partial_ready?(partial, from) + |> then(&Map.put(state, manifest, &1)) + + {:noreply, state} + end + + @impl true + def handle_cast({:preload_hint, room_id, filename, from}, state) do + with {:ok, _partial} <- EtsHelper.get_partial(room_id, filename) do + send(from, :preload_hint_ready) + {:noreply, state} + else + {:error, _reason} -> + {:noreply, %{state | preload_hints: [from | state.preload_hints]}} + end + end + + @impl true + def handle_cast(:shutdown, state) do + {:stop, :normal, state} + end + + @impl true + def terminate(_reason, %{room_id: room_id}) do + EtsHelper.remove_room(room_id) + end + + @impl true + def handle_info({:DOWN, _ref, :process, pid, _reason}, %{room_pid: pid} = state) do + {:stop, :normal, state} + end + + ### + ### PRIVATE FUNCTIONS + ### + + defp wait_for_manifest_ready(room_id, partial, manifest) do + GenServer.cast(registry_id(room_id), {:partial_ready?, partial, self(), manifest}) + + receive do + :manifest_ready -> + :ok + end + end + + defp wait_for_partial_ready(room_id, filename) do + GenServer.cast(registry_id(room_id), {:preload_hint, room_id, filename, self()}) + + receive do + :preload_hint_ready -> + EtsHelper.get_partial(room_id, filename) + end + end + + defp update_and_notify_preload_hint_ready(preload_hints) do + send_preload_hint_ready(preload_hints) + [] + end + + defp update_and_notify_manifest_ready(%{waiting_pids: waiting_pids} = status, last_partial) do + partials_ready = + waiting_pids + |> Map.keys() + |> Enum.filter(fn partial -> partial_ready?(partial, last_partial) end) + + partials_ready + |> Enum.flat_map(fn partial -> Map.fetch!(waiting_pids, partial) end) + |> then(&send_partial_ready(&1)) + + waiting_pids = Map.drop(waiting_pids, partials_ready) + + %{status | waiting_pids: waiting_pids, last_partial: last_partial} + end + + defp handle_partial_ready?(status, partial, from) do + if partial_ready?(partial, status.last_partial) do + send(from, :manifest_ready) + status + else + waiting_pids = + Map.update(status.waiting_pids, partial, [from], fn pids_list -> + [from | pids_list] + end) + + %{status | waiting_pids: waiting_pids} + end + end + + defp preload_hint?(room_id, filename) do + partial_sn = get_partial_sn(filename) + + with {:ok, recent_partial_sn} <- EtsHelper.get_recent_partial(room_id) do + {:ok, check_if_preload_hint(partial_sn, recent_partial_sn)} + end + end + + defp check_if_preload_hint({segment_sn, partial_sn}, {recent_segment_sn, recent_partial_sn}) do + cond do + segment_sn - recent_segment_sn == 1 and partial_sn == 0 -> true + segment_sn == recent_segment_sn and (partial_sn - recent_partial_sn) in [0, 1] -> true + true -> false + end + end + + defp check_if_preload_hint(_partial_sn, _recent_partial_sn) do + require Logger + + Logger.warning("Unable to parse partial segment filename") + false + end + + # Filename example: muxed_segment_32_g2QABXZpZGVv_5_part.m4s + defp get_partial_sn(filename) do + filename + |> String.split("_") + |> Enum.filter(fn s -> match?({_integer, ""}, Integer.parse(s)) end) + |> Enum.map(fn sn -> String.to_integer(sn) end) + |> List.to_tuple() + end + + defp registry_id(room_id), do: {:via, Registry, {Fishjam.RequestHandlerRegistry, room_id}} + + defp send_partial_ready(waiting_pids) do + Enum.each(waiting_pids, fn pid -> send(pid, :manifest_ready) end) + end + + defp send_preload_hint_ready(waiting_pids) do + Enum.each(waiting_pids, fn pid -> send(pid, :preload_hint_ready) end) + end + + defp partial_ready?(_partial, nil) do + false + end + + defp partial_ready?({segment_sn, partial_sn}, {last_segment_sn, last_partial_sn}) do + cond do + last_segment_sn > segment_sn -> true + last_segment_sn < segment_sn -> false + true -> last_partial_sn >= partial_sn + end + end +end diff --git a/lib/fishjam/component/hls/manager.ex b/lib/fishjam/component/hls/manager.ex index 42d2d42f..bda8776e 100644 --- a/lib/fishjam/component/hls/manager.ex +++ b/lib/fishjam/component/hls/manager.ex @@ -12,19 +12,19 @@ defmodule Fishjam.Component.HLS.Manager do require Logger alias Fishjam.Event - alias Fishjam.Room + alias Fishjam.Room.ID @hls_extensions [".m4s", ".m3u8", ".mp4"] @playlist_content_type "application/vnd.apple.mpegurl" @type options :: %{ - room_id: Room.id(), + room_id: ID.id(), engine_pid: pid(), hls_dir: String.t(), hls_options: map() } - @spec start(Room.id(), pid(), String.t(), map()) :: {:ok, pid()} | {:error, term()} + @spec start(ID.id(), pid(), String.t(), map()) :: {:ok, pid()} | {:error, term()} def start(room_id, engine_pid, hls_dir, hls_options) do DynamicSupervisor.start_child( Fishjam.HLS.ManagerSupervisor, diff --git a/lib/fishjam/component/hls/recording.ex b/lib/fishjam/component/hls/recording.ex index 3e55189c..92057d0e 100644 --- a/lib/fishjam/component/hls/recording.ex +++ b/lib/fishjam/component/hls/recording.ex @@ -2,12 +2,12 @@ defmodule Fishjam.Component.HLS.Recording do @moduledoc false alias Fishjam.Component.HLS.EtsHelper - alias Fishjam.Room + alias Fishjam.Room.ID alias Fishjam.Utils.PathValidation @recordings_folder "recordings" - @spec validate_recording(Room.id()) :: :ok | {:error, :not_found} | {:error, :invalid_recording} + @spec validate_recording(ID.id()) :: :ok | {:error, :not_found} | {:error, :invalid_recording} def validate_recording(id) do path = directory(id) @@ -18,7 +18,7 @@ defmodule Fishjam.Component.HLS.Recording do end end - @spec list_all() :: {:ok, [Room.id()]} | :error + @spec list_all() :: {:ok, [ID.id()]} | :error def list_all() do case File.ls(root_directory()) do {:ok, files} -> {:ok, Enum.filter(files, &exists?(&1))} @@ -27,14 +27,14 @@ defmodule Fishjam.Component.HLS.Recording do end end - @spec delete(Room.id()) :: :ok | {:error, :not_found} | {:error, :invalid_recording} + @spec delete(ID.id()) :: :ok | {:error, :not_found} | {:error, :invalid_recording} def delete(id) do with :ok <- validate_recording(id) do do_delete(id) end end - @spec directory(Room.id()) :: String.t() + @spec directory(ID.id()) :: String.t() def directory(id) do root_directory() |> Path.join(id) |> Path.expand() end diff --git a/lib/fishjam/component/hls/request_handler.ex b/lib/fishjam/component/hls/request_handler.ex index 796b2659..131e4e27 100644 --- a/lib/fishjam/component/hls/request_handler.ex +++ b/lib/fishjam/component/hls/request_handler.ex @@ -1,328 +1,42 @@ defmodule Fishjam.Component.HLS.RequestHandler do - @moduledoc false - - use GenServer - use Bunch.Access - - alias Fishjam.Utils.PathValidation - alias Fishjam.Component.HLS.{EtsHelper, Recording} - alias Fishjam.Room + @moduledoc """ + Behaviour for Fishjam.Component.HLS.{Cluster, Local}.RequestHandler + """ - @enforce_keys [:room_id, :room_pid] - defstruct @enforce_keys ++ - [ - preload_hints: [], - manifest: %{waiting_pids: %{}, last_partial: nil}, - delta_manifest: %{waiting_pids: %{}, last_partial: nil} - ] + alias Fishjam.Room.ID @type segment_sn :: non_neg_integer() @type partial_sn :: non_neg_integer() @type partial :: {segment_sn(), partial_sn()} - @type status :: %{waiting_pids: %{partial() => [pid()]}, last_partial: partial() | nil} - - @type t :: %__MODULE__{ - room_id: Room.id(), - room_pid: pid(), - manifest: status(), - delta_manifest: status(), - preload_hints: [pid()] - } - ### - ### HLS Controller API - ### - - # FIXME: Opportunity for Improvement - # - # During stress test simulations involving 500 clients (at a rate of 1 Gb/s) - # it has been observed that RAM usage can surge up to 1 GB due solely to HLS requests. - # This spike is primarily caused by the current strategy of reading files individually for each request, rather than caching them in memory. - # - # Recommendation: - # To mitigate this issue, consider implementing a cache storage mechanism that maintains the last six segments. - # This way, whenever possible, these segments are retrieved from the cache instead of being repeatedly read from the file. + @type cluster_error :: :invalid_room_id | :node_not_found | :rpc_failed @doc """ Handles requests: playlists (regular hls), master playlist, headers, regular segments """ - @spec handle_file_request(Room.id(), String.t()) :: {:ok, binary()} | {:error, atom()} - def handle_file_request(room_id, filename) do - with {:ok, room_path} <- EtsHelper.get_hls_folder_path(room_id) do - file_path = room_path |> Path.join(filename) |> Path.expand() - - if PathValidation.inside_directory?(file_path, Path.expand(room_path)), - do: File.read(file_path), - else: {:error, :invalid_path} - end - end - - @doc """ - Handles VOD requests: master playlist, headers, regular segments - """ - @spec handle_recording_request(Room.id(), String.t()) :: {:ok, binary()} | {:error, atom()} - def handle_recording_request(recording_id, filename) do - with :ok <- Recording.validate_recording(recording_id) do - recording_path = Recording.directory(recording_id) + @callback handle_file_request(ID.id(), String.t()) :: {:ok, binary()} | {:error, atom()} - file_path = - recording_path - |> Path.join(filename) - |> Path.expand() - - if PathValidation.inside_directory?(file_path, recording_path), - do: File.read(file_path), - else: {:error, :invalid_path} - end - end + # TODO: uncomment once recording requests are routed + # @doc """ + # Handles VOD requests: master playlist, headers, regular segments + # """ + # @callback handle_recording_request(ID.id(), String.t()) :: {:ok, binary()} | {:error, atom()} @doc """ Handles ll-hls partial requests """ - @spec handle_partial_request(Room.id(), String.t()) :: - {:ok, binary()} | {:error, atom()} - def handle_partial_request(room_id, filename) do - with {:ok, partial} <- EtsHelper.get_partial(room_id, filename) do - {:ok, partial} - else - {:error, :file_not_found} -> - case preload_hint?(room_id, filename) do - {:ok, true} -> - wait_for_partial_ready(room_id, filename) - - _other -> - {:error, :file_not_found} - end - - error -> - error - end - end + @callback handle_partial_request(ID.id(), String.t()) :: + {:ok, binary()} | {:error, atom()} @doc """ Handles manifest requests with specific partial requested (ll-hls) """ - @spec handle_manifest_request(Room.id(), partial()) :: - {:ok, String.t()} | {:error, atom()} - def handle_manifest_request(room_id, partial) do - with {:ok, last_partial} <- EtsHelper.get_recent_partial(room_id) do - unless partial_ready?(partial, last_partial) do - wait_for_manifest_ready(room_id, partial, :manifest) - end - - EtsHelper.get_manifest(room_id) - end - end + @callback handle_manifest_request(ID.id(), partial()) :: + {:ok, String.t()} | {:error, atom()} @doc """ Handles delta manifest requests with specific partial requested (ll-hls) """ - @spec handle_delta_manifest_request(Room.id(), partial()) :: - {:ok, String.t()} | {:error, atom()} - def handle_delta_manifest_request(room_id, partial) do - with {:ok, last_partial} <- EtsHelper.get_delta_recent_partial(room_id) do - unless partial_ready?(partial, last_partial) do - wait_for_manifest_ready(room_id, partial, :delta_manifest) - end - - EtsHelper.get_delta_manifest(room_id) - end - end - - ### - ### STORAGE API - ### - - @spec update_recent_partial(Room.id(), partial()) :: :ok - def update_recent_partial(room_id, partial) do - GenServer.cast(registry_id(room_id), {:update_recent_partial, partial, :manifest}) - end - - @spec update_delta_recent_partial(Room.id(), partial()) :: :ok - def update_delta_recent_partial(room_id, partial) do - GenServer.cast(registry_id(room_id), {:update_recent_partial, partial, :delta_manifest}) - end - - ### - ### MANAGMENT API - ### - - def start(room_id) do - # Request handler monitors the room process. - # This ensures that it will be killed if room crashes. - # In case of different use of this module it has to be refactored - GenServer.start(__MODULE__, %{room_id: room_id, room_pid: self()}, name: registry_id(room_id)) - end - - def stop(room_id) do - GenServer.cast(registry_id(room_id), :shutdown) - end - - @impl true - def init(%{room_id: room_id, room_pid: room_pid}) do - Process.monitor(room_pid) - {:ok, %__MODULE__{room_id: room_id, room_pid: room_pid}} - end - - @impl true - def handle_cast( - {:update_recent_partial, last_partial, manifest}, - %{preload_hints: preload_hints} = state - ) do - status = Map.fetch!(state, manifest) - - state = - state - |> Map.put(manifest, update_and_notify_manifest_ready(status, last_partial)) - |> Map.put(:preload_hints, update_and_notify_preload_hint_ready(preload_hints)) - - {:noreply, state} - end - - @impl true - def handle_cast({:partial_ready?, partial, from, manifest}, state) do - state = - state - |> Map.fetch!(manifest) - |> handle_partial_ready?(partial, from) - |> then(&Map.put(state, manifest, &1)) - - {:noreply, state} - end - - @impl true - def handle_cast({:preload_hint, room_id, filename, from}, state) do - with {:ok, _partial} <- EtsHelper.get_partial(room_id, filename) do - send(from, :preload_hint_ready) - {:noreply, state} - else - {:error, _reason} -> - {:noreply, %{state | preload_hints: [from | state.preload_hints]}} - end - end - - @impl true - def handle_cast(:shutdown, state) do - {:stop, :normal, state} - end - - @impl true - def terminate(_reason, %{room_id: room_id}) do - EtsHelper.remove_room(room_id) - end - - @impl true - def handle_info({:DOWN, _ref, :process, pid, _reason}, %{room_pid: pid} = state) do - {:stop, :normal, state} - end - - ### - ### PRIVATE FUNCTIONS - ### - - defp wait_for_manifest_ready(room_id, partial, manifest) do - GenServer.cast(registry_id(room_id), {:partial_ready?, partial, self(), manifest}) - - receive do - :manifest_ready -> - :ok - end - end - - defp wait_for_partial_ready(room_id, filename) do - GenServer.cast(registry_id(room_id), {:preload_hint, room_id, filename, self()}) - - receive do - :preload_hint_ready -> - EtsHelper.get_partial(room_id, filename) - end - end - - defp update_and_notify_preload_hint_ready(preload_hints) do - send_preload_hint_ready(preload_hints) - [] - end - - defp update_and_notify_manifest_ready(%{waiting_pids: waiting_pids} = status, last_partial) do - partials_ready = - waiting_pids - |> Map.keys() - |> Enum.filter(fn partial -> partial_ready?(partial, last_partial) end) - - partials_ready - |> Enum.flat_map(fn partial -> Map.fetch!(waiting_pids, partial) end) - |> then(&send_partial_ready(&1)) - - waiting_pids = Map.drop(waiting_pids, partials_ready) - - %{status | waiting_pids: waiting_pids, last_partial: last_partial} - end - - defp handle_partial_ready?(status, partial, from) do - if partial_ready?(partial, status.last_partial) do - send(from, :manifest_ready) - status - else - waiting_pids = - Map.update(status.waiting_pids, partial, [from], fn pids_list -> - [from | pids_list] - end) - - %{status | waiting_pids: waiting_pids} - end - end - - defp preload_hint?(room_id, filename) do - partial_sn = get_partial_sn(filename) - - with {:ok, recent_partial_sn} <- EtsHelper.get_recent_partial(room_id) do - {:ok, check_if_preload_hint(partial_sn, recent_partial_sn)} - end - end - - defp check_if_preload_hint({segment_sn, partial_sn}, {recent_segment_sn, recent_partial_sn}) do - cond do - segment_sn - recent_segment_sn == 1 and partial_sn == 0 -> true - segment_sn == recent_segment_sn and (partial_sn - recent_partial_sn) in [0, 1] -> true - true -> false - end - end - - defp check_if_preload_hint(_partial_sn, _recent_partial_sn) do - require Logger - - Logger.warning("Unable to parse partial segment filename") - false - end - - # Filename example: muxed_segment_32_g2QABXZpZGVv_5_part.m4s - defp get_partial_sn(filename) do - filename - |> String.split("_") - |> Enum.filter(fn s -> match?({_integer, ""}, Integer.parse(s)) end) - |> Enum.map(fn sn -> String.to_integer(sn) end) - |> List.to_tuple() - end - - defp registry_id(room_id), do: {:via, Registry, {Fishjam.RequestHandlerRegistry, room_id}} - - defp send_partial_ready(waiting_pids) do - Enum.each(waiting_pids, fn pid -> send(pid, :manifest_ready) end) - end - - defp send_preload_hint_ready(waiting_pids) do - Enum.each(waiting_pids, fn pid -> send(pid, :preload_hint_ready) end) - end - - defp partial_ready?(_partial, nil) do - false - end - - defp partial_ready?({segment_sn, partial_sn}, {last_segment_sn, last_partial_sn}) do - cond do - last_segment_sn > segment_sn -> true - last_segment_sn < segment_sn -> false - true -> last_partial_sn >= partial_sn - end - end + @callback handle_delta_manifest_request(ID.id(), partial()) :: + {:ok, String.t()} | {:error, atom()} end diff --git a/lib/fishjam/feature_flags.ex b/lib/fishjam/feature_flags.ex index 4d11286c..0904a09c 100644 --- a/lib/fishjam/feature_flags.ex +++ b/lib/fishjam/feature_flags.ex @@ -5,11 +5,22 @@ defmodule Fishjam.FeatureFlags do """ @doc """ - Flag for disabling custom room names, which will be replaced by the generated based on the node name. + Flag for enabling request routing within the cluster of Fishjams. + When toggled, it disallows setting custom room names - they will instead be generated based on the node name. Introduced: 28/05/2024 - Removal: Once we move on to generated room_ids permanently. + Removal: Once we move on to generated room_ids and cluster-wide request routing permanently. """ - def custom_room_name_disabled?, - do: Application.get_env(:fishjam, :feature_flags)[:custom_room_name_disabled] + def request_routing_enabled?, + do: Application.get_env(:fishjam, :feature_flags)[:request_routing_enabled?] + + @doc "Info about currently enabled feature flags" + def info do + """ + Feature flags: + * Request routing: #{status(request_routing_enabled?())} + """ + end + + defp status(flag), do: if(flag, do: "ENABLED", else: "disabled") end diff --git a/lib/fishjam/local/room.ex b/lib/fishjam/local/room.ex new file mode 100644 index 00000000..be1abc74 --- /dev/null +++ b/lib/fishjam/local/room.ex @@ -0,0 +1,587 @@ +defmodule Fishjam.Local.Room do + @moduledoc """ + Module representing a room present on this Fishjam instance. + """ + + @behaviour Fishjam.Room + + use GenServer + + import Fishjam.Room.State + + require Logger + + alias Fishjam.Component + alias Fishjam.Component.{HLS, Recording, SIP} + alias Fishjam.Peer + alias Fishjam.Room.{Config, ID, State} + + alias Membrane.RTC.Engine + alias Membrane.RTC.Engine.Endpoint + + alias Membrane.RTC.Engine.Message.{ + EndpointAdded, + EndpointCrashed, + EndpointMessage, + EndpointMetadataUpdated, + EndpointRemoved, + TrackAdded, + TrackMetadataUpdated, + TrackRemoved + } + + @type t :: State.t() + + ## NON-ROUTABLE FUNCTIONS + + def registry_id(room_id), do: {:via, Registry, {Fishjam.RoomRegistry, room_id}} + + @spec start(Config.t()) :: {:ok, pid(), ID.id()} | {:error, :room_already_exists} + def start(%Config{room_id: id} = config) do + with {:ok, pid} <- GenServer.start(__MODULE__, [id, config], name: registry_id(id)) do + {:ok, pid, id} + else + {:error, {:already_started, _pid}} -> + {:error, :room_already_exists} + end + end + + @spec get_state(ID.id()) :: State.t() | nil + def get_state(room_id) do + registry_room_id = registry_id(room_id) + + try do + GenServer.call(registry_room_id, :get_state) + catch + :exit, {reason, {GenServer, :call, [^registry_room_id, :get_state, _timeout]}} + when reason in [:noproc, :normal] -> + Logger.warning( + "Cannot get state of #{inspect(room_id)}, the room's process doesn't exist anymore" + ) + + nil + end + end + + @spec get_num_forwarded_tracks(ID.id()) :: integer() + def get_num_forwarded_tracks(room_id) do + GenServer.call(registry_id(room_id), :get_num_forwarded_tracks) + end + + ## CLUSTER-ROUTABLE FUNCTIONS + + @impl Fishjam.Room + def add_peer(room_id, peer_type, options \\ %{}) do + GenServer.call(registry_id(room_id), {:add_peer, peer_type, options}) + end + + @impl Fishjam.Room + def set_peer_connected(room_id, peer_id) do + GenServer.call(registry_id(room_id), {:set_peer_connected, peer_id}) + end + + @impl Fishjam.Room + def get_peer_connection_status(room_id, peer_id) do + GenServer.call(registry_id(room_id), {:get_peer_connection_status, peer_id}) + end + + @impl Fishjam.Room + def remove_peer(room_id, peer_id) do + GenServer.call(registry_id(room_id), {:remove_peer, peer_id}) + end + + @impl Fishjam.Room + def add_component(room_id, component_type, options \\ %{}) do + GenServer.call(registry_id(room_id), {:add_component, component_type, options}) + end + + @impl Fishjam.Room + def remove_component(room_id, component_id) do + GenServer.call(registry_id(room_id), {:remove_component, component_id}) + end + + @impl Fishjam.Room + def subscribe(room_id, component_id, origins) do + GenServer.call(registry_id(room_id), {:subscribe, component_id, origins}) + end + + @impl Fishjam.Room + def dial(room_id, component_id, phone_number) do + GenServer.call(registry_id(room_id), {:dial, component_id, phone_number}) + end + + @impl Fishjam.Room + def end_call(room_id, component_id) do + GenServer.call(registry_id(room_id), {:end_call, component_id}) + end + + @impl Fishjam.Room + def receive_media_event(room_id, peer_id, event) do + GenServer.cast(registry_id(room_id), {:media_event, peer_id, event}) + end + + ## GENSERVER CALLBACKS + + @impl true + def init([id, config]) do + state = State.new(id, config) + + Logger.metadata(room_id: id) + Logger.info("Initialize room") + + {:ok, state} + end + + @impl true + def handle_call(:get_state, _from, state) do + {:reply, state, state} + end + + @impl true + def handle_call({:add_peer, peer_type, override_options}, _from, state) do + with :ok <- State.check_peer_allowed(peer_type, state), + options <- State.generate_peer_options(state, override_options), + {:ok, peer} <- Peer.new(peer_type, options) do + state = State.add_peer(state, peer) + + {:reply, {:ok, peer}, state} + else + {:error, :peer_disabled_globally} -> + type = Peer.to_string!(peer_type) + Logger.warning("Unable to add peer: #{type} peers are disabled globally") + {:reply, {:error, {:peer_disabled_globally, type}}, state} + + {:error, :reached_peers_limit} -> + type = Peer.to_string!(peer_type) + Logger.warning("Unable to add peer: Reached #{type} peers limit") + {:reply, {:error, {:reached_peers_limit, type}}, state} + + {:error, reason} -> + Logger.warning("Unable to add peer: #{inspect(reason)}") + {:reply, :error, state} + end + end + + @impl true + def handle_call({:set_peer_connected, peer_id}, {socket_pid, _tag}, state) do + {reply, state} = + case State.fetch_peer(state, peer_id) do + {:ok, %{status: :disconnected} = peer} -> + Process.monitor(socket_pid) + + state = State.connect_peer(state, peer, socket_pid) + + {:ok, state} + + {:ok, %{status: :connected}} -> + {{:error, :peer_already_connected}, state} + + :error -> + {{:error, :peer_not_found}, state} + end + + {:reply, reply, state} + end + + @impl true + def handle_call({:get_peer_connection_status, peer_id}, _from, state) do + reply = + case State.fetch_peer(state, peer_id) do + {:ok, peer} -> {:ok, peer.status} + :error -> {:error, :peer_not_found} + end + + {:reply, reply, state} + end + + @impl true + def handle_call({:remove_peer, peer_id}, _from, state) do + {reply, state} = + if peer_exists?(state, peer_id) do + state = State.remove_peer(state, peer_id, :peer_removed) + + {:ok, state} + else + {{:error, :peer_not_found}, state} + end + + {:reply, reply, state} + end + + @impl true + def handle_call({:add_component, component_type, options}, _from, state) do + engine_pid = State.engine_pid(state) + + options = + Map.merge( + %{engine_pid: engine_pid, room_id: State.id(state)}, + options + ) + + with :ok <- State.check_component_allowed(component_type, state), + {:ok, component} <- Component.new(component_type, options) do + state = State.put_component(state, component) + + component_type.after_init(state, component, options) + + :ok = Engine.add_endpoint(engine_pid, component.engine_endpoint, id: component.id) + + Logger.info("Added component #{inspect(component.id)}") + + {:reply, {:ok, component}, state} + else + {:error, :component_disabled_globally} -> + type = Component.to_string!(component_type) + Logger.warning("Unable to add component: #{type} components are disabled globally") + {:reply, {:error, {:component_disabled_globally, type}}, state} + + {:error, :incompatible_codec} -> + Logger.warning("Unable to add component: incompatible codec") + {:reply, {:error, :incompatible_codec}, state} + + {:error, :reached_components_limit} -> + type = Component.to_string!(component_type) + Logger.warning("Unable to add component: reached #{type} components limit") + {:reply, {:error, {:reached_components_limit, type}}, state} + + {:error, :file_does_not_exist} -> + Logger.warning("Unable to add component: file does not exist") + {:reply, {:error, :file_does_not_exist}, state} + + {:error, :bad_parameter_framerate_for_audio} -> + Logger.warning("Unable to add component: attempted to set framerate for audio component") + {:reply, {:error, :bad_parameter_framerate_for_audio}, state} + + {:error, {:invalid_framerate, passed_framerate}} -> + Logger.warning( + "Unable to add component: expected framerate to be a positive integer, got: #{passed_framerate}" + ) + + {:reply, {:error, :invalid_framerate}, state} + + {:error, :invalid_file_path} -> + Logger.warning("Unable to add component: invalid file path") + {:reply, {:error, :invalid_file_path}, state} + + {:error, :unsupported_file_type} -> + Logger.warning("Unable to add component: unsupported file type") + {:reply, {:error, :unsupported_file_type}, state} + + {:error, {:missing_parameter, name}} -> + Logger.warning("Unable to add component: missing parameter #{inspect(name)}") + {:reply, {:error, {:missing_parameter, name}}, state} + + {:error, :missing_s3_credentials} -> + Logger.warning("Unable to add component: missing s3 credentials") + {:reply, {:error, :missing_s3_credentials}, state} + + {:error, :overridding_credentials} -> + Logger.warning("Unable to add component: tried to override s3 credentials") + {:reply, {:error, :overridding_credentials}, state} + + {:error, :overridding_path_prefix} -> + Logger.warning("Unable to add component: tried to override s3 path_prefix") + {:reply, {:error, :overridding_path_prefix}, state} + + {:error, reason} -> + Logger.warning("Unable to add component: #{inspect(reason)}") + {:reply, :error, state} + end + end + + @impl true + def handle_call({:remove_component, component_id}, _from, state) do + {reply, state} = + if component_exists?(state, component_id) do + state = State.remove_component(state, component_id, :component_removed) + {:ok, state} + else + {{:error, :component_not_found}, state} + end + + {:reply, reply, state} + end + + @impl true + def handle_call({:subscribe, component_id, origins}, _from, state) do + component = State.get_component_by_id(state, component_id) + + engine_pid = State.engine_pid(state) + + reply = + case validate_subscription_mode(component) do + :ok when component.type == HLS -> + Endpoint.HLS.subscribe(engine_pid, component.id, origins) + + :ok when component.type == Recording -> + Endpoint.Recording.subscribe(engine_pid, component.id, origins) + + :ok when component.type not in [HLS, Recording] -> + {:error, :invalid_component_type} + + {:error, _reason} = error -> + error + end + + {:reply, reply, state} + end + + @impl true + def handle_call(:get_num_forwarded_tracks, _from, state) do + forwarded_tracks = + state + |> State.engine_pid() + |> Engine.get_num_forwarded_tracks() + + {:reply, forwarded_tracks, state} + end + + @impl true + def handle_call({:dial, component_id, phone_number}, _from, state) do + case State.fetch_component(state, component_id) do + {:ok, component} when component.type == SIP -> + state + |> State.engine_pid() + |> Endpoint.SIP.dial(component_id, phone_number) + + {:reply, :ok, state} + + {:ok, _component} -> + {:reply, {:error, :bad_component_type}, state} + + :error -> + {:reply, {:error, :component_does_not_exist}, state} + end + end + + @impl true + def handle_call({:end_call, component_id}, _from, state) do + case State.fetch_component(state, component_id) do + {:ok, component} when component.type == SIP -> + state + |> State.engine_pid() + |> Endpoint.SIP.end_call(component_id) + + {:reply, :ok, state} + + :error -> + {:reply, {:error, :component_does_not_exist}, state} + + {:ok, _component} -> + {:reply, {:error, :bad_component_type}, state} + end + end + + @impl true + def handle_cast({:media_event, peer_id, event}, state) do + state + |> State.engine_pid() + |> Engine.message_endpoint(peer_id, {:media_event, event}) + + {:noreply, state} + end + + @impl true + def handle_info(%EndpointMessage{endpoint_id: to, message: {:media_event, data}}, state) do + with {:ok, peer} <- State.fetch_peer(state, to), + socket_pid when is_pid(socket_pid) <- Map.get(peer, :socket_pid) do + send(socket_pid, {:media_event, data}) + else + nil -> + Logger.warning( + "Received Media Event from RTC Engine to peer #{inspect(to)} without established signaling connection" + ) + + :error -> + Logger.warning( + "Received Media Event from RTC Engine to non existent peer (target id: #{inspect(to)})" + ) + end + + {:noreply, state} + end + + @impl true + def handle_info(%EndpointCrashed{endpoint_id: endpoint_id, reason: reason}, state) do + Logger.error("RTC Engine endpoint #{inspect(endpoint_id)} crashed: #{inspect(reason)}") + + state = + if peer_exists?(state, endpoint_id) do + State.remove_peer(state, endpoint_id, {:peer_crashed, parse_crash_reason(reason)}) + else + State.remove_component(state, endpoint_id, :component_crashed) + end + + {:noreply, state} + end + + @impl true + def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do + state = State.disconnect_peer(state, pid) + + {:noreply, state} + end + + @impl true + def handle_info({:playlist_playable, :audio, _playlist_id}, state), do: {:noreply, state} + + @impl true + def handle_info({:playlist_playable, :video, _playlist_id}, state) do + state = State.set_hls_playable(state) + + {:noreply, state} + end + + @impl true + def handle_info(%EndpointMessage{} = msg, state) do + Logger.debug("Received msg from endpoint: #{inspect(msg)}") + {:noreply, state} + end + + @impl true + def handle_info( + %EndpointRemoved{endpoint_id: endpoint_id}, + state + ) + when not endpoint_exists?(state, endpoint_id) do + {:noreply, state} + end + + @impl true + def handle_info(%EndpointRemoved{endpoint_id: endpoint_id}, state) + when peer_exists?(state, endpoint_id) do + # The peer has been either removed, crashed or disconnected + # The changes in state are applied in appropriate callbacks + {:noreply, state} + end + + def handle_info(%EndpointRemoved{endpoint_id: endpoint_id}, state) + when component_exists?(state, endpoint_id) do + state = State.remove_component(state, endpoint_id, :component_finished) + {:noreply, state} + end + + @impl true + def handle_info( + %EndpointAdded{endpoint_id: endpoint_id}, + state + ) + when endpoint_exists?(state, endpoint_id) do + {:noreply, state} + end + + @impl true + def handle_info( + %EndpointMetadataUpdated{endpoint_id: endpoint_id, endpoint_metadata: metadata}, + state + ) + when peer_exists?(state, endpoint_id) do + Logger.info("Peer #{endpoint_id} metadata updated: #{inspect(metadata)}") + + state = State.update_peer_metadata(state, endpoint_id, metadata) + {:noreply, state} + end + + @impl true + def handle_info(%EndpointMetadataUpdated{}, state) do + {:noreply, state} + end + + @impl true + def handle_info(%TrackAdded{endpoint_id: endpoint_id} = track_info, state) + when endpoint_exists?(state, endpoint_id) do + state = State.put_track(state, track_info) + + {:noreply, state} + end + + @impl true + def handle_info(%TrackAdded{endpoint_id: endpoint_id} = track_info, state) do + Logger.error("Unknown endpoint #{endpoint_id} added track #{inspect(track_info)}") + {:noreply, state} + end + + @impl true + def handle_info(%TrackMetadataUpdated{endpoint_id: endpoint_id} = track_info, state) + when endpoint_exists?(state, endpoint_id) do + state = State.update_track(state, track_info) + + {:noreply, state} + end + + @impl true + def handle_info(%TrackMetadataUpdated{endpoint_id: endpoint_id} = track_info, state) do + Logger.error("Unknown endpoint #{endpoint_id} updated track #{inspect(track_info)}") + {:noreply, state} + end + + @impl true + def handle_info(%TrackRemoved{endpoint_id: endpoint_id} = track_info, state) + when endpoint_exists?(state, endpoint_id) do + state = State.remove_track(state, track_info) + + {:noreply, state} + end + + @impl true + def handle_info(%TrackRemoved{endpoint_id: endpoint_id} = track_info, state) do + Logger.error("Unknown endpoint #{endpoint_id} removed track #{inspect(track_info)}") + {:noreply, state} + end + + @impl true + def handle_info(:peerless_purge, state) do + if State.peerless_long_enough?(state) do + Logger.info( + "Removing room because it was peerless for #{State.peerless_purge_timeout(state)} seconds" + ) + + {:stop, :normal, state} + else + Logger.debug("Ignore peerless purge message") + + {:noreply, state} + end + end + + @impl true + def handle_info({:peer_purge, peer_id}, state) do + with {:ok, peer} <- State.fetch_peer(state, peer_id), + true <- State.peer_disconnected_long_enough?(state, peer) do + Logger.info( + "Removing peer because it was disconnected for #{State.peerless_purge_timeout(state)} seconds" + ) + + state = State.remove_peer(state, peer_id, :timeout) + + {:noreply, state} + else + _other -> + Logger.debug("Ignore peer purge message for peer: #{peer_id}") + + {:noreply, state} + end + end + + @impl true + def handle_info(info, state) do + Logger.warning("Received unexpected info: #{inspect(info)}") + {:noreply, state} + end + + @impl true + def terminate(_reason, %{engine_pid: engine_pid} = state) do + Engine.terminate(engine_pid, asynchronous?: true, timeout: 10_000) + + State.remove_all_endpoints(state) + + :ok + end + + defp parse_crash_reason( + {:membrane_child_crash, _child, {%RuntimeError{message: reason}, _stack}} + ), + do: reason + + defp parse_crash_reason(_reason), do: nil +end diff --git a/lib/fishjam/local/room_service.ex b/lib/fishjam/local/room_service.ex new file mode 100644 index 00000000..c854b7b5 --- /dev/null +++ b/lib/fishjam/local/room_service.ex @@ -0,0 +1,236 @@ +defmodule Fishjam.Local.RoomService do + @moduledoc """ + Module responsible for managing rooms on this Fishjam instance. + """ + + @behaviour Fishjam.RoomService + + use GenServer + + require Logger + + alias Fishjam.Event + alias Fishjam.Local.Room + alias Fishjam.Room.ID + alias Fishjam.WebhookNotifier + + @metric_interval_in_seconds Application.compile_env!(:fishjam, :room_metrics_scrape_interval) + @metric_interval_in_milliseconds @metric_interval_in_seconds * 1_000 + + ## NON-ROUTABLE FUNCTIONS + + @spec start_link(term()) :: GenServer.on_start() + def start_link(args) do + GenServer.start_link(__MODULE__, args, name: __MODULE__) + end + + @spec get_resource_usage() :: %{ + node: Node.t(), + forwarded_tracks_number: integer(), + rooms_number: integer() + } + def get_resource_usage() do + room_ids = get_rooms_ids() + + room_ids + |> Enum.map(fn room_id -> + Task.Supervisor.async_nolink(Fishjam.TaskSupervisor, fn -> + Room.get_num_forwarded_tracks(room_id) + end) + end) + |> Task.yield_many() + |> Enum.map(fn {task, res} -> + res || Task.shutdown(task, :brutal_kill) + end) + |> Enum.filter(fn + {:ok, _res} -> true + _other -> false + end) + |> Enum.map(fn {:ok, res} -> res end) + |> then( + &%{node: Node.self(), forwarded_tracks_number: Enum.sum(&1), rooms_number: Enum.count(&1)} + ) + end + + ## CLUSTER-ROUTABLE FUNCTIONS + + @impl Fishjam.RoomService + def find_room(room_id) do + case Registry.lookup(Fishjam.RoomRegistry, room_id) do + [{room_pid, _value}] -> + {:ok, room_pid} + + _not_found -> + {:error, :room_not_found} + end + end + + @impl Fishjam.RoomService + def get_room(room_id) do + room = Room.get_state(room_id) + + if is_nil(room) do + {:error, :room_not_found} + else + {:ok, room} + end + end + + @impl Fishjam.RoomService + def list_rooms() do + get_rooms_ids() + |> Enum.map(&Room.get_state(&1)) + |> Enum.reject(&(&1 == nil)) + end + + @impl Fishjam.RoomService + def create_room(config) do + GenServer.call(__MODULE__, {:create_room, config}) + end + + @impl Fishjam.RoomService + def delete_room(room_id) do + GenServer.call(__MODULE__, {:delete_room, room_id}) + end + + ## GENSERVER CALLBACKS + + @impl true + def init(_opts) do + {:ok, %{rooms: %{}}, {:continue, nil}} + end + + @impl true + def handle_continue(_continue_arg, state) do + Process.send_after(self(), :rooms_metrics, @metric_interval_in_milliseconds) + :ok = Phoenix.PubSub.subscribe(Fishjam.PubSub, "fishjams") + {:noreply, state} + end + + @impl true + def handle_call({:create_room, config}, _from, state) do + Logger.debug("Creating a new room") + + # Re-generate the room ID to ensure it has the correct node name part + # FIXME: This is a quick fix for the issue, but this logic probably should be someplace else + {:ok, room_id} = ID.generate(config.room_id) + config = %{config | room_id: room_id} + + with {:ok, room_pid, room_id} <- Room.start(config) do + Logger.debug("Room created successfully") + room = Room.get_state(room_id) + Process.monitor(room_pid) + + state = put_in(state, [:rooms, room_pid], room_id) + + WebhookNotifier.add_webhook(room_id, config.webhook_url) + + Logger.info("Created room #{inspect(room.id)}") + + Event.broadcast_server_notification({:room_created, room_id}) + + {:reply, {:ok, room, Fishjam.address()}, state} + else + {:error, :room_already_exists} = error -> + Logger.warning("Room creation failed, because it already exists") + + {:reply, error, state} + + reason -> + Logger.warning("Room creation failed with reason: #{inspect(reason)}") + {:reply, {:error, :room_doesnt_start}, state} + end + end + + @impl true + def handle_call({:delete_room, room_id}, _from, state) do + response = + case find_room(room_id) do + {:ok, _room_pid} -> + remove_room(room_id) + :ok + + {:error, _} -> + {:error, :room_not_found} + end + + {:reply, response, state} + end + + @impl true + def handle_info(:rooms_metrics, state) do + rooms = list_rooms() + + :telemetry.execute( + [:fishjam], + %{ + rooms: Enum.count(rooms) + } + ) + + for room <- rooms do + peer_count = room.peers |> Enum.count() + + :telemetry.execute( + [:fishjam, :room], + %{ + peers: peer_count, + peer_time: peer_count * @metric_interval_in_seconds, + duration: @metric_interval_in_seconds + }, + %{room_id: room.id} + ) + end + + Process.send_after(self(), :rooms_metrics, @metric_interval_in_milliseconds) + + {:noreply, state} + end + + @impl true + def handle_info({:DOWN, _ref, :process, pid, :normal}, state) do + {room_id, state} = pop_in(state, [:rooms, pid]) + + Logger.debug("Room #{inspect(room_id)} is down with reason: normal") + + Phoenix.PubSub.broadcast(Fishjam.PubSub, room_id, :room_stopped) + Event.broadcast_server_notification({:room_deleted, room_id}) + clear_room_metrics(room_id) + + {:noreply, state} + end + + @impl true + def handle_info({:DOWN, _ref, :process, pid, reason}, state) do + {room_id, state} = pop_in(state, [:rooms, pid]) + + Logger.warning("Process #{room_id} is down with reason: #{inspect(reason)}") + + Phoenix.PubSub.broadcast(Fishjam.PubSub, room_id, :room_crashed) + Event.broadcast_server_notification({:room_crashed, room_id}) + clear_room_metrics(room_id) + + {:noreply, state} + end + + defp clear_room_metrics(room_id) do + :telemetry.execute([:fishjam, :room], %{peers: 0}, %{room_id: room_id}) + end + + defp get_rooms_ids() do + Fishjam.RoomRegistry + |> Registry.select([{{:"$1", :_, :_}, [], [:"$1"]}]) + end + + defp remove_room(room_id) do + room = Room.registry_id(room_id) + + try do + :ok = GenServer.stop(room, :normal) + Logger.info("Deleted room #{inspect(room_id)}") + catch + :exit, {:noproc, {GenServer, :stop, [^room, :normal, :infinity]}} -> + Logger.warning("Room process with id #{inspect(room_id)} doesn't exist") + end + end +end diff --git a/lib/fishjam/resource_manager.ex b/lib/fishjam/resource_manager.ex index 65310e47..e27031aa 100644 --- a/lib/fishjam/resource_manager.ex +++ b/lib/fishjam/resource_manager.ex @@ -9,7 +9,7 @@ defmodule Fishjam.ResourceManager do require Logger alias Fishjam.Component.Recording - alias Fishjam.RoomService + alias Fishjam.Local.RoomService @type seconds :: pos_integer() @type opts :: %{interval: seconds(), recording_timeout: seconds()} diff --git a/lib/fishjam/room.ex b/lib/fishjam/room.ex index ac41daa7..a373a06c 100644 --- a/lib/fishjam/room.ex +++ b/lib/fishjam/room.ex @@ -1,604 +1,56 @@ defmodule Fishjam.Room do @moduledoc """ - Module representing room. + Behaviour for Fishjam.{Cluster, Local}.Room """ - use Bunch.Access - use GenServer + alias Fishjam.{Component, Peer} + alias Fishjam.Room.ID - import Fishjam.Room.State + @type cluster_error :: :invalid_room_id | :node_not_found | :rpc_failed - require Logger + @callback add_peer(ID.id(), Peer.peer(), map()) :: + {:ok, Peer.t()} + | :error + | {:error, + cluster_error() + | {:peer_disabled_globally, String.t()} + | {:reached_peers_limit, String.t()}} - alias Fishjam.Component - alias Fishjam.Component.{HLS, Recording, SIP} - alias Fishjam.Peer - alias Fishjam.Room.{Config, State} + @callback set_peer_connected(ID.id(), Peer.id()) :: + :ok | {:error, cluster_error() | :peer_not_found | :peer_already_connected} - alias Membrane.RTC.Engine - alias Membrane.RTC.Engine.Endpoint + @callback get_peer_connection_status(ID.id(), Peer.id()) :: + {:ok, Peer.status()} | {:error, :peer_not_found} - alias Membrane.RTC.Engine.Message.{ - EndpointAdded, - EndpointCrashed, - EndpointMessage, - EndpointMetadataUpdated, - EndpointRemoved, - TrackAdded, - TrackMetadataUpdated, - TrackRemoved - } + @callback remove_peer(ID.id(), Peer.id()) :: :ok | {:error, cluster_error() | :peer_not_found} - @type id :: String.t() - @type t :: State.t() + @callback add_component(ID.id(), Component.component(), map()) :: + {:ok, Component.t()} + | :error + | {:error, + cluster_error() + | {:component_disabled_globally, String.t()} + | :incompatible_codec + | {:reached_components_limit, String.t()} + | :file_does_not_exist + | :bad_parameter_framerate_for_audio + | :invalid_framerate + | :invalid_file_path + | :unsupported_file_type + | {:missing_parameter, term()} + | :missing_s3_credentials + | :overriding_credentials + | :overriding_path_prefix} - def registry_id(room_id), do: {:via, Registry, {Fishjam.RoomRegistry, room_id}} + @callback remove_component(ID.id(), Component.id()) :: + :ok | {:error, cluster_error() | :component_not_found} - @spec start(Config.t()) :: {:ok, pid(), id()} - def start(%Config{room_id: id} = config) do - with {:ok, pid} <- GenServer.start(__MODULE__, [id, config], name: registry_id(id)) do - {:ok, pid, id} - else - {:error, {:already_started, _pid}} -> - {:error, :room_already_exists} - end - end + @callback subscribe(ID.id(), Component.id(), [Peer.id() | Component.id()]) :: + :ok | {:error, term()} - @spec get_state(id()) :: State.t() | nil - def get_state(room_id) do - registry_room_id = registry_id(room_id) + @callback dial(ID.id(), Component.id(), String.t()) :: :ok | {:error, term()} - try do - GenServer.call(registry_room_id, :get_state) - catch - :exit, {reason, {GenServer, :call, [^registry_room_id, :get_state, _timeout]}} - when reason in [:noproc, :normal] -> - Logger.warning( - "Cannot get state of #{inspect(room_id)}, the room's process doesn't exist anymore" - ) + @callback end_call(ID.id(), Component.id()) :: :ok | {:error, term()} - nil - end - end - - @spec get_num_forwarded_tracks(id()) :: integer() - def get_num_forwarded_tracks(room_id) do - GenServer.call(registry_id(room_id), :get_num_forwarded_tracks) - end - - @spec add_peer(id(), Peer.peer(), map()) :: - {:ok, Peer.t()} - | :error - | {:error, {:peer_disabled_globally, String.t()} | {:reached_peers_limit, String.t()}} - def add_peer(room_id, peer_type, options \\ %{}) do - GenServer.call(registry_id(room_id), {:add_peer, peer_type, options}) - end - - @spec set_peer_connected(id(), Peer.id()) :: - :ok | {:error, :peer_not_found | :peer_already_connected} - def set_peer_connected(room_id, peer_id) do - GenServer.call(registry_id(room_id), {:set_peer_connected, peer_id}) - end - - @spec get_peer_connection_status(id(), Peer.id()) :: - {:ok, Peer.status()} | {:error, :peer_not_found} - def get_peer_connection_status(room_id, peer_id) do - GenServer.call(registry_id(room_id), {:get_peer_connection_status, peer_id}) - end - - @spec remove_peer(id(), Peer.id()) :: :ok | {:error, :peer_not_found} - def remove_peer(room_id, peer_id) do - GenServer.call(registry_id(room_id), {:remove_peer, peer_id}) - end - - @spec add_component(id(), Component.component(), map()) :: - {:ok, Component.t()} - | :error - | {:error, - {:component_disabled_globally, String.t()} - | :incompatible_codec - | {:reached_components_limit, String.t()} - | :file_does_not_exist - | :bad_parameter_framerate_for_audio - | :invalid_framerate - | :invalid_file_path - | :unsupported_file_type - | {:missing_parameter, term()} - | :missing_s3_credentials - | :overriding_credentials - | :overriding_path_prefix} - def add_component(room_id, component_type, options \\ %{}) do - GenServer.call(registry_id(room_id), {:add_component, component_type, options}) - end - - @spec remove_component(id(), Component.id()) :: :ok | {:error, :component_not_found} - def remove_component(room_id, component_id) do - GenServer.call(registry_id(room_id), {:remove_component, component_id}) - end - - @spec subscribe(id(), Component.id(), [Peer.id() | Component.id()]) :: - :ok | {:error, term()} - def subscribe(room_id, component_id, origins) do - GenServer.call(registry_id(room_id), {:subscribe, component_id, origins}) - end - - @spec dial(id(), Component.id(), String.t()) :: - :ok | {:error, term()} - def dial(room_id, component_id, phone_number) do - GenServer.call(registry_id(room_id), {:dial, component_id, phone_number}) - end - - @spec end_call(id(), Component.id()) :: - :ok | {:error, term()} - def end_call(room_id, component_id) do - GenServer.call(registry_id(room_id), {:end_call, component_id}) - end - - @spec receive_media_event(id(), Peer.id(), String.t()) :: :ok - def receive_media_event(room_id, peer_id, event) do - GenServer.cast(registry_id(room_id), {:media_event, peer_id, event}) - end - - @impl true - def init([id, config]) do - state = State.new(id, config) - - Logger.metadata(room_id: id) - Logger.info("Initialize room") - - {:ok, state} - end - - @impl true - def handle_call(:get_state, _from, state) do - {:reply, state, state} - end - - @impl true - def handle_call({:add_peer, peer_type, override_options}, _from, state) do - with :ok <- State.check_peer_allowed(peer_type, state), - options <- State.generate_peer_options(state, override_options), - {:ok, peer} <- Peer.new(peer_type, options) do - state = State.add_peer(state, peer) - - {:reply, {:ok, peer}, state} - else - {:error, :peer_disabled_globally} -> - type = Peer.to_string!(peer_type) - Logger.warning("Unable to add peer: #{type} peers are disabled globally") - {:reply, {:error, {:peer_disabled_globally, type}}, state} - - {:error, :reached_peers_limit} -> - type = Peer.to_string!(peer_type) - Logger.warning("Unable to add peer: Reached #{type} peers limit") - {:reply, {:error, {:reached_peers_limit, type}}, state} - - {:error, reason} -> - Logger.warning("Unable to add peer: #{inspect(reason)}") - {:reply, :error, state} - end - end - - @impl true - def handle_call({:set_peer_connected, peer_id}, {socket_pid, _tag}, state) do - {reply, state} = - case State.fetch_peer(state, peer_id) do - {:ok, %{status: :disconnected} = peer} -> - Process.monitor(socket_pid) - - state = State.connect_peer(state, peer, socket_pid) - - {:ok, state} - - {:ok, %{status: :connected}} -> - {{:error, :peer_already_connected}, state} - - :error -> - {{:error, :peer_not_found}, state} - end - - {:reply, reply, state} - end - - @impl true - def handle_call({:get_peer_connection_status, peer_id}, _from, state) do - reply = - case State.fetch_peer(state, peer_id) do - {:ok, peer} -> {:ok, peer.status} - :error -> {:error, :peer_not_found} - end - - {:reply, reply, state} - end - - @impl true - def handle_call({:remove_peer, peer_id}, _from, state) do - {reply, state} = - if peer_exists?(state, peer_id) do - state = State.remove_peer(state, peer_id, :peer_removed) - - {:ok, state} - else - {{:error, :peer_not_found}, state} - end - - {:reply, reply, state} - end - - @impl true - def handle_call({:add_component, component_type, options}, _from, state) do - engine_pid = State.engine_pid(state) - - options = - Map.merge( - %{engine_pid: engine_pid, room_id: State.id(state)}, - options - ) - - with :ok <- State.check_component_allowed(component_type, state), - {:ok, component} <- Component.new(component_type, options) do - state = State.put_component(state, component) - - component_type.after_init(state, component, options) - - :ok = Engine.add_endpoint(engine_pid, component.engine_endpoint, id: component.id) - - Logger.info("Added component #{inspect(component.id)}") - - {:reply, {:ok, component}, state} - else - {:error, :component_disabled_globally} -> - type = Component.to_string!(component_type) - Logger.warning("Unable to add component: #{type} components are disabled globally") - {:reply, {:error, {:component_disabled_globally, type}}, state} - - {:error, :incompatible_codec} -> - Logger.warning("Unable to add component: incompatible codec") - {:reply, {:error, :incompatible_codec}, state} - - {:error, :reached_components_limit} -> - type = Component.to_string!(component_type) - Logger.warning("Unable to add component: reached #{type} components limit") - {:reply, {:error, {:reached_components_limit, type}}, state} - - {:error, :file_does_not_exist} -> - Logger.warning("Unable to add component: file does not exist") - {:reply, {:error, :file_does_not_exist}, state} - - {:error, :bad_parameter_framerate_for_audio} -> - Logger.warning("Unable to add component: attempted to set framerate for audio component") - {:reply, {:error, :bad_parameter_framerate_for_audio}, state} - - {:error, {:invalid_framerate, passed_framerate}} -> - Logger.warning( - "Unable to add component: expected framerate to be a positive integer, got: #{passed_framerate}" - ) - - {:reply, {:error, :invalid_framerate}, state} - - {:error, :invalid_file_path} -> - Logger.warning("Unable to add component: invalid file path") - {:reply, {:error, :invalid_file_path}, state} - - {:error, :unsupported_file_type} -> - Logger.warning("Unable to add component: unsupported file type") - {:reply, {:error, :unsupported_file_type}, state} - - {:error, {:missing_parameter, name}} -> - Logger.warning("Unable to add component: missing parameter #{inspect(name)}") - {:reply, {:error, {:missing_parameter, name}}, state} - - {:error, :missing_s3_credentials} -> - Logger.warning("Unable to add component: missing s3 credentials") - {:reply, {:error, :missing_s3_credentials}, state} - - {:error, :overridding_credentials} -> - Logger.warning("Unable to add component: tried to override s3 credentials") - {:reply, {:error, :overridding_credentials}, state} - - {:error, :overridding_path_prefix} -> - Logger.warning("Unable to add component: tried to override s3 path_prefix") - {:reply, {:error, :overridding_path_prefix}, state} - - {:error, reason} -> - Logger.warning("Unable to add component: #{inspect(reason)}") - {:reply, :error, state} - end - end - - @impl true - def handle_call({:remove_component, component_id}, _from, state) do - {reply, state} = - if component_exists?(state, component_id) do - state = State.remove_component(state, component_id, :component_removed) - {:ok, state} - else - {{:error, :component_not_found}, state} - end - - {:reply, reply, state} - end - - @impl true - def handle_call({:subscribe, component_id, origins}, _from, state) do - component = State.get_component_by_id(state, component_id) - - engine_pid = State.engine_pid(state) - - reply = - case validate_subscription_mode(component) do - :ok when component.type == HLS -> - Endpoint.HLS.subscribe(engine_pid, component.id, origins) - - :ok when component.type == Recording -> - Endpoint.Recording.subscribe(engine_pid, component.id, origins) - - :ok when component.type not in [HLS, Recording] -> - {:error, :invalid_component_type} - - {:error, _reason} = error -> - error - end - - {:reply, reply, state} - end - - @impl true - def handle_call(:get_num_forwarded_tracks, _from, state) do - forwarded_tracks = - state - |> State.engine_pid() - |> Engine.get_num_forwarded_tracks() - - {:reply, forwarded_tracks, state} - end - - @impl true - def handle_call({:dial, component_id, phone_number}, _from, state) do - case State.fetch_component(state, component_id) do - {:ok, component} when component.type == SIP -> - state - |> State.engine_pid() - |> Endpoint.SIP.dial(component_id, phone_number) - - {:reply, :ok, state} - - {:ok, _component} -> - {:reply, {:error, :bad_component_type}, state} - - :error -> - {:reply, {:error, :component_does_not_exist}, state} - end - end - - @impl true - def handle_call({:end_call, component_id}, _from, state) do - case State.fetch_component(state, component_id) do - {:ok, component} when component.type == SIP -> - state - |> State.engine_pid() - |> Endpoint.SIP.end_call(component_id) - - {:reply, :ok, state} - - :error -> - {:reply, {:error, :component_does_not_exist}, state} - - {:ok, _component} -> - {:reply, {:error, :bad_component_type}, state} - end - end - - @impl true - def handle_cast({:media_event, peer_id, event}, state) do - state - |> State.engine_pid() - |> Engine.message_endpoint(peer_id, {:media_event, event}) - - {:noreply, state} - end - - @impl true - def handle_info(%EndpointMessage{endpoint_id: to, message: {:media_event, data}}, state) do - with {:ok, peer} <- State.fetch_peer(state, to), - socket_pid when is_pid(socket_pid) <- Map.get(peer, :socket_pid) do - send(socket_pid, {:media_event, data}) - else - nil -> - Logger.warning( - "Received Media Event from RTC Engine to peer #{inspect(to)} without established signaling connection" - ) - - :error -> - Logger.warning( - "Received Media Event from RTC Engine to non existent peer (target id: #{inspect(to)})" - ) - end - - {:noreply, state} - end - - @impl true - def handle_info(%EndpointCrashed{endpoint_id: endpoint_id, reason: reason}, state) do - Logger.error("RTC Engine endpoint #{inspect(endpoint_id)} crashed: #{inspect(reason)}") - - state = - if peer_exists?(state, endpoint_id) do - State.remove_peer(state, endpoint_id, {:peer_crashed, parse_crash_reason(reason)}) - else - State.remove_component(state, endpoint_id, :component_crashed) - end - - {:noreply, state} - end - - @impl true - def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do - state = State.disconnect_peer(state, pid) - - {:noreply, state} - end - - @impl true - def handle_info({:playlist_playable, :audio, _playlist_id}, state), do: {:noreply, state} - - @impl true - def handle_info({:playlist_playable, :video, _playlist_id}, state) do - state = State.set_hls_playable(state) - - {:noreply, state} - end - - @impl true - def handle_info(%EndpointMessage{} = msg, state) do - Logger.debug("Received msg from endpoint: #{inspect(msg)}") - {:noreply, state} - end - - @impl true - def handle_info( - %EndpointRemoved{endpoint_id: endpoint_id}, - state - ) - when not endpoint_exists?(state, endpoint_id) do - {:noreply, state} - end - - @impl true - def handle_info(%EndpointRemoved{endpoint_id: endpoint_id}, state) - when peer_exists?(state, endpoint_id) do - # The peer has been either removed, crashed or disconnected - # The changes in state are applied in appropriate callbacks - {:noreply, state} - end - - def handle_info(%EndpointRemoved{endpoint_id: endpoint_id}, state) - when component_exists?(state, endpoint_id) do - state = State.remove_component(state, endpoint_id, :component_finished) - {:noreply, state} - end - - @impl true - def handle_info( - %EndpointAdded{endpoint_id: endpoint_id}, - state - ) - when endpoint_exists?(state, endpoint_id) do - {:noreply, state} - end - - @impl true - def handle_info( - %EndpointMetadataUpdated{endpoint_id: endpoint_id, endpoint_metadata: metadata}, - state - ) - when peer_exists?(state, endpoint_id) do - Logger.info("Peer #{endpoint_id} metadata updated: #{inspect(metadata)}") - - state = State.update_peer_metadata(state, endpoint_id, metadata) - {:noreply, state} - end - - @impl true - def handle_info(%EndpointMetadataUpdated{}, state) do - {:noreply, state} - end - - @impl true - def handle_info(%TrackAdded{endpoint_id: endpoint_id} = track_info, state) - when endpoint_exists?(state, endpoint_id) do - state = State.put_track(state, track_info) - - {:noreply, state} - end - - @impl true - def handle_info(%TrackAdded{endpoint_id: endpoint_id} = track_info, state) do - Logger.error("Unknown endpoint #{endpoint_id} added track #{inspect(track_info)}") - {:noreply, state} - end - - @impl true - def handle_info(%TrackMetadataUpdated{endpoint_id: endpoint_id} = track_info, state) - when endpoint_exists?(state, endpoint_id) do - state = State.update_track(state, track_info) - - {:noreply, state} - end - - @impl true - def handle_info(%TrackMetadataUpdated{endpoint_id: endpoint_id} = track_info, state) do - Logger.error("Unknown endpoint #{endpoint_id} updated track #{inspect(track_info)}") - {:noreply, state} - end - - @impl true - def handle_info(%TrackRemoved{endpoint_id: endpoint_id} = track_info, state) - when endpoint_exists?(state, endpoint_id) do - state = State.remove_track(state, track_info) - - {:noreply, state} - end - - @impl true - def handle_info(%TrackRemoved{endpoint_id: endpoint_id} = track_info, state) do - Logger.error("Unknown endpoint #{endpoint_id} removed track #{inspect(track_info)}") - {:noreply, state} - end - - @impl true - def handle_info(:peerless_purge, state) do - if State.peerless_long_enough?(state) do - Logger.info( - "Removing room because it was peerless for #{State.peerless_purge_timeout(state)} seconds" - ) - - {:stop, :normal, state} - else - Logger.debug("Ignore peerless purge message") - - {:noreply, state} - end - end - - @impl true - def handle_info({:peer_purge, peer_id}, state) do - with {:ok, peer} <- State.fetch_peer(state, peer_id), - true <- State.peer_disconnected_long_enough?(state, peer) do - Logger.info( - "Removing peer because it was disconnected for #{State.peerless_purge_timeout(state)} seconds" - ) - - state = State.remove_peer(state, peer_id, :timeout) - - {:noreply, state} - else - _other -> - Logger.debug("Ignore peer purge message for peer: #{peer_id}") - - {:noreply, state} - end - end - - @impl true - def handle_info(info, state) do - Logger.warning("Received unexpected info: #{inspect(info)}") - {:noreply, state} - end - - @impl true - def terminate(_reason, %{engine_pid: engine_pid} = state) do - Engine.terminate(engine_pid, asynchronous?: true, timeout: 10_000) - - State.remove_all_endpoints(state) - - :ok - end - - defp parse_crash_reason( - {:membrane_child_crash, _child, {%RuntimeError{message: reason}, _stack}} - ), - do: reason - - defp parse_crash_reason(_reason), do: nil + @callback receive_media_event(ID.id(), Peer.id(), String.t()) :: :ok | {:error, cluster_error()} end diff --git a/lib/fishjam/room/id.ex b/lib/fishjam/room/id.ex index 5c16f521..fcebaf07 100644 --- a/lib/fishjam/room/id.ex +++ b/lib/fishjam/room/id.ex @@ -6,22 +6,23 @@ defmodule Fishjam.Room.ID do @type id :: String.t() @doc """ - Based on the Room ID determines to which node it belongs to. - Returns an error if the node isn't present in the cluster. - - DISCLAIMER: - It should be used only with room_ids generated by generate/0, otherwise it can raise. + Depending on feature flag "request_routing_enabled": + - if `true`, determines the node holding the room based on the room ID + and returns an error if the node isn't present in the cluster + - if `false`, returns `{:ok, Node.self()}` """ @spec determine_node(id()) :: - {:ok, node()} | {:error, :invalid_room_id} | {:error, :invalid_node} + {:ok, node()} | {:error, :invalid_room_id | :node_not_found} def determine_node(room_id) do with {:ok, room_id} <- validate_room_id(room_id), - node_name <- decode_node_name(room_id), - true <- node_present_in_cluster?(node_name) do + {:flag?, true} <- {:flag?, Fishjam.FeatureFlags.request_routing_enabled?()}, + {:ok, node_name} <- decode_node_name(room_id), + {:present?, true} <- {:present?, node_present_in_cluster?(node_name)} do {:ok, node_name} else - {:error, :invalid_room_id} -> {:error, :invalid_room_id} - false -> {:error, :invalid_node} + {:flag?, false} -> {:ok, Node.self()} + {:present?, false} -> {:error, :node_not_found} + {:error, _reason} = error -> error end end @@ -44,16 +45,15 @@ defmodule Fishjam.Room.ID do end @doc """ - Depending on feature flag "custom_room_name_disabled" - - uses `generate/0` to generate room_id - or - - parses the `room_id` provided by the client + Depending on feature flag "request_routing_enabled": + - if `true`, uses `generate/0` to generate room_id + - if `false`, parses the `room_id` provided by the client """ @spec generate(nil | String.t()) :: {:ok, id()} | {:error, :invalid_room_id} def generate(nil), do: generate(UUID.uuid4()) def generate(room_id) do - if Fishjam.FeatureFlags.custom_room_name_disabled?() do + if Fishjam.FeatureFlags.request_routing_enabled?() do {:ok, generate()} else validate_room_id(room_id) @@ -65,18 +65,28 @@ defmodule Fishjam.Room.ID do |> String.split("-") |> Enum.take(-1) |> Enum.at(0) - |> Base.decode16!(case: :lower) - |> String.to_existing_atom() + |> Base.decode16(case: :lower) + |> case do + {:ok, node_name} -> + try do + {:ok, String.to_existing_atom(node_name)} + rescue + ArgumentError -> {:error, :node_not_found} + end + + :error -> + {:error, :invalid_room_id} + end end - defp encoded_node_name do + defp encoded_node_name() do Node.self() |> Atom.to_string() |> Base.encode16(case: :lower) end - defp node_present_in_cluster?(node) do - node in [Node.self() | Node.list()] + defp node_present_in_cluster?(node_name) do + node_name in [Node.self() | Node.list()] end defp validate_room_id(room_id) when is_binary(room_id) do diff --git a/lib/fishjam/room/state.ex b/lib/fishjam/room/state.ex index fd2ac547..a00f747d 100644 --- a/lib/fishjam/room/state.ex +++ b/lib/fishjam/room/state.ex @@ -5,9 +5,9 @@ defmodule Fishjam.Room.State do require Logger - alias Fishjam.{Component, Event, Peer, Room, Track} + alias Fishjam.{Component, Event, Peer, Track} alias Fishjam.Component.{HLS, Recording, RTSP} - alias Fishjam.Room.Config + alias Fishjam.Room.{Config, ID} alias Membrane.ICE.TURNManager alias Membrane.RTC.Engine @@ -39,7 +39,7 @@ defmodule Fishjam.Room.State do * `last_peer_left` - arbitrary timestamp with latest occurence of the room becoming peerless """ @type t :: %__MODULE__{ - id: Room.id(), + id: ID.id(), config: Config.t(), components: %{Component.id() => Component.t()}, peers: %{Peer.id() => Peer.t()}, @@ -55,7 +55,7 @@ defmodule Fishjam.Room.State do defguard endpoint_exists?(state, endpoint_id) when peer_exists?(state, endpoint_id) or component_exists?(state, endpoint_id) - @spec new(id :: Room.id(), config :: Config.t()) :: t() + @spec new(id :: ID.id(), config :: Config.t()) :: t() def new(id, config) do rtc_engine_options = [ id: id @@ -98,7 +98,7 @@ defmodule Fishjam.Room.State do state end - @spec id(state :: t()) :: Room.id() + @spec id(state :: t()) :: ID.id() def id(state), do: state.id @spec engine_pid(state :: t()) :: pid() diff --git a/lib/fishjam/room_service.ex b/lib/fishjam/room_service.ex index 13f159d8..55b8c124 100644 --- a/lib/fishjam/room_service.ex +++ b/lib/fishjam/room_service.ex @@ -1,258 +1,22 @@ defmodule Fishjam.RoomService do @moduledoc """ - Module responsible for managing rooms. + Behaviour for Fishjam.{Cluster, Local}.RoomService """ - use GenServer + alias Fishjam.Local.Room + alias Fishjam.Room.{Config, ID} - require Logger + @type cluster_error :: :invalid_room_id | :node_not_found | :rpc_failed - alias Fishjam.Event - alias Fishjam.Room - alias Fishjam.WebhookNotifier + @callback find_room(ID.id()) :: {:ok, pid()} | {:error, cluster_error() | :room_not_found} - @metric_interval_in_seconds Application.compile_env!(:fishjam, :room_metrics_scrape_interval) - @metric_interval_in_milliseconds @metric_interval_in_seconds * 1_000 + @callback get_room(ID.id()) :: {:ok, Room.t()} | {:error, cluster_error() | :room_not_found} - def start_link(args) do - GenServer.start_link(__MODULE__, args, name: __MODULE__) - end + @callback list_rooms() :: [Room.t()] - @spec find_room(Room.id()) :: {:ok, pid()} | {:error, :room_not_found} - def find_room(room_id) do - case Registry.lookup(Fishjam.RoomRegistry, room_id) do - [{room_pid, _value}] -> - {:ok, room_pid} + @callback create_room(Config.t()) :: + {:ok, Room.t(), String.t()} + | {:error, :rpc_failed | :room_already_exists | :room_doesnt_start} - _not_found -> - {:error, :room_not_found} - end - end - - @spec find_room!(Room.id()) :: pid() | no_return() - def find_room!(room_id) do - case find_room(room_id) do - {:ok, pid} -> - pid - - _not_found -> - raise "Room with id #{room_id} doesn't exist" - end - end - - @spec get_room(Room.id()) :: {:ok, Room.t()} | {:error, :room_not_found} - def get_room(room_id) do - room = Room.get_state(room_id) - - if is_nil(room) do - {:error, :room_not_found} - else - {:ok, room} - end - end - - @spec list_rooms() :: [Room.t()] - def list_rooms() do - get_rooms_ids() - |> Enum.map(&Room.get_state(&1)) - |> Enum.reject(&(&1 == nil)) - end - - @spec create_room(Room.Config.t()) :: {:ok, Room.t(), String.t()} | {:error, atom()} - def create_room(config) do - case Fishjam.RPCClient.multicall(Fishjam.RoomService, :get_resource_usage, []) do - [_only_self_resources] -> - GenServer.call(__MODULE__, {:create_room, config}) - - nodes_resources -> - min_node = find_best_node(nodes_resources) - Logger.info("Node with least used resources is #{inspect(min_node)}") - GenServer.call({__MODULE__, min_node}, {:create_room, config}) - end - end - - @spec delete_room(Room.id()) :: :ok | {:error, :room_not_found} - def delete_room(room_id) do - GenServer.call(__MODULE__, {:delete_room, room_id}) - end - - @spec get_resource_usage() :: %{ - node: Node.t(), - forwarded_tracks_number: integer(), - rooms_number: integer() - } - def get_resource_usage() do - room_ids = get_rooms_ids() - - room_ids - |> Enum.map(fn room_id -> - Task.Supervisor.async_nolink(Fishjam.TaskSupervisor, fn -> - Room.get_num_forwarded_tracks(room_id) - end) - end) - |> Task.yield_many() - |> Enum.map(fn {task, res} -> - res || Task.shutdown(task, :brutal_kill) - end) - |> Enum.filter(fn - {:ok, _res} -> true - _other -> false - end) - |> Enum.map(fn {:ok, res} -> res end) - |> then( - &%{node: Node.self(), forwarded_tracks_number: Enum.sum(&1), rooms_number: Enum.count(&1)} - ) - end - - @impl true - def init(_opts) do - {:ok, %{rooms: %{}}, {:continue, nil}} - end - - @impl true - def handle_continue(_continue_arg, state) do - Process.send_after(self(), :rooms_metrics, @metric_interval_in_milliseconds) - :ok = Phoenix.PubSub.subscribe(Fishjam.PubSub, "fishjams") - {:noreply, state} - end - - @impl true - def handle_call({:create_room, config}, _from, state) do - Logger.debug("Creating a new room") - - with {:ok, room_pid, room_id} <- Room.start(config) do - Logger.debug("Room created successfully") - room = Room.get_state(room_id) - Process.monitor(room_pid) - - state = put_in(state, [:rooms, room_pid], room_id) - - WebhookNotifier.add_webhook(room_id, config.webhook_url) - - Logger.info("Created room #{inspect(room.id)}") - - Event.broadcast_server_notification({:room_created, room_id}) - - {:reply, {:ok, room, Fishjam.address()}, state} - else - {:error, :room_already_exists} = error -> - Logger.warning("Room creation failed, because it already exists") - - {:reply, error, state} - - reason -> - Logger.warning("Room creation failed with reason: #{inspect(reason)}") - {:reply, {:error, :room_doesnt_start}, state} - end - end - - @impl true - def handle_call({:delete_room, room_id}, _from, state) do - response = - case find_room(room_id) do - {:ok, _room_pid} -> - remove_room(room_id) - :ok - - {:error, _} -> - {:error, :room_not_found} - end - - {:reply, response, state} - end - - @impl true - def handle_info(:rooms_metrics, state) do - rooms = list_rooms() - - :telemetry.execute( - [:fishjam], - %{ - rooms: Enum.count(rooms) - } - ) - - for room <- rooms do - peer_count = room.peers |> Enum.count() - - :telemetry.execute( - [:fishjam, :room], - %{ - peers: peer_count, - peer_time: peer_count * @metric_interval_in_seconds, - duration: @metric_interval_in_seconds - }, - %{room_id: room.id} - ) - end - - Process.send_after(self(), :rooms_metrics, @metric_interval_in_milliseconds) - - {:noreply, state} - end - - @impl true - def handle_info({:DOWN, _ref, :process, pid, :normal}, state) do - {room_id, state} = pop_in(state, [:rooms, pid]) - - Logger.debug("Room #{inspect(room_id)} is down with reason: normal") - - Phoenix.PubSub.broadcast(Fishjam.PubSub, room_id, :room_stopped) - Event.broadcast_server_notification({:room_deleted, room_id}) - clear_room_metrics(room_id) - - {:noreply, state} - end - - @impl true - def handle_info({:DOWN, _ref, :process, pid, reason}, state) do - {room_id, state} = pop_in(state, [:rooms, pid]) - - Logger.warning("Process #{room_id} is down with reason: #{inspect(reason)}") - - Phoenix.PubSub.broadcast(Fishjam.PubSub, room_id, :room_crashed) - Event.broadcast_server_notification({:room_crashed, room_id}) - clear_room_metrics(room_id) - - {:noreply, state} - end - - defp clear_room_metrics(room_id) do - :telemetry.execute([:fishjam, :room], %{peers: 0}, %{room_id: room_id}) - end - - defp find_best_node(node_resources) do - %{node: min_node} = - Enum.min( - node_resources, - fn - %{forwarded_tracks_number: forwarded_tracks, rooms_number: rooms_num1}, - %{forwarded_tracks_number: forwarded_tracks, rooms_number: rooms_num2} -> - rooms_num1 < rooms_num2 - - %{forwarded_tracks_number: forwarded_tracks1}, - %{forwarded_tracks_number: forwarded_tracks2} -> - forwarded_tracks1 < forwarded_tracks2 - end - ) - - min_node - end - - defp get_rooms_ids() do - Fishjam.RoomRegistry - |> Registry.select([{{:"$1", :_, :_}, [], [:"$1"]}]) - end - - defp remove_room(room_id) do - room = Room.registry_id(room_id) - - try do - :ok = GenServer.stop(room, :normal) - Logger.info("Deleted room #{inspect(room_id)}") - catch - :exit, {:noproc, {GenServer, :stop, [^room, :normal, :infinity]}} -> - Logger.warning("Room process with id #{inspect(room_id)} doesn't exist") - end - end + @callback delete_room(ID.id()) :: :ok | {:error, cluster_error() | :room_not_found} end diff --git a/lib/fishjam/rpc_client.ex b/lib/fishjam/rpc_client.ex index 047973de..5edb8232 100644 --- a/lib/fishjam/rpc_client.ex +++ b/lib/fishjam/rpc_client.ex @@ -11,16 +11,16 @@ defmodule Fishjam.RPCClient do @doc """ Executes mfa on a remote node. Function returns {:ok, result} tuple only if the execution succeeded. - In case of any exceptions we are catching them logging and returning simple :error atom. + In case of any exceptions we are catching them logging and returning the {:error, :rpc_failed} tuple. """ - @spec call(node(), module(), atom(), term(), timeout()) :: {:ok, term()} | :error - def call(node, module, function, args, timeout \\ :infinity) do + @spec call(node(), module(), atom(), term(), timeout()) :: {:ok, term()} | {:error, :rpc_failed} + def call(node, module, function, args \\ [], timeout \\ :infinity) do try do {:ok, :erpc.call(node, module, function, args, timeout)} rescue e -> Logger.warning("RPC call to node #{node} failed with exception: #{inspect(e)}") - :error + {:error, :rpc_failed} end end @@ -29,7 +29,7 @@ defmodule Fishjam.RPCClient do It filters out any errors or exceptions from return so you may end up with empty list. """ @spec multicall(module(), atom(), term(), timeout()) :: list(term) - def multicall(module, function, args, timeout \\ :infinity) do + def multicall(module, function, args \\ [], timeout \\ :infinity) do nodes() |> :erpc.multicall(module, function, args, timeout) |> handle_result() diff --git a/lib/fishjam_web/controllers/component_controller.ex b/lib/fishjam_web/controllers/component_controller.ex index f50a8024..a33e94af 100644 --- a/lib/fishjam_web/controllers/component_controller.ex +++ b/lib/fishjam_web/controllers/component_controller.ex @@ -2,9 +2,9 @@ defmodule FishjamWeb.ComponentController do use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs + alias Fishjam.Cluster.Room + alias Fishjam.Cluster.RoomService alias Fishjam.Component - alias Fishjam.Room - alias Fishjam.RoomService alias FishjamWeb.ApiSpec alias OpenApiSpex.{Response, Schema} @@ -38,7 +38,8 @@ defmodule FishjamWeb.ComponentController do created: ApiSpec.data("Successfully added component", ApiSpec.ComponentDetailsResponse), bad_request: ApiSpec.error("Invalid request"), not_found: ApiSpec.error("Room doesn't exist"), - unauthorized: ApiSpec.error("Unauthorized") + unauthorized: ApiSpec.error("Unauthorized"), + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] operation :delete, @@ -58,8 +59,10 @@ defmodule FishjamWeb.ComponentController do ], responses: [ no_content: %Response{description: "Successfully deleted"}, + bad_request: ApiSpec.error("Invalid request"), not_found: ApiSpec.error("Either component or the room doesn't exist"), - unauthorized: ApiSpec.error("Unauthorized") + unauthorized: ApiSpec.error("Unauthorized"), + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] def create(conn, %{"room_id" => room_id} = params) do @@ -76,9 +79,6 @@ defmodule FishjamWeb.ComponentController do :error -> {:error, :bad_request, "Invalid request body structure"} - {:error, :room_not_found} -> - {:error, :not_found, "Room #{room_id} does not exist"} - {:error, :invalid_type} -> {:error, :bad_request, "Invalid component type"} @@ -121,6 +121,9 @@ defmodule FishjamWeb.ComponentController do {:error, :overridding_path_prefix} -> {:error, :bad_request, "Conflicting S3 path prefix supplied via environment variables and the REST API. Overrides on existing values are disallowed"} + + {:error, reason} -> + {:rpc_error, reason, room_id} end end @@ -129,8 +132,11 @@ defmodule FishjamWeb.ComponentController do :ok <- Room.remove_component(room_id, id) do send_resp(conn, :no_content, "") else - {:error, :room_not_found} -> {:error, :not_found, "Room #{room_id} does not exist"} - {:error, :component_not_found} -> {:error, :not_found, "Component #{id} does not exist"} + {:error, :component_not_found} -> + {:error, :not_found, "Component #{id} does not exist"} + + {:error, reason} -> + {:rpc_error, reason, room_id} end end end diff --git a/lib/fishjam_web/controllers/fallback_controller.ex b/lib/fishjam_web/controllers/fallback_controller.ex index 0388e4dc..f1f55a24 100644 --- a/lib/fishjam_web/controllers/fallback_controller.ex +++ b/lib/fishjam_web/controllers/fallback_controller.ex @@ -3,6 +3,23 @@ defmodule FishjamWeb.FallbackController do require Logger + def call(conn, {:rpc_error, reason, room_id}) do + case reason do + :invalid_room_id -> + call(conn, {:error, :bad_request, "Invalid room ID: #{room_id}"}) + + not_found when not_found in [:node_not_found, :room_not_found] -> + call(conn, {:error, :not_found, "Room #{room_id} does not exist"}) + + :rpc_failed -> + call( + conn, + {:error, :service_unavailable, + "Unable to reach Fishjam instance holding room #{room_id}"} + ) + end + end + def call(conn, {:error, status, reason}) do Logger.debug("Generic error handler status: #{status}, reason: #{reason}") diff --git a/lib/fishjam_web/controllers/hls_content_controller.ex b/lib/fishjam_web/controllers/hls_content_controller.ex index 940d2524..c210d827 100644 --- a/lib/fishjam_web/controllers/hls_content_controller.ex +++ b/lib/fishjam_web/controllers/hls_content_controller.ex @@ -4,7 +4,7 @@ defmodule FishjamWeb.HLSContentController do require Logger - alias Fishjam.Component.HLS.RequestHandler + alias Fishjam.Component.HLS.Cluster.RequestHandler alias FishjamWeb.ApiSpec alias FishjamWeb.ApiSpec.HLS.{Params, Response} @@ -33,7 +33,11 @@ defmodule FishjamWeb.HLSContentController do responses: [ ok: ApiSpec.data("File was found", Response), not_found: ApiSpec.error("File not found"), - bad_request: ApiSpec.error("Invalid filename") + bad_request: ApiSpec.error("Invalid filename"), + moved_permanently: %OpenApiSpex.Response{ + description: "Resource available on another Fishjam instance" + }, + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] @playlist_content_type "application/vnd.apple.mpegurl" @@ -74,6 +78,9 @@ defmodule FishjamWeb.HLSContentController do |> put_resp_content_type(@playlist_content_type, nil) |> Conn.send_resp(200, manifest) + {:redirect, fishjam_address} -> + send_redirect_response(conn, fishjam_address) + {:error, reason} -> Logger.error("Error handling manifest request, reason: #{inspect(reason)}") {:error, :not_found, "File not found"} @@ -97,6 +104,9 @@ defmodule FishjamWeb.HLSContentController do Conn.send_resp(conn, 200, file) + {:redirect, fishjam_address} -> + send_redirect_response(conn, fishjam_address) + {:error, :invalid_path} -> {:error, :bad_request, "Invalid filename, got #{filename}"} @@ -104,4 +114,16 @@ defmodule FishjamWeb.HLSContentController do {:error, :not_found, "File not found"} end end + + defp send_redirect_response(conn, fishjam_address) do + location = + "#{conn.scheme}://#{fishjam_address}" + |> URI.parse() + |> Map.put(:path, conn.request_path) + |> URI.to_string() + + conn + |> put_status(:moved_permanently) + |> redirect(external: location) + end end diff --git a/lib/fishjam_web/controllers/local_room_controller.ex b/lib/fishjam_web/controllers/local_room_controller.ex new file mode 100644 index 00000000..9c6219d0 --- /dev/null +++ b/lib/fishjam_web/controllers/local_room_controller.ex @@ -0,0 +1,32 @@ +defmodule FishjamWeb.LocalRoomController do + use FishjamWeb, :controller + + alias Fishjam.Local.RoomService + alias FishjamWeb.RoomJSON + + action_fallback FishjamWeb.FallbackController + + def index(conn, _params) do + response = + %{rooms: RoomService.list_rooms() |> Enum.map(&maps_to_lists/1)} + |> RoomJSON.index() + |> Jason.encode!() + + conn + |> put_resp_content_type("application/json") + |> resp(200, response) + end + + defp maps_to_lists(room) do + # Values of component/peer maps also contain the ids + components = + room.components + |> Enum.map(fn {_id, component} -> component end) + + peers = + room.peers + |> Enum.map(fn {_id, peer} -> peer end) + + %{room | components: components, peers: peers} + end +end diff --git a/lib/fishjam_web/controllers/peer_controller.ex b/lib/fishjam_web/controllers/peer_controller.ex index 9b26d2b0..c62f545d 100644 --- a/lib/fishjam_web/controllers/peer_controller.ex +++ b/lib/fishjam_web/controllers/peer_controller.ex @@ -3,9 +3,10 @@ defmodule FishjamWeb.PeerController do use OpenApiSpex.ControllerSpecs require Logger + + alias Fishjam.Cluster.Room + alias Fishjam.Cluster.RoomService alias Fishjam.Peer - alias Fishjam.Room - alias Fishjam.RoomService alias FishjamWeb.ApiSpec alias FishjamWeb.PeerToken alias OpenApiSpex.{Response, Schema} @@ -40,8 +41,8 @@ defmodule FishjamWeb.PeerController do created: ApiSpec.data("Peer successfully created", ApiSpec.PeerDetailsResponse), bad_request: ApiSpec.error("Invalid request body structure"), not_found: ApiSpec.error("Room doesn't exist"), - service_unavailable: ApiSpec.error("Peer limit has been reached"), - unauthorized: ApiSpec.error("Unauthorized") + unauthorized: ApiSpec.error("Unauthorized"), + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] operation :delete, @@ -61,8 +62,10 @@ defmodule FishjamWeb.PeerController do ], responses: [ no_content: %Response{description: "Peer successfully deleted"}, + bad_request: ApiSpec.error("Invalid request body structure"), not_found: ApiSpec.error("Room ID or Peer ID references a resource that doesn't exist"), - unauthorized: ApiSpec.error("Unauthorized") + unauthorized: ApiSpec.error("Unauthorized"), + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] def create(conn, %{"room_id" => room_id} = params) do @@ -73,6 +76,9 @@ defmodule FishjamWeb.PeerController do with peer_options <- Map.get(params, "options", %{}), {:ok, peer_type_string} <- Map.fetch(params, "type"), {:ok, peer_type} <- Peer.parse_type(peer_type_string), + # FIXME: Opportunity for improvement + # This performs two RPCs, but a small refactor in Cluster.Room will let us get rid of one of them + # Same thing happens in the other controllers {:ok, _room_pid} <- RoomService.find_room(room_id), {:ok, peer} <- Room.add_peer(room_id, peer_type, peer_options) do Logger.debug("Successfully added peer to room: #{room_id}") @@ -91,14 +97,8 @@ defmodule FishjamWeb.PeerController do :error -> msg = "Invalid request body structure" log_warning(room_id, msg) - {:error, :bad_request, msg} - {:error, :room_not_found} -> - msg = "Room #{room_id} does not exist" - log_warning(room_id, msg) - {:error, :not_found, msg} - {:error, :invalid_type} -> msg = "Invalid peer type" log_warning(room_id, msg) @@ -113,6 +113,9 @@ defmodule FishjamWeb.PeerController do msg = "Reached #{type} peers limit in room #{room_id}" log_warning(room_id, msg) {:error, :service_unavailable, msg} + + {:error, reason} -> + {:rpc_error, reason, room_id} end end @@ -121,8 +124,11 @@ defmodule FishjamWeb.PeerController do :ok <- Room.remove_peer(room_id, id) do send_resp(conn, :no_content, "") else - {:error, :room_not_found} -> {:error, :not_found, "Room #{room_id} does not exist"} - {:error, :peer_not_found} -> {:error, :not_found, "Peer #{id} does not exist"} + {:error, :peer_not_found} -> + {:error, :not_found, "Peer #{id} does not exist"} + + {:error, reason} -> + {:rpc_error, reason, room_id} end end diff --git a/lib/fishjam_web/controllers/recording_content_controller.ex b/lib/fishjam_web/controllers/recording_content_controller.ex index 17facf1e..36574317 100644 --- a/lib/fishjam_web/controllers/recording_content_controller.ex +++ b/lib/fishjam_web/controllers/recording_content_controller.ex @@ -4,7 +4,7 @@ defmodule FishjamWeb.RecordingContentController do require Logger - alias Fishjam.Component.HLS.RequestHandler + alias Fishjam.Component.HLS.Local.RequestHandler alias FishjamWeb.ApiSpec alias Plug.Conn diff --git a/lib/fishjam_web/controllers/recording_controller.ex b/lib/fishjam_web/controllers/recording_controller.ex index d1059941..074bc44e 100644 --- a/lib/fishjam_web/controllers/recording_controller.ex +++ b/lib/fishjam_web/controllers/recording_controller.ex @@ -4,7 +4,8 @@ defmodule FishjamWeb.RecordingController do require Logger - alias Fishjam.Component.HLS.{Recording, RequestHandler} + alias Fishjam.Component.HLS.Local.RequestHandler + alias Fishjam.Component.HLS.Recording alias FishjamWeb.ApiSpec alias Plug.Conn diff --git a/lib/fishjam_web/controllers/room_controller.ex b/lib/fishjam_web/controllers/room_controller.ex index 62db8b78..8fd0b2f2 100644 --- a/lib/fishjam_web/controllers/room_controller.ex +++ b/lib/fishjam_web/controllers/room_controller.ex @@ -3,8 +3,9 @@ defmodule FishjamWeb.RoomController do use OpenApiSpex.ControllerSpecs require Logger - alias Fishjam.Room - alias Fishjam.RoomService + + alias Fishjam.Cluster.RoomService + alias Fishjam.Room.Config alias FishjamWeb.ApiSpec alias OpenApiSpex.Response @@ -29,7 +30,8 @@ defmodule FishjamWeb.RoomController do responses: [ created: ApiSpec.data("Room successfully created", ApiSpec.RoomCreateDetailsResponse), bad_request: ApiSpec.error("Invalid request structure"), - unauthorized: ApiSpec.error("Unauthorized") + unauthorized: ApiSpec.error("Unauthorized"), + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] operation :show, @@ -44,8 +46,10 @@ defmodule FishjamWeb.RoomController do ], responses: [ ok: ApiSpec.data("Success", ApiSpec.RoomDetailsResponse), + bad_request: ApiSpec.error("Invalid request"), not_found: ApiSpec.error("Room doesn't exist"), - unauthorized: ApiSpec.error("Unauthorized") + unauthorized: ApiSpec.error("Unauthorized"), + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] operation :delete, @@ -60,8 +64,10 @@ defmodule FishjamWeb.RoomController do ], responses: [ no_content: %Response{description: "Successfully deleted room"}, + bad_request: ApiSpec.error("Invalid request"), not_found: ApiSpec.error("Room doesn't exist"), - unauthorized: ApiSpec.error("Unauthorized") + unauthorized: ApiSpec.error("Unauthorized"), + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] def index(conn, _params) do @@ -77,7 +83,7 @@ defmodule FishjamWeb.RoomController do def create(conn, params) do Logger.debug("Start creating room") - with {:ok, config} <- Room.Config.from_params(params), + with {:ok, config} <- Config.from_params(params), {:ok, room, fishjam_address} <- RoomService.create_room(config) do conn |> put_resp_content_type("application/json") @@ -111,6 +117,12 @@ defmodule FishjamWeb.RoomController do room_id = Map.get(params, "roomId") {:error, :bad_request, "Cannot add room with id \"#{room_id}\" - unexpected error"} + {:error, :rpc_failed} -> + room_id = Map.get(params, "roomId") + + {:error, :service_unavailable, + "Cannot add room with id \"#{room_id}\" - unable to communicate with designated Fishjam instance"} + {:error, :invalid_room_id} -> room_id = Map.get(params, "roomId") @@ -128,8 +140,8 @@ defmodule FishjamWeb.RoomController do |> put_resp_content_type("application/json") |> render("show.json", room: room) - {:error, :room_not_found} -> - {:error, :not_found, "Room #{id} does not exist"} + {:error, reason} -> + {:rpc_error, reason, id} end end @@ -138,8 +150,8 @@ defmodule FishjamWeb.RoomController do :ok -> send_resp(conn, :no_content, "") - {:error, :room_not_found} -> - {:error, :not_found, "Room #{id} does not exist"} + {:error, reason} -> + {:rpc_error, reason, id} end end diff --git a/lib/fishjam_web/controllers/sip_call_controller.ex b/lib/fishjam_web/controllers/sip_call_controller.ex index a1ee43f0..93bc07bd 100644 --- a/lib/fishjam_web/controllers/sip_call_controller.ex +++ b/lib/fishjam_web/controllers/sip_call_controller.ex @@ -2,8 +2,7 @@ defmodule FishjamWeb.SIPCallController do use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs - alias Fishjam.Room - alias Fishjam.RoomService + alias Fishjam.Cluster.{Room, RoomService} alias FishjamWeb.ApiSpec alias OpenApiSpex.Response @@ -25,7 +24,8 @@ defmodule FishjamWeb.SIPCallController do created: %Response{description: "Call started"}, bad_request: ApiSpec.error("Invalid request structure"), not_found: ApiSpec.error("Room doesn't exist"), - unauthorized: ApiSpec.error("Unauthorized") + unauthorized: ApiSpec.error("Unauthorized"), + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] operation :delete, @@ -39,7 +39,8 @@ defmodule FishjamWeb.SIPCallController do created: %Response{description: "Call ended"}, bad_request: ApiSpec.error("Invalid request structure"), not_found: ApiSpec.error("Room doesn't exist"), - unauthorized: ApiSpec.error("Unauthorized") + unauthorized: ApiSpec.error("Unauthorized"), + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] def create(conn, %{"room_id" => room_id, "component_id" => component_id} = params) do @@ -51,14 +52,14 @@ defmodule FishjamWeb.SIPCallController do :error -> {:error, :bad_request, "Invalid request body structure"} - {:error, :room_not_found} -> - {:error, :not_found, "Room #{room_id} does not exist"} - {:error, :component_does_not_exist} -> {:error, :bad_request, "Component #{component_id} does not exist"} {:error, :bad_component_type} -> {:error, :bad_request, "Component #{component_id} is not a SIP component"} + + {:error, reason} -> + {:rpc_error, reason, room_id} end end @@ -72,14 +73,14 @@ defmodule FishjamWeb.SIPCallController do :error -> {:error, :bad_request, "Invalid request body structure"} - {:error, :room_not_found} -> - {:error, :not_found, "Room #{room_id} does not exist"} - {:error, :component_does_not_exist} -> {:error, :bad_request, "Component #{component_id} does not exist"} {:error, :bad_component_type} -> {:error, :bad_request, "Component #{component_id} is not SIP component"} + + {:error, reason} -> + {:rpc_error, reason, room_id} end end end diff --git a/lib/fishjam_web/controllers/subscription_controller.ex b/lib/fishjam_web/controllers/subscription_controller.ex index c62a5c35..d5462519 100644 --- a/lib/fishjam_web/controllers/subscription_controller.ex +++ b/lib/fishjam_web/controllers/subscription_controller.ex @@ -2,8 +2,7 @@ defmodule FishjamWeb.SubscriptionController do use FishjamWeb, :controller use OpenApiSpex.ControllerSpecs - alias Fishjam.Room - alias Fishjam.RoomService + alias Fishjam.Cluster.{Room, RoomService} alias FishjamWeb.ApiSpec alias OpenApiSpex.Response @@ -25,7 +24,8 @@ defmodule FishjamWeb.SubscriptionController do created: %Response{description: "Tracks succesfully added."}, bad_request: ApiSpec.error("Invalid request structure"), not_found: ApiSpec.error("Room doesn't exist"), - unauthorized: ApiSpec.error("Unauthorized") + unauthorized: ApiSpec.error("Unauthorized"), + service_unavailable: ApiSpec.error("Service temporarily unavailable") ] def create(conn, %{"room_id" => room_id, "component_id" => component_id} = params) do @@ -37,9 +37,6 @@ defmodule FishjamWeb.SubscriptionController do :error -> {:error, :bad_request, "Invalid request body structure"} - {:error, :room_not_found} -> - {:error, :not_found, "Room #{room_id} does not exist"} - {:error, :component_not_exists} -> {:error, :bad_request, "Component #{component_id} does not exist"} @@ -50,6 +47,9 @@ defmodule FishjamWeb.SubscriptionController do {:error, :invalid_subscribe_mode} -> {:error, :bad_request, "Component #{component_id} option `subscribe_mode` is set to :auto"} + + {:error, reason} -> + {:rpc_error, reason, room_id} end end end diff --git a/lib/fishjam_web/peer_socket.ex b/lib/fishjam_web/peer_socket.ex index ed129b77..bfbb66a8 100644 --- a/lib/fishjam_web/peer_socket.ex +++ b/lib/fishjam_web/peer_socket.ex @@ -6,7 +6,7 @@ defmodule FishjamWeb.PeerSocket do alias Fishjam.Event alias Fishjam.PeerMessage alias Fishjam.PeerMessage.{Authenticated, AuthRequest, MediaEvent} - alias Fishjam.{Room, RoomService} + alias Fishjam.Local.{Room, RoomService} alias FishjamWeb.PeerToken @heartbeat_interval 30_000 diff --git a/lib/fishjam_web/router.ex b/lib/fishjam_web/router.ex index 930af3a4..b8a3adda 100644 --- a/lib/fishjam_web/router.ex +++ b/lib/fishjam_web/router.ex @@ -35,6 +35,12 @@ defmodule FishjamWeb.Router do delete "/:recording_id", RecordingController, :delete get "/", RecordingController, :show end + + if Application.compile_env(:fishjam, :test_routes) do + scope "/test" do + get "/local/room", LocalRoomController, :index + end + end end # Paths which DO NOT require auth diff --git a/openapi.yaml b/openapi.yaml index 3d8c9cc2..adc9da58 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -946,6 +946,8 @@ paths: schema: $ref: '#/components/schemas/HlsResponse' description: File was found + '301': + description: Resource available on another Fishjam instance '400': content: application/json: @@ -958,6 +960,12 @@ paths: schema: $ref: '#/components/schemas/Error' description: File not found + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Service temporarily unavailable summary: Retrieve HLS Content tags: - hls @@ -1119,6 +1127,12 @@ paths: schema: $ref: '#/components/schemas/Error' description: Unauthorized + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Service temporarily unavailable security: - authorization: [] summary: Creates a room @@ -1138,6 +1152,12 @@ paths: responses: '204': description: Successfully deleted room + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Invalid request '401': content: application/json: @@ -1150,6 +1170,12 @@ paths: schema: $ref: '#/components/schemas/Error' description: Room doesn't exist + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Service temporarily unavailable security: - authorization: [] summary: Delete the room @@ -1172,6 +1198,12 @@ paths: schema: $ref: '#/components/schemas/RoomDetailsResponse' description: Success + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Invalid request '401': content: application/json: @@ -1184,6 +1216,12 @@ paths: schema: $ref: '#/components/schemas/Error' description: Room doesn't exist + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Service temporarily unavailable security: - authorization: [] summary: Shows information about the room @@ -1239,6 +1277,12 @@ paths: schema: $ref: '#/components/schemas/Error' description: Room doesn't exist + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Service temporarily unavailable security: - authorization: [] summary: Creates the component and adds it to the room @@ -1289,6 +1333,12 @@ paths: schema: $ref: '#/components/schemas/Error' description: Room doesn't exist + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Service temporarily unavailable security: - authorization: [] summary: Subscribe component to the tracks of peers or components @@ -1314,6 +1364,12 @@ paths: responses: '204': description: Successfully deleted + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Invalid request '401': content: application/json: @@ -1326,6 +1382,12 @@ paths: schema: $ref: '#/components/schemas/Error' description: Either component or the room doesn't exist + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Service temporarily unavailable security: - authorization: [] summary: Delete the component from the room @@ -1387,7 +1449,7 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - description: Peer limit has been reached + description: Service temporarily unavailable security: - authorization: [] summary: Create peer @@ -1413,6 +1475,12 @@ paths: responses: '204': description: Peer successfully deleted + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Invalid request body structure '401': content: application/json: @@ -1425,6 +1493,12 @@ paths: schema: $ref: '#/components/schemas/Error' description: Room ID or Peer ID references a resource that doesn't exist + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Service temporarily unavailable security: - authorization: [] summary: Delete peer @@ -1468,6 +1542,12 @@ paths: schema: $ref: '#/components/schemas/Error' description: Room doesn't exist + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Service temporarily unavailable security: - authorization: [] summary: Finish call made by SIP component @@ -1517,6 +1597,12 @@ paths: schema: $ref: '#/components/schemas/Error' description: Room doesn't exist + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Service temporarily unavailable security: - authorization: [] summary: Make a call from the SIP component to the provided phone number diff --git a/test/fishjam/cluster/api_test.exs b/test/fishjam/cluster/api_test.exs new file mode 100644 index 00000000..0244108f --- /dev/null +++ b/test/fishjam/cluster/api_test.exs @@ -0,0 +1,241 @@ +defmodule Fishjam.Cluster.ApiTest do + @moduledoc false + + # These tests can only be run with `mix test.cluster.epmd` or `mix test.cluster.dns`. + + use ExUnit.Case, async: false + + import FishjamWeb.WS, only: [subscribe: 2] + + alias Fishjam.ServerMessage.{Authenticated, HlsPlayable} + alias FishjamWeb.WS + + @token Application.compile_env(:fishjam, :server_api_token) + @headers [Authorization: "Bearer #{@token}", Accept: "Application/json; Charset=utf-8"] + @post_headers @headers ++ ["Content-Type": "application/json"] + @nodes ["app1:4001", "app2:4002"] + + @moduletag :cluster + @max_test_duration 400_000 + + setup do + if Mix.env() != :test_cluster do + raise "Load balancing tests can only be run with MIX_ENV=test_cluster" + end + + # Delete all leftover rooms after each test + on_exit(fn -> + for node <- @nodes do + with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <- + HTTPoison.get("http://#{node}/test/local/room", @headers) do + rooms = body |> Jason.decode!() |> Map.get("data") + + for room <- rooms, do: HTTPoison.delete("http://#{node}/room/#{room["id"]}", @headers) + end + end + end) + + {:ok, %{nodes: @nodes}} + end + + @tag timeout: @max_test_duration + test "adding a single room in a cluster", %{nodes: [node1, _node2]} do + %{node: room_node} = add_room(node1) + + other_node = determine_other_node(room_node) + + assert_room_counts(%{room_node => 1, other_node => 0}) + end + + @tag timeout: @max_test_duration + test "load-balancing when adding two rooms", %{nodes: [node1, _node2]} do + %{node: room_node1} = add_room(node1) + + other_node1 = determine_other_node(room_node1) + + assert_room_counts(%{room_node1 => 1, other_node1 => 0}) + + %{node: room_node2} = add_room(node1) + + assert room_node1 != room_node2 + assert_room_counts(%{room_node1 => 1, room_node2 => 1}) + end + + @tag timeout: @max_test_duration + test "load-balancing and request routing when deleting rooms", %{nodes: [node1, node2]} do + %{id: room_id1, node: room_node1} = add_room(node1) + %{node: room_node2} = add_room(node1) + + assert room_node1 != room_node2 + assert_room_counts(%{room_node1 => 1, room_node2 => 1}) + + delete_room(room_node2, room_id1) + + assert_room_counts(%{room_node1 => 0, room_node2 => 1}) + + %{node: room_node3} = add_room(node1) + + assert room_node3 != room_node2 + assert_room_count_on_fishjam(room_node3, 1) + assert_room_counts(%{node1 => 1, node2 => 1}) + end + + @tag timeout: @max_test_duration + test "request routing when adding peers", %{nodes: [node1, _node2]} do + %{id: room_id, node: room_node} = add_room(node1) + + other_node = determine_other_node(room_node) + + assert_room_counts(%{room_node => 1, other_node => 0}) + + add_peer(other_node, room_id) + + assert_peer_count_in_room(room_node, room_id, 1) + assert_peer_count_in_room(other_node, room_id, 1) + + delete_room(other_node, room_id) + + assert_room_counts(%{room_node => 0, other_node => 0}) + end + + @tag timeout: @max_test_duration + test "request routing + explicit forwarding of HLS retrieve content requests", %{ + nodes: [node1, _node2] + } do + %{id: room_id, node: room_node} = add_room(node1) + + other_node = determine_other_node(room_node) + + assert_room_counts(%{room_node => 1, other_node => 0}) + + {:ok, ws} = WS.start_link("ws://#{room_node}/socket/server/websocket", :server) + WS.send_auth_request(ws, @token) + assert_receive %Authenticated{}, 1000 + subscribe(ws, :server_notification) + + add_hls_component(other_node, room_id) + add_file_component(other_node, room_id) + + assert_receive %HlsPlayable{room_id: ^room_id}, 20_000 + assert_successful_redirect(other_node, room_id) + end + + defp add_room(fishjam_instance) do + request_body = %{videoCodec: "h264"} |> Jason.encode!() + + assert {:ok, %HTTPoison.Response{status_code: 201, body: body}} = + HTTPoison.post("http://#{fishjam_instance}/room", request_body, @post_headers) + + room_data = body |> Jason.decode!() |> Map.fetch!("data") + + %{ + node: get_fishjam_address(room_data), + id: get_in(room_data, ["room", "id"]) + } + end + + defp add_peer(fishjam_instance, room_id) do + request_body = %{type: "webrtc", options: %{}} |> Jason.encode!() + + assert {:ok, %HTTPoison.Response{status_code: 201}} = + HTTPoison.post( + "http://#{fishjam_instance}/room/#{room_id}/peer", + request_body, + @post_headers + ) + end + + defp add_hls_component(fishjam_instance, room_id) do + request_body = %{type: "hls", options: %{}} |> Jason.encode!() + + assert {:ok, %HTTPoison.Response{status_code: 201}} = + HTTPoison.post( + "http://#{fishjam_instance}/room/#{room_id}/component", + request_body, + @post_headers + ) + end + + defp add_file_component(fishjam_instance, room_id) do + request_body = + %{type: "file", options: %{filePath: "video.h264", framerate: 30}} |> Jason.encode!() + + assert {:ok, %HTTPoison.Response{status_code: 201}} = + HTTPoison.post( + "http://#{fishjam_instance}/room/#{room_id}/component", + request_body, + @post_headers + ) + end + + defp delete_room(fishjam_instance, room_id) do + assert {:ok, %HTTPoison.Response{status_code: 204}} = + HTTPoison.delete("http://#{fishjam_instance}/room/#{room_id}", @headers) + end + + defp get_fishjam_address(response_data) do + response_data + |> Map.fetch!("fishjam_address") + |> map_fishjam_address() + end + + defp map_fishjam_address(fishjam) do + %{ + "localhost:4001" => "app1:4001", + "localhost:4002" => "app2:4002" + } + |> Map.fetch!(fishjam) + end + + defp assert_room_counts(instances) do + rooms_in_cluster = instances |> Map.values() |> Enum.sum() + + for {instance, rooms} <- instances do + assert_room_count_on_fishjam(instance, rooms) + assert_room_count_in_cluster(instance, rooms_in_cluster) + end + end + + defp assert_room_count_on_fishjam(fishjam_instance, rooms) do + assert {:ok, %HTTPoison.Response{status_code: 200, body: body}} = + HTTPoison.get("http://#{fishjam_instance}/test/local/room", @headers) + + assert ^rooms = body |> Jason.decode!() |> Map.get("data") |> Enum.count() + end + + defp assert_room_count_in_cluster(fishjam_instance, rooms) do + assert {:ok, %HTTPoison.Response{status_code: 200, body: body}} = + HTTPoison.get("http://#{fishjam_instance}/room", @headers) + + assert ^rooms = body |> Jason.decode!() |> Map.get("data") |> Enum.count() + end + + defp assert_peer_count_in_room(fishjam_instance, room_id, peers) do + assert {:ok, %HTTPoison.Response{status_code: 200, body: body}} = + HTTPoison.get("http://#{fishjam_instance}/room/#{room_id}", @headers) + + assert ^peers = body |> Jason.decode!() |> get_in(["data", "peers"]) |> Enum.count() + end + + defp assert_successful_redirect(fishjam_instance, room_id) do + assert {:ok, %HTTPoison.Response{status_code: 301, headers: headers}} = + HTTPoison.get("http://#{fishjam_instance}/hls/#{room_id}/index.m3u8", @headers) + + assert {"location", location} = List.keyfind(headers, "location", 0) + + location_uri = URI.parse(location) + fishjam_address = map_fishjam_address("#{location_uri.host}:#{location_uri.port}") + + location = + "#{location_uri.scheme}://#{fishjam_address}" + |> URI.parse() + |> Map.put(:path, location_uri.path) + |> URI.to_string() + + assert {:ok, %HTTPoison.Response{status_code: 200}} = HTTPoison.get(location, @headers) + end + + defp determine_other_node(room_node) do + Enum.find(@nodes, &(&1 != room_node)) + end +end diff --git a/test/fishjam/cluster/load_balancing_test.exs b/test/fishjam/cluster/load_balancing_test.exs deleted file mode 100644 index 0f9bdcd4..00000000 --- a/test/fishjam/cluster/load_balancing_test.exs +++ /dev/null @@ -1,91 +0,0 @@ -defmodule Fishjam.Cluster.LoadBalancingTest do - @moduledoc false - - # These tests can only be run with `mix test.cluster.epmd` or `mix test.cluster.dns`. - - use ExUnit.Case, async: false - - @token Application.compile_env(:fishjam, :server_api_token) - @headers [Authorization: "Bearer #{@token}", Accept: "Application/json; Charset=utf-8"] - - @moduletag :cluster - @max_test_duration 400_000 - - setup do - {:ok, %{}} - end - - @tag timeout: @max_test_duration - test "spawning tasks on a cluster" do - if Mix.env() != :test_cluster do - raise "Load balancing tests can only be run with MIX_ENV=test_cluster" - end - - [node1, node2] = ["app1:4001", "app2:4002"] - - response_body1 = add_room(node1) - - fishjam_instance1 = get_fishjam_address(response_body1) - - assert_rooms_number_on_fishjam(fishjam_instance1, 1) - - response_body2 = add_room(node1) - - fishjam_instance2 = get_fishjam_address(response_body2) - - assert_rooms_number_on_fishjam(fishjam_instance2, 1) - - assert_rooms_number_on_fishjam(node1, 1) - assert_rooms_number_on_fishjam(node2, 1) - - room_id = response_body1 |> Jason.decode!() |> get_in(["data", "room", "id"]) - - delete_room(fishjam_instance1, room_id) - - assert_rooms_number_on_fishjam(fishjam_instance1, 0) - assert_rooms_number_on_fishjam(fishjam_instance2, 1) - - response_body3 = add_room(node1) - fishjam_instance3 = get_fishjam_address(response_body3) - assert_rooms_number_on_fishjam(fishjam_instance3, 1) - - assert_rooms_number_on_fishjam(node1, 1) - assert_rooms_number_on_fishjam(node2, 1) - end - - defp add_room(fishjam_instance) do - assert {:ok, %HTTPoison.Response{status_code: 201, body: body}} = - HTTPoison.post("http://#{fishjam_instance}/room", [], @headers) - - body - end - - defp delete_room(fishjam_instance, room_id) do - assert {:ok, %HTTPoison.Response{status_code: 204, body: body}} = - HTTPoison.delete("http://#{fishjam_instance}/room/#{room_id}", @headers) - - body - end - - defp map_fishjam_address(fishjam) do - %{ - "localhost:4001" => "app1:4001", - "localhost:4002" => "app2:4002" - } - |> Map.get(fishjam) - end - - defp get_fishjam_address(response_body) do - response_body - |> Jason.decode!() - |> get_in(["data", "fishjam_address"]) - |> map_fishjam_address() - end - - defp assert_rooms_number_on_fishjam(fishjam_instance, rooms) do - assert {:ok, %HTTPoison.Response{status_code: 200, body: body}} = - HTTPoison.get("http://#{fishjam_instance}/room", @headers) - - assert ^rooms = body |> Jason.decode!() |> Map.get("data") |> Enum.count() - end -end diff --git a/test/fishjam/component/hls/ll_storage_test.exs b/test/fishjam/component/hls/ll_storage_test.exs index c0a23508..7b409331 100644 --- a/test/fishjam/component/hls/ll_storage_test.exs +++ b/test/fishjam/component/hls/ll_storage_test.exs @@ -3,7 +3,8 @@ defmodule Fishjam.Component.HLS.LLStorageTest do use ExUnit.Case, async: true - alias Fishjam.Component.HLS.{EtsHelper, LLStorage, RequestHandler} + alias Fishjam.Component.HLS.{EtsHelper, LLStorage} + alias Fishjam.Component.HLS.Local.RequestHandler @segment_name "segment" @segment_content <<1, 2, 3>> diff --git a/test/fishjam/component/hls/request_handler_test.exs b/test/fishjam/component/hls/request_handler_test.exs index d01516e4..c4147e8c 100644 --- a/test/fishjam/component/hls/request_handler_test.exs +++ b/test/fishjam/component/hls/request_handler_test.exs @@ -4,7 +4,8 @@ defmodule Fishjam.Component.HLS.RequestHandlerTest do use ExUnit.Case, async: true alias Fishjam.Component.HLS - alias Fishjam.Component.HLS.{EtsHelper, RequestHandler} + alias Fishjam.Component.HLS.EtsHelper + alias Fishjam.Component.HLS.Local.RequestHandler @wrong_room_id "321" diff --git a/test/fishjam/room/id_test.exs b/test/fishjam/room/id_test.exs index 10fa92c5..642476a1 100644 --- a/test/fishjam/room/id_test.exs +++ b/test/fishjam/room/id_test.exs @@ -3,6 +3,14 @@ defmodule Fishjam.Room.IDTest do alias Fishjam.Room.ID, as: Subject + setup_all do + current = Application.fetch_env!(:fishjam, :feature_flags) + + on_exit(fn -> + Application.put_env(:fishjam, :feature_flags, current) + end) + end + describe "determine_node/1" do test "resolves node name from the provided room_id" do node_name = Node.self() @@ -14,7 +22,10 @@ defmodule Fishjam.Room.IDTest do test "returns error if node is not detected in cluster" do invalid_node = :invalid_node |> Atom.to_string() |> Base.encode16(case: :lower) invalid_room_id = "room-id-#{invalid_node}" - assert {:error, :invalid_node} == Subject.determine_node(invalid_room_id) + + if Fishjam.FeatureFlags.request_routing_enabled?() do + assert {:error, :node_not_found} == Subject.determine_node(invalid_room_id) + end end end @@ -36,20 +47,18 @@ defmodule Fishjam.Room.IDTest do end describe "generate/1" do - setup do - Application.delete_env(:fishjam, :feature_flags) - end - test "executes generate/0 when feature flag is enabled and generates random id" do - Application.put_env(:fishjam, :feature_flags, custom_room_name_disabled: true) + Application.put_env(:fishjam, :feature_flags, request_routing_enabled?: true) refute {:ok, "custom_room_name"} == Subject.generate("custom_room_name") end test "parses custom room name when feature flag is disabled" do + Application.put_env(:fishjam, :feature_flags, request_routing_enabled?: false) assert {:ok, "custom_room_name"} == Subject.generate("custom_room_name") end test "returns error when custom room doesn't meet naming criteria" do + Application.put_env(:fishjam, :feature_flags, request_routing_enabled?: false) assert {:error, :invalid_room_id} = Subject.generate("invalid_characters//??$@!") end end diff --git a/test/fishjam_web/controllers/component/hls_component_test.exs b/test/fishjam_web/controllers/component/hls_component_test.exs index 032564a1..f83d3800 100644 --- a/test/fishjam_web/controllers/component/hls_component_test.exs +++ b/test/fishjam_web/controllers/component/hls_component_test.exs @@ -4,7 +4,7 @@ defmodule FishjamWeb.Component.HlsComponentTest do import Mox - alias Fishjam.RoomService + alias Fishjam.Local.RoomService alias Fishjam.Component.HLS diff --git a/test/fishjam_web/controllers/component/recording_component_test.exs b/test/fishjam_web/controllers/component/recording_component_test.exs index ebc82777..24b009e3 100644 --- a/test/fishjam_web/controllers/component/recording_component_test.exs +++ b/test/fishjam_web/controllers/component/recording_component_test.exs @@ -4,7 +4,7 @@ defmodule FishjamWeb.Component.RecordingComponentTest do import Mox - alias Fishjam.RoomService + alias Fishjam.Local.RoomService @s3_credentials %{ accessKeyId: "access_key_id", diff --git a/test/fishjam_web/controllers/component_controller_test.exs b/test/fishjam_web/controllers/component_controller_test.exs index 5f1a3bc2..189bad41 100644 --- a/test/fishjam_web/controllers/component_controller_test.exs +++ b/test/fishjam_web/controllers/component_controller_test.exs @@ -2,6 +2,8 @@ defmodule FishjamWeb.ComponentControllerTest do use FishjamWeb.ConnCase use FishjamWeb.ComponentCase + alias Fishjam.Room.ID + @source_uri "rtsp://placeholder-19inrifjbsjb.it:12345/afwefae" setup_all do @@ -24,7 +26,7 @@ defmodule FishjamWeb.ComponentControllerTest do end test "renders errors when room doesn't exists", %{conn: conn} do - room_id = "abc" + room_id = ID.generate() conn = post(conn, ~p"/room/#{room_id}/component", type: "hls") response = model_response(conn, :not_found, "Error") @@ -73,7 +75,7 @@ defmodule FishjamWeb.ComponentControllerTest do end test "deletes component from not exisiting room", %{conn: conn, component_id: component_id} do - room_id = "abc" + room_id = ID.generate() conn = delete(conn, ~p"/room/#{room_id}/component/#{component_id}") assert model_response(conn, :not_found, "Error")["errors"] == diff --git a/test/fishjam_web/controllers/dial_controller_test.exs b/test/fishjam_web/controllers/dial_controller_test.exs index 03c3f62f..2faa671a 100644 --- a/test/fishjam_web/controllers/dial_controller_test.exs +++ b/test/fishjam_web/controllers/dial_controller_test.exs @@ -1,6 +1,8 @@ defmodule FishjamWeb.DialControllerTest do use FishjamWeb.ConnCase + alias Fishjam.Room.ID + @source_uri "rtsp://placeholder-19inrifjbsjb.it:12345/afwefae" @sip_registrar_credentials %{ @@ -41,8 +43,9 @@ defmodule FishjamWeb.DialControllerTest do describe "dial" do test "returns error when room doesn't exist", %{conn: conn} do - conn = post(conn, ~p"/sip/invalid_room_id/component_id/call", phoneNumber: "+123456") - assert json_response(conn, :not_found)["errors"] == "Room invalid_room_id does not exist" + invalid_room_id = ID.generate() + conn = post(conn, ~p"/sip/#{invalid_room_id}/component_id/call", phoneNumber: "+123456") + assert json_response(conn, :not_found)["errors"] == "Room #{invalid_room_id} does not exist" end test "returns error when sip component doesn't exist", %{conn: conn, room_id: room_id} do diff --git a/test/fishjam_web/controllers/hls_controller_test.exs b/test/fishjam_web/controllers/hls_controller_test.exs index bab35ecd..ba14f9c4 100644 --- a/test/fishjam_web/controllers/hls_controller_test.exs +++ b/test/fishjam_web/controllers/hls_controller_test.exs @@ -4,10 +4,12 @@ defmodule FishjamWeb.HLSControllerTest do import OpenApiSpex.TestAssertions alias Fishjam.Component.HLS - alias Fishjam.Component.HLS.{EtsHelper, RequestHandler} + alias Fishjam.Component.HLS.EtsHelper + alias Fishjam.Component.HLS.Local.RequestHandler + alias Fishjam.Room.ID - @room_id "hls_controller_test" - @wrong_room_id "wrong_id" + @room_id ID.generate() + @wrong_room_id ID.generate() @header_name "header_name.mp4" @header_content <<1>> diff --git a/test/fishjam_web/controllers/peer_controller_test.exs b/test/fishjam_web/controllers/peer_controller_test.exs index 9ef1878c..87a7ed7f 100644 --- a/test/fishjam_web/controllers/peer_controller_test.exs +++ b/test/fishjam_web/controllers/peer_controller_test.exs @@ -3,6 +3,8 @@ defmodule FishjamWeb.PeerControllerTest do import OpenApiSpex.TestAssertions + alias Fishjam.Room.ID + @schema FishjamWeb.ApiSpec.spec() @peer_type "webrtc" @@ -80,7 +82,7 @@ defmodule FishjamWeb.PeerControllerTest do end test "renders errors when room doesn't exist", %{conn: conn} do - room_id = "invalid_room" + room_id = ID.generate() conn = post(conn, ~p"/room/#{room_id}/peer", type: @peer_type) assert json_response(conn, :not_found)["errors"] == "Room #{room_id} does not exist" end @@ -128,7 +130,7 @@ defmodule FishjamWeb.PeerControllerTest do end test "deletes peer from not exisiting room", %{conn: conn, peer_id: peer_id} do - room_id = "invalid_room" + room_id = ID.generate() conn = delete(conn, ~p"/room/#{room_id}/peer/#{peer_id}") assert json_response(conn, :not_found)["errors"] == "Room #{room_id} does not exist" end diff --git a/test/fishjam_web/controllers/room_controller_test.exs b/test/fishjam_web/controllers/room_controller_test.exs index 16a0fa6a..887aab09 100644 --- a/test/fishjam_web/controllers/room_controller_test.exs +++ b/test/fishjam_web/controllers/room_controller_test.exs @@ -5,8 +5,9 @@ defmodule FishjamWeb.RoomControllerTest do alias __MODULE__.Endpoint + alias Fishjam.Local.RoomService alias Fishjam.PeerMessage.Authenticated - alias Fishjam.RoomService + alias Fishjam.Room.ID alias FishjamWeb.{PeerSocket, WS} @schema FishjamWeb.ApiSpec.spec() @@ -126,10 +127,19 @@ defmodule FishjamWeb.RoomControllerTest do test "renders room when data is valid, custom room_id + max_peers and peerless_purge_timeout not present", %{conn: conn} do - room_id = UUID.uuid4() <> "_ABCD-123_xyz" + room_id = + if Fishjam.FeatureFlags.request_routing_enabled?() do + conn = post(conn, ~p"/room") + assert %{"id" => id} = json_response(conn, :created)["data"]["room"] - conn = post(conn, ~p"/room", roomId: room_id) - json_response(conn, :created) + id + else + id = UUID.uuid4() <> "_ABCD-123_xyz" + conn = post(conn, ~p"/room", roomId: id) + assert %{"id" => ^id} = json_response(conn, :created)["data"]["room"] + + id + end conn = get(conn, ~p"/room/#{room_id}") response = json_response(conn, :ok) @@ -143,16 +153,18 @@ defmodule FishjamWeb.RoomControllerTest do } = response["data"] end - test "renders error when adding two rooms with same room_id", %{conn: conn} do - room_id = UUID.uuid4() + unless Fishjam.FeatureFlags.request_routing_enabled?() do + test "renders error when adding two rooms with same room_id", %{conn: conn} do + room_id = UUID.uuid4() - conn = post(conn, ~p"/room", roomId: room_id) - json_response(conn, :created) + conn = post(conn, ~p"/room", roomId: room_id) + json_response(conn, :created) - conn = post(conn, ~p"/room", roomId: room_id) + conn = post(conn, ~p"/room", roomId: room_id) - assert json_response(conn, :bad_request)["errors"] == - "Cannot add room with id \"#{room_id}\" - room already exists" + assert json_response(conn, :bad_request)["errors"] == + "Cannot add room with id \"#{room_id}\" - room already exists" + end end test "renders errors when data is invalid", %{conn: conn} do @@ -181,15 +193,17 @@ defmodule FishjamWeb.RoomControllerTest do assert json_response(conn, :bad_request)["errors"] == "Expected peerDisconnectedTimeout to be a positive integer, got: nan" - conn = post(conn, ~p"/room", roomId: "test/path") + unless Fishjam.FeatureFlags.request_routing_enabled?() do + conn = post(conn, ~p"/room", roomId: "test/path") - assert json_response(conn, :bad_request)["errors"] == - "Cannot add room with id \"test/path\" - roomId may contain only alphanumeric characters, hyphens and underscores" + assert json_response(conn, :bad_request)["errors"] == + "Cannot add room with id \"test/path\" - roomId may contain only alphanumeric characters, hyphens and underscores" - conn = post(conn, ~p"/room", roomId: "") + conn = post(conn, ~p"/room", roomId: "") - assert json_response(conn, :bad_request)["errors"] == - "Cannot add room with id \"\" - roomId may contain only alphanumeric characters, hyphens and underscores" + assert json_response(conn, :bad_request)["errors"] == + "Cannot add room with id \"\" - roomId may contain only alphanumeric characters, hyphens and underscores" + end end end @@ -430,7 +444,7 @@ defmodule FishjamWeb.RoomControllerTest do setup [:create_room] test "deletes chosen room", %{conn: conn, room_id: room_id} do - room_pid = RoomService.find_room!(room_id) + assert {:ok, room_pid} = RoomService.find_room(room_id) %{engine_pid: engine_pid} = :sys.get_state(room_pid) assert Process.alive?(room_pid) @@ -450,7 +464,7 @@ defmodule FishjamWeb.RoomControllerTest do end test "returns 404 if room doesn't exists", %{conn: conn} do - conn = delete(conn, ~p"/room/#{"invalid_room"}") + conn = delete(conn, ~p"/room/#{ID.generate()}") assert response(conn, :not_found) end end @@ -461,7 +475,7 @@ defmodule FishjamWeb.RoomControllerTest do test "roomService removes room on crash", %{room_id: room_id} = state do %{room_id: room2_id} = create_room(state) - room_pid = RoomService.find_room!(room_id) + assert {:ok, room_pid} = RoomService.find_room(room_id) %{engine_pid: engine_pid} = :sys.get_state(room_pid) assert Process.alive?(engine_pid) @@ -484,7 +498,7 @@ defmodule FishjamWeb.RoomControllerTest do test "room closes on engine crash", %{room_id: room_id} = state do %{room_id: room2_id} = create_room(state) - room_pid = RoomService.find_room!(room_id) + assert {:ok, room_pid} = RoomService.find_room(room_id) :erlang.trace(Process.whereis(RoomService), true, [:receive]) diff --git a/test/fishjam_web/controllers/subscription_controller_test.exs b/test/fishjam_web/controllers/subscription_controller_test.exs index 8c2af05b..6dbe5164 100644 --- a/test/fishjam_web/controllers/subscription_controller_test.exs +++ b/test/fishjam_web/controllers/subscription_controller_test.exs @@ -1,6 +1,8 @@ defmodule FishjamWeb.SubscriptionControllerTest do use FishjamWeb.ConnCase + alias Fishjam.Room.ID + @s3_credentials %{ accessKeyId: "access_key_id", secretAccessKey: "secret_access_key", @@ -47,12 +49,14 @@ defmodule FishjamWeb.SubscriptionControllerTest do describe "subscription overall" do test "returns error when room doesn't exist", %{conn: conn} do + invalid_room_id = ID.generate() + conn = - post(conn, ~p"/room/invalid_room_id/component/invalid_component_id/subscribe/", + post(conn, ~p"/room/#{invalid_room_id}/component/invalid_component_id/subscribe/", origins: ["peer-1", "rtsp-2"] ) - assert json_response(conn, :not_found)["errors"] == "Room invalid_room_id does not exist" + assert json_response(conn, :not_found)["errors"] == "Room #{invalid_room_id} does not exist" end test "returns error when hls component doesn't exist", %{conn: conn, room_id: room_id} do diff --git a/test/fishjam_web/integration/peer_socket_test.exs b/test/fishjam_web/integration/peer_socket_test.exs index 634a4267..b0977454 100644 --- a/test/fishjam_web/integration/peer_socket_test.exs +++ b/test/fishjam_web/integration/peer_socket_test.exs @@ -2,9 +2,9 @@ defmodule FishjamWeb.Integration.PeerSocketTest do use FishjamWeb.ConnCase alias __MODULE__.Endpoint + alias Fishjam.Local.RoomService alias Fishjam.PeerMessage alias Fishjam.PeerMessage.{Authenticated, MediaEvent} - alias Fishjam.RoomService alias FishjamWeb.{PeerSocket, WS} @port 5908 diff --git a/test/fishjam_web/integration/server_notification_test.exs b/test/fishjam_web/integration/server_notification_test.exs index 726c118d..f806ab2f 100644 --- a/test/fishjam_web/integration/server_notification_test.exs +++ b/test/fishjam_web/integration/server_notification_test.exs @@ -12,7 +12,8 @@ defmodule FishjamWeb.Integration.ServerNotificationTest do alias Fishjam.Component alias Fishjam.Component.HLS alias Fishjam.Component.HLS.Manager - alias Fishjam.{PeerMessage, Room, RoomService, ServerMessage} + alias Fishjam.Local.RoomService + alias Fishjam.{PeerMessage, ServerMessage} alias Fishjam.ServerMessage.{ Authenticated, @@ -287,7 +288,7 @@ defmodule FishjamWeb.Integration.ServerNotificationTest do test "sends a message when peer connects and room crashes", %{conn: conn} do {room_id, peer_id, _conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) - {:ok, room_pid} = Fishjam.RoomService.find_room(room_id) + {:ok, room_pid} = RoomService.find_room(room_id) Process.exit(room_pid, :kill) @@ -305,7 +306,7 @@ defmodule FishjamWeb.Integration.ServerNotificationTest do test "sends a message when peer connects and it crashes", %{conn: conn} do {room_id, peer_id, conn, _ws} = subscribe_on_notifications_and_connect_peer(conn) - {:ok, room_pid} = Fishjam.RoomService.find_room(room_id) + {:ok, room_pid} = RoomService.find_room(room_id) state = :sys.get_state(room_pid) @@ -355,9 +356,9 @@ defmodule FishjamWeb.Integration.ServerNotificationTest do ws = create_and_authenticate() subscribe(ws, :server_notification) - {:ok, config} = Room.Config.from_params(%{"webhookUrl" => @webhook_url}) + {:ok, config} = Fishjam.Room.Config.from_params(%{"webhookUrl" => @webhook_url}) - {:ok, room_pid, room_id} = Room.start(config) + {:ok, room_pid, room_id} = Fishjam.Local.Room.start(config) Fishjam.WebhookNotifier.add_webhook(room_id, config.webhook_url) {peer_id, token, _conn} = add_peer(conn, room_id) diff --git a/test/support/component_case.ex b/test/support/component_case.ex index 66dbfa04..b3af70e5 100644 --- a/test/support/component_case.ex +++ b/test/support/component_case.ex @@ -7,7 +7,7 @@ defmodule FishjamWeb.ComponentCase do use ExUnit.CaseTemplate use FishjamWeb.ConnCase - alias Fishjam.RoomService + alias Fishjam.Local.RoomService using do quote do From 5ddd4aab342b198c1528408d10a434689443b9ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Thu, 20 Jun 2024 10:55:17 +0200 Subject: [PATCH 50/51] [RTC-566] Update fake_turn to 0.4.3 (#214) --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 88a7787e..c0eea677 100644 --- a/mix.lock +++ b/mix.lock @@ -24,7 +24,7 @@ "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_sdp": {:hex, :ex_sdp, "0.15.0", "53815fb5b5e4fae0f3b26de90f372446bb8e0eed62a3cc20394d3c29519698be", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "d3f23596b73e7057521ff0f0d55b1189c6320a2f04388aa3a80a0aa97ffb379f"}, "excoveralls": {:hex, :excoveralls, "0.15.3", "54bb54043e1cf5fe431eb3db36b25e8fd62cf3976666bafe491e3fa5e29eba47", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f8eb5d8134d84c327685f7bb8f1db4147f1363c3c9533928234e496e3070114e"}, - "fake_turn": {:hex, :fake_turn, "0.4.2", "8cd6c29d7ef8d2b42078ab2347c781ff90bd30e62d2e36f8a493ebe026947476", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d260a3b487c732f44ef7989c685e586dc51974076eb524504639c7f0080c14ac"}, + "fake_turn": {:hex, :fake_turn, "0.4.3", "3ccb56d813d3cd3dad7341ed8d1f4a7a3489f97008e68eaceced503389ad8b01", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "b80a21513c2f754b1a700f9d1a4e386ab1e75155b8c7d29e1833d5d3901344fd"}, "fast_tls": {:hex, :fast_tls, "1.1.13", "828cdc75e1e8fce8158846d2b971d8b4fe2b2ddcc75b759e88d751079bf78afd", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d1f422af40c7777fe534496f508ee86515cb929ad10f7d1d56aa94ce899b44a0"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, From b39cb001f10f197cb3dde54067f8d5237fca22aa Mon Sep 17 00:00:00 2001 From: Adrian Czerwiec <33912477+czerwiukk@users.noreply.github.com> Date: Wed, 2 Apr 2025 11:19:18 +0200 Subject: [PATCH 51/51] Update README.md (#215) * Update README.md Update readme to link to fishjam.io * Update README.md 2 --- README.md | 41 ++++++----------------------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 87ce9e03..9ea225b3 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,12 @@ -# Fishjam +# Moving forward with Fishjam -[![codecov](https://codecov.io/gh/fishjam-dev/fishjam/branch/main/graph/badge.svg?token=ANWFKV2EDP)](https://codecov.io/gh/fishjam-dev/fishjam) -[![CircleCI](https://circleci.com/gh/fishjam-dev/fishjam.svg?style=svg)](https://circleci.com/gh/fishjam-dev/fishjam) +Fishjam Media Server is evolving into [Fishjam](https://fishjam.io/). -Fishjam is an open-source, general-purpose media server that ships with support for multiple media protocols. -It can be thought of as a multimedia bridge meant for creating different types of multimedia systems that lets -you easily create a real-time video conferencing system, a broadcasting solution, or both at the same time. +## Please note +This repository refers to the original Fishjam media server, which was an open-source general purpose media server written in Elixir. +If you run your product on Fishjam Media Server and these changes affect your business, please contact us via projects@swmansion.com. -It leverages the [Membrane RTC Engine](https://github.com/fishjam-dev/membrane_rtc_engine), a real-time communication engine/SFU library built with [Membrane](https://membrane.stream/). - -## Installation - -There are two ways of running Fishjam: -- building from source (requires Elixir and native dependencies) -- using Fishjam Docker images - -To learn more, refer to [Installation page](https://fishjam-dev.github.io/fishjam-docs/getting_started/installation) in Fishjam docs. - -## SDKs - -Fishjam provides server SDKs (used to manage the state of Fishjam server) and client SDKs (used to connect to the Fishjam instance, receive media, etc.). - -To get the list of all available SDKs, go to [SDKs page](https://fishjam-dev.github.io/fishjam-docs/getting_started/sdks) in Fishjam docs. - -## Examples - -- WebRTC Dashboard - - A standalone dashboard that can create rooms, add peers and send media between the peers. Available [here](https://github.com/fishjam-dev/fishjam-dashboard). -To use the dashboard, you need to set up Fishjam with WebRTC, refer to [WebRTC peer page](https://fishjam-dev.github.io/fishjam-docs/getting_started/peers/webrtc) in Fishjam docs to learn how to do that. -Dashboard makes HTTP requests to Fishjam that need to be authorized and requires a token to do so, learn more from [Authentication page](https://fishjam-dev.github.io/fishjam-docs/getting_started/authentication) in Fishjam docs. - -## Documentation - -Everything you need to get started with Fishjam is available in the [Fishjam docs](https://fishjam-dev.github.io/fishjam-docs/). - -You can read about theoretical concepts and problems we encountered in the [Fishjambook](https://fishjam-dev.github.io/book/). +**This project is no longer being maintained or updated.** ## Copyright and License