From addb72f81fa5dd3da25509d6e82731f123b2a3a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Wed, 18 Oct 2023 18:52:01 +0200 Subject: [PATCH] wip --- examples/example.exs | 1 - lib/ex_webrtc/media_stream_track.ex | 7 ++ lib/ex_webrtc/peer_connection.ex | 93 ++++++++++--------- .../peer_connection/configuration.ex | 2 +- lib/ex_webrtc/rtp_transceiver.ex | 16 ++++ lib/ex_webrtc/utils.ex | 6 ++ test/peer_connection_test.exs | 41 ++++++++ 7 files changed, 118 insertions(+), 48 deletions(-) create mode 100644 lib/ex_webrtc/media_stream_track.ex create mode 100644 test/peer_connection_test.exs diff --git a/examples/example.exs b/examples/example.exs index 4000e310..02fed4e5 100644 --- a/examples/example.exs +++ b/examples/example.exs @@ -31,7 +31,6 @@ defmodule Peer do Process.send_after(self(), :ws_ping, 1000) {:ok, pc} = PeerConnection.start_link( - bundle_policy: :max_bundle, ice_servers: @ice_servers ) diff --git a/lib/ex_webrtc/media_stream_track.ex b/lib/ex_webrtc/media_stream_track.ex new file mode 100644 index 00000000..62cf9c8e --- /dev/null +++ b/lib/ex_webrtc/media_stream_track.ex @@ -0,0 +1,7 @@ +defmodule ExWebRTC.MediaStreamTrack do + defstruct [] + + def from_transceiver(_tr) do + %__MODULE__{} + end +end diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index cee8caaa..4d8897ee 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -7,7 +7,7 @@ defmodule ExWebRTC.PeerConnection do alias __MODULE__.Configuration alias ExICE.ICEAgent - alias ExWebRTC.{IceCandidate, RTPTransceiver, SessionDescription} + alias ExWebRTC.{IceCandidate, MediaStreamTrack, RTPTransceiver, SessionDescription} import ExWebRTC.Utils @@ -28,7 +28,7 @@ defmodule ExWebRTC.PeerConnection do :dtls_client, :dtls_buffered_packets, dtls_finished: false, - transceivers: %{}, + transceivers: [], signaling_state: :stable ] @@ -293,53 +293,54 @@ defmodule ExWebRTC.PeerConnection do defp apply_remote_description(_type, sdp, state) do # TODO apply steps listed in RFC 8829 5.10 media = hd(sdp.media) - {:ice_ufrag, ufrag} = ExSDP.Media.get_attribute(media, :ice_ufrag) - {:ice_pwd, pwd} = ExSDP.Media.get_attribute(media, :ice_pwd) - - :ok = ICEAgent.set_remote_credentials(state.ice_agent, ufrag, pwd) - :ok = ICEAgent.gather_candidates(state.ice_agent) - - transceivers = - sdp.media - |> Enum.reduce(%{}, fn media -> - {:mid, mid} = ExSDP.Media.get_attribute(media, :mid) - # if there is no direction, the default is sendrecv - # see RFC 3264, sec. 6.1 - direction = get_media_direction(media) || :sendrecv - - if mid == nil or direction == :inactive do - # FIXME instead of raising return an error; - # FIXME what about inactive mlines? - # Should we create transceivers for them too?; - raise "Invalid remote description: missing or invalid mid or direction" - end - - tr = - Map.get(state.transceivers, mid) || - find_or_create_transceiver(mid, direction, state.transceivers) - - {tr.mid, tr} - end) - |> Map.merge(state.transceivers) - - {:ok, %{state | current_remote_desc: sdp, transceivers: transceivers}} - end - defp find_or_create_transceiver(mid, direction, transceivers) - when direction in [:sendrecv, :recvonly] do - Enum.find(transceivers, %RTPTransceiver{mid: mid, direction: :recvonly}, fn - {nil, tr} when tr.direction == direction -> tr - _other -> nil - end) - end - - defp find_or_create_transceiver(mid, :sendonly, _transceivers) do - %RTPTransceiver{mid: mid, direction: :recvonly} + with {:ice_ufrag, ufrag} <- ExSDP.Media.get_attribute(media, :ice_ufrag), + {:ice_pwd, pwd} <- ExSDP.Media.get_attribute(media, :ice_pwd), + {:ok, transceivers} <- get_transceivers(sdp, state) do + :ok = ICEAgent.set_remote_credentials(state.ice_agent, ufrag, pwd) + :ok = ICEAgent.gather_candidates(state.ice_agent) + + new_remote_tracks = + transceivers + # only take new transceivers that can receive tracks + |> Enum.filter(fn tr -> + RTPTransceiver.find_by_mid(state.transceivers, tr.mid) == nil and + tr.direction in [:recvonly, :sendrecv] + end) + |> Enum.map(fn tr -> MediaStreamTrack.from_transceiver(tr) end) + + for track <- new_remote_tracks do + send(state.owner, {:ex_webrtc, self(), {:track, track}}) + end + + {:ok, %{state | current_remote_desc: sdp, transceivers: transceivers}} + else + nil -> {:error, :missing_ice_ufrag_or_pwd} + end end - defp get_media_direction(media) do - Enum.find(media.attributes, fn attr -> - attr in [:sendrecv, :sendonly, :recvonly, :inactive] + defp get_transceivers(sdp, state) do + Enum.reduce_while(sdp.media, {:ok, state.transceivers}, fn mline, {:ok, transceivers} -> + case ExSDP.Media.get_attribute(mline, :mid) do + {:mid, mid} -> + case RTPTransceiver.find_by_mid(transceivers, mid) do + {idx, %RTPTransceiver{} = tr} -> + case RTPTransceiver.update(tr, mline) do + {:ok, tr} -> + {:cont, {:ok, List.replace_at(transceivers, idx, tr)}} + + {:error, :remove} -> + {:cont, {:ok, List.delete_at(transceivers, idx)}} + end + + nil -> + tr = %RTPTransceiver{mid: mid, direction: :recvonly} + {:cont, {:ok, transceivers ++ [tr]}} + end + + _other -> + {:halt, {:error, :missing_mid}} + end end) end diff --git a/lib/ex_webrtc/peer_connection/configuration.ex b/lib/ex_webrtc/peer_connection/configuration.ex index 34fdc038..c197389e 100644 --- a/lib/ex_webrtc/peer_connection/configuration.ex +++ b/lib/ex_webrtc/peer_connection/configuration.ex @@ -33,7 +33,7 @@ defmodule ExWebRTC.PeerConnection.Configuration do rtcp_mux_policy: rtcp_mux_policy() } - defstruct bundle_policy: :balanced, + defstruct bundle_policy: :max_bundle, certificates: nil, ice_candidate_pool_size: 0, ice_servers: [], diff --git a/lib/ex_webrtc/rtp_transceiver.ex b/lib/ex_webrtc/rtp_transceiver.ex index 777e47f2..38240cfc 100644 --- a/lib/ex_webrtc/rtp_transceiver.ex +++ b/lib/ex_webrtc/rtp_transceiver.ex @@ -6,4 +6,20 @@ defmodule ExWebRTC.RTPTransceiver do @enforce_keys [:mid, :direction] defstruct @enforce_keys + + def find_by_mid(transceivers, mid) do + transceivers + |> Enum.with_index(fn tr, idx -> {idx, tr} end) + |> Enum.find(fn {_idx, tr} -> tr.mid == mid end) + end + + def update(transceiver, mline) do + # if there is no direction, the default is sendrecv + # see RFC 3264, sec. 6.1 + + case ExWebRTC.Utils.get_media_direction(mline) || :sendrecv do + :inactive -> {:error, :remove} + other_direction -> {:ok, %__MODULE__{transceiver | direction: other_direction}} + end + end end diff --git a/lib/ex_webrtc/utils.ex b/lib/ex_webrtc/utils.ex index 2d6d9b43..0581524f 100644 --- a/lib/ex_webrtc/utils.ex +++ b/lib/ex_webrtc/utils.ex @@ -7,4 +7,10 @@ defmodule ExWebRTC.Utils do |> :binary.bin_to_list() |> Enum.map_join(":", &Base.encode16(<<&1>>)) end + + def get_media_direction(media) do + Enum.find(media.attributes, fn attr -> + attr in [:sendrecv, :sendonly, :recvonly, :inactive] + end) + end end diff --git a/test/peer_connection_test.exs b/test/peer_connection_test.exs new file mode 100644 index 00000000..ae4cbd13 --- /dev/null +++ b/test/peer_connection_test.exs @@ -0,0 +1,41 @@ +defmodule ExWebRTC.PeerConnectionTest do + use ExUnit.Case, async: true + + alias ExWebRTC.{MediaStreamTrack, PeerConnection, SessionDescription} + + @offer """ + v=0 + o=- 6788894006044524728 2 IN IP4 127.0.0.1 + s=- + t=0 0 + a=group:BUNDLE 0 + a=extmap-allow-mixed + a=msid-semantic: WMS + m=audio 9 UDP/TLS/RTP/SAVPF 111 + c=IN IP4 0.0.0.0 + a=rtcp:9 IN IP4 0.0.0.0 + a=ice-ufrag:cDua + a=ice-pwd:v9SCmZHxJWtgpyzn8Ts1puT6 + a=ice-options:trickle + a=fingerprint:sha-256 11:35:68:66:A4:C3:C0:AA:37:4E:0F:97:D7:9F:76:11:08:DB:56:DA:4B:83:77:50:9A:D2:71:8D:2A:A8:E3:07 + a=setup:actpass + a=mid:0 + a=sendrecv + a=msid:- 54f0751b-086f-433c-af40-79c179182423 + a=rtcp-mux + a=rtpmap:111 opus/48000/2 + a=rtcp-fb:111 transport-cc + a=fmtp:111 minptime=10;useinbandfec=1 + a=ssrc:1463342914 cname:poWwjNZ4I2ZZgzY7 + a=ssrc:1463342914 msid:- 54f0751b-086f-433c-af40-79c179182423 + """ + + test "transceivers" do + {:ok, pc} = PeerConnection.start_link() + + offer = %SessionDescription{type: :offer, sdp: @offer} + :ok = PeerConnection.set_remote_description(pc, offer) + + assert_receive {:ex_webrtc, ^pc, {:track, %MediaStreamTrack{}}} + end +end