Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 committed Oct 19, 2023
1 parent 0e615c5 commit 33c2479
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 48 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
7 changes: 7 additions & 0 deletions lib/ex_webrtc/media_stream_track.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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.
defstruct []

def from_transceiver(_tr) do
%__MODULE__{}
end
end
88 changes: 42 additions & 46 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, RTPTransceiver, SessionDescription}
alias ExWebRTC.{IceCandidate, MediaStreamTrack, RTPTransceiver, SessionDescription}

import ExWebRTC.Utils

Expand All @@ -28,7 +28,7 @@ defmodule ExWebRTC.PeerConnection do
:dtls_client,
:dtls_buffered_packets,
dtls_finished: false,
transceivers: %{},
transceivers: [],
signaling_state: :stable
]

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

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

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/peer_connection.ex#L240

Added line #L240 was not covered by tests

{:noreply, state}
end
Expand Down Expand Up @@ -293,53 +298,42 @@ 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)
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

defp find_or_create_transceiver(mid, :sendonly, _transceivers) do
%RTPTransceiver{mid: mid, direction: :recvonly}
end
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}}

defp get_media_direction(media) do
Enum.find(media.attributes, fn attr ->
attr in [:sendrecv, :sendonly, :recvonly, :inactive]
_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

Expand Down Expand Up @@ -367,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
38 changes: 38 additions & 0 deletions lib/ex_webrtc/rtp_transceiver.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,47 @@
defmodule ExWebRTC.RTPTransceiver do
@moduledoc """
RTPTransceiver
"""

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

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

@doc false
def find_by_mid(transceivers, mid) do
transceivers
|> Enum.with_index(fn tr, idx -> {idx, tr} end)

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

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/rtp_transceiver.ex#L17

Added line #L17 was not covered by tests
|> 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 31 in lib/ex_webrtc/rtp_transceiver.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/rtp_transceiver.ex#L29-L31

Added lines #L29 - L31 were not covered by tests
end

nil ->
transceivers ++ [%__MODULE__{mid: mid, direction: :recvonly}]
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}
other_direction -> {:ok, %__MODULE__{transceiver | direction: other_direction}}

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#L42-L44

Added lines #L42 - L44 were not covered by tests
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]

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
41 changes: 41 additions & 0 deletions test/peer_connection_test.exs
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 33c2479

Please sign in to comment.