Skip to content

Commit db02ef6

Browse files
committed
Restart connection on tracks change
1 parent 7e72442 commit db02ef6

File tree

5 files changed

+105
-67
lines changed

5 files changed

+105
-67
lines changed

assets/player.js

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,43 @@
11
export function createPlayerHook(iceServers = []) {
22
return {
33
async mounted() {
4-
this.videoQuality = document.getElementById("lexp-video-quality");
5-
this.videoQuality.onchange = () => {
6-
this.pushEventTo(this.el, "layer", this.videoQuality.value);
4+
const view = this;
5+
6+
view.handleEvent(`connect-${view.el.id}`, async () => await view.connect(view));
7+
8+
const eventName = "answer" + "-" + view.el.id;
9+
view.handleEvent(eventName, async (answer) => {
10+
if (view.pc) {
11+
await view.pc.setRemoteDescription(answer);
12+
}
13+
});
14+
15+
view.videoQuality = document.getElementById("lexp-video-quality");
16+
view.videoQuality.onchange = () => {
17+
view.pushEventTo(view.el, "layer", view.videoQuality.value);
718
};
19+
},
820

9-
this.pc = new RTCPeerConnection({ iceServers: iceServers });
21+
async connect(view) {
22+
view.el.srcObject = undefined;
23+
view.pc = new RTCPeerConnection({ iceServers: iceServers });
1024

11-
this.pc.onicecandidate = (ev) => {
12-
this.pushEventTo(this.el, "ice", JSON.stringify(ev.candidate));
25+
view.pc.onicecandidate = (ev) => {
26+
view.pushEventTo(view.el, "ice", JSON.stringify(ev.candidate));
1327
};
1428

15-
this.pc.ontrack = (ev) => {
16-
if (!this.el.srcObject) {
17-
this.el.srcObject = ev.streams[0];
29+
view.pc.ontrack = (ev) => {
30+
if (!view.el.srcObject) {
31+
view.el.srcObject = ev.streams[0];
1832
}
1933
};
20-
this.pc.addTransceiver("audio", { direction: "recvonly" });
21-
this.pc.addTransceiver("video", { direction: "recvonly" });
34+
view.pc.addTransceiver("audio", { direction: "recvonly" });
35+
view.pc.addTransceiver("video", { direction: "recvonly" });
2236

23-
const offer = await this.pc.createOffer();
24-
await this.pc.setLocalDescription(offer);
25-
26-
const eventName = "answer" + "-" + this.el.id;
27-
this.handleEvent(eventName, async (answer) => {
28-
await this.pc.setRemoteDescription(answer);
29-
});
37+
const offer = await view.pc.createOffer();
38+
await view.pc.setLocalDescription(offer);
3039

31-
this.pushEventTo(this.el, "offer", offer);
40+
view.pushEventTo(view.el, "offer", offer);
3241
},
3342
};
3443
}

lib/live_ex_webrtc/player.ex

Lines changed: 71 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ defmodule LiveExWebRTC.Player do
6161

6262
require Logger
6363

64-
import LiveExWebRTC.CoreComponents
65-
6664
alias ExWebRTC.RTPCodecParameters
6765
alias ExWebRTC.RTP.{H264, VP8}
6866
alias LiveExWebRTC.Player
@@ -142,7 +140,6 @@ defmodule LiveExWebRTC.Player do
142140
* `id` - player id. This is typically your user id (if there is users database).
143141
It is used to identify live view and generated HTML video player.
144142
* `publisher_id` - publisher id that this player is going to subscribe to.
145-
* `pubsub` - a pubsub that player live view will subscribe to for audio and video packets. See module doc for more.
146143
* `on_connected` - callback called when the underlying peer connection changes its state to the `:connected`. See `t:on_connected/0`.
147144
* `on_packet` - callback called for each audio and video RTP packet. Can be used to modify the packet before sending via WebRTC to the other side. See `t:on_packet/0`.
148145
* `ice_servers` - a list of `t:ExWebRTC.PeerConnection.Configuration.ice_server/0`,
@@ -224,7 +221,10 @@ defmodule LiveExWebRTC.Player do
224221
</div>
225222
<div class="py-4 px-8 pr-4 ">
226223
<select id="lexp-video-quality" class="z-40 ">
227-
<option :for={{id, layer} <- @player.video_layers} value={id}>{layer}</option>
224+
<%= for {id, layer} <- @player.video_layers do %>
225+
<option :if={id == @player.layer} value={id} selected>{layer}</option>
226+
<option :if={id != @player.layer} value={id}>{layer}</option>
227+
<% end %>
228228
</select>
229229
</div>
230230
</div>
@@ -269,11 +269,18 @@ defmodule LiveExWebRTC.Player do
269269

270270
# subscribe only if we managed to negotiate tracks
271271
if player.audio_track_id != nil do
272-
PubSub.subscribe(player.pubsub, "streams:audio:#{player.publisher_id}")
272+
PubSub.subscribe(
273+
player.pubsub,
274+
"streams:audio:#{player.publisher_id}:#{player.publisher_audio_track.id}"
275+
)
273276
end
274277

275278
if player.video_track_id != nil do
276-
PubSub.subscribe(player.pubsub, "streams:video:#{player.publisher_id}:#{player.layer}")
279+
PubSub.subscribe(
280+
player.pubsub,
281+
"streams:video:#{player.publisher_id}:#{player.publisher_video_track.id}:#{player.layer}"
282+
)
283+
277284
broadcast_keyframe_req(socket)
278285
end
279286

@@ -300,35 +307,57 @@ defmodule LiveExWebRTC.Player do
300307
def handle_info({:live_ex_webrtc, :info, publisher_audio_track, publisher_video_track}, socket) do
301308
%{player: player} = socket.assigns
302309

303-
player =
304-
case player do
305-
%Player{publisher_audio_track: nil, publisher_video_track: nil} ->
306-
video_layers = (publisher_video_track && publisher_video_track.rids) || ["h"]
307-
308-
video_layers =
309-
Enum.map(video_layers, fn
310-
"h" -> {"h", "high"}
311-
"m" -> {"m", "medium"}
312-
"l" -> {"l", "low"}
313-
end)
314-
315-
%Player{
316-
player
317-
| publisher_audio_track: publisher_audio_track,
318-
publisher_video_track: publisher_video_track,
319-
video_layers: video_layers
320-
}
321-
322-
%Player{
323-
publisher_audio_track: ^publisher_audio_track,
324-
publisher_video_track: ^publisher_video_track
325-
} ->
310+
case player do
311+
%Player{
312+
publisher_audio_track: ^publisher_audio_track,
313+
publisher_video_track: ^publisher_video_track
314+
} ->
315+
# tracks are the same, do nothing
316+
{:noreply, socket}
317+
318+
%Player{
319+
publisher_audio_track: old_publisher_audio_track,
320+
publisher_video_track: old_publisher_video_track,
321+
video_layers: old_layers
322+
} ->
323+
video_layers = (publisher_video_track && publisher_video_track.rids) || ["h"]
324+
325+
video_layers =
326+
Enum.map(video_layers, fn
327+
"h" -> {"h", "high"}
328+
"m" -> {"m", "medium"}
329+
"l" -> {"l", "low"}
330+
end)
331+
332+
player = %Player{
326333
player
327-
end
334+
| publisher_audio_track: publisher_audio_track,
335+
publisher_video_track: publisher_video_track,
336+
layer: "h",
337+
target_layer: "h",
338+
video_layers: video_layers,
339+
munger: nil
340+
}
328341

329-
socket = assign(socket, :player, player)
342+
socket = assign(socket, :player, player)
330343

331-
{:noreply, socket}
344+
if old_publisher_audio_track != nil or old_publisher_video_track != nil do
345+
PubSub.unsubscribe(
346+
player.pubsub,
347+
"streams:audio:#{player.publisher_id}:#{old_publisher_audio_track.id}"
348+
)
349+
350+
Enum.each(old_layers, fn {id, _layer} ->
351+
PubSub.unsubscribe(
352+
player.pubsub,
353+
"streams:video:#{player.publisher_id}:#{old_publisher_video_track.id}:#{id}"
354+
)
355+
end)
356+
end
357+
358+
socket = push_event(socket, "connect-#{player.id}", %{})
359+
{:noreply, socket}
360+
end
332361
end
333362

334363
def handle_info({:live_ex_webrtc, :audio, packet}, socket) do
@@ -368,7 +397,7 @@ defmodule LiveExWebRTC.Player do
368397

369398
PubSub.unsubscribe(
370399
socket.assigns.player.pubsub,
371-
"streams:video:#{player.publisher_id}:#{player.layer}"
400+
"streams:video:#{player.publisher_id}:#{player.publisher_video_track.id}:#{player.layer}"
372401
)
373402

374403
flush_layer(player.layer)
@@ -381,14 +410,7 @@ defmodule LiveExWebRTC.Player do
381410
end
382411

383412
true ->
384-
# this should never happen
385-
Logger.warning("unexpected unsubscribe")
386-
387-
PubSub.unsubscribe(
388-
socket.assigns.player.pubsub,
389-
"streams:video:#{player.publisher_id}:#{rid}"
390-
)
391-
413+
Logger.warning("Unexpected packet. Ignoring.")
392414
{:noreply, socket}
393415
end
394416
end
@@ -490,9 +512,15 @@ defmodule LiveExWebRTC.Player do
490512
if player.layer == layer do
491513
{:noreply, socket}
492514
else
515+
# this shouldn't be needed but just to make sure we won't duplicate subscription
516+
PubSub.unsubscribe(
517+
player.pubsub,
518+
"streams:video:#{player.publisher_id}:#{player.publisher_video_track.id}:#{layer}"
519+
)
520+
493521
PubSub.subscribe(
494-
socket.assigns.player.pubsub,
495-
"streams:video:#{socket.assigns.player.publisher_id}:#{layer}"
522+
player.pubsub,
523+
"streams:video:#{player.publisher_id}:#{player.publisher_video_track.id}:#{layer}"
496524
)
497525

498526
player = %Player{player | target_layer: layer}

lib/live_ex_webrtc/publisher.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ defmodule LiveExWebRTC.Publisher do
490490
# for non-simulcast track, push everything with "h" identifier
491491
PubSub.broadcast(
492492
publisher.pubsub,
493-
"streams:video:#{publisher.id}:#{rid || "h"}",
493+
"streams:video:#{publisher.id}:#{publisher.video_track.id}:#{rid || "h"}",
494494
{:live_ex_webrtc, :video, rid || "h", packet}
495495
)
496496

@@ -499,7 +499,7 @@ defmodule LiveExWebRTC.Publisher do
499499
%Publisher{audio_track: audio_track} when audio_track.id == track_id ->
500500
PubSub.broadcast(
501501
publisher.pubsub,
502-
"streams:audio:#{publisher.id}",
502+
"streams:audio:#{publisher.id}:#{publisher.audio_track.id}",
503503
{:live_ex_webrtc, :audio, packet}
504504
)
505505

mix.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ defmodule LiveExWebrtc.MixProject do
4242
[
4343
{:phoenix_live_view, "~> 1.0"},
4444
{:jason, "~> 1.0"},
45-
{:ex_webrtc, "~> 0.8.0"},
45+
# {:ex_webrtc, "~> 0.8.0"},
46+
{:ex_webrtc, github: "elixir-webrtc/ex_webrtc", override: true},
4647
{:ex_doc, "~> 0.31", only: :dev, runtime: false}
4748
]
4849
end

mix.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
1010
"ex_doc": {:hex, :ex_doc, "0.37.0", "970f92b39e62c460aa8a367508e938f5e4da6e2ff3eaed3f8530b25870f45471", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "b0ee7f17373948e0cf471e59c3a0ee42f3bd1171c67d91eb3626456ef9c6202c"},
1111
"ex_dtls": {:hex, :ex_dtls, "0.16.0", "3ae38025ccc77f6db573e2e391602fa9bbc02253c137d8d2d59469a66cbe806b", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2a4e30d74c6ddf95cc5b796423293c06a0da295454c3823819808ff031b4b361"},
12-
"ex_ice": {:hex, :ex_ice, "0.9.3", "46700963acaba72737032500b6ee298a4effa7ad7189ab48887be5e9f4fe2107", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "4fd98d20a39ab70a62dd301c44e87437d479292c528ec7f21522ebfe0654b9cb"},
12+
"ex_ice": {:hex, :ex_ice, "0.9.4", "793121989164e49d8dc64b82bcb7842a4c2e0d224a2f00379ab415293a78c8e7", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "fc328ed721c558440266def81a2cd5138d163164218ebe449fa9a10fcda72574"},
1313
"ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"},
1414
"ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"},
1515
"ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"},
1616
"ex_sdp": {:hex, :ex_sdp, "1.1.1", "1a7b049491e5ec02dad9251c53d960835dc5631321ae978ec331831f3e4f6d5f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "1b13a72ac9c5c695b8824dbdffc671be8cbb4c0d1ccb4ff76a04a6826759f233"},
1717
"ex_stun": {:hex, :ex_stun, "0.2.0", "feb1fc7db0356406655b2a617805e6c712b93308c8ea2bf0ba1197b1f0866deb", [:mix], [], "hexpm", "1e01ba8290082ccbf37acaa5190d1f69b51edd6de2026a8d6d51368b29d115d0"},
1818
"ex_turn": {:hex, :ex_turn, "0.2.0", "4e1f9b089e9a5ee44928d12370cc9ea7a89b84b2f6256832de65271212eb80de", [:mix], [{:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "08e884f0af2c4a147e3f8cd4ffe33e3452a256389f0956e55a8c4d75bf0e74cd"},
19-
"ex_webrtc": {:hex, :ex_webrtc, "0.8.0", "109f408e9a4e687018b365cbb94de554f184d31bb42623e2f7a93de06575fd30", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: false]}, {:ex_dtls, "~> 0.16.0", [hex: :ex_dtls, repo: "hexpm", optional: false]}, {:ex_ice, "~> 0.9.0", [hex: :ex_ice, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.7.1", [hex: :ex_libsrtp, repo: "hexpm", optional: false]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:ex_sctp, "0.1.2", [hex: :ex_sctp, repo: "hexpm", optional: true]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}], "hexpm", "ce5c1b2792589975c7d59d56bad367dbe7d7fab35195e3edd85bbc72dd3bd621"},
19+
"ex_webrtc": {:git, "https://github.com/elixir-webrtc/ex_webrtc.git", "d3ec3c94f371e72ab5550c701ff023b0a2fd5905", []},
2020
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
2121
"hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"},
2222
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},

0 commit comments

Comments
 (0)