diff --git a/config/runtime.exs b/config/runtime.exs index c6c03d54..a0f1ce46 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -30,7 +30,8 @@ config :jellyfish, metrics_ip: ConfigReader.read_ip("JF_METRICS_IP") || {127, 0, 0, 1}, metrics_port: ConfigReader.read_port("JF_METRICS_PORT") || 9568, dist_config: ConfigReader.read_dist_config(), - webrtc_config: ConfigReader.read_webrtc_config() + webrtc_config: ConfigReader.read_webrtc_config(), + sip_config: ConfigReader.read_sip_config() case System.get_env("JF_SERVER_API_TOKEN") do nil when prod? == true -> diff --git a/lib/jellyfish/component.ex b/lib/jellyfish/component.ex index 705ab67b..b257accd 100644 --- a/lib/jellyfish/component.ex +++ b/lib/jellyfish/component.ex @@ -10,7 +10,8 @@ defmodule Jellyfish.Component do use Bunch.Access - alias Jellyfish.Component.{File, HLS, RTSP} + alias Jellyfish.Room + alias Jellyfish.Component.{File, HLS, RTSP, SIP} alias Jellyfish.Track @enforce_keys [ @@ -22,8 +23,8 @@ defmodule Jellyfish.Component do defstruct @enforce_keys ++ [tracks: %{}] @type id :: String.t() - @type component :: HLS | RTSP | File - @type properties :: HLS.properties() | RTSP.properties() | File.properties() + @type component :: HLS | RTSP | File | SIP + @type properties :: HLS.properties() | RTSP.properties() | File.properties() | SIP.properties() @typedoc """ This module contains: @@ -40,12 +41,64 @@ defmodule Jellyfish.Component do tracks: %{Track.id() => Track.t()} } + @doc """ + 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( + room_state :: Room.t(), + component :: __MODULE__.t(), + component_options :: map() + ) :: :ok + + @doc """ + This callback is run after scheduling removing of component. + In it some additional cleanup can be done. + """ + @callback on_remove( + room_state :: Room.t(), + component :: __MODULE__.t() + ) :: :ok + + defmacro __using__(_opts) do + quote location: :keep do + @behaviour Jellyfish.Component + + @impl true + def after_init(_room_state, _component, _component_options), do: :ok + + @impl true + def on_remove(_room_state, _component), do: :ok + + defoverridable after_init: 3, on_remove: 2 + + def serialize_options(opts, opts_schema) do + with {:ok, valid_opts} <- OpenApiSpex.Cast.cast(opts_schema, opts) do + valid_opts = + valid_opts + |> Map.from_struct() + |> Map.new(fn {k, v} -> {underscore(k), serialize(v)} end) + + {:ok, valid_opts} + end + end + + defp serialize(v) when is_struct(v), + do: v |> Map.from_struct() |> Map.new(fn {k, v} -> {underscore(k), v} end) + + defp serialize(v), do: v + + defp underscore(k), do: k |> Atom.to_string() |> Macro.underscore() |> String.to_atom() + end + end + @spec parse_type(String.t()) :: {:ok, component()} | {:error, :invalid_type} def parse_type(type) do case type do "hls" -> {:ok, HLS} "rtsp" -> {:ok, RTSP} "file" -> {:ok, File} + "sip" -> {:ok, SIP} _other -> {:error, :invalid_type} end end diff --git a/lib/jellyfish/component/file.ex b/lib/jellyfish/component/file.ex index 6925b051..3c465aff 100644 --- a/lib/jellyfish/component/file.ex +++ b/lib/jellyfish/component/file.ex @@ -4,6 +4,7 @@ defmodule Jellyfish.Component.File do """ @behaviour Jellyfish.Endpoint.Config + use Jellyfish.Component alias ExSDP.Attribute.FMTP alias Membrane.RTC.Engine.Endpoint.File, as: FileEndpoint diff --git a/lib/jellyfish/component/hls.ex b/lib/jellyfish/component/hls.ex index c9efcaad..ace0bd2d 100644 --- a/lib/jellyfish/component/hls.ex +++ b/lib/jellyfish/component/hls.ex @@ -4,8 +4,17 @@ defmodule Jellyfish.Component.HLS do """ @behaviour Jellyfish.Endpoint.Config + use Jellyfish.Component + + alias Jellyfish.Component.HLS.{ + EtsHelper, + LLStorage, + Manager, + Recording, + RequestHandler, + Storage + } - alias Jellyfish.Component.HLS.{LLStorage, Recording, Storage} alias Jellyfish.Room alias JellyfishWeb.ApiSpec.Component.HLS.Options @@ -25,11 +34,14 @@ defmodule Jellyfish.Component.HLS do @impl true def config(options) do - with {:ok, valid_opts} <- serialize_options(options) do - hls_config = create_hls_config(options.room_id, valid_opts) + options = Map.delete(options, "s3") + + with {:ok, serialized_opts} <- serialize_options(options, Options.schema()), + result_opts <- Map.update!(serialized_opts, :subscribe_mode, &String.to_atom/1) do + hls_config = create_hls_config(options.room_id, result_opts) properties = - valid_opts + result_opts |> Map.put(:playable, false) |> Enum.into(%{}) @@ -51,7 +63,7 @@ defmodule Jellyfish.Component.HLS do } }, hls_config: hls_config, - subscribe_mode: valid_opts.subscribe_mode + subscribe_mode: result_opts.subscribe_mode }, properties: properties }} @@ -60,6 +72,25 @@ defmodule Jellyfish.Component.HLS do end end + @impl true + def after_init(room_state, component, options) do + on_hls_startup(room_state.id, component.properties) + + spawn_hls_manager(options) + :ok + end + + @impl true + def on_remove(room_state, component) do + room_id = room_state.id + + %{low_latency: low_latency} = component.properties + + EtsHelper.delete_hls_folder_path(room_id) + + if low_latency, do: remove_request_handler(room_id) + end + @spec output_dir(Room.id(), persistent: boolean()) :: String.t() def output_dir(room_id, persistent: true) do Recording.directory(room_id) @@ -70,20 +101,27 @@ defmodule Jellyfish.Component.HLS do Path.join([base_path, "temporary_hls", "#{room_id}"]) end - def serialize_options(options) do - with {:ok, valid_opts} <- OpenApiSpex.Cast.cast(Options.schema(), options) do - valid_opts = - valid_opts - |> Map.from_struct() - |> Map.new(fn {k, v} -> {underscore(k), serialize(v)} end) - |> Map.update!(:subscribe_mode, &String.to_atom/1) + defp on_hls_startup(room_id, %{low_latency: low_latency, persistent: persistent}) do + room_id + |> output_dir(persistent: persistent) + |> then(&EtsHelper.add_hls_folder_path(room_id, &1)) - {:ok, valid_opts} - else - {:error, _reason} = error -> error - end + if low_latency, do: spawn_request_handler(room_id) + end + + defp spawn_hls_manager(%{engine_pid: engine_pid, room_id: room_id} = options) do + {:ok, hls_dir} = EtsHelper.get_hls_folder_path(room_id) + {:ok, valid_opts} = serialize_options(options, Options.schema()) + + {:ok, _pid} = Manager.start(room_id, engine_pid, hls_dir, valid_opts) end + defp spawn_request_handler(room_id), + do: RequestHandler.start(room_id) + + defp remove_request_handler(room_id), + do: RequestHandler.stop(room_id) + defp create_hls_config( room_id, %{ @@ -113,11 +151,4 @@ defmodule Jellyfish.Component.HLS do defp setup_hls_storage(_room_id, low_latency: false) do fn directory -> %Storage{directory: directory} end end - - defp underscore(k), do: k |> Atom.to_string() |> Macro.underscore() |> String.to_atom() - - defp serialize(v) when is_struct(v), - do: v |> Map.from_struct() |> Map.new(fn {k, v} -> {underscore(k), v} end) - - defp serialize(v), do: v end diff --git a/lib/jellyfish/component/hls/manager.ex b/lib/jellyfish/component/hls/manager.ex index ac4df615..2812220d 100644 --- a/lib/jellyfish/component/hls/manager.ex +++ b/lib/jellyfish/component/hls/manager.ex @@ -119,6 +119,7 @@ defmodule Jellyfish.Component.HLS.Manager do defp remove_hls(hls_dir, room_id) do File.rm_rf!(hls_dir) + Logger.info("Remove hls from a disk, room: #{room_id}") end diff --git a/lib/jellyfish/component/rtsp.ex b/lib/jellyfish/component/rtsp.ex index 4d4d51b3..dae6bdc9 100644 --- a/lib/jellyfish/component/rtsp.ex +++ b/lib/jellyfish/component/rtsp.ex @@ -4,6 +4,7 @@ defmodule Jellyfish.Component.RTSP do """ @behaviour Jellyfish.Endpoint.Config + use Jellyfish.Component alias Membrane.RTC.Engine.Endpoint.RTSP @@ -21,18 +22,14 @@ defmodule Jellyfish.Component.RTSP do def config(%{engine_pid: engine} = options) do options = Map.drop(options, [:engine_pid, :room_id]) - with {:ok, valid_opts} <- OpenApiSpex.cast_value(options, Options.schema()) do + with {:ok, serialized_opts} <- serialize_options(options, Options.schema()) do endpoint_spec = - Map.from_struct(valid_opts) - # OpenApiSpex will remove invalid options, so the following conversion, while ugly, is memory-safe - |> Map.new(fn {k, v} -> - {Atom.to_string(k) |> Macro.underscore() |> String.to_atom(), v} - end) + serialized_opts |> Map.put(:rtc_engine, engine) |> Map.put(:max_reconnect_attempts, :infinity) |> then(&struct(RTSP, &1)) - properties = valid_opts |> Map.from_struct() + properties = serialized_opts {:ok, %{endpoint: endpoint_spec, properties: properties}} else diff --git a/lib/jellyfish/component/sip.ex b/lib/jellyfish/component/sip.ex new file mode 100644 index 00000000..589db844 --- /dev/null +++ b/lib/jellyfish/component/sip.ex @@ -0,0 +1,60 @@ +defmodule Jellyfish.Component.SIP do + @moduledoc """ + Module representing the SIP component. + """ + + @behaviour Jellyfish.Endpoint.Config + use Jellyfish.Component + + alias Membrane.RTC.Engine.Endpoint.SIP + alias Membrane.RTC.Engine.Endpoint.SIP.RegistrarCredentials + + alias JellyfishWeb.ApiSpec.Component.SIP.Options + + @type properties :: %{ + registrar_credentials: %{ + address: String.t(), + username: String.t(), + password: String.t() + } + } + + @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 + + with {:ok, serialized_opts} <- serialize_options(options, Options.schema()) do + endpoint_spec = %SIP{ + rtc_engine: engine, + external_ip: external_ip, + registrar_credentials: create_register_credentials(serialized_opts.registrar_credentials) + } + + properties = serialized_opts + + {:ok, %{endpoint: endpoint_spec, properties: properties}} + else + {:error, [%OpenApiSpex.Cast.Error{reason: :missing_field, name: name} | _rest]} -> + {:error, {:missing_parameter, name}} + + {:error, _reason} = error -> + error + end + end + + defp create_register_credentials(credentials) do + credentials + |> Map.to_list() + |> Keyword.new() + |> RegistrarCredentials.new() + end +end diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex index fc4adba3..245dfeaf 100644 --- a/lib/jellyfish/config_reader.ex +++ b/lib/jellyfish/config_reader.ex @@ -98,11 +98,11 @@ defmodule Jellyfish.ConfigReader do end def read_webrtc_config() do - webrtc_used = read_boolean("JF_WEBRTC_USED") + webrtc_used? = read_boolean("JF_WEBRTC_USED") - if webrtc_used != false do + if webrtc_used? != false do [ - webrtc_used: true, + 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_port_range: read_port_range("JF_WEBRTC_TURN_PORT_RANGE") || {50_000, 59_999}, @@ -110,7 +110,7 @@ defmodule Jellyfish.ConfigReader do ] else [ - webrtc_used: false, + webrtc_used?: false, turn_ip: nil, turn_listen_ip: nil, turn_port_range: nil, @@ -119,6 +119,30 @@ defmodule Jellyfish.ConfigReader do end end + def read_sip_config() do + sip_used? = read_boolean("JF_SIP_USED") + 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` + """ + 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/peer/webrtc.ex b/lib/jellyfish/peer/webrtc.ex index 20c72def..a3a34807 100644 --- a/lib/jellyfish/peer/webrtc.ex +++ b/lib/jellyfish/peer/webrtc.ex @@ -14,7 +14,7 @@ defmodule Jellyfish.Peer.WebRTC do @impl true def config(options) do - if not Application.fetch_env!(:jellyfish, :webrtc_config)[:webrtc_used] 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\"" ) diff --git a/lib/jellyfish/room.ex b/lib/jellyfish/room.ex index 62a76bb6..bf0a50ca 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} + alias Jellyfish.Component.{HLS, RTSP, SIP} alias Jellyfish.Event alias Jellyfish.Peer alias Jellyfish.Room.Config @@ -17,6 +17,7 @@ defmodule Jellyfish.Room do alias Membrane.ICE.TURNManager alias Membrane.RTC.Engine + alias Membrane.RTC.Engine.Endpoint alias Membrane.RTC.Engine.Message.{ EndpointAdded, @@ -137,6 +138,18 @@ defmodule Jellyfish.Room do GenServer.call(registry_id(room_id), {:hls_subscribe, 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}) @@ -250,17 +263,11 @@ defmodule Jellyfish.Room do options ) - component_options = Map.delete(options, "s3") - with :ok <- check_component_allowed(component_type, state), - {:ok, component} <- - Component.new(component_type, component_options) do + {:ok, component} <- Component.new(component_type, options) do state = put_in(state, [:components, component.id], component) - if component_type == HLS do - on_hls_startup(state.id, component.properties) - spawn_hls_manager(options) - end + component_type.after_init(state, component, options) :ok = Engine.add_endpoint(state.engine_pid, component.engine_endpoint, id: component.id) @@ -330,7 +337,7 @@ defmodule Jellyfish.Room do reply = case validate_hls_subscription(hls_component) do :ok -> - Engine.message_endpoint(state.engine_pid, hls_component.id, {:subscribe, origins}) + Endpoint.HLS.subscribe(state.engine_pid, hls_component.id, origins) {:error, _reason} = error -> error @@ -345,6 +352,36 @@ defmodule Jellyfish.Room do {: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 + {:ok, component} when component.type == SIP -> + Endpoint.SIP.dial(state.engine_pid, 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 Map.fetch(state.components, component_id) do + {:ok, component} when component.type == SIP -> + Endpoint.SIP.end_call(state.engine_pid, 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 Engine.message_endpoint(state.engine_pid, peer_id, {:media_event, event}) @@ -575,9 +612,6 @@ defmodule Jellyfish.Room do def terminate(_reason, %{engine_pid: engine_pid} = state) do Engine.terminate(engine_pid, asynchronous?: true, timeout: 10_000) - hls_component = get_hls_component(state) - unless is_nil(hls_component), do: on_hls_removal(state.id, hls_component.properties) - state.peers |> Map.values() |> Enum.each(&handle_remove_peer(&1.id, state, :room_stopped)) @@ -600,7 +634,7 @@ defmodule Jellyfish.Room do webrtc_config = Application.fetch_env!(:jellyfish, :webrtc_config) turn_options = - if webrtc_config[:webrtc_used] do + if webrtc_config[:webrtc_used?] do turn_ip = webrtc_config[:turn_listen_ip] turn_mock_ip = webrtc_config[:turn_ip] @@ -615,7 +649,7 @@ defmodule Jellyfish.Room do tcp_turn_port = webrtc_config[:turn_tcp_port] - if webrtc_config[:webrtc_used] and tcp_turn_port != nil do + if webrtc_config[:webrtc_used?] and tcp_turn_port != nil do TURNManager.ensure_tcp_turn_launched(turn_options, port: tcp_turn_port) end @@ -664,7 +698,7 @@ defmodule Jellyfish.Room do Logger.info("Removed component #{inspect(component_id)}") - if component.type == HLS, do: on_hls_removal(state.id, component.properties) + component.type.on_remove(state, component) if reason == :component_crashed, do: Event.broadcast_server_notification({:component_crashed, state.id, component_id}) @@ -702,26 +736,6 @@ defmodule Jellyfish.Room do if component.type == HLS, do: component end) - defp on_hls_startup(room_id, %{low_latency: low_latency, persistent: persistent}) do - room_id - |> HLS.output_dir(persistent: persistent) - |> then(&HLS.EtsHelper.add_hls_folder_path(room_id, &1)) - - if low_latency, do: spawn_request_handler(room_id) - end - - defp spawn_request_handler(room_id), - do: HLS.RequestHandler.start(room_id) - - defp on_hls_removal(room_id, %{low_latency: low_latency}) do - HLS.EtsHelper.delete_hls_folder_path(room_id) - - if low_latency, do: remove_request_handler(room_id) - end - - defp remove_request_handler(room_id), - do: HLS.RequestHandler.stop(room_id) - defp check_component_allowed(HLS, %{ config: %{video_codec: video_codec}, components: components @@ -751,13 +765,6 @@ defmodule Jellyfish.Room do defp hls_component_already_present?(components), do: components |> Map.values() |> Enum.any?(&(&1.type == HLS)) - defp spawn_hls_manager(%{engine_pid: engine_pid, room_id: room_id} = options) do - {:ok, hls_dir} = HLS.EtsHelper.get_hls_folder_path(room_id) - {:ok, valid_opts} = HLS.serialize_options(options) - - {:ok, _pid} = HLS.Manager.start(room_id, engine_pid, hls_dir, valid_opts) - end - defp validate_hls_subscription(nil), do: {:error, :hls_component_not_exists} defp validate_hls_subscription(%{properties: %{subscribe_mode: :auto}}), diff --git a/lib/jellyfish_web/api_spec/component.ex b/lib/jellyfish_web/api_spec/component.ex index 2ea82dbc..cf65afda 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} + alias JellyfishWeb.ApiSpec.Component.{File, HLS, RTSP, SIP} defmodule Type do @moduledoc false @@ -30,7 +30,8 @@ defmodule JellyfishWeb.ApiSpec.Component do oneOf: [ HLS.Options, RTSP.Options, - File.Options + File.Options, + SIP.Options ] }) end @@ -42,14 +43,16 @@ defmodule JellyfishWeb.ApiSpec.Component do oneOf: [ HLS, RTSP, - File + File, + SIP ], discriminator: %OpenApiSpex.Discriminator{ propertyName: "type", mapping: %{ "hls" => HLS, "rtsp" => RTSP, - "file" => File + "file" => File, + "sip" => SIP } } }) diff --git a/lib/jellyfish_web/api_spec/component/sip.ex b/lib/jellyfish_web/api_spec/component/sip.ex new file mode 100644 index 00000000..eaa8b8ac --- /dev/null +++ b/lib/jellyfish_web/api_spec/component/sip.ex @@ -0,0 +1,79 @@ +defmodule JellyfishWeb.ApiSpec.Component.SIP do + @moduledoc false + + require OpenApiSpex + alias OpenApiSpex.Schema + + defmodule SIPCredentials do + @moduledoc false + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "SIPCredentials", + description: "Credentials used to authorize in SIP Provider service", + type: :object, + properties: %{ + address: %Schema{ + type: :string, + description: + "SIP provider address. Can be in the form of FQDN (my-sip-registrar.net) or IPv4 (1.2.3.4). Port can be specified e.g: 5.6.7.8:9999. If not given, the default SIP port `5060` will be assumed", + example: "example.org" + }, + username: %Schema{type: :string, description: "Username in SIP service provider"}, + password: %Schema{type: :string, description: "Password in SIP service provider"} + }, + required: [:address, :username, :password] + }) + end + + defmodule Properties do + @moduledoc false + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "ComponentPropertiesSIP", + description: "Properties specific to the SIP component", + type: :object, + properties: %{ + registrarCredentials: SIPCredentials.schema() + }, + required: [:registrarCredentials] + }) + end + + defmodule Options do + @moduledoc false + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "ComponentOptionsSIP", + description: "Options specific to the SIP component", + type: :object, + properties: %{ + registrarCredentials: SIPCredentials.schema() + }, + required: [:registrarCredentials] + }) + end + + OpenApiSpex.schema(%{ + title: "ComponentSIP", + description: "Describes the SIP 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: "sip"}, + 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/api_spec/dial.ex b/lib/jellyfish_web/api_spec/dial.ex new file mode 100644 index 00000000..51ce1060 --- /dev/null +++ b/lib/jellyfish_web/api_spec/dial.ex @@ -0,0 +1,24 @@ +defmodule JellyfishWeb.ApiSpec.Dial do + @moduledoc false + require OpenApiSpex + + alias OpenApiSpex.Schema + + defmodule PhoneNumber do + @moduledoc false + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "DialConfig", + description: "Dial config", + type: :object, + properties: %{ + phoneNumber: %Schema{ + type: :string, + description: "Phone number on which SIP Component will call" + } + } + }) + end +end diff --git a/lib/jellyfish_web/controllers/component_json.ex b/lib/jellyfish_web/controllers/component_json.ex index 15bade2f..a89e7f61 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} + alias Jellyfish.Component.{File, HLS, RTSP, SIP} alias Jellyfish.Utils.ParserJSON def show(%{component: component}) do @@ -14,6 +14,7 @@ defmodule JellyfishWeb.ComponentJSON do HLS -> "hls" RTSP -> "rtsp" File -> "file" + SIP -> "sip" end %{ diff --git a/lib/jellyfish_web/controllers/sip_call_controller.ex b/lib/jellyfish_web/controllers/sip_call_controller.ex new file mode 100644 index 00000000..c60116ec --- /dev/null +++ b/lib/jellyfish_web/controllers/sip_call_controller.ex @@ -0,0 +1,83 @@ +defmodule JellyfishWeb.SIPCallController do + use JellyfishWeb, :controller + use OpenApiSpex.ControllerSpecs + + alias Jellyfish.Room + alias Jellyfish.RoomService + alias JellyfishWeb.ApiSpec + alias OpenApiSpex.Response + + action_fallback JellyfishWeb.FallbackController + + tags [:sip] + + operation :create, + operation_id: "dial", + summary: "Make a call from the SIP component to the provided phone number", + parameters: [ + room_id: [in: :path, description: "Room ID", type: :string], + component_id: [in: :path, description: "SIP Component ID", type: :string] + ], + request_body: {"Phone Number configuration", "application/json", ApiSpec.Dial.PhoneNumber}, + responses: [ + created: %Response{description: "Call started"}, + bad_request: ApiSpec.error("Invalid request structure"), + not_found: ApiSpec.error("Room doesn't exist"), + unauthorized: ApiSpec.error("Unauthorized") + ] + + operation :delete, + operation_id: "end_call", + summary: "Finish call made by SIP component", + parameters: [ + room_id: [in: :path, description: "Room ID", type: :string], + component_id: [in: :path, description: "SIP Component ID", type: :string] + ], + responses: [ + created: %Response{description: "Call ended"}, + bad_request: ApiSpec.error("Invalid request structure"), + not_found: ApiSpec.error("Room doesn't exist"), + unauthorized: ApiSpec.error("Unauthorized") + ] + + def create(conn, %{"room_id" => room_id, "component_id" => component_id} = params) do + with {:ok, phone_number} <- Map.fetch(params, "phoneNumber"), + {:ok, _room_pid} <- RoomService.find_room(room_id), + :ok <- Room.dial(room_id, component_id, phone_number) do + send_resp(conn, :created, "Successfully schedule calling phone_number: #{phone_number}") + else + :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"} + end + end + + def delete(conn, %{"room_id" => room_id, "component_id" => component_id}) do + with {:ok, _room_pid} <- RoomService.find_room(room_id), + :ok <- Room.end_call(room_id, component_id) do + conn + |> put_resp_content_type("application/json") + |> send_resp(:no_content, "") + else + :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"} + end + end +end diff --git a/lib/jellyfish_web/router.ex b/lib/jellyfish_web/router.ex index 537f4004..b171a5a0 100644 --- a/lib/jellyfish_web/router.ex +++ b/lib/jellyfish_web/router.ex @@ -27,6 +27,13 @@ defmodule JellyfishWeb.Router do post "/:room_id/subscribe", SubscriptionController, :create end + scope "/sip" do + resources("/:room_id/:component_id/call", SIPCallController, + only: [:create, :delete], + singleton: true + ) + end + scope "/recording" do delete "/:recording_id", RecordingController, :delete get "/", RecordingController, :show diff --git a/mix.exs b/mix.exs index 303161c6..510c7856 100644 --- a/mix.exs +++ b/mix.exs @@ -74,10 +74,12 @@ defmodule Jellyfish.MixProject do 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_rtsp, "~> 0.4.0"}, + {:membrane_rtc_engine_rtsp, + github: "jellyfish-dev/membrane_rtc_engine", sparse: "rtsp", override: true}, {:membrane_rtc_engine_file, github: "jellyfish-dev/membrane_rtc_engine", sparse: "file", override: true}, - {:membrane_ice_plugin, "~> 0.18.0"}, + {: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 7b562432..0a7e44a5 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.4.5", "ea06cb441af636baaf5232dced24c6b1ee5ccbe7a7cad8a348eb3100fa1d7b52", [: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", "bd4136100d3120740bf8eaa73ad74859d5ccd659cf0b27aa1645590a67a0172b"}, + "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"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, @@ -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.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, "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.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [: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", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"}, + "credo": {:hex, :credo, "1.7.4", "68ca5cf89071511c12fd9919eb84e388d231121988f6932756596195ccf7fd35", [: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", "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2"}, "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"}, @@ -29,6 +29,7 @@ "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.17.0", "17d06e1d44d891d20dbd437335eebe844e2426a0cd7e3a3e220b461127c73f70", [: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", "8d014a661bb6a437263d4b5abf0bcbd3cf0deb26b1e8596f2a271d22e48934c7"}, + "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"}, @@ -44,12 +45,15 @@ "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.0", "1b543aefd952283be1f2a215a1db213aa4d91222722ba03cd35280622f1905ee", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "352c90fd0a29942143c4bf7a727cc05c632e323f50a1a4e99321b1e8982f1533"}, + "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_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"}, + "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_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, - "membrane_h264_plugin": {:hex, :membrane_h264_plugin, "0.9.1", "ea140ab1ca21c528563675fdd7e14c80607e120e320dc930cac3dcfb4db3fc2b", [: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", "8f10db817e691fc1234ed85fe674b3f8718d3a410e4582736dcdd53664cae725"}, + "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_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"}, @@ -60,14 +64,17 @@ "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_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": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "164b5dc3fa2f56a5da696e5173794f37acce7959", [sparse: "engine"]}, - "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "765a7a7403de672ed3f389690ef6c3974160d3ba", [sparse: "file"]}, - "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "765a7a7403de672ed3f389690ef6c3974160d3ba", [sparse: "hls"]}, - "membrane_rtc_engine_rtsp": {:hex, :membrane_rtc_engine_rtsp, "0.4.0", "a4ed1d3aca0b8795c745d2e787e60caaa820f985fdf7ee74b144fdef4e273d4a", [: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.19.0", [hex: :membrane_rtc_engine, repo: "hexpm", optional: false]}, {:membrane_rtc_engine_webrtc, "~> 0.5.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.12.0", [hex: :membrane_udp_plugin, repo: "hexpm", optional: false]}], "hexpm", "5a74afeeeae126a704a6ae1299f7cdab79f72573ab229c66a7a1050c4e3aaf56"}, - "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "765a7a7403de672ed3f389690ef6c3974160d3ba", [sparse: "webrtc"]}, + "membrane_rtc_engine": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "07341b4d6c73f41c0101fcd27bb66f0c4abb84db", [sparse: "engine"]}, + "membrane_rtc_engine_file": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "07341b4d6c73f41c0101fcd27bb66f0c4abb84db", [sparse: "file"]}, + "membrane_rtc_engine_hls": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "07341b4d6c73f41c0101fcd27bb66f0c4abb84db", [sparse: "hls"]}, + "membrane_rtc_engine_rtsp": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "07341b4d6c73f41c0101fcd27bb66f0c4abb84db", [sparse: "rtsp"]}, + "membrane_rtc_engine_sip": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "07341b4d6c73f41c0101fcd27bb66f0c4abb84db", [sparse: "sip"]}, + "membrane_rtc_engine_webrtc": {:git, "https://github.com/jellyfish-dev/membrane_rtc_engine.git", "07341b4d6c73f41c0101fcd27bb66f0c4abb84db", [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_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"}, @@ -75,7 +82,7 @@ "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_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.12.0", "f3930a592f975f5aef924ff70b1072e55451de16ec5dce7dd264ecf9d034b9ad", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "65e846f7523e443215b6954136971d4f00a8e8f375ef015153daa535ce607769"}, + "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"}, @@ -86,28 +93,31 @@ "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_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"}, "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.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [: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.6", [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", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"}, + "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_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"}, "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.6.2", "753611b23b29231fb916b0cdd96028084b12aff57bfd7b71781bd04b1dbeb5c9", [: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", "951ed2433df22f4c97b85fdb145d4cee561f36b74854d64c06d896d7cd2921a7"}, + "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_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"}, "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.8", "2b754a3925ddbf4ad78c56f30208ced6aefe111a7ea07fb56c23dccc13eb87ae", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.9", [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]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7146e51d52593bb7f20d00b5308a5d7d17d663d6e85cd071452b613a8277100c"}, + "req": {:hex, :req, "0.4.9", "d079876e952f28b60fb0f595ae6492d1bc5b727b376746776014d541b9b56187", [:mix], [{:aws_signature, "~> 0.3.0", [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.0", [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", "c8c2bff52a336586b63ee9f047078637e2e91073bf948e312f84caade28b521b"}, "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_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"}, "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"}, diff --git a/openapi.yaml b/openapi.yaml index 4bbbdca0..4dfab307 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -65,6 +65,34 @@ components: title: ComponentOptionsRTSP type: object x-struct: Elixir.JellyfishWeb.ApiSpec.Component.RTSP.Options + ComponentOptionsSIP: + description: Options specific to the SIP component + properties: + registrarCredentials: + description: Credentials used to authorize in SIP Provider service + properties: + address: + description: 'SIP provider address. Can be in the form of FQDN (my-sip-registrar.net) or IPv4 (1.2.3.4). Port can be specified e.g: 5.6.7.8:9999. If not given, the default SIP port `5060` will be assumed' + example: example.org + type: string + password: + description: Password in SIP service provider + type: string + username: + description: Username in SIP service provider + type: string + required: + - address + - username + - password + title: SIPCredentials + type: object + x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP.SIPCredentials + required: + - registrarCredentials + title: ComponentOptionsSIP + type: object + x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP.Options ComponentPropertiesHLS: description: Properties specific to the HLS component properties: @@ -130,11 +158,13 @@ components: file: '#/components/schemas/ComponentFile' hls: '#/components/schemas/ComponentHLS' rtsp: '#/components/schemas/ComponentRTSP' + sip: '#/components/schemas/ComponentSIP' propertyName: type oneOf: - $ref: '#/components/schemas/ComponentHLS' - $ref: '#/components/schemas/ComponentRTSP' - $ref: '#/components/schemas/ComponentFile' + - $ref: '#/components/schemas/ComponentSIP' title: Component type: object x-struct: Elixir.JellyfishWeb.ApiSpec.Component @@ -158,6 +188,32 @@ components: title: HealthcheckResponse type: object x-struct: Elixir.JellyfishWeb.ApiSpec.HealthcheckResponse + ComponentSIP: + description: Describes the SIP component + properties: + id: + description: Assigned component ID + example: component-1 + type: string + properties: + $ref: '#/components/schemas/ComponentPropertiesSIP' + tracks: + description: List of all component's tracks + items: + $ref: '#/components/schemas/Track' + type: array + type: + description: Component type + example: sip + type: string + required: + - id + - type + - properties + - tracks + title: ComponentSIP + type: object + x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP ComponentHLS: description: Describes the HLS component properties: @@ -259,6 +315,15 @@ components: title: HlsMsn type: integer x-struct: Elixir.JellyfishWeb.ApiSpec.HLS.Params.HlsMsn + DialConfig: + description: Dial config + properties: + phoneNumber: + description: Phone number on which SIP Component will call + type: string + title: DialConfig + type: object + x-struct: Elixir.JellyfishWeb.ApiSpec.Dial.PhoneNumber ComponentRTSP: description: Describes the RTSP component properties: @@ -334,6 +399,26 @@ components: title: ComponentOptionsHLS type: object x-struct: Elixir.JellyfishWeb.ApiSpec.Component.HLS.Options + SIPCredentials: + description: Credentials used to authorize in SIP Provider service + properties: + address: + description: 'SIP provider address. Can be in the form of FQDN (my-sip-registrar.net) or IPv4 (1.2.3.4). Port can be specified e.g: 5.6.7.8:9999. If not given, the default SIP port `5060` will be assumed' + example: example.org + type: string + password: + description: Password in SIP service provider + type: string + username: + description: Username in SIP service provider + type: string + required: + - address + - username + - password + title: SIPCredentials + type: object + x-struct: Elixir.JellyfishWeb.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: @@ -375,6 +460,7 @@ components: - $ref: '#/components/schemas/ComponentOptionsHLS' - $ref: '#/components/schemas/ComponentOptionsRTSP' - $ref: '#/components/schemas/ComponentOptionsFile' + - $ref: '#/components/schemas/ComponentOptionsSIP' title: ComponentOptions type: object x-struct: Elixir.JellyfishWeb.ApiSpec.Component.Options @@ -512,6 +598,34 @@ components: title: RoomsListingResponse type: object x-struct: Elixir.JellyfishWeb.ApiSpec.RoomsListingResponse + ComponentPropertiesSIP: + description: Properties specific to the SIP component + properties: + registrarCredentials: + description: Credentials used to authorize in SIP Provider service + properties: + address: + description: 'SIP provider address. Can be in the form of FQDN (my-sip-registrar.net) or IPv4 (1.2.3.4). Port can be specified e.g: 5.6.7.8:9999. If not given, the default SIP port `5060` will be assumed' + example: example.org + type: string + password: + description: Password in SIP service provider + type: string + username: + description: Username in SIP service provider + type: string + required: + - address + - username + - password + title: SIPCredentials + type: object + x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP.SIPCredentials + required: + - registrarCredentials + title: ComponentPropertiesSIP + type: object + x-struct: Elixir.JellyfishWeb.ApiSpec.Component.SIP.Properties PeerDetailsResponse: description: Response containing peer details and their token properties: @@ -1161,6 +1275,94 @@ paths: summary: Delete peer tags: - room + /sip/{room_id}/{component_id}/call: + delete: + callbacks: {} + operationId: end_call + parameters: + - description: Room ID + in: path + name: room_id + required: true + schema: + type: string + - description: SIP Component ID + in: path + name: component_id + required: true + schema: + type: string + responses: + '201': + description: Call ended + '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 + summary: Finish call made by SIP component + tags: + - sip + post: + callbacks: {} + operationId: dial + parameters: + - description: Room ID + in: path + name: room_id + required: true + schema: + type: string + - description: SIP Component ID + in: path + name: component_id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DialConfig' + description: Phone Number configuration + required: false + responses: + '201': + description: Call started + '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 + summary: Make a call from the SIP component to the provided phone number + tags: + - sip security: - authorization: [] servers: [] diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs index 4e51f6b4..747e1ab0 100644 --- a/test/jellyfish/config_reader_test.exs +++ b/test/jellyfish/config_reader_test.exs @@ -128,6 +128,21 @@ 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] + + System.put_env("JF_SIP_USED", "true") + assert_raise RuntimeError, fn -> ConfigReader.read_sip_config() end + + System.put_env("JF_SIP_IP", "abcdefg") + assert_raise RuntimeError, fn -> ConfigReader.read_sip_config() 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"] + end + end + test "read_dist_config/0 NODES_LIST" do with_env [ "JF_DIST_ENABLED", diff --git a/test/jellyfish_web/controllers/component/hls_component_test.exs b/test/jellyfish_web/controllers/component/hls_component_test.exs index 7a54bb21..3c0e2357 100644 --- a/test/jellyfish_web/controllers/component/hls_component_test.exs +++ b/test/jellyfish_web/controllers/component/hls_component_test.exs @@ -64,7 +64,10 @@ defmodule JellyfishWeb.Component.HlsComponentTest do assert_hls_path(room_id, persistent: true) # It is persistent stream so we have to remove it manually - assert {:ok, _removed_files} = room_id |> HLS.Recording.directory() |> File.rm_rf() + assert {:ok, _removed_files} = + room_id + |> HLS.Recording.directory() + |> File.rm_rf() end setup :set_mox_from_context diff --git a/test/jellyfish_web/controllers/component/sip_component_test.exs b/test/jellyfish_web/controllers/component/sip_component_test.exs new file mode 100644 index 00000000..aafe7ca6 --- /dev/null +++ b/test/jellyfish_web/controllers/component/sip_component_test.exs @@ -0,0 +1,54 @@ +defmodule JellyfishWeb.Component.SIPComponentTest do + use JellyfishWeb.ConnCase + use JellyfishWeb.ComponentCase + + @sip_credentials %{ + address: "my-sip-registrar.net", + username: "user-name", + password: "pass-word" + } + + @sip_default_properties %{ + registrarCredentials: map_keys_to_string(@sip_credentials) + } + |> map_keys_to_string() + + setup_all 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) + end + + describe "create SIP component" do + test "renders component with required options", %{conn: conn, room_id: room_id} do + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "sip", + options: %{registrarCredentials: @sip_credentials} + ) + + assert %{ + "data" => %{ + "id" => id, + "type" => "sip", + "properties" => @sip_default_properties + } + } = + model_response(conn, :created, "ComponentDetailsResponse") + + assert_component_created(conn, room_id, id, "sip") + 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: "sip") + + assert model_response(conn, :bad_request, "Error")["errors"] == + "Required field \"registrarCredentials\" missing" + end + end +end diff --git a/test/jellyfish_web/controllers/dial_controller_test.exs b/test/jellyfish_web/controllers/dial_controller_test.exs new file mode 100644 index 00000000..1eeed858 --- /dev/null +++ b/test/jellyfish_web/controllers/dial_controller_test.exs @@ -0,0 +1,119 @@ +defmodule JellyfishWeb.DialControllerTest do + use JellyfishWeb.ConnCase + + @source_uri "rtsp://placeholder-19inrifjbsjb.it:12345/afwefae" + + @sip_registrar_credentials %{ + address: "my-sip-registrar.net", + username: "user-name", + password: "pass-word" + } + + setup_all 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) + 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) + conn = put_req_header(conn, "accept", "application/json") + + conn = post(conn, ~p"/room", videoCodec: "h264") + assert %{"id" => id} = json_response(conn, :created)["data"]["room"] + + on_exit(fn -> + conn = delete(conn, ~p"/room/#{id}") + assert response(conn, :no_content) + end) + + {:ok, %{conn: conn, room_id: id}} + end + + 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" + end + + test "returns error when sip component doesn't exist", %{conn: conn, room_id: room_id} do + conn = post(conn, ~p"/sip/#{room_id}/invalid_component_id/call", phoneNumber: "+123456") + + assert json_response(conn, :bad_request)["errors"] == + "Component invalid_component_id does not exist" + end + + test "returns error when sip component doesn't exist with this id", %{ + conn: conn, + room_id: room_id + } do + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "rtsp", + options: %{sourceUri: @source_uri} + ) + + assert %{ + "data" => %{ + "id" => component_id, + "type" => "rtsp" + } + } = + json_response(conn, :created) + + conn = post(conn, ~p"/sip/#{room_id}/#{component_id}/call", phoneNumber: "+123456") + + assert json_response(conn, :bad_request)["errors"] == + "Component #{component_id} is not a SIP component" + end + + test "return success for proper dial", %{conn: conn, room_id: room_id} do + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "sip", + options: %{registrarCredentials: @sip_registrar_credentials} + ) + + assert %{ + "data" => %{ + "id" => component_id, + "type" => "sip" + } + } = + json_response(conn, :created) + + conn = post(conn, ~p"/sip/#{room_id}/#{component_id}/call", phoneNumber: "+123456") + + assert response(conn, :created) == + "Successfully schedule calling phone_number: +123456" + end + + test "return success for proper end_call", %{conn: conn, room_id: room_id} do + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "sip", + options: %{registrarCredentials: @sip_registrar_credentials} + ) + + assert %{ + "data" => %{ + "id" => component_id, + "type" => "sip" + } + } = + json_response(conn, :created) + + conn = post(conn, ~p"/sip/#{room_id}/#{component_id}/call", phoneNumber: "+123456") + + assert response(conn, :created) == + "Successfully schedule calling phone_number: +123456" + + conn = delete(conn, ~p"/sip/#{room_id}/#{component_id}/call") + + assert response(conn, :no_content) + end + end +end diff --git a/test/jellyfish_web/integration/server_notification_test.exs b/test/jellyfish_web/integration/server_notification_test.exs index 14a00582..36461cf3 100644 --- a/test/jellyfish_web/integration/server_notification_test.exs +++ b/test/jellyfish_web/integration/server_notification_test.exs @@ -15,6 +15,7 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do alias Jellyfish.ServerMessage.{ Authenticated, + ComponentCrashed, HlsPlayable, HlsUploadCrashed, HlsUploaded, @@ -56,6 +57,12 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do bucket: "bucket" } + @asterisk_credentials %{ + address: "127.0.0.1:5061", + username: "mymediaserver0", + password: "yourpassword" + } + Application.put_env( :jellyfish, Endpoint, @@ -81,6 +88,12 @@ 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") + + on_exit(fn -> + Application.put_env(:jellyfish, :sip_config, sip_used?: false, sip_external_ip: nil) + end) + assert {:ok, _pid} = Endpoint.start_link() webserver = {Plug.Cowboy, plug: WebHookPlug, scheme: :http, options: [port: @webhook_port]} @@ -354,6 +367,25 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do end end + @tag :asterisk + test "dial asterisk from rtc_engine", %{conn: conn} do + {room_id, _peer_id, conn, _peer_ws} = subscribe_on_notifications_and_connect_peer(conn) + + {conn, component_id} = add_sip_component(conn, room_id) + + conn = post(conn, ~p"/sip/#{room_id}/#{component_id}/call", phoneNumber: "1230") + + assert response(conn, :created) == + "Successfully schedule calling phone_number: 1230" + + refute_receive %ComponentCrashed{component_id: ^component_id}, 2_500 + refute_received {:webhook_notification, %ComponentCrashed{component_id: ^component_id}} + + conn = delete(conn, ~p"/sip/#{room_id}/#{component_id}/call") + + assert response(conn, :no_content) + end + test "sends metrics", %{conn: conn} do server_api_token = Application.fetch_env!(:jellyfish, :server_api_token) ws = create_and_authenticate() @@ -458,6 +490,23 @@ defmodule JellyfishWeb.Integration.ServerNotificationTest do {conn, id} end + defp add_sip_component(conn, room_id) do + conn = + post(conn, ~p"/room/#{room_id}/component", + type: "sip", + options: %{registrarCredentials: @asterisk_credentials} + ) + + assert %{ + "data" => %{ + "id" => component_id, + "type" => "sip" + } + } = json_response(conn, :created) + + {conn, component_id} + 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) diff --git a/test/test_helper.exs b/test/test_helper.exs index 7bd33e19..4818be81 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -2,4 +2,6 @@ Application.put_env(:jellyfish, :media_files_path, "tmp/jellyfish_resources/") Mox.defmock(ExAws.Request.HttpMock, for: ExAws.Request.HttpClient) +ExUnit.configure(exclude: [:asterisk]) + ExUnit.start(capture_log: true)