Skip to content

Commit 72c80d6

Browse files
committed
Add stop_transceiver/2
1 parent 1154d15 commit 72c80d6

File tree

8 files changed

+642
-118
lines changed

8 files changed

+642
-118
lines changed

lib/ex_webrtc/peer_connection.ex

Lines changed: 276 additions & 93 deletions
Large diffs are not rendered by default.

lib/ex_webrtc/rtp_transceiver.ex

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,30 @@ defmodule ExWebRTC.RTPTransceiver do
2020
@type t() :: %__MODULE__{
2121
id: id(),
2222
mid: String.t() | nil,
23+
mline_idx: non_neg_integer() | nil,
2324
direction: direction(),
2425
current_direction: direction() | nil,
2526
fired_direction: direction() | nil,
2627
kind: kind(),
2728
rtp_hdr_exts: [ExSDP.Attribute.Extmap.t()],
2829
codecs: [RTPCodecParameters.t()],
2930
receiver: RTPReceiver.t(),
30-
sender: RTPSender.t()
31+
sender: RTPSender.t(),
32+
stopping: boolean(),
33+
stopped: boolean()
3134
}
3235

3336
@enforce_keys [:id, :direction, :kind, :sender, :receiver]
3437
defstruct @enforce_keys ++
3538
[
3639
:mid,
40+
:mline_idx,
3741
:current_direction,
3842
:fired_direction,
3943
codecs: [],
40-
rtp_hdr_exts: []
44+
rtp_hdr_exts: [],
45+
stopping: false,
46+
stopped: false
4147
]
4248

4349
@doc false
@@ -77,8 +83,8 @@ defmodule ExWebRTC.RTPTransceiver do
7783
end
7884

7985
@doc false
80-
@spec from_mline(ExSDP.Media.t(), Configuration.t()) :: t()
81-
def from_mline(mline, config) do
86+
@spec from_mline(ExSDP.Media.t(), non_neg_integer(), Configuration.t()) :: t()
87+
def from_mline(mline, mline_idx, config) do
8288
codecs = get_codecs(mline, config)
8389
rtp_hdr_exts = get_rtp_hdr_extensions(mline, config)
8490
{:mid, mid} = ExSDP.get_attribute(mline, :mid)
@@ -88,6 +94,7 @@ defmodule ExWebRTC.RTPTransceiver do
8894
%__MODULE__{
8995
id: Utils.generate_id(),
9096
mid: mid,
97+
mline_idx: mline_idx,
9198
direction: :recvonly,
9299
kind: mline.type,
93100
codecs: codecs,
@@ -115,30 +122,58 @@ defmodule ExWebRTC.RTPTransceiver do
115122
@doc false
116123
@spec to_answer_mline(t(), ExSDP.Media.t(), Keyword.t()) :: ExSDP.Media.t()
117124
def to_answer_mline(transceiver, mline, opts) do
118-
if transceiver.codecs == [] do
119-
# reject mline and skip further processing
120-
# see RFC 8299 sec. 5.3.1 and RFC 3264 sec. 6
121-
%ExSDP.Media{mline | port: 0}
122-
else
123-
offered_direction = SDPUtils.get_media_direction(mline)
124-
direction = get_direction(offered_direction, transceiver.direction)
125-
opts = Keyword.put(opts, :direction, direction)
126-
to_mline(transceiver, opts)
125+
offered_direction = SDPUtils.get_media_direction(mline)
126+
direction = get_direction(offered_direction, transceiver.direction)
127+
opts = Keyword.put(opts, :direction, direction)
128+
129+
# Reject mline. See RFC 8829 sec. 5.3.1 and RFC 3264 sec. 6.
130+
# We could reject earlier (as RFC suggests) but we generate
131+
# answer mline at first to have consistent fingerprint, ice_ufrag and
132+
# ice_pwd values across mlines.
133+
cond do
134+
transceiver.codecs == [] ->
135+
# there has to be at least one format so take it from the offer
136+
codecs = SDPUtils.get_rtp_codec_parameters(mline)
137+
transceiver = %__MODULE__{transceiver | codecs: codecs}
138+
mline = to_mline(transceiver, opts)
139+
%ExSDP.Media{mline | port: 0}
140+
141+
transceiver.stopping == true or transceiver.stopped == true ->
142+
mline = to_mline(transceiver, opts)
143+
%ExSDP.Media{mline | port: 0}
144+
145+
true ->
146+
to_mline(transceiver, opts)
127147
end
128148
end
129149

130150
@doc false
131151
@spec to_offer_mline(t(), Keyword.t()) :: ExSDP.Media.t()
132152
def to_offer_mline(transceiver, opts) do
133-
to_mline(transceiver, opts)
153+
mline = to_mline(transceiver, opts)
154+
if transceiver.stopping, do: %ExSDP.Media{mline | port: 0}, else: mline
134155
end
135156

136157
@doc false
137158
# asssings mid to the transceiver and its sender
138159
@spec assign_mid(t(), String.t()) :: t()
139160
def assign_mid(transceiver, mid) do
140161
sender = %RTPSender{transceiver.sender | mid: mid}
141-
%{transceiver | mid: mid, sender: sender}
162+
%__MODULE__{transceiver | mid: mid, sender: sender}
163+
end
164+
165+
@spec stop(t()) :: t()
166+
def stop(transceiver) do
167+
tr = if transceiver.stopping, do: transceiver, else: stop_sending_and_receiving(transceiver)
168+
# should we reset stopping or leave it as true?
169+
%__MODULE__{tr | stopped: true, stopping: false, current_direction: nil}
170+
end
171+
172+
@spec stop_sending_and_receiving(t()) :: t()
173+
def stop_sending_and_receiving(transceiver) do
174+
# TODO send RTCP BYE for each RTP stream
175+
# TODO stop receiving media
176+
%__MODULE__{transceiver | direction: :inactive, stopping: true}
142177
end
143178

144179
defp to_mline(transceiver, opts) do

lib/ex_webrtc/sdp_utils.ex

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,25 @@ defmodule ExWebRTC.SDPUtils do
253253
|> Map.new()
254254
end
255255

256+
@spec find_mline_by_mid(ExSDP.t(), binary()) :: ExSDP.Media.t() | nil
257+
def find_mline_by_mid(sdp, mid) do
258+
Enum.find(sdp.media, fn mline ->
259+
{:mid, mline_mid} = ExSDP.get_attribute(mline, :mid)
260+
mline_mid == mid
261+
end)
262+
end
263+
264+
@spec find_free_mline_idx(ExSDP.t(), [non_neg_integer()]) :: non_neg_integer() | nil
265+
def find_free_mline_idx(sdp, indices) do
266+
sdp.media
267+
|> Stream.with_index()
268+
|> Enum.find_index(fn {mline, idx} -> mline.port == 0 and idx not in indices end)
269+
end
270+
271+
@spec is_rejected(ExSDP.Media.t()) :: boolean()
272+
def is_rejected(%ExSDP.Media{port: 0}), do: true
273+
def is_rejected(%ExSDP.Media{}), do: false
274+
256275
defp do_get_ice_credentials(sdp_or_mline) do
257276
ice_ufrag =
258277
case ExSDP.get_attribute(sdp_or_mline, :ice_ufrag) do

mix.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule ExWebRTC.MixProject do
99
app: :ex_webrtc,
1010
version: @version,
1111
elixir: "~> 1.15",
12+
elixirc_paths: elixirc_paths(Mix.env()),
1213
start_permanent: Mix.env() == :prod,
1314
description: "Implementation of WebRTC",
1415
package: package(),
@@ -36,6 +37,9 @@ defmodule ExWebRTC.MixProject do
3637
]
3738
end
3839

40+
defp elixirc_paths(:test), do: ["lib", "test/support"]
41+
defp elixirc_paths(_env), do: ["lib"]
42+
3943
def package do
4044
[
4145
licenses: ["Apache-2.0"],

test/ex_webrtc/peer_connection_test.exs

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
defmodule ExWebRTC.PeerConnectionTest do
22
use ExUnit.Case, async: true
33

4+
import ExWebRTC.Support.TestUtils
5+
46
alias ExWebRTC.{
57
RTPTransceiver,
68
RTPSender,
@@ -441,6 +443,104 @@ defmodule ExWebRTC.PeerConnectionTest do
441443
end
442444
end
443445

446+
describe "stop_transceiver/2" do
447+
test "with renegotiation" do
448+
{:ok, pc1} = PeerConnection.start_link()
449+
{:ok, pc2} = PeerConnection.start_link()
450+
{:ok, tr} = PeerConnection.add_transceiver(pc1, :audio)
451+
452+
assert_receive {:ex_webrtc, ^pc1, :negotiation_needed}
453+
454+
:ok = negotiate(pc1, pc2)
455+
456+
:ok = PeerConnection.stop_transceiver(pc1, tr.id)
457+
458+
assert [
459+
%RTPTransceiver{
460+
current_direction: :sendonly,
461+
direction: :inactive,
462+
stopping: true,
463+
stopped: false
464+
}
465+
] = PeerConnection.get_transceivers(pc1)
466+
467+
assert_receive {:ex_webrtc, ^pc1, :negotiation_needed}
468+
469+
{:ok, offer} = PeerConnection.create_offer(pc1)
470+
:ok = PeerConnection.set_local_description(pc1, offer)
471+
472+
# nothing should change
473+
assert [
474+
%RTPTransceiver{
475+
current_direction: :sendonly,
476+
direction: :inactive,
477+
stopping: true,
478+
stopped: false
479+
}
480+
] = PeerConnection.get_transceivers(pc1)
481+
482+
# on the remote side, transceiver should be stopped
483+
# immediately after setting remote description
484+
:ok = PeerConnection.set_remote_description(pc2, offer)
485+
486+
assert [
487+
%RTPTransceiver{
488+
current_direction: nil,
489+
direction: :inactive,
490+
stopping: false,
491+
stopped: true
492+
}
493+
] = PeerConnection.get_transceivers(pc2)
494+
495+
{:ok, answer} = PeerConnection.create_answer(pc2)
496+
:ok = PeerConnection.set_local_description(pc2, answer)
497+
498+
assert [] == PeerConnection.get_transceivers(pc2)
499+
500+
:ok = PeerConnection.set_remote_description(pc1, answer)
501+
502+
assert [] == PeerConnection.get_transceivers(pc1)
503+
504+
# renegotiate without changes
505+
{:ok, offer} = PeerConnection.create_offer(pc1)
506+
sdp = ExSDP.parse!(offer.sdp)
507+
assert Enum.count(sdp.media) == 1
508+
509+
:ok = PeerConnection.set_local_description(pc1, offer)
510+
assert [] == PeerConnection.get_transceivers(pc1)
511+
512+
# on setting remote description, a stopped transceiver
513+
# should be created and on setting local description
514+
# it should be removed
515+
:ok = PeerConnection.set_remote_description(pc2, offer)
516+
517+
[
518+
%RTPTransceiver{
519+
current_direction: nil,
520+
direction: :inactive,
521+
stopped: true,
522+
stopping: false
523+
}
524+
] = PeerConnection.get_transceivers(pc2)
525+
526+
{:ok, answer} = PeerConnection.create_answer(pc2)
527+
sdp = ExSDP.parse!(answer.sdp)
528+
assert Enum.count(sdp.media) == 1
529+
530+
:ok = PeerConnection.set_local_description(pc2, answer)
531+
assert [] == PeerConnection.get_transceivers(pc2)
532+
533+
:ok = PeerConnection.set_remote_description(pc1, answer)
534+
assert [] == PeerConnection.get_transceivers(pc1)
535+
end
536+
537+
test "with invalid transceiver id" do
538+
{:ok, pc} = PeerConnection.start_link()
539+
{:ok, tr} = PeerConnection.add_transceiver(pc, :audio)
540+
assert {:error, :invalid_transceiver_id} == PeerConnection.stop_transceiver(pc, tr.id + 1)
541+
end
542+
end
543+
444544
describe "add_track/2" do
445545
test "with no available transceivers" do
446546
{:ok, pc} = PeerConnection.start_link()
@@ -785,14 +885,4 @@ defmodule ExWebRTC.PeerConnectionTest do
785885
assert [%RTPTransceiver{direction: :inactive, current_direction: :inactive}] =
786886
PeerConnection.get_transceivers(pc2)
787887
end
788-
789-
defp negotiate(pc1, pc2) do
790-
{:ok, offer} = PeerConnection.create_offer(pc1)
791-
:ok = PeerConnection.set_local_description(pc1, offer)
792-
:ok = PeerConnection.set_remote_description(pc2, offer)
793-
{:ok, answer} = PeerConnection.create_answer(pc2)
794-
:ok = PeerConnection.set_local_description(pc2, answer)
795-
:ok = PeerConnection.set_remote_description(pc1, answer)
796-
:ok
797-
end
798888
end

0 commit comments

Comments
 (0)