defmodule Membrane.WebRTC.Extension do @moduledoc """ A module that provides mappings between `ExSDP.Attribute.Extmap` and modules implementing `Membrane.WebRTC.Extension` behaviour. """ alias ExSDP.Attribute.Extmap alias ExSDP.Media alias Membrane.{RTP, WebRTC} @enforce_keys [:module, :uri, :name] defstruct @enforce_keys ++ [rtp_opts: Keyword.new()] @typedoc """ * module - extension specific module which implement `Membrane.WebRTC.Extension` behaviour * rtp_opts - options for RTP module. This allows configuring RTP module to your needs * uri - a URI that identifies extension in SDP * name - an atom identifying the extension in `Membrane.RTP.SessionBin` """ @type t :: %__MODULE__{ module: module(), rtp_opts: Keyword.t(), uri: String.t(), name: atom() } @type maybe_t :: t() | :not_supported @doc """ Creates a `#{inspect(__MODULE__)}` struct for a specific extension. """ @callback new(opts :: Keyword.t()) :: t() @doc """ Returns a boolean indicating whether an extension is compatible with given encoding. """ @callback compatible?(WebRTC.Track.encoding_key()) :: boolean() @doc """ Returns a module that implements the extension in `Membrane.RTP.SessionBin` or `:no_rtp_module` if such such module would only forward buffers to next element. `:inbound`/`:outbound` denotes track type this extension was specified for. """ @callback get_rtp_module(Extmap.extension_id(), Keyword.t(), :inbound | :outbound) :: Membrane.ChildrenSpec.child_definition() | :no_rtp_module @doc """ Adds information about extension to an SDP media. """ @callback add_to_media( Media.t(), Extmap.extension_id(), Extmap.direction(), [RTP.payload_type_t()] ) :: Media.t() @doc """ Returns an URI that identifies extension in SDP """ @callback uri() :: String.t() @doc """ Given a list of supported extensions, checks if there is an extension that corresponds to given `Extmap` and encoding. """ @spec supported?([t()], Extmap.t(), atom()) :: boolean() def supported?(extensions, %Extmap{uri: uri}, encoding), do: Enum.any?(extensions, &(&1.uri == uri and &1.module.compatible?(encoding))) @doc """ Given a list of supported extensions, returns an extension that corresponds to given `Extmap` or `:not_supported` if there is no such extension. """ @spec from_extmap([t()], Extmap.t()) :: maybe_t() def from_extmap(extensions, %Extmap{uri: uri}), do: Enum.find(extensions, :not_supported, &(&1.uri == uri)) @doc """ Given an SDP media, a list of supported extensions and supported `Extmap`s, adds corresponding extensions to the media. """ @spec add_to_media(Media.t(), [t()], [Extmap.t()], Extmap.direction(), [RTP.payload_type_t()]) :: Media.t() def add_to_media(media, _extensions, [], _direction, _pt), do: media def add_to_media(media, extensions, [extmap | rest], direction, payload_types) do extension = from_extmap(extensions, extmap) media |> extension.module.add_to_media(extmap.id, direction, payload_types) |> add_to_media(extensions, rest, direction, payload_types) end @doc """ Given a list of supported extensions, maps a supported `Extmap` to an `RTP.SessionBin.rtp_extension_t()`. `:inbound`/`:outbound` denotes track type `extmap` was specified for. """ @spec as_rtp_extension([t()], Extmap.t(), :inbound | :outbound) :: RTP.SessionBin.rtp_extension_option_t() def as_rtp_extension(extensions, extmap, track_type) do extension = from_extmap(extensions, extmap) {extension.name, extension.module.get_rtp_module(extmap.id, extension.rtp_opts, track_type)} end @doc """ Given a list of supported extensions and a supported `Extmap`, generates a mapping from a name provided by an extension to an ID provided by the `Extmap`. """ @spec as_rtp_mapping([t()], Extmap.t()) :: {RTP.SessionBin.rtp_extension_name_t(), Extmap.extension_id()} def as_rtp_mapping(extensions, extmap) do extension = from_extmap(extensions, extmap) {extension.name, extmap.id} end end