@@ -16,6 +16,12 @@ export const Publisher = {
16
16
17
17
this . previewPlayer = document . getElementById ( "previewPlayer" ) ;
18
18
19
+ this . audioBitrate = document . getElementById ( "audio-bitrate" ) ;
20
+ this . videoBitrate = document . getElementById ( "video-bitrate" ) ;
21
+ this . packetLoss = document . getElementById ( "packet-loss" ) ;
22
+ this . status = document . getElementById ( "status" ) ;
23
+ this . time = document . getElementById ( "time" ) ;
24
+
19
25
this . audioApplyButton = document . getElementById ( "audioApplyButton" ) ;
20
26
this . videoApplyButton = document . getElementById ( "videoApplyButton" ) ;
21
27
this . button = document . getElementById ( "button" ) ;
@@ -181,6 +187,96 @@ export const Publisher = {
181
187
view . pc = new RTCPeerConnection ( ) ;
182
188
183
189
// handle local events
190
+ view . pc . onconnectionstatechange = ( ) => {
191
+ if ( view . pc . connectionState === "connected" ) {
192
+ view . startTime = new Date ( ) ;
193
+ view . status . classList . remove ( "bg-red-500" ) ;
194
+ // TODO use tailwind
195
+ view . status . style . backgroundColor = "rgb(34, 197, 94)" ;
196
+
197
+ view . statsIntervalId = setInterval ( async function ( ) {
198
+ if ( ! view . pc ) {
199
+ clearInterval ( view . statsIntervalId ) ;
200
+ view . statsIntervalId = undefined ;
201
+ return ;
202
+ }
203
+
204
+ view . time . innerText = view . toHHMMSS ( new Date ( ) - view . startTime ) ;
205
+
206
+ const stats = await view . pc . getStats ( null ) ;
207
+ let bitrate ;
208
+
209
+ stats . forEach ( ( report ) => {
210
+ if ( report . type === "outbound-rtp" && report . kind === "video" ) {
211
+ if ( ! view . lastVideoReport ) {
212
+ bitrate = ( report . bytesSent * 8 ) / 1000 ;
213
+ } else {
214
+ const timeDiff =
215
+ ( report . timestamp - view . lastVideoReport . timestamp ) / 1000 ;
216
+ if ( timeDiff == 0 ) {
217
+ // this should never happen as we are getting stats every second
218
+ bitrate = 0 ;
219
+ } else {
220
+ bitrate =
221
+ ( ( report . bytesSent - view . lastVideoReport . bytesSent ) * 8 ) /
222
+ timeDiff ;
223
+ }
224
+ }
225
+
226
+ view . videoBitrate . innerText = ( bitrate / 1000 ) . toFixed ( ) ;
227
+ view . lastVideoReport = report ;
228
+ } else if (
229
+ report . type === "outbound-rtp" &&
230
+ report . kind === "audio"
231
+ ) {
232
+ if ( ! view . lastAudioReport ) {
233
+ bitrate = report . bytesSent ;
234
+ } else {
235
+ const timeDiff =
236
+ ( report . timestamp - view . lastAudioReport . timestamp ) / 1000 ;
237
+ if ( timeDiff == 0 ) {
238
+ // this should never happen as we are getting stats every second
239
+ bitrate = 0 ;
240
+ } else {
241
+ bitrate =
242
+ ( ( report . bytesSent - view . lastAudioReport . bytesSent ) * 8 ) /
243
+ timeDiff ;
244
+ }
245
+ }
246
+
247
+ view . audioBitrate . innerText = ( bitrate / 1000 ) . toFixed ( ) ;
248
+ view . lastAudioReport = report ;
249
+ }
250
+ } ) ;
251
+
252
+ // calculate packet loss
253
+ if ( ! view . lastAudioReport || ! view . lastVideoReport ) {
254
+ view . packetLoss . innerText = 0 ;
255
+ } else {
256
+ const packetsSent =
257
+ view . lastVideoReport . packetsSent +
258
+ view . lastAudioReport . packetsSent ;
259
+ const rtxPacketsSent =
260
+ view . lastVideoReport . retransmittedPacketsSent +
261
+ view . lastAudioReport . retransmittedPacketsSent ;
262
+ const nackReceived =
263
+ view . lastVideoReport . nackCount + view . lastAudioReport . nackCount ;
264
+
265
+ if ( nackReceived == 0 ) {
266
+ view . packetLoss . innerText = 0 ;
267
+ } else {
268
+ view . packetLoss . innerText = (
269
+ ( nackReceived / ( packetsSent - rtxPacketsSent ) ) *
270
+ 100
271
+ ) . toFixed ( ) ;
272
+ }
273
+ }
274
+ } , 1000 ) ;
275
+ } else if ( view . pc . connectionState === "failed" ) {
276
+ view . stopStreaming ( view ) ;
277
+ }
278
+ } ;
279
+
184
280
view . pc . onicecandidate = ( ev ) => {
185
281
view . pushEventTo ( view . el , "ice" , JSON . stringify ( ev . candidate ) ) ;
186
282
} ;
@@ -205,16 +301,45 @@ export const Publisher = {
205
301
} ,
206
302
207
303
stopStreaming ( view ) {
208
- view . button . innerText = "Start Streaming" ;
209
- view . button . onclick = function ( ) {
210
- view . startStreaming ( view ) ;
211
- } ;
212
-
213
304
if ( view . pc ) {
214
305
view . pc . close ( ) ;
215
306
view . pc = undefined ;
216
307
}
217
308
309
+ view . resetStats ( view ) ;
310
+
218
311
view . enableControls ( view ) ;
312
+
313
+ view . button . innerText = "Start Streaming" ;
314
+ view . button . onclick = function ( ) {
315
+ view . startStreaming ( view ) ;
316
+ } ;
317
+ } ,
318
+
319
+ resetStats ( view ) {
320
+ view . startTime = undefined ;
321
+ view . lastAudioReport = undefined ;
322
+ view . lastVideoReport = undefined ;
323
+ view . audioBitrate . innerText = 0 ;
324
+ view . videoBitrate . innerText = 0 ;
325
+ view . packetLoss . innerText = 0 ;
326
+ view . time . innerText = "00:00:00" ;
327
+ view . status . style . backgroundColor = "rgb(239, 68, 68)" ;
328
+ } ,
329
+
330
+ toHHMMSS ( milliseconds ) {
331
+ // Calculate hours
332
+ let hours = Math . floor ( milliseconds / ( 1000 * 60 * 60 ) ) ;
333
+ // Calculate minutes, subtracting the hours part
334
+ let minutes = Math . floor ( ( milliseconds % ( 1000 * 60 * 60 ) ) / ( 1000 * 60 ) ) ;
335
+ // Calculate seconds, subtracting the hours and minutes parts
336
+ let seconds = Math . floor ( ( milliseconds % ( 1000 * 60 ) ) / 1000 ) ;
337
+
338
+ // Formatting each unit to always have at least two digits
339
+ hours = hours < 10 ? "0" + hours : hours ;
340
+ minutes = minutes < 10 ? "0" + minutes : minutes ;
341
+ seconds = seconds < 10 ? "0" + seconds : seconds ;
342
+
343
+ return hours + ":" + minutes + ":" + seconds ;
219
344
} ,
220
345
} ;
0 commit comments