@@ -41,6 +41,7 @@ defmodule ExWebRTC.PeerConnection do
4141 | { :connection_state_change , connection_state ( ) }
4242 | { :track , MediaStreamTrack . t ( ) }
4343 | { :track_muted , MediaStreamTrack . id ( ) }
44+ | { :track_ended , MediaStreamTrack . id ( ) }
4445 | { :rtp , MediaStreamTrack . id ( ) , ExRTP.Packet . t ( ) } }
4546
4647 @ typedoc """
@@ -124,6 +125,12 @@ defmodule ExWebRTC.PeerConnection do
124125 GenServer . call ( peer_connection , { :set_transceiver_direction , transceiver_id , direction } )
125126 end
126127
128+ @ spec stop_transceiver ( peer_connection ( ) , RTPTransceiver . id ( ) ) ::
129+ :ok | { :error , :invalid_transceiver_id }
130+ def stop_transceiver ( peer_connection , transceiver_id ) do
131+ GenServer . call ( peer_connection , { :stop_transceiver , transceiver_id } )
132+ end
133+
127134 @ spec add_track ( peer_connection ( ) , MediaStreamTrack . t ( ) ) :: { :ok , RTPSender . t ( ) }
128135 def add_track ( peer_connection , track ) do
129136 GenServer . call ( peer_connection , { :add_track , track } )
@@ -206,9 +213,6 @@ defmodule ExWebRTC.PeerConnection do
206213 :ok = state . ice_transport . restart ( state . ice_pid )
207214 end
208215
209- next_mid = find_next_mid ( state )
210- transceivers = assign_mids ( state . transceivers , next_mid )
211-
212216 { :ok , ice_ufrag , ice_pwd } =
213217 state . ice_transport . get_local_credentials ( state . ice_pid )
214218
@@ -229,7 +233,72 @@ defmodule ExWebRTC.PeerConnection do
229233 rtcp: true
230234 ]
231235
232- mlines = Enum . map ( transceivers , & RTPTransceiver . to_offer_mline ( & 1 , opts ) )
236+ next_mid = find_next_mid ( state )
237+
238+ { transceivers , mlines } =
239+ if state . current_local_desc == nil do
240+ # TODO stopped transceivers
241+ { transceivers , _next_mid } =
242+ Enum . map_reduce ( state . transceivers , next_mid , fn % { mid: nil } = tr , nm ->
243+ if tr . stopped do
244+ { tr , nm }
245+ else
246+ tr = RTPTransceiver . assign_mid ( tr , to_string ( nm ) )
247+ tr = % RTPTransceiver { tr | mline_idx: nm }
248+ { tr , nm + 1 }
249+ end
250+ end )
251+
252+ mlines =
253+ transceivers
254+ |> Enum . reject ( fn tr -> tr . stopped == true end )
255+ |> Enum . map ( & RTPTransceiver . to_offer_mline ( & 1 , opts ) )
256+
257+ { transceivers , mlines }
258+ else
259+ last_answer = get_last_answer ( state )
260+
261+ { transceivers , _next_mid } =
262+ Enum . map_reduce ( state . transceivers , { next_mid , [ ] } , fn
263+ % { mid: nil } = tr , { nm , mline_indices } ->
264+ if tr . stopped do
265+ { tr , nm }
266+ else
267+ tr = RTPTransceiver . assign_mid ( tr , to_string ( nm ) )
268+ # idx might be nil, this means that
269+ # there is no mline to recycle
270+ idx = SDPUtils . find_free_mline_idx ( last_answer , mline_indices )
271+ tr = % RTPTransceiver { tr | mline_idx: idx }
272+ { tr , { nm + 1 , [ idx | mline_indices ] } }
273+ end
274+
275+ tr , acc ->
276+ { tr , acc }
277+ end )
278+
279+ transceivers =
280+ transceivers
281+ |> Enum . reject ( fn tr -> tr . stopped == true end )
282+ |> Enum . sort_by ( fn tr -> tr . mline_idx end )
283+ |> Enum . with_index ( )
284+ |> Enum . map ( fn { tr , idx } -> % RTPTransceiver { tr | mline_idx: idx } end )
285+
286+ mlines = Enum . map ( transceivers , fn tr -> RTPTransceiver . to_offer_mline ( tr , opts ) end )
287+
288+ final_mlines =
289+ last_answer . media
290+ |> Stream . with_index ( )
291+ |> Enum . map ( fn { answer_mline , idx } ->
292+ case Enum . at ( mlines , idx ) do
293+ nil -> answer_mline
294+ mline -> mline
295+ end
296+ end )
297+
298+ mlines = final_mlines ++ ( mlines -- final_mlines )
299+
300+ { transceivers , mlines }
301+ end
233302
234303 mids =
235304 Enum . map ( mlines , fn mline ->
@@ -361,6 +430,17 @@ defmodule ExWebRTC.PeerConnection do
361430 { :reply , { :ok , transceiver } , state }
362431 end
363432
433+ @ impl true
434+ def handle_call ( { :add_transceiver , % MediaStreamTrack { } = track , options } , _from , state ) do
435+ options = Keyword . put ( options , :ssrc , generate_ssrc ( state ) )
436+ transceiver = RTPTransceiver . new ( track . kind , track , state . config , options )
437+ state = % { state | transceivers: state . transceivers ++ [ transceiver ] }
438+
439+ state = update_negotiation_needed ( state )
440+
441+ { :reply , { :ok , transceiver } , state }
442+ end
443+
364444 @ impl true
365445 def handle_call ( { :set_transceiver_direction , tr_id , direction } , _from , state ) do
366446 idx = Enum . find_index ( state . transceivers , fn tr -> tr . id == tr_id end )
@@ -380,14 +460,28 @@ defmodule ExWebRTC.PeerConnection do
380460 end
381461
382462 @ impl true
383- def handle_call ( { :add_transceiver , % MediaStreamTrack { } = track , options } , _from , state ) do
384- options = Keyword . put ( options , :ssrc , generate_ssrc ( state ) )
385- transceiver = RTPTransceiver . new ( track . kind , track , state . config , options )
386- state = % { state | transceivers: state . transceivers ++ [ transceiver ] }
463+ def handle_call ( { :stop_transceiver , tr_id } , _from , state ) do
464+ idx = Enum . find_index ( state . transceivers , fn tr -> tr . id == tr_id end )
387465
388- state = update_negotiation_needed ( state )
466+ case idx do
467+ nil ->
468+ { :reply , { :error , :invalid_transceiver_id } , state }
389469
390- { :reply , { :ok , transceiver } , state }
470+ idx ->
471+ tr = Enum . at ( state . transceivers , idx )
472+
473+ if tr . stopping do
474+ { :reply , :ok , state }
475+ else
476+ # TODO send RTCP BYE for each RTP stream
477+ # TODO stop receiving media
478+ tr = % RTPTransceiver { tr | direction: :inactive , stopping: true }
479+ transceivers = List . replace_at ( state . transceivers , idx , tr )
480+ state = % { state | transceivers: transceivers }
481+ state = update_negotiation_needed ( state )
482+ { :reply , :ok , state }
483+ end
484+ end
391485 end
392486
393487 @ impl true
@@ -638,12 +732,15 @@ defmodule ExWebRTC.PeerConnection do
638732
639733 transceivers = update_transceiver_directions ( state . transceivers , sdp , :local , type )
640734
735+ # TODO re-think order of those functions
736+ # and demuxer update
641737 state =
642738 state
643739 |> set_description ( :local , type , sdp )
740+ |> Map . replace! ( :transceivers , transceivers )
741+ |> remove_stopped_transceivers ( type , sdp )
644742 |> update_signaling_state ( next_sig_state )
645743 |> Map . update! ( :demuxer , & Demuxer . update ( & 1 , sdp ) )
646- |> Map . replace! ( :transceivers , transceivers )
647744
648745 if state . signaling_state == :stable do
649746 state = % { state | negotiation_needed: false }
@@ -672,9 +769,7 @@ defmodule ExWebRTC.PeerConnection do
672769 state = % { state | config: config }
673770
674771 transceivers =
675- state
676- |> update_transceivers ( sdp )
677- |> update_transceiver_directions ( sdp , :remote , type )
772+ update_transceivers ( sdp . media , state . transceivers , type , state . config , state . owner )
678773
679774 # TODO: this can result in ICE restart (when it should, e.g. when this is answer)
680775 :ok = state . ice_transport . set_remote_credentials ( state . ice_pid , ice_ufrag , ice_pwd )
@@ -690,9 +785,10 @@ defmodule ExWebRTC.PeerConnection do
690785 state =
691786 state
692787 |> set_description ( :remote , type , sdp )
788+ |> Map . replace! ( :transceivers , transceivers )
789+ |> remove_stopped_transceivers ( type , sdp )
693790 |> update_signaling_state ( next_sig_state )
694791 |> Map . update! ( :demuxer , & Demuxer . update ( & 1 , sdp ) )
695- |> Map . replace! ( :transceivers , transceivers )
696792
697793 if state . signaling_state == :stable do
698794 state = % { state | negotiation_needed: false }
@@ -710,6 +806,29 @@ defmodule ExWebRTC.PeerConnection do
710806 end
711807 end
712808
809+ defp remove_stopped_transceivers ( state , :answer , sdp ) do
810+ # see W3C 4.4.1.5-4.7.12 xd
811+ transceivers =
812+ Enum . reject ( state . transceivers , fn tr ->
813+ if tr . mid != nil do
814+ # TODO refactor
815+ mline =
816+ Enum . find ( sdp . media , fn mline ->
817+ { :mid , mid } = ExSDP . get_attribute ( mline , :mid )
818+ mid == tr . mid
819+ end )
820+
821+ tr . stopped == true and mline . port == 0
822+ else
823+ false
824+ end
825+ end )
826+
827+ % { state | transceivers: transceivers }
828+ end
829+
830+ defp remove_stopped_transceivers ( state , :offer , _sdp ) , do: state
831+
713832 defp next_signaling_state ( current_signaling_state , source , type )
714833 defp next_signaling_state ( :stable , :remote , :offer ) , do: { :ok , :have_remote_offer }
715834 defp next_signaling_state ( :stable , :local , :offer ) , do: { :ok , :have_local_offer }
@@ -797,25 +916,39 @@ defmodule ExWebRTC.PeerConnection do
797916 end
798917
799918 # this function is only called when applying remote description
800- defp update_transceivers ( state , sdp ) do
801- Enum . reduce ( sdp . media , state . transceivers , fn mline , transceivers ->
802- { :mid , mid } = ExSDP . get_attribute ( mline , :mid )
919+ defp update_transceivers ( mlines , transceivers , sdp_type , config , owner )
920+ defp update_transceivers ( [ ] , transceivers , _sdp_type , _config , _owner ) , do: transceivers
803921
804- direction = SDPUtils . get_media_direction ( mline ) |> reverse_direction ( )
922+ defp update_transceivers ( [ mline | mlines ] , transceivers , sdp_type , config , owner ) do
923+ { :mid , mid } = ExSDP . get_attribute ( mline , :mid )
805924
806- # TODO: consider recycled transceivers
807- case find_transceiver ( transceivers , mid ) do
808- { idx , % RTPTransceiver { } = tr } ->
809- new_tr = RTPTransceiver . update ( tr , mline , state . config )
810- new_tr = process_remote_track ( new_tr , direction , state . owner )
811- List . replace_at ( transceivers , idx , new_tr )
925+ direction =
926+ if SDPUtils . is_rejected ( mline ) ,
927+ do: :inactive ,
928+ else: SDPUtils . get_media_direction ( mline ) |> reverse_direction ( )
812929
813- nil ->
814- new_tr = RTPTransceiver . from_mline ( mline , state . config )
815- new_tr = process_remote_track ( new_tr , direction , state . owner )
816- transceivers ++ [ new_tr ]
930+ # TODO: consider recycled transceivers
931+ # Note: in theory we should update transceiver codecs
932+ # after processing remote track but this shouldn't have any impact
933+ { idx , tr } =
934+ case find_transceiver ( transceivers , mid ) do
935+ { idx , % RTPTransceiver { } = tr } -> { idx , RTPTransceiver . update ( tr , mline , config ) }
936+ nil -> { nil , RTPTransceiver . from_mline ( mline , config ) }
817937 end
818- end )
938+
939+ tr = process_remote_track ( tr , direction , owner )
940+ tr = if sdp_type == :answer , do: % RTPTransceiver { tr | current_direction: direction } , else: tr
941+ tr = if SDPUtils . is_rejected ( mline ) , do: RTPTransceiver . stop ( tr ) , else: tr
942+
943+ case idx do
944+ nil ->
945+ transceivers = transceivers ++ [ tr ]
946+ update_transceivers ( mlines , transceivers , sdp_type , config , owner )
947+
948+ idx ->
949+ transceivers = List . replace_at ( transceivers , idx , tr )
950+ update_transceivers ( mlines , transceivers , sdp_type , config , owner )
951+ end
819952 end
820953
821954 # see W3C WebRTC 5.1.1
@@ -867,19 +1000,6 @@ defmodule ExWebRTC.PeerConnection do
8671000 |> Enum . find ( fn { _idx , tr } -> tr . mid == mid end )
8681001 end
8691002
870- defp assign_mids ( transceivers , next_mid ) do
871- { new_transceivers , _next_mid } =
872- Enum . map_reduce ( transceivers , next_mid , fn
873- % { mid: nil } = t , nm ->
874- { RTPTransceiver . assign_mid ( t , to_string ( nm ) ) , nm + 1 }
875-
876- other , nm ->
877- { other , nm }
878- end )
879-
880- new_transceivers
881- end
882-
8831003 defp find_next_mid ( state ) do
8841004 # next mid must be unique, it's acomplished by looking for values
8851005 # greater than any mid in remote description or our own transceivers
@@ -913,6 +1033,9 @@ defmodule ExWebRTC.PeerConnection do
9131033 end )
9141034 end
9151035
1036+ defp get_last_answer ( % { current_local_desc: { :answer , desc } } ) , do: desc
1037+ defp get_last_answer ( % { current_remote_desc: { :answer , desc } } ) , do: desc
1038+
9161039 # TODO support :disconnected state - our ICE doesn't provide disconnected state for now
9171040 # TODO support :closed state
9181041 # the order of these clauses is important
0 commit comments