Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create transceivers on set_remote_description #2

Merged
merged 2 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion examples/example.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
23 changes: 23 additions & 0 deletions lib/ex_webrtc/media_stream_track.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule ExWebRTC.MediaStreamTrack do
@moduledoc """
MediaStreamTrack
"""

@type t() :: %__MODULE__{
kind: :audio | :video,
id: integer(),
mid: String.t()
}

@enforce_keys [:id, :kind]
defstruct @enforce_keys ++ [:mid]

def from_transceiver(tr) do
%__MODULE__{kind: tr.kind, id: generate_id(), mid: tr.mid}
end

defp generate_id() do
<<id::12*8>> = :crypto.strong_rand_bytes(12)
id
end
end
55 changes: 48 additions & 7 deletions lib/ex_webrtc/peer_connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

alias __MODULE__.Configuration
alias ExICE.ICEAgent
alias ExWebRTC.{IceCandidate, SessionDescription}
alias ExWebRTC.{IceCandidate, MediaStreamTrack, RTPTransceiver, SessionDescription}

import ExWebRTC.Utils

Expand Down Expand Up @@ -108,6 +108,11 @@
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)

Check warning on line 113 in lib/ex_webrtc/peer_connection.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/peer_connection.ex#L113

Added line #L113 was not covered by tests
end

#### CALLBACKS ####

@impl true
Expand Down Expand Up @@ -213,6 +218,10 @@
{:reply, :ok, state}
end

def handle_call(:get_transceivers, _from, state) do
{:reply, state.transceivers, state}

Check warning on line 222 in lib/ex_webrtc/peer_connection.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/peer_connection.ex#L222

Added line #L222 was not covered by tests
end

@impl true
def handle_info({:ex_ice, _from, :connected}, state) do
if state.dtls_buffered_packets do
Expand All @@ -232,7 +241,7 @@
# username_fragment: "vx/1"
}

send(state.owner, {:ex_webrtc, {:ice_candidate, candidate}})
notify(state.owner, {:ice_candidate, candidate})

{:noreply, state}
end
Expand Down Expand Up @@ -293,13 +302,43 @@
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)
with {:ice_ufrag, ufrag} <- ExSDP.Media.get_attribute(media, :ice_ufrag),
{:ice_pwd, pwd} <- ExSDP.Media.get_attribute(media, :ice_pwd),
{:ok, new_transceivers} <- update_transceivers(state.transceivers, sdp) do
:ok = ICEAgent.set_remote_credentials(state.ice_agent, ufrag, pwd)
:ok = ICEAgent.gather_candidates(state.ice_agent)

new_remote_tracks =
new_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
notify(state.owner, {:track, track})
end
Comment on lines +321 to +323
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose you know that and this is only temporary, but I believe this notification should appear when only tracks are negotiated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we accept everything :D


{:ok, %{state | current_remote_desc: sdp, transceivers: new_transceivers}}
else
nil -> {:error, :missing_ice_ufrag_or_pwd}
end
end

{:ok, %{state | current_remote_desc: sdp}}
defp update_transceivers(transceivers, sdp) do
Enum.reduce_while(sdp.media, {:ok, transceivers}, fn mline, {:ok, transceivers} ->
case ExSDP.Media.get_attribute(mline, :mid) do
{:mid, mid} ->
transceivers = RTPTransceiver.update_or_create(transceivers, mid, mline)
{:cont, {:ok, transceivers}}

_other ->

Check warning on line 338 in lib/ex_webrtc/peer_connection.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/peer_connection.ex#L338

Added line #L338 was not covered by tests
{:halt, {:error, :missing_mid}}
end
end)
end

# Signaling state machine, RFC 8829 3.2
Expand All @@ -326,4 +365,6 @@

defp maybe_next_state(:have_remote_pranswer, :remote, :answer), do: {:ok, :stable}
defp maybe_next_state(:have_remote_pranswer, _, _), do: {:error, :invalid_transition}

defp notify(pid, msg), do: send(pid, {:ex_webrtc, self(), msg})
end
2 changes: 1 addition & 1 deletion lib/ex_webrtc/peer_connection/configuration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand Down
38 changes: 38 additions & 0 deletions lib/ex_webrtc/rtp_transceiver.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule ExWebRTC.RTPTransceiver do
LVala marked this conversation as resolved.
Show resolved Hide resolved
@moduledoc """
RTPTransceiver
"""

@type t() :: %__MODULE__{
mid: String.t(),
direction: :sendonly | :recvonly | :sendrecv | :inactive | :stopped,
kind: :audio | :video
}

@enforce_keys [:mid, :direction, :kind]
defstruct @enforce_keys

@doc false
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

# searches for transceiver for a given mline
# if it exists, updates its configuration
# if it doesn't exist, creats a new one
# returns list of updated transceivers
@doc false
def update_or_create(transceivers, mid, mline) do
case find_by_mid(transceivers, mid) do
{idx, %__MODULE__{} = tr} ->
List.replace_at(transceivers, idx, update(tr, mline))

nil ->
transceivers ++ [%__MODULE__{mid: mid, direction: :recvonly, kind: mline.type}]
end
end

defp update(transceiver, _mline), do: transceiver
end
6 changes: 6 additions & 0 deletions lib/ex_webrtc/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@
|> :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]

Check warning on line 13 in lib/ex_webrtc/utils.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/utils.ex#L12-L13

Added lines #L12 - L13 were not covered by tests
end)
end
end
98 changes: 98 additions & 0 deletions test/peer_connection_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
defmodule ExWebRTC.PeerConnectionTest do
use ExUnit.Case, async: true

alias ExWebRTC.{MediaStreamTrack, PeerConnection, SessionDescription}

@single_audio_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
"""

@audio_video_offer """
v=0
o=- 3253533641493747086 5 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
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:SOct
a=ice-pwd:k9PRXt7zT32ADt/juUpt4Gx3
a=ice-options:trickle
a=fingerprint:sha-256 45:B5:2D:3A:DA:29:93:27:B6:59:F1:5B:77:62:F5:C2:CE:16:8B:12:C7:B8:34:EF:C0:12:45:17:D0:1A:E6:F4
a=setup:actpass
a=mid:0
a=sendrecv
a=msid:- 0970fb0b-4750-4302-902e-70d2e403ad0d
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=ssrc:560549895 cname:QQJypppcjR+gR484
a=ssrc:560549895 msid:- 0970fb0b-4750-4302-902e-70d2e403ad0d
m=video 9 UDP/TLS/RTP/SAVPF 96
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:SOct
a=ice-pwd:k9PRXt7zT32ADt/juUpt4Gx3
a=ice-options:trickle
a=fingerprint:sha-256 45:B5:2D:3A:DA:29:93:27:B6:59:F1:5B:77:62:F5:C2:CE:16:8B:12:C7:B8:34:EF:C0:12:45:17:D0:1A:E6:F4
a=setup:actpass
a=mid:1
a=sendrecv
a=msid:- 1259ea70-c6b7-445a-9c20-49cec7433ccb
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=ssrc-group:FID 381060598 184440407
a=ssrc:381060598 cname:QQJypppcjR+gR484
a=ssrc:381060598 msid:- 1259ea70-c6b7-445a-9c20-49cec7433ccb
a=ssrc:184440407 cname:QQJypppcjR+gR484
a=ssrc:184440407 msid:- 1259ea70-c6b7-445a-9c20-49cec7433ccb
"""

test "transceivers" do
{:ok, pc} = PeerConnection.start_link()

offer = %SessionDescription{type: :offer, sdp: @single_audio_offer}
:ok = PeerConnection.set_remote_description(pc, offer)

assert_receive {:ex_webrtc, ^pc, {:track, %MediaStreamTrack{mid: "0", kind: :audio}}}

offer = %SessionDescription{type: :offer, sdp: @audio_video_offer}
:ok = PeerConnection.set_remote_description(pc, offer)

assert_receive {:ex_webrtc, ^pc, {:track, %MediaStreamTrack{mid: "1", kind: :video}}}
refute_receive {:ex_webrtc, ^pc, {:track, %MediaStreamTrack{}}}
end
end
Loading