Skip to content

Commit

Permalink
Change enforceEncoding to enforcedVideoCodec (#55)
Browse files Browse the repository at this point in the history
* Change enforceEncoding to enforcedVideoCodec

* Add handling create component when video codec is wrong

* Refactor
  • Loading branch information
Karolk99 committed Jul 18, 2023
1 parent 338c4cb commit ea67d58
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 65 deletions.
2 changes: 1 addition & 1 deletion lib/jellyfish/peer/webrtc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ defmodule Jellyfish.Peer.WebRTC do
network_options = options.network_options

filter_codecs =
case options.enforce_encoding do
case options.video_codec do
:h264 ->
&filter_codecs_h264/1

Expand Down
40 changes: 26 additions & 14 deletions lib/jellyfish/room.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ defmodule Jellyfish.Room do

@type id :: String.t()
@type max_peers :: non_neg_integer() | nil
@type enforce_encoding :: :h264 | :vp8 | nil
@type video_codec :: :h264 | :vp8 | nil

@typedoc """
This module contains:
Expand All @@ -38,7 +38,7 @@ defmodule Jellyfish.Room do
id: id(),
config: %{
max_peers: max_peers(),
enforce_encoding: enforce_encoding(),
video_codec: video_codec(),
simulcast?: boolean()
},
components: %{Component.id() => Component.t()},
Expand All @@ -47,12 +47,11 @@ defmodule Jellyfish.Room do
network_options: map()
}

@spec start(max_peers(), enforce_encoding()) :: {:ok, pid(), id()}
def start(max_peers, enforce_encoding) do
@spec start(max_peers(), video_codec()) :: {:ok, pid(), id()}
def start(max_peers, video_codec) do
id = UUID.uuid4()

{:ok, pid} =
GenServer.start(__MODULE__, [id, max_peers, enforce_encoding], name: registry_id(id))
{:ok, pid} = GenServer.start(__MODULE__, [id, max_peers, video_codec], name: registry_id(id))

{:ok, pid, id}
end
Expand Down Expand Up @@ -95,7 +94,8 @@ defmodule Jellyfish.Room do
GenServer.call(registry_id(room_id), {:remove_peer, peer_id})
end

@spec add_component(id(), Component.component(), map() | nil) :: {:ok, Component.t()} | :error
@spec add_component(id(), Component.component(), map() | nil) ::
{:ok, Component.t()} | :error | {:error, :incompatible_codec}
def add_component(room_id, component_type, options) do
GenServer.call(registry_id(room_id), {:add_component, component_type, options})
end
Expand All @@ -111,8 +111,8 @@ defmodule Jellyfish.Room do
end

@impl true
def init([id, max_peers, enforce_encoding]) do
state = new(id, max_peers, enforce_encoding)
def init([id, max_peers, video_codec]) do
state = new(id, max_peers, video_codec)
Logger.metadata(room_id: id)
Logger.info("Initialize room")

Expand All @@ -133,7 +133,7 @@ defmodule Jellyfish.Room do
options = %{
engine_pid: state.engine_pid,
network_options: state.network_options,
enforce_encoding: state.config.enforce_encoding
video_codec: state.config.video_codec
}

peer = Peer.new(peer_type, options)
Expand Down Expand Up @@ -205,14 +205,19 @@ defmodule Jellyfish.Room do
end

@impl true
def handle_call({:add_component, component_type, options}, _from, state) do
def handle_call(
{:add_component, component_type, options},
_from,
%{config: %{video_codec: video_codec}} = state
) do
options =
Map.merge(
%{engine_pid: state.engine_pid, room_id: state.id},
if(is_nil(options), do: %{}, else: options)
)

with {:ok, component} <- Component.new(component_type, options) do
with :ok <- check_video_codec(video_codec, component_type),
{:ok, component} <- Component.new(component_type, options) do
state = put_in(state, [:components, component.id], component)

:ok = Engine.add_endpoint(state.engine_pid, component.engine_endpoint, id: component.id)
Expand All @@ -221,6 +226,9 @@ defmodule Jellyfish.Room do

{:reply, {:ok, component}, state}
else
{:error, :incompatible_codec} ->
{:reply, {:error, :incompatible_codec}, state}

{:error, reason} ->
Logger.warn("Unable to add component: #{inspect(reason)}")
{:reply, :error, state}
Expand Down Expand Up @@ -319,7 +327,7 @@ defmodule Jellyfish.Room do
{:noreply, state}
end

defp new(id, max_peers, enforce_encoding) do
defp new(id, max_peers, video_codec) do
rtc_engine_options = [
id: id
]
Expand Down Expand Up @@ -350,11 +358,15 @@ defmodule Jellyfish.Room do

%__MODULE__{
id: id,
config: %{max_peers: max_peers, enforce_encoding: enforce_encoding},
config: %{max_peers: max_peers, video_codec: video_codec},
engine_pid: pid,
network_options: [integrated_turn_options: integrated_turn_options]
}
end

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
end
27 changes: 14 additions & 13 deletions lib/jellyfish/room_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ defmodule Jellyfish.RoomService do
|> Enum.reject(&(&1 == nil))
end

@spec create_room(Room.max_peers(), Room.enforce_encoding()) ::
{:ok, Room.t()} | {:error, :invalid_max_peers | :invalid_enforce_encoding}
def create_room(max_peers, enforce_encoding) do
GenServer.call(__MODULE__, {:create_room, max_peers, enforce_encoding})
@spec create_room(Room.max_peers(), String.t()) ::
{:ok, Room.t()} | {:error, :invalid_max_peers | :invalid_video_codec}
def create_room(max_peers, video_codec) do
GenServer.call(__MODULE__, {:create_room, max_peers, video_codec})
end

@spec delete_room(Room.id()) :: :ok | {:error, :room_not_found}
Expand All @@ -71,10 +71,11 @@ defmodule Jellyfish.RoomService do
end

@impl true
def handle_call({:create_room, max_peers, enforce_encoding}, _from, state) do
def handle_call({:create_room, max_peers, video_codec}, _from, state) do
with :ok <- validate_max_peers(max_peers),
{:ok, enforce_encoding} <- encoding_to_atom(enforce_encoding),
{:ok, room_pid, room_id} <- Room.start(max_peers, enforce_encoding) do
{:ok, video_codec} <- codec_to_atom(video_codec) do
{:ok, room_pid, room_id} = Room.start(max_peers, video_codec)

room = Room.get_state(room_id)
Process.monitor(room_pid)

Expand All @@ -93,8 +94,8 @@ defmodule Jellyfish.RoomService do
{:error, :max_peers} ->
{:reply, {:error, :invalid_max_peers}, state}

{:error, :enforce_encoding} ->
{:reply, {:error, :invalid_enforce_encoding}, state}
{:error, :video_codec} ->
{:reply, {:error, :invalid_video_codec}, state}
end
end

Expand Down Expand Up @@ -154,8 +155,8 @@ defmodule Jellyfish.RoomService do
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, :max_peers}

defp encoding_to_atom("h264"), do: {:ok, :h264}
defp encoding_to_atom("vp8"), do: {:ok, :vp8}
defp encoding_to_atom(nil), do: {:ok, nil}
defp encoding_to_atom(_encoding), do: {:error, :enforce_encoding}
defp codec_to_atom("h264"), do: {:ok, :h264}
defp codec_to_atom("vp8"), do: {:ok, :vp8}
defp codec_to_atom(nil), do: {:ok, nil}
defp codec_to_atom(_codec), do: {:error, :video_codec}
end
2 changes: 1 addition & 1 deletion lib/jellyfish_web/api_spec/room.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule JellyfishWeb.ApiSpec.Room do
description: "Maximum amount of peers allowed into the room",
nullable: true
},
enforceEncoding: %Schema{
videoCodec: %Schema{
description: "Enforces video codec for each peer in the room",
type: :string,
enum: ["h264", "vp8"],
Expand Down
14 changes: 11 additions & 3 deletions lib/jellyfish_web/controllers/component_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,17 @@ defmodule JellyfishWeb.ComponentController do
|> put_status(:created)
|> render("show.json", component: component)
else
:error -> {:error, :bad_request, "Invalid request body structure"}
{:error, :invalid_type} -> {:error, :bad_request, "Invalid component type"}
{:error, :room_not_found} -> {:error, :not_found, "Room #{room_id} does not exist"}
:error ->
{:error, :bad_request, "Invalid request body structure"}

{:error, :invalid_type} ->
{:error, :bad_request, "Invalid component type"}

{:error, :room_not_found} ->
{:error, :not_found, "Room #{room_id} does not exist"}

{:error, :incompatible_codec} ->
{:error, :bad_request, "HLS component needs room with video codec 'h264' enforced"}
end
end

Expand Down
8 changes: 4 additions & 4 deletions lib/jellyfish_web/controllers/room_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ defmodule JellyfishWeb.RoomController do

def create(conn, params) do
with max_peers <- Map.get(params, "maxPeers"),
enforce_encoding <- Map.get(params, "enforceEncoding"),
{:ok, room} <- RoomService.create_room(max_peers, enforce_encoding) do
video_codec <- Map.get(params, "videoCodec"),
{:ok, room} <- RoomService.create_room(max_peers, video_codec) do
conn
|> put_resp_content_type("application/json")
|> put_status(:created)
Expand All @@ -78,8 +78,8 @@ defmodule JellyfishWeb.RoomController do
{:error, :invalid_max_peers} ->
{:error, :bad_request, "maxPeers must be a number"}

{:error, :invalid_enforce_encoding} ->
{:error, :bad_request, "enforceEncoding must be 'h264' or 'vp8'"}
{:error, :invalid_video_codec} ->
{:error, :bad_request, "videoCodec must be 'h264' or 'vp8'"}
end
end

Expand Down
8 changes: 4 additions & 4 deletions lib/jellyfish_web/server_socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ defmodule JellyfishWeb.ServerSocket do

config =
room.config
|> Map.update!(:enforce_encoding, &to_proto_encoding/1)
|> Map.update!(:video_codec, &to_proto_codec/1)
|> then(&struct!(RoomState.Config, &1))

%RoomState{id: room.id, config: config, peers: peers, components: components}
Expand All @@ -239,9 +239,9 @@ defmodule JellyfishWeb.ServerSocket do
defp to_proto_type(Jellyfish.Component.RTSP), do: :TYPE_RTSP
defp to_proto_type(Jellyfish.Peer.WebRTC), do: :TYPE_WEBRTC

defp to_proto_encoding(:h264), do: :ENCODING_H264
defp to_proto_encoding(:vp8), do: :ENCODING_VP8
defp to_proto_encoding(nil), do: :ENCODING_UNSPECIFIED
defp to_proto_codec(:h264), do: :CODEC_H264
defp to_proto_codec(:vp8), do: :CODEC_VP8
defp to_proto_codec(nil), do: :CODEC_UNSPECIFIED

defp to_proto_status(:disconnected), do: :STATUS_DISCONNECTED
defp to_proto_status(:connected), do: :STATUS_CONNECTED
Expand Down
14 changes: 7 additions & 7 deletions lib/protos/jellyfish/server_notifications.pb.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ defmodule Jellyfish.ServerMessage.SubscribeRequest.ServerNotification.Option do
field :OPTION_ALL, 1
end

defmodule Jellyfish.ServerMessage.SubscribeResponse.RoomState.Config.Encoding do
defmodule Jellyfish.ServerMessage.SubscribeResponse.RoomState.Config.Codec do
@moduledoc false

use Protobuf, enum: true, protoc_gen_elixir_version: "0.12.0", syntax: :proto3

field :ENCODING_UNSPECIFIED, 0
field :ENCODING_H264, 1
field :ENCODING_VP8, 2
field :CODEC_UNSPECIFIED, 0
field :CODEC_H264, 1
field :CODEC_VP8, 2
end

defmodule Jellyfish.ServerMessage.SubscribeResponse.RoomState.Peer.Type do
Expand Down Expand Up @@ -149,9 +149,9 @@ defmodule Jellyfish.ServerMessage.SubscribeResponse.RoomState.Config do

field :max_peers, 1, type: :uint32, json_name: "maxPeers"

field :enforce_encoding, 2,
type: Jellyfish.ServerMessage.SubscribeResponse.RoomState.Config.Encoding,
json_name: "enforceEncoding",
field :video_codec, 2,
type: Jellyfish.ServerMessage.SubscribeResponse.RoomState.Config.Codec,
json_name: "videoCodec",
enum: true
end

Expand Down
14 changes: 7 additions & 7 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -176,19 +176,19 @@ components:
RoomConfig:
description: Room configuration
properties:
enforceEncoding:
description: Enforces video codec for each peer in the room
enum:
- h264
- vp8
nullable: true
type: string
maxPeers:
description: Maximum amount of peers allowed into the room
example: 10
minimum: 1
nullable: true
type: integer
videoCodec:
description: Enforces video codec for each peer in the room
enum:
- h264
- vp8
nullable: true
type: string
title: RoomConfig
type: object
x-struct: Elixir.JellyfishWeb.ApiSpec.Room.Config
Expand Down
2 changes: 1 addition & 1 deletion protos
28 changes: 23 additions & 5 deletions test/jellyfish_web/controllers/component_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule JellyfishWeb.ComponentControllerTest do
import OpenApiSpex.TestAssertions

@schema JellyfishWeb.ApiSpec.spec()
@source_uri "rtsp://placeholder-19inrifjbsjb.it:12345/afwefae"

setup %{conn: conn} do
server_api_token = Application.fetch_env!(:jellyfish, :server_api_token)
Expand Down Expand Up @@ -35,7 +36,10 @@ defmodule JellyfishWeb.ComponentControllerTest do
end

describe "create hls component" do
test "renders component when data is valid", %{conn: conn, room_id: room_id} do
test "renders component when data is valid", %{conn: conn} do
room_conn = post(conn, ~p"/room", videoCodec: "h264")
assert %{"id" => room_id} = json_response(room_conn, :created)["data"]

conn = post(conn, ~p"/room/#{room_id}/component", type: "hls")

assert response = %{"data" => %{"id" => id}} = json_response(conn, :created)
Expand All @@ -49,21 +53,31 @@ defmodule JellyfishWeb.ComponentControllerTest do
%{"id" => ^id, "type" => "hls"}
]
} = json_response(conn, :ok)["data"]

room_conn = delete(conn, ~p"/room/#{room_id}")
assert response(room_conn, :no_content)
end

test "renders errors when request body structure is invalid", %{conn: conn, room_id: room_id} do
conn = post(conn, ~p"/room/#{room_id}/component", invalid_parameter: "hls")

assert json_response(conn, :bad_request)["errors"] == "Invalid request body structure"
end

test "renders errors when video codec is different than h264", %{conn: conn, room_id: room_id} do
conn = post(conn, ~p"/room/#{room_id}/component", type: "hls")

assert json_response(conn, :bad_request)["errors"] ==
"HLS component needs room with video codec 'h264' enforced"
end
end

describe "create rtsp component" do
test "renders component with required options", %{conn: conn, room_id: room_id} do
conn =
post(conn, ~p"/room/#{room_id}/component",
type: "rtsp",
options: %{sourceUri: "rtsp://placeholder-19inrifjbsjb.it:12345/afwefae"}
options: %{sourceUri: @source_uri}
)

assert response = %{"data" => %{"id" => id}} = json_response(conn, :created)
Expand All @@ -90,7 +104,7 @@ defmodule JellyfishWeb.ComponentControllerTest do
end

describe "delete component" do
setup [:create_hls_component]
setup [:create_rtsp_component]

test "deletes chosen component", %{conn: conn, room_id: room_id, component_id: component_id} do
conn = delete(conn, ~p"/room/#{room_id}/component/#{component_id}")
Expand Down Expand Up @@ -120,8 +134,12 @@ defmodule JellyfishWeb.ComponentControllerTest do
end
end

defp create_hls_component(state) do
conn = post(state.conn, ~p"/room/#{state.room_id}/component", type: "hls")
defp create_rtsp_component(state) do
conn =
post(state.conn, ~p"/room/#{state.room_id}/component",
type: "rtsp",
options: %{sourceUri: @source_uri}
)

assert %{"id" => id} = json_response(conn, :created)["data"]

Expand Down
Loading

0 comments on commit ea67d58

Please sign in to comment.