11import {
22 DataPacket_Kind , LocalParticipant ,
3+ LocalTrack ,
34 MediaDeviceFailure ,
45 Participant ,
56 ParticipantEvent ,
6- RemoteParticipant , Room , RoomConnectOptions , RoomEvent ,
7+ RemoteAudioTrack ,
8+ RemoteParticipant , RemoteVideoTrack , Room , RoomConnectOptions , RoomEvent ,
79 RoomOptions , RoomState , setLogLevel , Track , TrackPublication ,
810 VideoCaptureOptions , VideoPresets ,
911} from '../src/index' ;
@@ -16,8 +18,8 @@ const state = {
1618 encoder : new TextEncoder ( ) ,
1719 decoder : new TextDecoder ( ) ,
1820 defaultDevices : new Map < MediaDeviceKind , string > ( ) ,
21+ bitrateInterval : undefined as any ,
1922} ;
20-
2123let currentRoom : Room | undefined ;
2224
2325// handles actions from the HTML
@@ -56,6 +58,8 @@ const appActions = {
5658 await room . localParticipant . enableCameraAndMicrophone ( ) ;
5759 updateButtonsForPublishState ( ) ;
5860 }
61+
62+ state . bitrateInterval = setInterval ( renderBitrate , 1000 ) ;
5963 } ,
6064
6165 connectToRoom : async (
@@ -202,6 +206,9 @@ const appActions = {
202206 if ( currentRoom ) {
203207 currentRoom . disconnect ( ) ;
204208 }
209+ if ( state . bitrateInterval ) {
210+ clearInterval ( state . bitrateInterval ) ;
211+ }
205212 } ,
206213
207214 disconnectSignal : ( ) => {
@@ -342,7 +349,11 @@ function renderParticipant(participant: Participant, remove: boolean = false) {
342349 <div class="info-bar">
343350 <div id="name-${ participant . sid } " class="name">
344351 </div>
345- <div id="size-${ participant . sid } " class="size">
352+ <div style="text-align: center;">
353+ <span id="size-${ participant . sid } " class="size">
354+ </span>
355+ <span id="bitrate-${ participant . sid } " class="bitrate">
356+ </span>
346357 </div>
347358 <div class="right">
348359 <span id="signal-${ participant . sid } "></span>
@@ -387,11 +398,18 @@ function renderParticipant(participant: Participant, remove: boolean = false) {
387398
388399 const cameraEnabled = cameraPub && cameraPub . isSubscribed && ! cameraPub . isMuted ;
389400 if ( cameraEnabled ) {
390- cameraPub ?. videoTrack ?. attach ( videoElm ) ;
391401 if ( participant instanceof LocalParticipant ) {
392402 // flip
393403 videoElm . style . transform = 'scale(-1, 1)' ;
404+ } else if ( ! cameraPub ?. videoTrack ?. attachedElements . includes ( videoElm ) ) {
405+ const startTime = Date . now ( ) ;
406+ // measure time to render
407+ videoElm . addEventListener ( 'loadeddata' , ( ) => {
408+ const elapsed = Date . now ( ) - startTime ;
409+ appendLog ( `RemoteVideoTrack ${ cameraPub ?. trackSid } rendered in ${ elapsed } ms` ) ;
410+ } ) ;
394411 }
412+ cameraPub ?. videoTrack ?. attach ( videoElm ) ;
395413 } else if ( cameraPub ?. videoTrack ) {
396414 // detach manually whenever possible
397415 cameraPub . videoTrack ?. detach ( videoElm ) ;
@@ -465,6 +483,32 @@ function renderScreenShare() {
465483 }
466484}
467485
486+ function renderBitrate ( ) {
487+ if ( ! currentRoom || currentRoom . state !== RoomState . Connected ) {
488+ return ;
489+ }
490+ const participants : Participant [ ] = [ ...currentRoom . participants . values ( ) ] ;
491+ participants . push ( currentRoom . localParticipant ) ;
492+
493+ for ( const p of participants ) {
494+ const elm = $ ( `bitrate-${ p . sid } ` ) ;
495+ let totalBitrate = 0 ;
496+ for ( const t of p . tracks . values ( ) ) {
497+ if ( t . track instanceof RemoteAudioTrack || t . track instanceof RemoteVideoTrack
498+ || t . track instanceof LocalTrack ) {
499+ totalBitrate += t . track . currentBitrate ;
500+ }
501+ }
502+ let displayText = '' ;
503+ if ( totalBitrate > 0 ) {
504+ displayText = `${ Math . round ( totalBitrate / 1024 ) . toLocaleString ( ) } kbps` ;
505+ }
506+ if ( elm ) {
507+ elm . innerHTML = displayText ;
508+ }
509+ }
510+ }
511+
468512function updateVideoSize ( element : HTMLVideoElement , target : HTMLElement ) {
469513 target . innerHTML = `(${ element . videoWidth } x${ element . videoHeight } )` ;
470514}
0 commit comments