diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index bb4058b8..76331b66 100644 --- a/lib/jellyfish/room.ex +++ b/lib/jellyfish/room.ex @@ -97,7 +97,9 @@ defmodule Jellyfish.Room do end @spec add_component(id(), Component.component(), map()) :: - {:ok, Component.t()} | :error | {:error, :incompatible_codec} + {:ok, Component.t()} + | :error + | {:error, :incompatible_codec | :reached_components_limit} def add_component(room_id, component_type, options \\ %{}) do GenServer.call(registry_id(room_id), {:add_component, component_type, options}) end @@ -216,18 +218,14 @@ defmodule Jellyfish.Room do end @impl true - def handle_call( - {:add_component, component_type, options}, - _from, - %{config: %{video_codec: video_codec}} = state - ) do + def handle_call({:add_component, component_type, options}, _from, state) do options = Map.merge( %{engine_pid: state.engine_pid, room_id: state.id}, options ) - with :ok <- check_video_codec(video_codec, component_type), + with :ok <- check_component_allowed(component_type, state), {:ok, component} <- Component.new(component_type, options) do state = put_in(state, [:components, component.id], component) @@ -241,6 +239,13 @@ defmodule Jellyfish.Room do Logger.warn("Unable to add component: incompatible codec, HLS needs 'h264' video codec.") {:reply, {:error, :incompatible_codec}, state} + {:error, :reached_components_limit} -> + Logger.warn( + "Unable to add component: reached components limit, max 1 HLS component allowed per room." + ) + + {:reply, {:error, :reached_components_limit}, state} + {:error, reason} -> Logger.warn("Unable to add component: #{inspect(reason)}") {:reply, :error, state} @@ -386,7 +391,24 @@ defmodule Jellyfish.Room do defp registry_id(room_id), do: {:via, Registry, {Jellyfish.RoomRegistry, room_id}} - defp check_video_codec(:h264, Jellyfish.Component.HLS), do: :ok - defp check_video_codec(_codec, Jellyfish.Component.HLS), do: {:error, :incompatible_codec} - defp check_video_codec(_codec, _component), do: :ok + defp check_component_allowed(Component.HLS, %{ + config: %{video_codec: video_codec}, + components: components + }) do + cond do + video_codec != :h264 -> + {:error, :incompatible_codec} + + hls_component_already_present?(components) -> + {:error, :reached_components_limit} + + true -> + :ok + end + end + + defp check_component_allowed(_component_type, _state), do: :ok + + defp hls_component_already_present?(components), + do: components |> Map.values() |> Enum.any?(&(&1.type == Component.HLS)) end diff --git a/lib/jellyfish_web/controllers/component_controller.ex b/lib/jellyfish_web/controllers/component_controller.ex index 2a4da461..1944ffb1 100644 --- a/lib/jellyfish_web/controllers/component_controller.ex +++ b/lib/jellyfish_web/controllers/component_controller.ex @@ -80,6 +80,9 @@ defmodule JellyfishWeb.ComponentController do {:error, :incompatible_codec} -> {:error, :bad_request, "HLS component needs room with video codec 'h264' enforced"} + + {:error, :reached_components_limit} -> + {:error, :bad_request, "Max 1 HLS component allowed per room"} end end diff --git a/test/jellyfish_web/controllers/component_controller_test.exs b/test/jellyfish_web/controllers/component_controller_test.exs index 2ce8f44b..16a690eb 100644 --- a/test/jellyfish_web/controllers/component_controller_test.exs +++ b/test/jellyfish_web/controllers/component_controller_test.exs @@ -36,7 +36,7 @@ defmodule JellyfishWeb.ComponentControllerTest do end describe "create hls component" do - test "renders component when data is valid", %{conn: conn} do + test "renders component when data is valid, allows max 1 hls per room", %{conn: conn} do room_conn = post(conn, ~p"/room", videoCodec: "h264") assert %{"id" => room_id} = json_response(room_conn, :created)["data"] @@ -57,6 +57,12 @@ defmodule JellyfishWeb.ComponentControllerTest do ] } = json_response(conn, :ok)["data"] + # Try to add another hls component + conn = post(conn, ~p"/room/#{room_id}/component", type: "hls") + + assert json_response(conn, :bad_request)["errors"] == + "Max 1 HLS component allowed per room" + room_conn = delete(conn, ~p"/room/#{room_id}") assert response(room_conn, :no_content) end