@@ -34,92 +34,149 @@ function BrowserBroadcaster() {
3434 const [ profileStreamKey , setProfileStreamKey ] = useState < string > ( "" )
3535
3636 const peerConnectionRef = useRef < RTCPeerConnection | null > ( null ) ;
37+ const localMediaStreamRef = useRef < MediaStream | null > ( null )
38+ const eventSourceRef = useRef < EventSource | null > ( null )
3739 const videoRef = useRef < HTMLVideoElement > ( null )
3840 const hasSignalRef = useRef < boolean > ( false ) ;
3941 const badSignalCountRef = useRef < number > ( 10 ) ;
4042
4143 const endStream = ( ) => navigate ( '/' )
4244
43- useEffect ( ( ) => {
44- peerConnectionRef . current = new RTCPeerConnection ( ) ;
45+ const stopLocalMediaStream = ( localMediaStream : MediaStream | null ) => {
46+ if ( ! localMediaStream ) {
47+ return
48+ }
49+
50+ localMediaStream
51+ . getTracks ( )
52+ . forEach ( ( streamTrack : MediaStreamTrack ) => streamTrack . stop ( ) )
53+ }
4554
46- return ( ) => peerConnectionRef . current ?. close ( )
55+ const getSenderByKind = ( peerConnection : RTCPeerConnection , kind : "audio" | "video" ) => {
56+ return peerConnection . getTransceivers ( ) . find ( transceiver => transceiver . sender . track ?. kind === kind ) ?. sender ??
57+ peerConnection . getTransceivers ( ) . find ( transceiver => transceiver . receiver . track . kind === kind ) ?. sender ??
58+ null
59+ }
60+
61+ useEffect ( ( ) => {
62+ return ( ) => {
63+ eventSourceRef . current ?. close ( )
64+ stopLocalMediaStream ( localMediaStreamRef . current )
65+ localMediaStreamRef . current = null
66+ peerConnectionRef . current ?. close ( )
67+ peerConnectionRef . current = null
68+ }
4769 } , [ ] )
4870
4971 useEffect ( ( ) => {
50- if ( useDisplayMedia === "None" || ! peerConnectionRef . current ) {
72+ if ( useDisplayMedia === "None" ) {
5173 return ;
5274 }
5375
54- let stream : MediaStream | undefined = undefined ;
55-
5676 if ( ! navigator . mediaDevices ) {
5777 setMediaAccessError ( ( ) => ErrorMessageEnum . NoMediaDevices ) ;
5878 setUseDisplayMedia ( ( ) => "None" )
5979 return
6080 }
6181
82+ let cancelled = false
83+
6284 const mediaPromise = useDisplayMedia == "Screen" ?
6385 navigator . mediaDevices . getDisplayMedia ( mediaOptions ) :
6486 navigator . mediaDevices . getUserMedia ( mediaOptions )
6587
66- mediaPromise . then ( mediaStream => {
67- if ( peerConnectionRef . current ! . connectionState === "closed" ) {
68- mediaStream
69- . getTracks ( )
70- . forEach ( mediaStreamTrack => mediaStreamTrack . stop ( ) )
88+ mediaPromise . then ( async mediaStream => {
89+ const nextLocalMediaStream = mediaStream
7190
91+ if ( cancelled ) {
92+ stopLocalMediaStream ( nextLocalMediaStream )
7293 return ;
7394 }
7495
75- stream = mediaStream
96+ const videoTrack = mediaStream . getVideoTracks ( ) [ 0 ] ?? null
97+ const audioTrack = mediaStream . getAudioTracks ( ) [ 0 ] ?? null
98+
99+ const existingPeerConnection = peerConnectionRef . current
100+ if ( existingPeerConnection ) {
101+ const videoSender = getSenderByKind ( existingPeerConnection , "video" )
102+ const audioSender = getSenderByKind ( existingPeerConnection , "audio" )
103+
104+ await Promise . all ( [
105+ videoSender ?. replaceTrack ( videoTrack ) ?? Promise . resolve ( ) ,
106+ audioSender ?. replaceTrack ( audioTrack ) ?? Promise . resolve ( ) ,
107+ ] )
108+
109+ if (
110+ cancelled ||
111+ peerConnectionRef . current !== existingPeerConnection
112+ ) {
113+ stopLocalMediaStream ( nextLocalMediaStream )
114+ return ;
115+ }
116+
117+ videoRef . current ! . srcObject = mediaStream
118+ const previousLocalMediaStream = localMediaStreamRef . current
119+ localMediaStreamRef . current = nextLocalMediaStream
120+ stopLocalMediaStream ( previousLocalMediaStream )
121+ return
122+ }
123+
124+ const peerConnection = new RTCPeerConnection ( ) ;
125+ peerConnectionRef . current = peerConnection
126+
127+ if (
128+ cancelled ||
129+ peerConnectionRef . current !== peerConnection
130+ ) {
131+ if ( peerConnectionRef . current === peerConnection ) {
132+ peerConnectionRef . current = null
133+ }
134+ peerConnection . close ( )
135+ stopLocalMediaStream ( nextLocalMediaStream )
136+ return
137+ }
138+
76139 videoRef . current ! . srcObject = mediaStream
77- const isFirefox = navigator . userAgent . toLowerCase ( ) . includes ( 'firefox' )
140+ const previousLocalMediaStream = localMediaStreamRef . current
141+ localMediaStreamRef . current = nextLocalMediaStream
142+ stopLocalMediaStream ( previousLocalMediaStream )
143+
144+ peerConnection . addTransceiver ( audioTrack ? audioTrack : "audio" , { direction : 'sendonly' } )
78145
146+ const isFirefox = navigator . userAgent . toLowerCase ( ) . includes ( 'firefox' )
79147 const encodingPrefix = "Web"
80- mediaStream
81- . getTracks ( )
82- . forEach ( mediaStreamTrack => {
83- if ( mediaStreamTrack . kind === 'audio' ) {
84- peerConnectionRef . current ! . addTransceiver ( mediaStreamTrack , {
85- direction : 'sendonly' ,
86- } )
87- } else {
88- peerConnectionRef . current ! . addTransceiver ( mediaStreamTrack , {
89- direction : 'sendonly' ,
90- sendEncodings : isFirefox ? undefined : [
91- {
92- rid : encodingPrefix + 'High' ,
93- } ,
94- {
95- rid : encodingPrefix + 'Mid' ,
96- scaleResolutionDownBy : 2.0
97- } ,
98- {
99- rid : encodingPrefix + 'Low' ,
100- scaleResolutionDownBy : 4.0
101- }
102- ]
103- } )
148+ peerConnection . addTransceiver ( videoTrack ? videoTrack : "video" , {
149+ direction : 'sendonly' ,
150+ sendEncodings : isFirefox ? undefined : [
151+ {
152+ rid : encodingPrefix + 'High' ,
153+ } ,
154+ {
155+ rid : encodingPrefix + 'Mid' ,
156+ scaleResolutionDownBy : 2.0
157+ } ,
158+ {
159+ rid : encodingPrefix + 'Low' ,
160+ scaleResolutionDownBy : 4.0
104161 }
105- } )
162+ ] ,
163+ } )
106164
107- peerConnectionRef . current ! . oniceconnectionstatechange = ( ) => {
108- if ( peerConnectionRef . current ! . iceConnectionState === 'connected' || peerConnectionRef . current ! . iceConnectionState === 'completed' ) {
165+ peerConnection . oniceconnectionstatechange = ( ) => {
166+ if ( peerConnection . iceConnectionState === 'connected' || peerConnection . iceConnectionState === 'completed' ) {
109167 setPublishSuccess ( ( ) => true )
110168 setMediaAccessError ( ( ) => null )
111169 setPeerConnectionDisconnected ( ( ) => false )
112- } else if ( peerConnectionRef . current ! . iceConnectionState === 'disconnected' || peerConnectionRef . current ! . iceConnectionState === 'failed' ) {
170+ } else if ( peerConnection . iceConnectionState === 'disconnected' || peerConnection . iceConnectionState === 'failed' ) {
113171 setPublishSuccess ( ( ) => false )
114172 setPeerConnectionDisconnected ( ( ) => true )
115173 }
116174 }
117175
118- peerConnectionRef
119- . current !
176+ peerConnection
120177 . createOffer ( )
121178 . then ( offer => {
122- peerConnectionRef . current ! . setLocalDescription ( offer )
179+ peerConnection . setLocalDescription ( offer )
123180 . catch ( ( err ) => console . error ( "SetLocalDescription" , err ) ) ;
124181
125182 fetch ( `/api/whip` , {
@@ -141,7 +198,9 @@ function BrowserBroadcaster() {
141198 throw new DOMException ( "Missing link header" ) ;
142199 }
143200
201+ eventSourceRef . current ?. close ( )
144202 const evtSource = new EventSource ( `${ parsedLinkHeader [ 'urn:ietf:params:whep:ext:core:server-sent-events' ] . url } ` )
203+ eventSourceRef . current = evtSource
145204
146205 evtSource . onerror = ( ) => evtSource . close ( ) ;
147206
@@ -150,7 +209,7 @@ function BrowserBroadcaster() {
150209
151210 return r . text ( )
152211 } ) . then ( answer => {
153- peerConnectionRef . current ! . setRemoteDescription ( {
212+ peerConnection . setRemoteDescription ( {
154213 sdp : answer ,
155214 type : 'answer'
156215 } ) . catch ( ( err ) => console . error ( "SetRemoteDescription" , err ) )
@@ -162,12 +221,7 @@ function BrowserBroadcaster() {
162221 } )
163222
164223 return ( ) => {
165- peerConnectionRef . current ?. close ( )
166- if ( stream ) {
167- stream
168- . getTracks ( )
169- . forEach ( ( streamTrack : MediaStreamTrack ) => streamTrack . stop ( ) )
170- }
224+ cancelled = true
171225 }
172226 // eslint-disable-next-line react-hooks/exhaustive-deps
173227 } , [ videoRef , useDisplayMedia , location . pathname ] )
0 commit comments