Skip to content

Commit

Permalink
Create transceivers on set_remote_description
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 committed Oct 19, 2023
1 parent 3c4fbfa commit c2a8c6d
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 9 deletions.
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
19 changes: 19 additions & 0 deletions lib/ex_webrtc/media_stream_track.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule ExWebRTC.MediaStreamTrack do

Check warning on line 1 in lib/ex_webrtc/media_stream_track.ex

View workflow job for this annotation

GitHub Actions / Lint (OTP 26 / Elixir 1.15)

Modules should have a @moduledoc tag.
@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
51 changes: 44 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 @@ defmodule ExWebRTC.PeerConnection do

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 @@ defmodule ExWebRTC.PeerConnection do
GenServer.call(peer_connection, {:add_ice_candidate, candidate})
end

@spec get_transceivers(peer_connection()) :: {:ok, [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 @@ -232,7 +237,7 @@ defmodule ExWebRTC.PeerConnection do
# 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 +298,43 @@ 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)
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
notify(state.owner, {:track, track})
end

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

{:ok, %{state | current_remote_desc: sdp}}
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} ->
transceivers = RTPTransceiver.update_or_create(transceivers, mid, mline)
{:cont, {:ok, transceivers}}

_other ->

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

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/peer_connection.ex#L334

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

# Signaling state machine, RFC 8829 3.2
Expand All @@ -326,4 +361,6 @@ defmodule ExWebRTC.PeerConnection do

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
48 changes: 48 additions & 0 deletions lib/ex_webrtc/rtp_transceiver.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
defmodule ExWebRTC.RTPTransceiver do
@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} ->
case update(tr, mline) do
{:ok, tr} -> List.replace_at(transceivers, idx, tr)
{:error, :remove} -> List.delete_at(transceivers, idx)

Check warning on line 32 in lib/ex_webrtc/rtp_transceiver.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/rtp_transceiver.ex#L32

Added line #L32 was not covered by tests
end

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

defp 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}

Check warning on line 44 in lib/ex_webrtc/rtp_transceiver.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/rtp_transceiver.ex#L44

Added line #L44 was not covered by tests
other_direction -> {:ok, %__MODULE__{transceiver | direction: other_direction}}
end
end
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 @@ 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
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

0 comments on commit c2a8c6d

Please sign in to comment.