@@ -68,10 +68,11 @@ defmodule LiveExWebRTC.Publisher do
68
68
use Phoenix.LiveView
69
69
70
70
alias LiveExWebRTC.Publisher
71
- alias ExWebRTC . { ICECandidate , PeerConnection , SessionDescription }
71
+ alias ExWebRTC . { ICECandidate , PeerConnection , Recorder , SessionDescription }
72
72
alias Phoenix.PubSub
73
73
74
74
@ type on_connected ( ) :: ( publisher_id :: String . t ( ) -> any ( ) )
75
+ @ type on_disconnected ( ) :: ( publisher_id :: String . t ( ) -> any ( ) )
75
76
76
77
@ type on_packet ( ) ::
77
78
( publisher_id :: String . t ( ) ,
@@ -85,11 +86,14 @@ defmodule LiveExWebRTC.Publisher do
85
86
defstruct id: nil ,
86
87
pc: nil ,
87
88
streaming?: false ,
89
+ recording?: false ,
88
90
audio_track_id: nil ,
89
91
video_track_id: nil ,
90
92
on_packet: nil ,
91
93
on_connected: nil ,
94
+ on_disconnected: nil ,
92
95
pubsub: nil ,
96
+ recorder: nil ,
93
97
ice_servers: nil ,
94
98
ice_ip_filter: nil ,
95
99
ice_port_range: nil ,
@@ -126,7 +130,9 @@ defmodule LiveExWebRTC.Publisher do
126
130
* `id` - publisher id. This is typically your user id (if there is users database).
127
131
It is used to identify live view and generated HTML elements.
128
132
* `pubsub` - a pubsub that publisher live view will use for broadcasting audio and video packets received from a browser. See module doc for more.
133
+ * `recorder` - WRITEME `t:GenServer.server/0`,
129
134
* `on_connected` - callback called when the underlying peer connection changes its state to the `:connected`. See `t:on_connected/0`.
135
+ * `on_disconnected` - callback called when the underlying peer connection process terminates. See `t:on_disconnected/0`.
130
136
* `on_packet` - callback called for each audio and video RTP packet. Can be used to modify the packet before publishing it on a pubsub. See `t:on_packet/0`.
131
137
* `ice_servers` - a list of `t:ExWebRTC.PeerConnection.Configuration.ice_server/0`,
132
138
* `ice_ip_filter` - `t:ExICE.ICEAgent.ip_filter/0`,
@@ -142,8 +148,10 @@ defmodule LiveExWebRTC.Publisher do
142
148
:id ,
143
149
:name ,
144
150
:pubsub ,
151
+ :recorder ,
145
152
:on_packet ,
146
153
:on_connected ,
154
+ :on_disconnected ,
147
155
:ice_servers ,
148
156
:ice_ip_filter ,
149
157
:ice_port_range ,
@@ -155,8 +163,10 @@ defmodule LiveExWebRTC.Publisher do
155
163
publisher = % Publisher {
156
164
id: Keyword . fetch! ( opts , :id ) ,
157
165
pubsub: Keyword . fetch! ( opts , :pubsub ) ,
166
+ recorder: Keyword . get ( opts , :recorder ) ,
158
167
on_packet: Keyword . get ( opts , :on_packet ) ,
159
168
on_connected: Keyword . get ( opts , :on_connected ) ,
169
+ on_disconnected: Keyword . get ( opts , :on_disconnected ) ,
160
170
ice_servers: Keyword . get ( opts , :ice_servers , [ % { urls: "stun:stun.l.google.com:19302" } ] ) ,
161
171
ice_ip_filter: Keyword . get ( opts , :ice_ip_filter ) ,
162
172
ice_port_range: Keyword . get ( opts , :ice_port_range ) ,
@@ -255,7 +265,7 @@ defmodule LiveExWebRTC.Publisher do
255
265
< input
256
266
type = "text "
257
267
id = "lex-fps "
258
- value = "24 "
268
+ value = "30 "
259
269
class = "rounded-lg disabled:text-gray-400 disabled:border-gray-400 focus:border-brand focus:outline-none focus:ring-0 "
260
270
/>
261
271
</ div >
@@ -268,6 +278,10 @@ defmodule LiveExWebRTC.Publisher do
268
278
class = "rounded-lg disabled:text-gray-400 disabled:border-gray-400 focus:border-brand focus:outline-none focus:ring-0 "
269
279
/>
270
280
</ div >
281
+ < div :if = { @ publisher . recorder } class = "flex gap-2.5 items-center " >
282
+ < label for = "lex-record-stream " > Record stream:</ label >
283
+ < input type = "checkbox " id = "lex-record-stream " class = "rounded-full " checked />
284
+ </ div >
271
285
</ div >
272
286
< button id = "lex-video-apply-button " class = "rounded-lg px-10 py-2.5 bg-brand disabled:bg-brand/50 hover:bg-brand/90 text-white font-bold " disabled > Apply</ button >
273
287
</ details >
@@ -358,37 +372,43 @@ defmodule LiveExWebRTC.Publisher do
358
372
def handle_info ( { :ex_webrtc , _pc , { :rtp , track_id , nil , packet } } , socket ) do
359
373
% { publisher: publisher } = socket . assigns
360
374
361
- case publisher do
362
- % Publisher { video_track_id: ^ track_id } ->
363
- packet =
364
- if publisher . on_packet ,
365
- do: publisher . on_packet . ( publisher . id , :video , packet , socket ) ,
366
- else: packet
367
-
368
- PubSub . broadcast (
369
- publisher . pubsub ,
370
- "streams:video:#{ publisher . id } " ,
371
- { :live_ex_webrtc , :video , packet }
372
- )
375
+ kind =
376
+ case publisher do
377
+ % Publisher { video_track_id: ^ track_id } -> :video
378
+ % Publisher { audio_track_id: ^ track_id } -> :audio
379
+ end
373
380
374
- { :noreply , socket }
381
+ packet =
382
+ if publisher . on_packet ,
383
+ do: publisher . on_packet . ( publisher . id , kind , packet , socket ) ,
384
+ else: packet
375
385
376
- % Publisher { audio_track_id: ^ track_id } ->
377
- PubSub . broadcast (
378
- publisher . pubsub ,
379
- "streams:audio:#{ publisher . id } " ,
380
- { :live_ex_webrtc , :audio , packet }
381
- )
386
+ if publisher . recording? , do: Recorder . record ( publisher . recorder , track_id , nil , packet )
382
387
383
- if publisher . on_packet , do: publisher . on_packet . ( publisher . id , :audio , packet , socket )
384
- { :noreply , socket }
385
- end
388
+ PubSub . broadcast (
389
+ publisher . pubsub ,
390
+ "streams:#{ kind } :#{ publisher . id } " ,
391
+ { :live_ex_webrtc , kind , packet }
392
+ )
393
+
394
+ { :noreply , socket }
386
395
end
387
396
388
397
@ impl true
389
398
def handle_info ( { :ex_webrtc , _pid , { :connection_state_change , :connected } } , socket ) do
390
399
% { publisher: pub } = socket . assigns
400
+
401
+ if pub . recording? do
402
+ [
403
+ % { kind: :audio , receiver: % { track: audio_track } } ,
404
+ % { kind: :video , receiver: % { track: video_track } }
405
+ ] = PeerConnection . get_transceivers ( pub . pc )
406
+
407
+ Recorder . add_tracks ( pub . recorder , [ audio_track , video_track ] )
408
+ end
409
+
391
410
if pub . on_connected , do: pub . on_connected . ( pub . id )
411
+
392
412
{ :noreply , socket }
393
413
end
394
414
@@ -397,6 +417,40 @@ defmodule LiveExWebRTC.Publisher do
397
417
{ :noreply , socket }
398
418
end
399
419
420
+ @ impl true
421
+ def handle_info ( { :DOWN , _ref , :process , pid , _reason } , socket ) do
422
+ % { publisher: pub } = socket . assigns
423
+
424
+ if pid == pub . pc do
425
+ recorder_result =
426
+ if pub . recording? do
427
+ { :ok , manifest , maybe_upload_ref } =
428
+ Recorder . end_tracks ( pub . recorder , [ pub . audio_track_id , pub . video_track_id ] )
429
+
430
+ { manifest , maybe_upload_ref }
431
+ end
432
+
433
+ if pub . on_disconnected , do: pub . on_disconnected . ( pub . id , recorder_result )
434
+ end
435
+
436
+ { :noreply , socket }
437
+ end
438
+
439
+ @ impl true
440
+ def handle_event ( "config-update" , config , socket ) do
441
+ % { publisher: publisher } = socket . assigns
442
+
443
+ new_publisher = % Publisher {
444
+ publisher
445
+ | recording?: config [ "recordStream" ]
446
+ }
447
+
448
+ # XXX I'm 95% sure there's a race condition here somewhere
449
+ { :noreply ,
450
+ socket
451
+ |> assign ( publisher: new_publisher ) }
452
+ end
453
+
400
454
@ impl true
401
455
def handle_event ( "start-streaming" , _ , socket ) do
402
456
{ :noreply ,
@@ -418,6 +472,7 @@ defmodule LiveExWebRTC.Publisher do
418
472
% { publisher: publisher } = socket . assigns
419
473
offer = SessionDescription . from_json ( unsigned_params )
420
474
{ :ok , pc } = spawn_peer_connection ( socket )
475
+ Process . monitor ( pc )
421
476
422
477
:ok = PeerConnection . set_remote_description ( pc , offer )
423
478
0 commit comments