From f21de60c9cd108001e9dd1af5ed2329dcf8ac663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wala?= Date: Tue, 7 May 2024 13:52:21 +0200 Subject: [PATCH 1/5] PeerConnection: improve docs, add missing getters --- lib/ex_webrtc/peer_connection.ex | 378 +++++++++++++++++++++++-------- 1 file changed, 284 insertions(+), 94 deletions(-) diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index 32cb6bd8..5fd41034 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -25,55 +25,65 @@ defmodule ExWebRTC.PeerConnection do } @twcc_interval 100 + @twcc_uri "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" @type peer_connection() :: GenServer.server() - @type offer_options() :: [ice_restart: boolean()] - @type answer_options() :: [] + @typedoc """ + Possible connection states. - @type transceiver_options() :: [direction: RTPTransceiver.direction()] + For the exact meaning, refer to the [RTCPeerConnection: connectionState property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState). + """ + @type connection_state() :: :new | :connecting | :connected | :disconnected | :failed | :closed @typedoc """ - Messages sent by the ExWebRTC. + Possible ICE gathering states. + + For the exact meaning, refer to the [RTCPeerConnection: iceGatheringState property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceGatheringState). """ - @type signal() :: - {:ex_webrtc, pid(), - :negotiation_needed - | {:ice_candidate, ICECandidate.t()} - | {:ice_gathering_state_change, gathering_state()} - | {:signaling_state_change, signaling_state()} - | {:connection_state_change, connection_state()} - | {:track, MediaStreamTrack.t()} - | {:track_muted, MediaStreamTrack.id()} - | {:track_ended, MediaStreamTrack.id()} - | {:rtp, MediaStreamTrack.id(), ExRTP.Packet.t()}} + @type ice_gathering_state() :: :new | :gathering | :complete @typedoc """ - Possible ICE gathering states. + Possible ICE connection states. - For the exact meaning, refer to the [WebRTC W3C, section 4.3.2](https://www.w3.org/TR/webrtc/#rtcicegatheringstate-enum) + For the exact meaning, refer to the [RTCPeerConnection: iceConnectionState property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState). """ - @type gathering_state() :: :new | :gathering | :complete + @type ice_connection_state() :: + :new | :checking | :connected | :completed | :failed | :disconnected | :closed @typedoc """ - Possible PeerConnection signaling states. + Possible signaling states. - For the exact meaning, refer to the [WebRTC W3C, section 4.3.1](https://www.w3.org/TR/webrtc/#dom-rtcsignalingstate) + For the exact meaning, refer to the [RTCPeerConnection: signalingState property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/signalingState). """ @type signaling_state() :: :stable | :have_local_offer | :have_remote_offer @typedoc """ - Possible PeerConnection states. + Messages sent by the the `ExWebRTC.PeerConnection` process to its controlling process. - For the exact meaning, refer to the [WebRTC W3C, section 4.3.3](https://www.w3.org/TR/webrtc/#rtcpeerconnectionstate-enum) + Most of the messages match the [RTCPeerConnection events](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection#events), + except for: + * `:track_muted`, `:track_ended` - these match the [MediaStreamTrack events](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack#events). + * `:rtp` and `:rtcp` - these contain packets received by the PeerConnection. """ - @type connection_state() :: :closed | :failed | :disconnected | :new | :connecting | :connected + @type message() :: + {:ex_webrtc, pid(), + {:connection_state_change, connection_state()} + | {:ice_candidate, ICECandidate.t()} + | {:ice_connection_state_change, ice_connection_state()} + | {:ice_gathering_state_change, ice_gathering_state()} + | :negotiation_needed + | {:signaling_state_change, signaling_state()} + | {:track, MediaStreamTrack.t()} + | {:track_muted, MediaStreamTrack.id()} + | {:track_ended, MediaStreamTrack.id()} + | {:rtp, MediaStreamTrack.id(), ExRTP.Packet.t()}} + | {:rtcp, [ExRTCP.Packet.packet()]} - @twcc_uri "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" + #### NON-MDN-API #### - #### API #### @doc """ - Returns a list of all running peer connections. + Returns a list of all running `ExWebRTC.PeerConnection` processes. """ @spec get_all_running() :: [pid()] def get_all_running() do @@ -81,6 +91,25 @@ defmodule ExWebRTC.PeerConnection do |> Enum.filter(fn pid -> Process.alive?(pid) end) end + @doc """ + Starts a new `ExWebRTC.PeerConnection` process. + + `ExWebRTC.PeerConnection` is a `GenServer` under the hood, thus this function allows for + passing the generic `t:GenServer.options/0` as an argument. + """ + @spec start(Configuration.options(), GenServer.options()) :: GenServer.on_start() + def start(pc_opts \\ [], gen_server_opts \\ []) do + {controlling_process, pc_opts} = Keyword.pop(pc_opts, :controlling_process) + controlling_process = controlling_process || self() + configuration = Configuration.from_options!(pc_opts) + GenServer.start(__MODULE__, {controlling_process, configuration}, gen_server_opts) + end + + @doc """ + Starts a new `ExWebRTC.PeerConnection` process. + + Works identically to `start/2`, but links to the calling process. + """ @spec start_link(Configuration.options(), GenServer.options()) :: GenServer.on_start() def start_link(pc_opts \\ [], gen_server_opts \\ []) do {controlling_process, pc_opts} = Keyword.pop(pc_opts, :controlling_process) @@ -89,147 +118,292 @@ defmodule ExWebRTC.PeerConnection do GenServer.start_link(__MODULE__, {controlling_process, configuration}, gen_server_opts) end - @spec start(Configuration.options()) :: GenServer.on_start() - def start(options \\ []) do - configuration = Configuration.from_options!(options) - GenServer.start(__MODULE__, {self(), configuration}) - end + @doc """ + Changes the controlling process of this `peer_connection` process. + Controlling process is a process that receives all of the messages (descirbed + by `t:message/0`) from this PeerConnection. + """ @spec controlling_process(peer_connection(), Process.dest()) :: :ok def controlling_process(peer_connection, controlling_process) do GenServer.call(peer_connection, {:controlling_process, controlling_process}) end - @spec create_offer(peer_connection(), offer_options()) :: - {:ok, SessionDescription.t()} | {:error, :invalid_state} - def create_offer(peer_connection, options \\ []) do - GenServer.call(peer_connection, {:create_offer, options}) + @doc """ + Sends an RTP packet to the remote peer using the track specified by the `track_id`. + + Options: + * `rtx?` - send the packet as if it was retransmited (use SSRC and payload type specific to RTX) + """ + @spec send_rtp( + peer_connection(), + MediaStreamTrack.id(), + ExRTP.Packet.t(), + rtx?: boolean() + ) :: :ok + def send_rtp(peer_connection, track_id, packet, opts \\ []) do + GenServer.cast(peer_connection, {:send_rtp, track_id, packet, opts}) + end + + @doc """ + Sends an RTCP PLI feedback to the remote peer using the track specified by the `track_id`. + """ + @spec send_pli(peer_connection(), MediaStreamTrack.id()) :: :ok + def send_pli(peer_connection, track_id) do + GenServer.cast(peer_connection, {:send_pli, track_id}) end - @spec create_answer(peer_connection(), answer_options()) :: - {:ok, SessionDescription.t()} | {:error, :invalid_state} - def create_answer(peer_connection, options \\ []) do - GenServer.call(peer_connection, {:create_answer, options}) + #### MDN-API #### + + @doc """ + Returns the connection state. + + For more information, refer to the [RTCPeerConnection: connectionState property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState). + """ + @spec get_connection_state(peer_connection()) :: connection_state() + def get_connection_state(peer_connection) do + GenServer.call(peer_connection, :get_connection_state) end - @spec set_local_description(peer_connection(), SessionDescription.t()) :: - :ok | {:error, atom()} - def set_local_description(peer_connection, description) do - GenServer.call(peer_connection, {:set_local_description, description}) + @doc """ + Returns the ICE connection state. + + For more information, refer to the [RTCPeerConnection: iceConnectionState property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState). + """ + @spec get_ice_connection_state(peer_connection()) :: ice_connection_state() + def get_ice_connection_state(peer_connection) do + GenServer.call(peer_connection, :get_ice_connection_state) end + @doc """ + Returns the ICE gathering state. + + For more information, refer to the [RTCPeerConnection: iceGatheringState property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceGatheringState). + """ + @spec get_ice_gathering_state(peer_connection()) :: ice_gathering_state() + def get_ice_gathering_state(peer_connection) do + GenServer.call(peer_connection, :get_ice_gathering_state) + end + + @doc """ + Returns the signaling state. + + For more information, refer to the [RTCPeerConnection: signalingState property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/signalingState). + """ + @spec get_signaling_state(peer_connection()) :: signaling_state() + def get_signaling_state(peer_connection) do + GenServer.call(peer_connection, :get_signaling_state) + end + + @doc """ + Returns the local description. + + For more information, refer to the [RTCPeerConnection: localDescription property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/localDescription). + """ @spec get_local_description(peer_connection()) :: SessionDescription.t() | nil def get_local_description(peer_connection) do GenServer.call(peer_connection, :get_local_description) end + @doc """ + Returns the remote description. + + For more information, refer to the [RTCPeerConnection: remoteDescription property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/remoteDescription). + """ + @spec get_remote_description(peer_connection()) :: SessionDescription.t() | nil + def get_remote_description(peer_connection) do + GenServer.call(peer_connection, :get_remote_description) + end + + @doc """ + Returns the pending local description. + + For more information, refer to the [RTCPeerConnection: pendingLocalDescription property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/pendingLocalDescription). + """ + @spec get_pending_local_description(peer_connection()) :: SessionDescription.t() | nil + def get_pending_local_description(peer_connection) do + GenServer.call(peer_connection, :get_pending_local_description) + end + + @doc """ + Returns the pending remote description. + + For more information, refer to the [RTCPeerConnection: pendingRemoteDescription property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/pendingRemoteDescription). + """ + @spec get_pending_remote_description(peer_connection()) :: SessionDescription.t() | nil + def get_pending_remote_description(peer_connection) do + GenServer.call(peer_connection, :get_pending_remote_description) + end + + @doc """ + Returns the current local description. + + For more information, refer to the [RTCPeerConnection: currentLocalDescription property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/currentLocalDescription). + """ @spec get_current_local_description(peer_connection()) :: SessionDescription.t() | nil def get_current_local_description(peer_connection) do GenServer.call(peer_connection, :get_current_local_description) end + @doc """ + Returns the current remote description. + + For more information, refer to the [RTCPeerConnection: currentRemoteDescription property](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/currentRemoteDescription). + """ + @spec get_current_remote_description(peer_connection()) :: SessionDescription.t() | nil + def get_current_remote_description(peer_connection) do + GenServer.call(peer_connection, :get_current_remote_description) + end + + @doc """ + Returns the list of transceivers. + + For more information, refer to the [RTCPeerConnection: getTransceivers() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/getTransceivers). + """ + @spec get_transceivers(peer_connection()) :: [RTPTransceiver.t()] + def get_transceivers(peer_connection) do + GenServer.call(peer_connection, :get_transceivers) + end + + @doc """ + Returns PeerConnection's statistics. + + For more information, refer to the [RTCPeerConnection: getStats() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/getStats). + See [RTCStatsReport](https://www.w3.org/TR/webrtc/#rtcstatsreport-object) for the output structure. + """ + @spec get_stats(peer_connection()) :: %{(atom() | integer()) => map()} + def get_stats(peer_connection) do + GenServer.call(peer_connection, :get_stats) + end + + @doc """ + Sets the local description. + + For more information, refer to the [RTCPeerConnection: setLocalDescription() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setLocalDescription). + """ + @spec set_local_description(peer_connection(), SessionDescription.t()) :: :ok | {:error, term()} + def set_local_description(peer_connection, description) do + GenServer.call(peer_connection, {:set_local_description, description}) + end + + @doc """ + Sets the remote description. + + For more information, refer to the [RTCPeerConnection: setRemoteDescription() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setRemoteDescription). + """ @spec set_remote_description(peer_connection(), SessionDescription.t()) :: - :ok | {:error, atom()} + :ok | {:error, term()} def set_remote_description(peer_connection, description) do GenServer.call(peer_connection, {:set_remote_description, description}) end - @spec get_remote_description(peer_connection()) :: SessionDescription.t() | nil - def get_remote_description(peer_connection) do - GenServer.call(peer_connection, :get_remote_description) + @doc """ + Creates an SDP offer. + + For more information, refer to the [RTCPeerConnection: createOffer() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer). + """ + @spec create_offer(peer_connection(), restart_ice?: boolean()) :: + {:ok, SessionDescription.t()} | {:error, term()} + def create_offer(peer_connection, options \\ []) do + GenServer.call(peer_connection, {:create_offer, options}) end - @spec get_current_remote_description(peer_connection()) :: SessionDescription.t() | nil - def get_current_remote_description(peer_connection) do - GenServer.call(peer_connection, :get_current_remote_description) + @doc """ + Creates an SDP answer. + + For more information, refer to the [RTCPeerConnection: createAnswer() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createAnswer). + """ + @spec create_answer(peer_connection()) :: {:ok, SessionDescription.t()} | {:error, term()} + def create_answer(peer_connection) do + GenServer.call(peer_connection, :create_answer) end - @spec add_ice_candidate(peer_connection(), ICECandidate.t()) :: - :ok | {:error, :no_remote_description} + @doc """ + Adds a new remote ICE candidate. + + For more information, refer to the [RTCPeerConnection: addIceCandidate() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addIceCandidate). + """ + @spec add_ice_candidate(peer_connection(), ICECandidate.t()) :: :ok | {:error, term()} def add_ice_candidate(peer_connection, candidate) do GenServer.call(peer_connection, {:add_ice_candidate, candidate}) end - @spec get_transceivers(peer_connection()) :: [RTPTransceiver.t()] - def get_transceivers(peer_connection) do - GenServer.call(peer_connection, :get_transceivers) - end + @doc """ + Adds a new transceiver to this PeerConnection. + For more information, refer to the [RTCPeerConnection: addTransceiver() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addTransceiver). + """ @spec add_transceiver( peer_connection(), RTPTransceiver.kind() | MediaStreamTrack.t(), - transceiver_options() + direction: RTPTransceiver.direction() ) :: {:ok, RTPTransceiver.t()} def add_transceiver(peer_connection, kind_or_track, options \\ []) do GenServer.call(peer_connection, {:add_transceiver, kind_or_track, options}) end + @doc """ + Sets the direction of transceiver specified by the `id`. + + For more information, refer to the [RTCRtpTransceiver: direction property](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/direction). + """ @spec set_transceiver_direction( peer_connection(), RTPTransceiver.id(), RTPTransceiver.direction() - ) :: :ok | {:error, :invalid_transceiver_id} + ) :: :ok | {:error, term()} def set_transceiver_direction(peer_connection, transceiver_id, direction) when direction in [:sendrecv, :sendonly, :recvonly, :inactive] do GenServer.call(peer_connection, {:set_transceiver_direction, transceiver_id, direction}) end - @spec stop_transceiver(peer_connection(), RTPTransceiver.id()) :: - :ok | {:error, :invalid_transceiver_id} + @doc """ + Stops the transceiver specified by the `id`. + + For more information, refer to the [RTCRtpTransceiver: stop() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/stop). + """ + @spec stop_transceiver(peer_connection(), RTPTransceiver.id()) :: :ok | {:error, term()} def stop_transceiver(peer_connection, transceiver_id) do GenServer.call(peer_connection, {:stop_transceiver, transceiver_id}) end + @doc """ + Adds a new track. + + For more information, refer to the [RTCPeerConnection: addTrack() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addTrack). + """ @spec add_track(peer_connection(), MediaStreamTrack.t()) :: {:ok, RTPSender.t()} def add_track(peer_connection, track) do GenServer.call(peer_connection, {:add_track, track}) end + @doc """ + Replaces the track assigned to the sender specified by the `sender_id`. + + For more information, refer to the [RTCRtpSender: replaceTrack() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/replaceTrack). + """ @spec replace_track(peer_connection(), RTPSender.id(), MediaStreamTrack.t()) :: - :ok | {:error, :invalid_sender_id | :invalid_track_type} + :ok | {:error, term()} def replace_track(peer_connection, sender_id, track) do GenServer.call(peer_connection, {:replace_track, sender_id, track}) end - @spec remove_track(peer_connection(), RTPSender.id()) :: :ok | {:error, :invalid_sender_id} - def remove_track(peer_connection, sender_id) do - GenServer.call(peer_connection, {:remove_track, sender_id}) - end - @doc """ - Send an RTP packet to the remote peer using specified track or its id. - - Options: - * `rtx?` - send the packet as if it was retransmited (use SSRC and payload type specific to RTX) - """ - @spec send_rtp( - peer_connection(), - MediaStreamTrack.id(), - ExRTP.Packet.t(), - rtx?: boolean() - ) :: :ok - def send_rtp(peer_connection, track_id, packet, opts \\ []) do - GenServer.cast(peer_connection, {:send_rtp, track_id, packet, opts}) - end + Removes the track assigned to the sender specified by the `sender_id`. - @doc """ - Send an RTCP PLI feedback to the remote peer using specified track. + For more information, refer to the [RTCPeerConnection: removeTrack() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/removeTrack). """ - @spec send_pli(peer_connection(), MediaStreamTrack.id()) :: :ok - def send_pli(peer_connection, track_id) do - GenServer.cast(peer_connection, {:send_pli, track_id}) + @spec remove_track(peer_connection(), RTPSender.id()) :: :ok | {:error, term()} + def remove_track(peer_connection, sender_id) do + GenServer.call(peer_connection, {:remove_track, sender_id}) end @doc """ - Returns peer connection's statistics. + Closes the PeerConnection. - See [RTCStatsReport](https://www.w3.org/TR/webrtc/#rtcstatsreport-object) for the output structure. + This function kills the PeerConnection process. + For more information, refer to the [RTCPeerConnection: close() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/close). """ - @spec get_stats(peer_connection()) :: %{(atom() | integer()) => map()} - def get_stats(peer_connection) do - GenServer.call(peer_connection, :get_stats) - end - @spec close(peer_connection()) :: :ok def close(peer_connection) do GenServer.stop(peer_connection) @@ -345,13 +519,13 @@ defmodule ExWebRTC.PeerConnection do end @impl true - def handle_call({:create_answer, _options}, _from, %{signaling_state: ss} = state) + def handle_call(:create_answer, _from, %{signaling_state: ss} = state) when ss not in [:have_remote_offer, :have_local_pranswer] do {:reply, {:error, :invalid_state}, state} end @impl true - def handle_call({:create_answer, _options}, _from, state) do + def handle_call(:create_answer, _from, state) do {:offer, remote_offer} = state.pending_remote_desc {:ok, ice_ufrag, ice_pwd} = @@ -415,6 +589,13 @@ defmodule ExWebRTC.PeerConnection do {:reply, desc, state} end + @impl true + def handle_call(:get_pending_local_description, _from, state) do + candidates = state.ice_transport.get_local_candidates(state.ice_pid) + desc = do_get_description(state.current_pending_desc, candidates) + {:reply, desc, state} + end + @impl true def handle_call(:get_current_local_description, _from, state) do candidates = state.ice_transport.get_local_candidates(state.ice_pid) @@ -438,6 +619,13 @@ defmodule ExWebRTC.PeerConnection do {:reply, desc, state} end + @impl true + def handle_call(:get_pending_remote_description, _from, state) do + candidates = state.ice_transport.get_local_candidates(state.ice_pid) + desc = do_get_description(state.current_remote_desc, candidates) + {:reply, desc, state} + end + @impl true def handle_call(:get_current_remote_description, _from, state) do candidates = state.ice_transport.get_remote_candidates(state.ice_pid) @@ -853,6 +1041,8 @@ defmodule ExWebRTC.PeerConnection do :ok = DTLSTransport.set_ice_connected(state.dtls_transport) end + notify(state.owner, {:ice_connection_state_change, new_ice_state}) + if next_conn_state == :failed do Logger.debug("Stopping PeerConnection") {:stop, {:shutdown, :conn_state_failed}, state} From 15875fb881f12f4b8dfe4ff7fa233fc4df52386a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wala?= Date: Wed, 8 May 2024 11:24:19 +0200 Subject: [PATCH 2/5] Refactor the public modules, use internal maps for transceivers, senders and receivers --- lib/ex_webrtc/peer_connection.ex | 81 ++++--- lib/ex_webrtc/rtp_receiver.ex | 72 ++++-- lib/ex_webrtc/rtp_receiver/nack_generator.ex | 5 +- lib/ex_webrtc/rtp_receiver/report_recorder.ex | 17 +- lib/ex_webrtc/rtp_sender.ex | 65 +++--- lib/ex_webrtc/rtp_sender/nack_responder.ex | 4 +- lib/ex_webrtc/rtp_sender/report_recorder.ex | 17 +- lib/ex_webrtc/rtp_transceiver.ex | 216 ++++++++++++------ 8 files changed, 291 insertions(+), 186 deletions(-) diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index 5fd41034..c70efc89 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -465,6 +465,26 @@ defmodule ExWebRTC.PeerConnection do {:reply, :ok, state} end + @impl true + def handle_call(:get_connection_state, _from, state) do + {:reply, state.conn_state, state} + end + + @impl true + def handle_call(:get_ice_connection_state, _from, state) do + {:reply, state.ice_state, state} + end + + @impl true + def handle_call(:get_ice_gathering_state, _from, state) do + {:reply, state.ice_gathering_state, state} + end + + @impl true + def handle_call(:get_signaling_state, _from, state) do + {:reply, state.signaling_state, state} + end + @impl true def handle_call({:create_offer, _options}, _from, %{signaling_state: ss} = state) when ss not in [:stable, :have_local_offer] do @@ -658,7 +678,8 @@ defmodule ExWebRTC.PeerConnection do @impl true def handle_call(:get_transceivers, _from, state) do - {:reply, state.transceivers, state} + transceivers = Enum.map(state.transceivers, &RTPTransceiver.to_struct/1) + {:reply, transceivers, state} end @impl true @@ -667,12 +688,12 @@ defmodule ExWebRTC.PeerConnection do {ssrc, rtx_ssrc} = generate_ssrcs(state) options = [{:ssrc, ssrc}, {:rtx_ssrc, rtx_ssrc} | options] - transceiver = RTPTransceiver.new(kind, nil, state.config, options) - state = %{state | transceivers: state.transceivers ++ [transceiver]} + tr = RTPTransceiver.new(kind, nil, state.config, options) + state = %{state | transceivers: state.transceivers ++ [tr]} state = update_negotiation_needed(state) - {:reply, {:ok, transceiver}, state} + {:reply, {:ok, RTPTransceiver.to_struct(tr)}, state} end @impl true @@ -680,12 +701,12 @@ defmodule ExWebRTC.PeerConnection do {ssrc, rtx_ssrc} = generate_ssrcs(state) options = [{:ssrc, ssrc}, {:rtx_ssrc, rtx_ssrc} | options] - transceiver = RTPTransceiver.new(track.kind, track, state.config, options) - state = %{state | transceivers: state.transceivers ++ [transceiver]} + tr = RTPTransceiver.new(track.kind, track, state.config, options) + state = %{state | transceivers: state.transceivers ++ [tr]} state = update_negotiation_needed(state) - {:reply, {:ok, transceiver}, state} + {:reply, {:ok, RTPTransceiver.to_struct(tr)}, state} end @impl true @@ -698,7 +719,7 @@ defmodule ExWebRTC.PeerConnection do idx -> tr = Enum.at(state.transceivers, idx) - tr = %RTPTransceiver{tr | direction: direction} + tr = %{tr | direction: direction} transceivers = List.replace_at(state.transceivers, idx, tr) state = %{state | transceivers: transceivers} state = update_negotiation_needed(state) @@ -735,9 +756,9 @@ defmodule ExWebRTC.PeerConnection do # we ignore the condition that sender has never been used to send free_transceiver_idx = Enum.find_index(state.transceivers, fn - %RTPTransceiver{ + %{ kind: ^kind, - sender: %RTPSender{track: nil}, + sender: %{track: nil}, current_direction: direction } when direction not in [:sendrecv, :sendonly] -> @@ -972,7 +993,12 @@ defmodule ExWebRTC.PeerConnection do end) case transceiver do - %RTPTransceiver{} -> + nil -> + Logger.warning( + "Attempted to send packet to track with unrecognized id: #{inspect(track_id)}" + ) + + _other -> {packet, state} = case Map.fetch(state.config.video_rtp_hdr_exts, @twcc_uri) do {:ok, %{id: id}} -> @@ -1000,11 +1026,6 @@ defmodule ExWebRTC.PeerConnection do {:noreply, state} - nil -> - Logger.warning( - "Attempted to send packet to track with unrecognized id: #{inspect(track_id)}" - ) - {:noreply, state} end end @@ -1083,7 +1104,7 @@ defmodule ExWebRTC.PeerConnection do @impl true def handle_info({:dtls_transport, _pid, {:rtp, data}}, state) do with {:ok, demuxer, mid, packet} <- Demuxer.demux(state.demuxer, data), - {idx, %RTPTransceiver{} = t} <- find_transceiver(state.transceivers, mid) do + {idx, t} <- find_transceiver(state.transceivers, mid) do # we always update the ssrc's for the one's from the latest packet # although this is not a necessity, the feedbacks are transport-wide twcc_recorder = %TWCCRecorder{ @@ -1248,13 +1269,13 @@ defmodule ExWebRTC.PeerConnection do Enum.map_reduce(state.transceivers, next_mid, fn # In the initial offer, we can't have stopped transceivers, only stopping ones. # Also, stopped transceivers are immediately removed. - %RTPTransceiver{stopping: true, mid: nil} = tr, nm -> + %{stopping: true, mid: nil} = tr, nm -> {tr, nm} - %RTPTransceiver{stopping: false, mid: nil} = tr, nm -> + %{stopping: false, mid: nil} = tr, nm -> tr = RTPTransceiver.assign_mid(tr, to_string(nm)) # in the initial offer, mline_idx is the same as mid - tr = %RTPTransceiver{tr | mline_idx: nm} + tr = %{tr | mline_idx: nm} {tr, nm + 1} end) @@ -1326,7 +1347,7 @@ defmodule ExWebRTC.PeerConnection do defp assign_mlines([], _, _, _, _, result), do: Enum.reverse(result) defp assign_mlines( - [%RTPTransceiver{mid: nil, mline_idx: nil, stopped: false} = tr | trs], + [%{mid: nil, mline_idx: nil, stopped: false} = tr | trs], last_answer, next_mid, next_mline_idx, @@ -1337,12 +1358,12 @@ defmodule ExWebRTC.PeerConnection do case SDPUtils.find_free_mline_idx(last_answer, recycled_mlines) do nil -> - tr = %RTPTransceiver{tr | mline_idx: next_mline_idx} + tr = %{tr | mline_idx: next_mline_idx} result = [tr | result] assign_mlines(trs, last_answer, next_mid + 1, next_mline_idx + 1, recycled_mlines, result) idx -> - tr = %RTPTransceiver{tr | mline_idx: idx} + tr = %{tr | mline_idx: idx} result = [tr | result] recycled_mlines = [idx | recycled_mlines] assign_mlines(trs, last_answer, next_mid + 1, next_mline_idx, recycled_mlines, result) @@ -1448,7 +1469,7 @@ defmodule ExWebRTC.PeerConnection do # This might result in unremovable transceiver when # we add and stop it before the first offer. # See https://github.com/w3c/webrtc-pc/issues/2923 - %RTPTransceiver{mid: nil} -> + %{mid: nil} -> false tr -> @@ -1566,7 +1587,7 @@ defmodule ExWebRTC.PeerConnection do notify(owner, {:track_muted, tr.receiver.track.id}) end - tr = %RTPTransceiver{tr | current_direction: direction, fired_direction: direction} + tr = %{tr | current_direction: direction, fired_direction: direction} # This is not defined in the W3C but see https://github.com/w3c/webrtc-pc/issues/2927 tr = @@ -1596,12 +1617,12 @@ defmodule ExWebRTC.PeerConnection do # after processing remote track but this shouldn't have any impact {idx, tr} = case find_transceiver_from_remote(transceivers, mline) do - {idx, %RTPTransceiver{} = tr} -> {idx, RTPTransceiver.update(tr, mline, config)} + {idx, tr} -> {idx, RTPTransceiver.update(tr, mline, config)} nil -> {nil, RTPTransceiver.from_mline(mline, idx, config)} end tr = process_remote_track(tr, direction, owner) - tr = if sdp_type == :answer, do: %RTPTransceiver{tr | current_direction: direction}, else: tr + tr = if sdp_type == :answer, do: %{tr | current_direction: direction}, else: tr tr = if SDPUtils.rejected?(mline), @@ -1623,7 +1644,7 @@ defmodule ExWebRTC.PeerConnection do {:mid, mid} = ExSDP.get_attribute(mline, :mid) case find_transceiver(transceivers, mid) do - {idx, %RTPTransceiver{} = tr} -> {idx, tr} + {idx, tr} -> {idx, tr} nil -> find_associable_transceiver(transceivers, mline) end end @@ -1649,7 +1670,7 @@ defmodule ExWebRTC.PeerConnection do :ok end - %RTPTransceiver{transceiver | fired_direction: direction} + %{transceiver | fired_direction: direction} end defp reverse_direction(:recvonly), do: :sendonly @@ -1819,7 +1840,7 @@ defmodule ExWebRTC.PeerConnection do # in case NACK was received, but RTX was not negotiated # as NACK and RTX are negotited independently - {%RTPTransceiver{sender: %RTPSender{rtx_pt: nil}}, _idx} -> + {%{sender: %{rtx_pt: nil}}, _idx} -> state {tr, idx} -> diff --git a/lib/ex_webrtc/rtp_receiver.ex b/lib/ex_webrtc/rtp_receiver.ex index 7635b824..bfad3c7d 100644 --- a/lib/ex_webrtc/rtp_receiver.ex +++ b/lib/ex_webrtc/rtp_receiver.ex @@ -8,7 +8,11 @@ defmodule ExWebRTC.RTPReceiver do alias ExWebRTC.{MediaStreamTrack, Utils, RTPCodecParameters} alias __MODULE__.{NACKGenerator, ReportRecorder} - @type t() :: %__MODULE__{ + @type id() :: integer() + + @typedoc false + @type receiver() :: %{ + id: id(), track: MediaStreamTrack.t(), codec: RTPCodecParameters.t() | nil, ssrc: non_neg_integer() | nil, @@ -19,42 +23,64 @@ defmodule ExWebRTC.RTPReceiver do nack_generator: NACKGenerator.t() } - @enforce_keys [:track, :codec, :report_recorder] - defstruct [ - ssrc: nil, - bytes_received: 0, - packets_received: 0, - markers_received: 0, - nack_generator: %NACKGenerator{} - ] ++ @enforce_keys + @typedoc """ + Struct representing a receiver. + + The fields mostly match these of [RTCRtpReceiver](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpReceiver), + except for: + * `id` - to uniquely identify the receiver. + * `codec` - codec this receiver is expected to receive. + """ + @type t() :: %__MODULE__{ + id: id(), + track: MediaStreamTrack.t(), + codec: RTPCodecParameters.t() | nil + } + + @enforce_keys [:id, :track, :codec] + defstruct @enforce_keys + + @doc false + @spec to_struct(receiver()) :: t() + def to_struct(receiver) do + receiver + |> Map.take([:id, :track, :codec]) + |> then(&struct!(__MODULE__, &1)) + end @doc false - @spec new(MediaStreamTrack.t(), RTPCodecParameters.t() | nil) :: t() + @spec new(MediaStreamTrack.t(), RTPCodecParameters.t() | nil) :: receiver() def new(track, codec) do report_recorder = %ReportRecorder{ clock_rate: codec && codec.clock_rate } - %__MODULE__{ + %{ + id: Utils.generate_id(), track: track, codec: codec, - report_recorder: report_recorder + ssrc: nil, + bytes_received: 0, + packets_received: 0, + markers_received: 0, + report_recorder: report_recorder, + nack_generator: %NACKGenerator{} } end @doc false - @spec update(t(), RTPCodecParameters.t() | nil) :: t() + @spec update(receiver(), RTPCodecParameters.t() | nil) :: receiver() def update(receiver, codec) do report_recorder = %ReportRecorder{ receiver.report_recorder | clock_rate: codec && codec.clock_rate } - %__MODULE__{receiver | codec: codec, report_recorder: report_recorder} + %{receiver | codec: codec, report_recorder: report_recorder} end @doc false - @spec receive_packet(t(), ExRTP.Packet.t(), non_neg_integer()) :: t() + @spec receive_packet(receiver(), ExRTP.Packet.t(), non_neg_integer()) :: receiver() def receive_packet(receiver, packet, size) do if packet.payload_type != receiver.codec.payload_type do Logger.warning("Received packet with unexpected payload_type \ @@ -65,7 +91,7 @@ defmodule ExWebRTC.RTPReceiver do nack_generator = NACKGenerator.record_packet(receiver.nack_generator, packet) # TODO assign ssrc when applying local/remote description. - %__MODULE__{ + %{ receiver | ssrc: packet.ssrc, bytes_received: receiver.bytes_received + size, @@ -76,7 +102,8 @@ defmodule ExWebRTC.RTPReceiver do } end - @spec receive_rtx(t(), ExRTP.Packet.t(), non_neg_integer()) :: {:ok, ExRTP.Packet.t()} | :error + @spec receive_rtx(receiver(), ExRTP.Packet.t(), non_neg_integer()) :: + {:ok, ExRTP.Packet.t()} | :error def receive_rtx(receiver, rtx_packet, apt) do with <> <- rtx_packet.payload, ssrc when ssrc != nil <- receiver.ssrc do @@ -94,23 +121,24 @@ defmodule ExWebRTC.RTPReceiver do end end - @spec receive_report(t(), ExRTCP.Packet.SenderReport.t()) :: t() + @spec receive_report(receiver(), ExRTCP.Packet.SenderReport.t()) :: receiver() def receive_report(receiver, report) do report_recorder = ReportRecorder.record_report(receiver.report_recorder, report) - %__MODULE__{receiver | report_recorder: report_recorder} + %{receiver | report_recorder: report_recorder} end @doc false - @spec update_sender_ssrc(t(), non_neg_integer()) :: t() + @spec update_sender_ssrc(receiver(), non_neg_integer()) :: receiver() def update_sender_ssrc(receiver, ssrc) do report_recorder = %ReportRecorder{receiver.report_recorder | sender_ssrc: ssrc} nack_generator = %NACKGenerator{receiver.nack_generator | sender_ssrc: ssrc} - %__MODULE__{receiver | report_recorder: report_recorder, nack_generator: nack_generator} + + %{receiver | report_recorder: report_recorder, nack_generator: nack_generator} end @doc false - @spec get_stats(t(), non_neg_integer()) :: map() + @spec get_stats(receiver(), non_neg_integer()) :: map() def get_stats(receiver, timestamp) do %{ id: receiver.track.id, diff --git a/lib/ex_webrtc/rtp_receiver/nack_generator.ex b/lib/ex_webrtc/rtp_receiver/nack_generator.ex index cad8fddf..2a5d002b 100644 --- a/lib/ex_webrtc/rtp_receiver/nack_generator.ex +++ b/lib/ex_webrtc/rtp_receiver/nack_generator.ex @@ -1,5 +1,5 @@ defmodule ExWebRTC.RTPReceiver.NACKGenerator do - @moduledoc nil + @moduledoc false # for now, it mimics the Pion implementation, but there's some issues and remarks # 1) NACKs are send at constant interval # 2) no timing rules (like rtt) are taken into account @@ -25,9 +25,6 @@ defmodule ExWebRTC.RTPReceiver.NACKGenerator do last_sn: nil, max_nack: @max_nack - @doc """ - Records incoming RTP Packet. - """ @spec record_packet(t(), ExRTP.Packet.t()) :: t() def record_packet(generator, packet) diff --git a/lib/ex_webrtc/rtp_receiver/report_recorder.ex b/lib/ex_webrtc/rtp_receiver/report_recorder.ex index fbbbc4f6..7c630ffd 100644 --- a/lib/ex_webrtc/rtp_receiver/report_recorder.ex +++ b/lib/ex_webrtc/rtp_receiver/report_recorder.ex @@ -1,5 +1,5 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do - @moduledoc nil + @moduledoc false # based on https://datatracker.ietf.org/doc/html/rfc3550#section-6.4.1 import Bitwise @@ -39,10 +39,7 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do jitter: 0.0, total_lost: 0 - @doc """ - Records incoming RTP Packet. - `time` parameter accepts output of `System.monotonic_time()` as a value. - """ + # `time` parameter accepts output of `System.monotonic_time()` as a value. @spec record_packet(t(), ExRTP.Packet.t(), integer()) :: t() def record_packet(recorder, packet, time \\ System.monotonic_time()) @@ -66,10 +63,7 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do |> record_jitter(packet.timestamp, time) end - @doc """ - Records incoming RTCP Sender Report. - `time` parameter accepts output of `System.monotonic_time()` as a value. - """ + # `time` parameter accepts output of `System.monotonic_time()` as a value. @spec record_report(t(), ExRTCP.Packet.SenderReport.t(), integer()) :: t() def record_report(recorder, sender_report, time \\ System.monotonic_time()) do # we take the middle 32 bits of the NTP timestamp @@ -78,10 +72,7 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do %__MODULE__{recorder | last_sr_ntp_timestamp: ntp_ts, last_sr_timestamp: time} end - @doc """ - Creates an RTCP Receiver Report. - `time` parameter accepts output of `System.monotonic_time()` as a value. - """ + # `time` parameter accepts output of `System.monotonic_time()` as a value. @spec get_report(t(), integer()) :: {:ok, ReceiverReport.t(), t()} | {:error, term()} def get_report(recorder, time \\ System.monotonic_time()) diff --git a/lib/ex_webrtc/rtp_sender.ex b/lib/ex_webrtc/rtp_sender.ex index 2329e910..a7ae245d 100644 --- a/lib/ex_webrtc/rtp_sender.ex +++ b/lib/ex_webrtc/rtp_sender.ex @@ -11,7 +11,8 @@ defmodule ExWebRTC.RTPSender do @type id() :: integer() - @type t() :: %__MODULE__{ + @typedoc false + @type sender() :: %{ id: id(), track: MediaStreamTrack.t() | nil, codec: RTPCodecParameters.t() | nil, @@ -28,21 +29,30 @@ defmodule ExWebRTC.RTPSender do nack_responder: NACKResponder.t() } - @enforce_keys [:id, :report_recorder, :nack_responder] - defstruct @enforce_keys ++ - [ - :track, - :codec, - :mid, - :pt, - :rtx_pt, - :ssrc, - :rtx_ssrc, - rtp_hdr_exts: %{}, - packets_sent: 0, - bytes_sent: 0, - markers_sent: 0 - ] + @typedoc """ + Struct representing a sender. + + The fields mostly match these of [RTCRtpSender](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender), + except for: + * `id` - to uniquely identify the sender. + * `codec` - codec this sender is going to send. + """ + @type t() :: %__MODULE__{ + id: id(), + track: MediaStreamTrack.t() | nil, + codec: RTPCodecParameters.t() | nil + } + + @enforce_keys [:id, :track, :codec] + defstruct @enforce_keys + + @doc false + @spec to_struct(sender()) :: t() + def to_struct(sender) do + sender + |> Map.take([:id, :track, :codec]) + |> then(&struct!(__MODULE__, &1)) + end @doc false @spec new( @@ -53,7 +63,7 @@ defmodule ExWebRTC.RTPSender do String.t() | nil, non_neg_integer() | nil, non_neg_integer() | nil - ) :: t() + ) :: sender() def new(track, codec, rtx_codec, rtp_hdr_exts, mid \\ nil, ssrc, rtx_ssrc) do # convert to a map to be able to find extension id using extension uri rtp_hdr_exts = Map.new(rtp_hdr_exts, fn extmap -> {extmap.uri, extmap} end) @@ -61,7 +71,7 @@ defmodule ExWebRTC.RTPSender do pt = if codec != nil, do: codec.payload_type, else: nil rtx_pt = if rtx_codec != nil, do: rtx_codec.payload_type, else: nil - %__MODULE__{ + %{ id: Utils.generate_id(), track: track, codec: codec, @@ -71,15 +81,18 @@ defmodule ExWebRTC.RTPSender do ssrc: ssrc, rtx_ssrc: rtx_ssrc, mid: mid, + packets_sent: 0, + bytes_sent: 0, + markers_sent: 0, report_recorder: %ReportRecorder{clock_rate: codec && codec.clock_rate}, nack_responder: %NACKResponder{} } end @doc false - @spec update(t(), String.t(), RTPCodecParameters.t() | nil, RTPCodecParameters.t() | nil, [ + @spec update(sender(), String.t(), RTPCodecParameters.t() | nil, RTPCodecParameters.t() | nil, [ Extmap.t() - ]) :: t() + ]) :: sender() def update(sender, mid, codec, rtx_codec, rtp_hdr_exts) do if sender.mid != nil and mid != sender.mid, do: raise(ArgumentError) # convert to a map to be able to find extension id using extension uri @@ -93,7 +106,7 @@ defmodule ExWebRTC.RTPSender do | clock_rate: codec && codec.clock_rate } - %__MODULE__{ + %{ sender | mid: mid, codec: codec, @@ -104,11 +117,8 @@ defmodule ExWebRTC.RTPSender do } end - # Prepares packet for sending i.e.: - # * assigns SSRC, pt, mid - # * serializes to binary @doc false - @spec send_packet(t(), ExRTP.Packet.t(), boolean()) :: {binary(), t()} + @spec send_packet(sender(), ExRTP.Packet.t(), boolean()) :: {binary(), sender()} def send_packet(sender, packet, rtx?) do %Extmap{} = mid_extmap = Map.fetch!(sender.rtp_hdr_exts, @mid_uri) @@ -146,7 +156,8 @@ defmodule ExWebRTC.RTPSender do end @doc false - @spec receive_nack(t(), ExRTCP.Packet.TransportFeedback.NACK.t()) :: {[ExRTP.Packet.t()], t()} + @spec receive_nack(sender(), ExRTCP.Packet.TransportFeedback.NACK.t()) :: + {[ExRTP.Packet.t()], sender()} def receive_nack(sender, nack) do {packets, nack_responder} = NACKResponder.get_rtx(sender.nack_responder, nack) sender = %{sender | nack_responder: nack_responder} @@ -155,7 +166,7 @@ defmodule ExWebRTC.RTPSender do end @doc false - @spec get_stats(t(), non_neg_integer()) :: map() + @spec get_stats(sender(), non_neg_integer()) :: map() def get_stats(sender, timestamp) do %{ timestamp: timestamp, diff --git a/lib/ex_webrtc/rtp_sender/nack_responder.ex b/lib/ex_webrtc/rtp_sender/nack_responder.ex index fb42cf35..c9993427 100644 --- a/lib/ex_webrtc/rtp_sender/nack_responder.ex +++ b/lib/ex_webrtc/rtp_sender/nack_responder.ex @@ -1,5 +1,5 @@ defmodule ExWebRTC.RTPSender.NACKResponder do - @moduledoc nil + @moduledoc false alias ExRTP.Packet alias ExRTCP.Packet.TransportFeedback.NACK @@ -14,7 +14,6 @@ defmodule ExWebRTC.RTPSender.NACKResponder do defstruct packets: %{}, seq_no: Enum.random(0..0xFFFF) - @doc false @spec record_packet(t(), Packet.t()) :: t() def record_packet(responder, packet) do key = rem(packet.sequence_number, @max_packets) @@ -23,7 +22,6 @@ defmodule ExWebRTC.RTPSender.NACKResponder do %__MODULE__{responder | packets: packets} end - @doc false @spec get_rtx(t(), NACK.t()) :: {[ExRTP.Packet.t()], t()} def get_rtx(responder, nack) do seq_nos = NACK.to_sequence_numbers(nack) diff --git a/lib/ex_webrtc/rtp_sender/report_recorder.ex b/lib/ex_webrtc/rtp_sender/report_recorder.ex index 27ba2e1b..b0b4a320 100644 --- a/lib/ex_webrtc/rtp_sender/report_recorder.ex +++ b/lib/ex_webrtc/rtp_sender/report_recorder.ex @@ -1,5 +1,5 @@ defmodule ExWebRTC.RTPSender.ReportRecorder do - @moduledoc nil + @moduledoc false import Bitwise @@ -29,10 +29,7 @@ defmodule ExWebRTC.RTPSender.ReportRecorder do packet_count: 0, octet_count: 0 - @doc """ - Records outgoing RTP Packet. - `time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units). - """ + # `time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units). @spec record_packet(t(), ExRTP.Packet.t(), integer()) :: t() def record_packet(recorder, packet, time \\ System.os_time(:native)) @@ -80,13 +77,9 @@ defmodule ExWebRTC.RTPSender.ReportRecorder do } end - @doc """ - Creates an RTCP Sender Report. - `time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units). - - This function can be called only if at least one packet has been recorded, - otherwise it will raise. - """ + # `time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units). + # This function can be called only if at least one packet has been recorded, + # otherwise it will raise. @spec get_report(t(), integer()) :: {:ok, SenderReport.t(), t()} | {:error, term()} def get_report(recorder, time \\ System.os_time(:native)) diff --git a/lib/ex_webrtc/rtp_transceiver.ex b/lib/ex_webrtc/rtp_transceiver.ex index 1c2d1385..8858e331 100644 --- a/lib/ex_webrtc/rtp_transceiver.ex +++ b/lib/ex_webrtc/rtp_transceiver.ex @@ -21,10 +21,9 @@ defmodule ExWebRTC.RTPTransceiver do @nack_interval 100 @type id() :: integer() - @type direction() :: :sendonly | :recvonly | :sendrecv | :inactive | :stopped - @type kind() :: :audio | :video - @type t() :: %__MODULE__{ + @typedoc false + @type transceiver() :: %{ id: id(), mid: String.t() | nil, mline_idx: non_neg_integer() | nil, @@ -34,29 +33,73 @@ defmodule ExWebRTC.RTPTransceiver do kind: kind(), rtp_hdr_exts: [ExSDP.Attribute.Extmap.t()], codecs: [RTPCodecParameters.t()], - receiver: RTPReceiver.t(), - sender: RTPSender.t(), + receiver: RTPReceiver.receiver(), + sender: RTPSender.sender(), stopping: boolean(), stopped: boolean(), added_by_add_track: boolean() } - @enforce_keys [:id, :direction, :kind, :sender, :receiver] - defstruct @enforce_keys ++ - [ - :mid, - :mline_idx, - :current_direction, - :fired_direction, - codecs: [], - rtp_hdr_exts: [], - stopping: false, - stopped: false, - added_by_add_track: false - ] + @typedoc """ + Possible directions of the transceiver. + + For the exact meaning, refer to the [RTCRtpTransceiver: direction property](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/direction). + """ + @type direction() :: :sendonly | :recvonly | :sendrecv | :inactive | :stopped + + @typedoc """ + Possible types of media that a transceiver can handle. + """ + @type kind() :: :audio | :video + + @typedoc """ + Struct representing a transceiver. + + The fields mostly match these of [RTCRtpTransceiver](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver), + except for: + * `id` - to uniquely identify the transceiver. + * `kind` - kind of the handled media, added for convinience. + * `codecs` and `rtp_hdr_exts` - codecs and RTP header extensions that the transceiver can handle. + """ + @type t() :: %__MODULE__{ + id: id(), + kind: kind(), + current_direction: direction() | nil, + direction: direction(), + mid: String.t() | nil, + receiver: RTPReceiver.t(), + sender: RTPSender.t(), + rtp_hdr_exts: [ExSDP.Attribute.Extmap.t()], + codecs: [RTPCodecParameters.t()] + } + + @enforce_keys [ + :id, + :kind, + :direction, + :current_direction, + :mid, + :receiver, + :sender, + :rtp_hdr_exts, + :codecs + ] + defstruct @enforce_keys + + @doc false + @spec to_struct(transceiver()) :: t() + def to_struct(transceiver) do + sender = RTPSender.to_struct(transceiver.sender) + receiver = RTPReceiver.to_struct(transceiver.receiver) + + transceiver + |> Map.take([:id, :kind, :direction, :current_direction, :mid, :rtp_hdr_exts, :codecs]) + |> Map.merge(%{sender: sender, receiver: receiver}) + |> then(&struct!(__MODULE__, &1)) + end @doc false - @spec new(kind(), MediaStreamTrack.t() | nil, Configuration.t(), Keyword.t()) :: t() + @spec new(kind(), MediaStreamTrack.t() | nil, Configuration.t(), Keyword.t()) :: transceiver() def new(kind, sender_track, config, options) do direction = Keyword.get(options, :direction, :sendrecv) @@ -80,28 +123,38 @@ defmodule ExWebRTC.RTPTransceiver do send(self(), {:send_report, :receiver, id}) send(self(), {:send_nack, id}) - %__MODULE__{ + receiver = RTPReceiver.new(track, codec) + + sender = + RTPSender.new( + sender_track, + codec, + rtx_codec, + rtp_hdr_exts, + options[:ssrc], + options[:rtx_ssrc] + ) + + %{ id: id, direction: direction, + current_direction: nil, + fired_direction: nil, + mid: nil, + mline_idx: nil, kind: kind, + receiver: receiver, + sender: sender, codecs: codecs, rtp_hdr_exts: rtp_hdr_exts, - receiver: RTPReceiver.new(track, codec), - sender: - RTPSender.new( - sender_track, - codec, - rtx_codec, - rtp_hdr_exts, - options[:ssrc], - options[:rtx_ssrc] - ), - added_by_add_track: Keyword.get(options, :added_by_add_track, false) + added_by_add_track: Keyword.get(options, :added_by_add_track, false), + stopping: false, + stopped: false } end @doc false - @spec from_mline(ExSDP.Media.t(), non_neg_integer(), Configuration.t()) :: t() + @spec from_mline(ExSDP.Media.t(), non_neg_integer(), Configuration.t()) :: transceiver() def from_mline(mline, mline_idx, config) do codecs = get_codecs(mline, config) @@ -125,23 +178,31 @@ defmodule ExWebRTC.RTPTransceiver do send(self(), {:send_report, :receiver, id}) send(self(), {:send_nack, id}) - %__MODULE__{ + receiver = RTPReceiver.new(track, codec) + sender = RTPSender.new(nil, codec, rtx_codec, rtp_hdr_exts, mid, nil, nil) + + %{ id: id, + direction: :recvonly, + current_direction: nil, + fired_direction: nil, mid: mid, mline_idx: mline_idx, - direction: :recvonly, kind: mline.type, + receiver: receiver, + sender: sender, codecs: codecs, rtp_hdr_exts: rtp_hdr_exts, - receiver: RTPReceiver.new(track, codec), - sender: RTPSender.new(nil, codec, rtx_codec, rtp_hdr_exts, mid, nil, nil) + added_by_add_track: false, + stopping: false, + stopped: false } end @doc false - @spec associable?(t(), ExSDP.Media.t()) :: boolean() + @spec associable?(transceiver(), ExSDP.Media.t()) :: boolean() def associable?(transceiver, mline) do - %__MODULE__{ + %{ mid: mid, kind: kind, added_by_add_track: added_by_add_track, @@ -156,7 +217,7 @@ defmodule ExWebRTC.RTPTransceiver do end @doc false - @spec update(t(), ExSDP.Media.t(), Configuration.t()) :: t() + @spec update(transceiver(), ExSDP.Media.t(), Configuration.t()) :: transceiver() def update(transceiver, mline, config) do {:mid, mid} = ExSDP.get_attribute(mline, :mid) if transceiver.mid != nil and mid != transceiver.mid, do: raise(ArgumentError) @@ -169,7 +230,7 @@ defmodule ExWebRTC.RTPTransceiver do receiver = RTPReceiver.update(transceiver.receiver, codec) sender = RTPSender.update(transceiver.sender, mid, codec, rtx_codec, rtp_hdr_exts) - %__MODULE__{ + %{ transceiver | mid: mid, codecs: codecs, @@ -180,9 +241,10 @@ defmodule ExWebRTC.RTPTransceiver do end @doc false - @spec add_track(t(), MediaStreamTrack.t(), non_neg_integer(), non_neg_integer()) :: t() + @spec add_track(transceiver(), MediaStreamTrack.t(), non_neg_integer(), non_neg_integer()) :: + transceiver() def add_track(transceiver, track, ssrc, rtx_ssrc) do - sender = %RTPSender{transceiver.sender | track: track, ssrc: ssrc, rtx_ssrc: rtx_ssrc} + sender = %{transceiver.sender | track: track, ssrc: ssrc, rtx_ssrc: rtx_ssrc} direction = case transceiver.direction do @@ -191,21 +253,22 @@ defmodule ExWebRTC.RTPTransceiver do other -> other end - %__MODULE__{transceiver | sender: sender, direction: direction} + %{transceiver | sender: sender, direction: direction} end @doc false - @spec replace_track(t(), MediaStreamTrack.t(), non_neg_integer(), non_neg_integer()) :: t() + @spec replace_track(transceiver(), MediaStreamTrack.t(), non_neg_integer(), non_neg_integer()) :: + transceiver() def replace_track(transceiver, track, ssrc, rtx_ssrc) do ssrc = transceiver.sender.ssrc || ssrc - sender = %RTPSender{transceiver.sender | track: track, ssrc: ssrc, rtx_ssrc: rtx_ssrc} - %__MODULE__{transceiver | sender: sender} + sender = %{transceiver.sender | track: track, ssrc: ssrc, rtx_ssrc: rtx_ssrc} + %{transceiver | sender: sender} end @doc false - @spec remove_track(t()) :: t() + @spec remove_track(transceiver()) :: transceiver() def remove_track(transceiver) do - sender = %RTPSender{transceiver.sender | track: nil} + sender = %{transceiver.sender | track: nil} direction = case transceiver.direction do @@ -214,13 +277,14 @@ defmodule ExWebRTC.RTPTransceiver do other -> other end - %__MODULE__{transceiver | sender: sender, direction: direction} + %{transceiver | sender: sender, direction: direction} end @doc false - @spec receive_packet(t(), ExRTP.Packet.t(), non_neg_integer()) :: - {:ok, t(), ExRTP.Packet.t()} | :error + @spec receive_packet(transceiver(), ExRTP.Packet.t(), non_neg_integer()) :: + {:ok, transceiver(), ExRTP.Packet.t()} | :error def receive_packet(transceiver, packet, size) do + # TODO: direction of returned values is agains the convention in this function case check_if_rtx(transceiver.codecs, packet) do {:ok, apt} -> RTPReceiver.receive_rtx(transceiver.receiver, packet, apt) :error -> {:ok, packet} @@ -228,7 +292,8 @@ defmodule ExWebRTC.RTPTransceiver do |> case do {:ok, packet} -> receiver = RTPReceiver.receive_packet(transceiver.receiver, packet, size) - {:ok, %__MODULE__{transceiver | receiver: receiver}, packet} + transceiver = %{transceiver | receiver: receiver} + {:ok, transceiver, packet} _other -> :error @@ -236,24 +301,23 @@ defmodule ExWebRTC.RTPTransceiver do end @doc false - @spec receive_report(t(), ExRTCP.Packet.SenderReport.t()) :: t() + @spec receive_report(transceiver(), ExRTCP.Packet.SenderReport.t()) :: transceiver() def receive_report(transceiver, report) do receiver = RTPReceiver.receive_report(transceiver.receiver, report) - - %__MODULE__{transceiver | receiver: receiver} + %{transceiver | receiver: receiver} end @doc false - @spec receive_nack(t(), ExRTCP.Packet.TransportFeedback.NACK.t()) :: {[ExRTP.Packet.t()], t()} + @spec receive_nack(transceiver(), ExRTCP.Packet.TransportFeedback.NACK.t()) :: + {[ExRTP.Packet.t()], transceiver()} def receive_nack(transceiver, nack) do {packets, sender} = RTPSender.receive_nack(transceiver.sender, nack) - - tr = %__MODULE__{transceiver | sender: sender} - {packets, tr} + transceiver = %{transceiver | sender: sender} + {packets, transceiver} end @doc false - @spec send_packet(t(), ExRTP.Packet.t(), boolean()) :: {binary(), t()} + @spec send_packet(transceiver(), ExRTP.Packet.t(), boolean()) :: {binary(), transceiver()} def send_packet(transceiver, packet, rtx?) do {packet, sender} = RTPSender.send_packet(transceiver.sender, packet, rtx?) @@ -264,11 +328,14 @@ defmodule ExWebRTC.RTPTransceiver do transceiver.receiver end - {packet, %__MODULE__{transceiver | sender: sender, receiver: receiver}} + transceiver = %{transceiver | sender: sender, receiver: receiver} + + {packet, transceiver} end @doc false - @spec get_report(t(), :sender | :receiver) :: {SenderReport.t() | ReceiverReport.t() | nil, t()} + @spec get_report(transceiver(), :sender | :receiver) :: + {SenderReport.t() | ReceiverReport.t() | nil, transceiver()} def get_report(transceiver, type) do Process.send_after(self(), {:send_report, type, transceiver.id}, report_interval()) @@ -293,7 +360,7 @@ defmodule ExWebRTC.RTPTransceiver do end @doc false - @spec get_nack(t()) :: {NACK.t() | nil, t()} + @spec get_nack(transceiver()) :: {NACK.t() | nil, transceiver()} def get_nack(transceiver) do Process.send_after(self(), {:send_nack, transceiver.id}, @nack_interval) @@ -309,7 +376,7 @@ defmodule ExWebRTC.RTPTransceiver do end @doc false - @spec to_answer_mline(t(), ExSDP.Media.t(), Keyword.t()) :: ExSDP.Media.t() + @spec to_answer_mline(transceiver(), ExSDP.Media.t(), Keyword.t()) :: ExSDP.Media.t() def to_answer_mline(transceiver, mline, opts) do # Reject mline. See RFC 8829 sec. 5.3.1 and RFC 3264 sec. 6. # We could reject earlier (as RFC suggests) but we generate @@ -321,7 +388,7 @@ defmodule ExWebRTC.RTPTransceiver do transceiver.codecs == [] -> # there has to be at least one format so take it from the offer codecs = SDPUtils.get_rtp_codec_parameters(mline) - transceiver = %__MODULE__{transceiver | codecs: codecs} + transceiver = %{transceiver | codecs: codecs} opts = Keyword.put(opts, :direction, :inactive) mline = to_mline(transceiver, opts) %ExSDP.Media{mline | port: 0} @@ -340,39 +407,38 @@ defmodule ExWebRTC.RTPTransceiver do end @doc false - @spec to_offer_mline(t(), Keyword.t()) :: ExSDP.Media.t() + @spec to_offer_mline(transceiver(), Keyword.t()) :: ExSDP.Media.t() def to_offer_mline(transceiver, opts) do mline = to_mline(transceiver, opts) if transceiver.stopping, do: %ExSDP.Media{mline | port: 0}, else: mline end @doc false - # asssings mid to the transceiver and its sender - @spec assign_mid(t(), String.t()) :: t() + @spec assign_mid(transceiver(), String.t()) :: transceiver() def assign_mid(transceiver, mid) do - sender = %RTPSender{transceiver.sender | mid: mid} - %__MODULE__{transceiver | mid: mid, sender: sender} + sender = %{transceiver.sender | mid: mid} + %{transceiver | mid: mid, sender: sender} end @doc false - @spec stop(t(), (-> term())) :: t() + @spec stop(transceiver(), (-> term())) :: transceiver() def stop(transceiver, on_track_ended) do - tr = + transceiver = if transceiver.stopping, do: transceiver, else: stop_sending_and_receiving(transceiver, on_track_ended) # should we reset stopping or leave it as true? - %__MODULE__{tr | stopped: true, stopping: false, current_direction: nil} + %{transceiver | stopped: true, stopping: false, current_direction: nil} end @doc false - @spec stop_sending_and_receiving(t(), (-> term())) :: t() + @spec stop_sending_and_receiving(transceiver(), (-> term())) :: transceiver() def stop_sending_and_receiving(transceiver, on_track_ended) do # TODO send RTCP BYE for each RTP stream # TODO stop receiving media on_track_ended.() - %__MODULE__{transceiver | direction: :inactive, stopping: true} + %{transceiver | direction: :inactive, stopping: true} end defp to_mline(transceiver, opts) do From 88fcd936303b80ca0fb28b12815ef8a9e3fe2e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wala?= Date: Wed, 8 May 2024 12:13:42 +0200 Subject: [PATCH 3/5] Add some properties back to RTPTransceivers --- lib/ex_webrtc/peer_connection.ex | 4 ++-- lib/ex_webrtc/rtp_transceiver.ex | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index c70efc89..ea1ec575 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -329,7 +329,7 @@ defmodule ExWebRTC.PeerConnection do end @doc """ - Adds a new transceiver to this PeerConnection. + Adds a new transceiver. For more information, refer to the [RTCPeerConnection: addTransceiver() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addTransceiver). """ @@ -793,7 +793,7 @@ defmodule ExWebRTC.PeerConnection do state = update_negotiation_needed(state) - {:reply, {:ok, sender}, state} + {:reply, {:ok, RTPSender.to_struct(sender)}, state} end @impl true diff --git a/lib/ex_webrtc/rtp_transceiver.ex b/lib/ex_webrtc/rtp_transceiver.ex index 8858e331..93545a21 100644 --- a/lib/ex_webrtc/rtp_transceiver.ex +++ b/lib/ex_webrtc/rtp_transceiver.ex @@ -67,6 +67,8 @@ defmodule ExWebRTC.RTPTransceiver do current_direction: direction() | nil, direction: direction(), mid: String.t() | nil, + stopping: boolean(), + stopped: boolean(), receiver: RTPReceiver.t(), sender: RTPSender.t(), rtp_hdr_exts: [ExSDP.Attribute.Extmap.t()], @@ -79,6 +81,8 @@ defmodule ExWebRTC.RTPTransceiver do :direction, :current_direction, :mid, + :stopping, + :stopped, :receiver, :sender, :rtp_hdr_exts, @@ -93,7 +97,17 @@ defmodule ExWebRTC.RTPTransceiver do receiver = RTPReceiver.to_struct(transceiver.receiver) transceiver - |> Map.take([:id, :kind, :direction, :current_direction, :mid, :rtp_hdr_exts, :codecs]) + |> Map.take([ + :id, + :kind, + :direction, + :current_direction, + :mid, + :rtp_hdr_exts, + :codecs, + :stopping, + :stopped + ]) |> Map.merge(%{sender: sender, receiver: receiver}) |> then(&struct!(__MODULE__, &1)) end From d330284845122892d09438bdf778c0137fadb006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wala?= Date: Thu, 9 May 2024 09:06:40 +0200 Subject: [PATCH 4/5] Apply requested changes --- lib/ex_webrtc/peer_connection.ex | 8 ++++---- lib/ex_webrtc/rtp_receiver/report_recorder.ex | 18 +++++++++++++++--- lib/ex_webrtc/rtp_sender/report_recorder.ex | 16 ++++++++++++---- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index ea1ec575..7333c48d 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -59,7 +59,7 @@ defmodule ExWebRTC.PeerConnection do @type signaling_state() :: :stable | :have_local_offer | :have_remote_offer @typedoc """ - Messages sent by the the `ExWebRTC.PeerConnection` process to its controlling process. + Messages sent by the `ExWebRTC.PeerConnection` process to its controlling process. Most of the messages match the [RTCPeerConnection events](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection#events), except for: @@ -343,7 +343,7 @@ defmodule ExWebRTC.PeerConnection do end @doc """ - Sets the direction of transceiver specified by the `id`. + Sets the direction of transceiver specified by the `transceiver_id`. For more information, refer to the [RTCRtpTransceiver: direction property](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/direction). """ @@ -358,7 +358,7 @@ defmodule ExWebRTC.PeerConnection do end @doc """ - Stops the transceiver specified by the `id`. + Stops the transceiver specified by the `transceiver_id`. For more information, refer to the [RTCRtpTransceiver: stop() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/stop). """ @@ -401,7 +401,7 @@ defmodule ExWebRTC.PeerConnection do @doc """ Closes the PeerConnection. - This function kills the PeerConnection process. + This function kills the `peer_connection` process. For more information, refer to the [RTCPeerConnection: close() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/close). """ @spec close(peer_connection()) :: :ok diff --git a/lib/ex_webrtc/rtp_receiver/report_recorder.ex b/lib/ex_webrtc/rtp_receiver/report_recorder.ex index 7c630ffd..bd44644c 100644 --- a/lib/ex_webrtc/rtp_receiver/report_recorder.ex +++ b/lib/ex_webrtc/rtp_receiver/report_recorder.ex @@ -39,7 +39,11 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do jitter: 0.0, total_lost: 0 - # `time` parameter accepts output of `System.monotonic_time()` as a value. + @doc """ + Record incoming RTP packet. + + `time` parameter accepts output of `System.monotonic_time()` as a value. + """ @spec record_packet(t(), ExRTP.Packet.t(), integer()) :: t() def record_packet(recorder, packet, time \\ System.monotonic_time()) @@ -63,7 +67,11 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do |> record_jitter(packet.timestamp, time) end - # `time` parameter accepts output of `System.monotonic_time()` as a value. + @doc """ + Record incoming RTCP Sender Report. + + `time` parameter accepts output of `System.monotonic_time()` as a value. + """ @spec record_report(t(), ExRTCP.Packet.SenderReport.t(), integer()) :: t() def record_report(recorder, sender_report, time \\ System.monotonic_time()) do # we take the middle 32 bits of the NTP timestamp @@ -72,7 +80,11 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do %__MODULE__{recorder | last_sr_ntp_timestamp: ntp_ts, last_sr_timestamp: time} end - # `time` parameter accepts output of `System.monotonic_time()` as a value. + @doc """ + Generate RTCP Receiver Report. + + `time` parameter accepts output of `System.monotonic_time()` as a value. + """ @spec get_report(t(), integer()) :: {:ok, ReceiverReport.t(), t()} | {:error, term()} def get_report(recorder, time \\ System.monotonic_time()) diff --git a/lib/ex_webrtc/rtp_sender/report_recorder.ex b/lib/ex_webrtc/rtp_sender/report_recorder.ex index b0b4a320..2ef355bb 100644 --- a/lib/ex_webrtc/rtp_sender/report_recorder.ex +++ b/lib/ex_webrtc/rtp_sender/report_recorder.ex @@ -29,7 +29,11 @@ defmodule ExWebRTC.RTPSender.ReportRecorder do packet_count: 0, octet_count: 0 - # `time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units). + @doc """ + Records incoming RTP packet. + + `time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units). + """ @spec record_packet(t(), ExRTP.Packet.t(), integer()) :: t() def record_packet(recorder, packet, time \\ System.os_time(:native)) @@ -77,9 +81,13 @@ defmodule ExWebRTC.RTPSender.ReportRecorder do } end - # `time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units). - # This function can be called only if at least one packet has been recorded, - # otherwise it will raise. + @doc """ + Generates a RTCP Sender Report. + + `time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units). + This function can be called only if at least one packet has been recorded, + otherwise it will raise. + """ @spec get_report(t(), integer()) :: {:ok, SenderReport.t(), t()} | {:error, term()} def get_report(recorder, time \\ System.os_time(:native)) From b5cb05edbff01fdf7ee01e76887ddd28fec1c407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wala?= Date: Thu, 9 May 2024 09:08:48 +0200 Subject: [PATCH 5/5] Fix typos in docs --- lib/ex_webrtc/rtp_receiver/report_recorder.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ex_webrtc/rtp_receiver/report_recorder.ex b/lib/ex_webrtc/rtp_receiver/report_recorder.ex index bd44644c..c1e386ea 100644 --- a/lib/ex_webrtc/rtp_receiver/report_recorder.ex +++ b/lib/ex_webrtc/rtp_receiver/report_recorder.ex @@ -40,7 +40,7 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do total_lost: 0 @doc """ - Record incoming RTP packet. + Records incoming RTP packet. `time` parameter accepts output of `System.monotonic_time()` as a value. """ @@ -68,7 +68,7 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do end @doc """ - Record incoming RTCP Sender Report. + Records incoming RTCP Sender Report. `time` parameter accepts output of `System.monotonic_time()` as a value. """ @@ -81,7 +81,7 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do end @doc """ - Generate RTCP Receiver Report. + Generates RTCP Receiver Report. `time` parameter accepts output of `System.monotonic_time()` as a value. """