11import { WHEPClient } from "@binbat/whip-whep/whep.js" ;
2- import { createEffect , createSignal , onCleanup } from "solid-js" ;
2+ import { createEffect , createSignal , onCleanup , onMount , Show } from "solid-js" ;
3+ import type { StatsNerds } from "./types" ;
34import "./player.css" ;
5+ import Stats from "./stats" ;
46
57export default ( ) => {
68 const [ streamId , setStreamId ] = createSignal ( "" ) ;
@@ -10,10 +12,14 @@ export default () => {
1012 const [ reconnect , setReconnect ] = createSignal ( 0 ) ;
1113 const [ token , setToken ] = createSignal ( "" ) ;
1214
15+ const [ statsNerds , setStatsNerds ] = createSignal < StatsNerds | null > ( null ) ;
16+
1317 let videoRef : HTMLVideoElement | undefined ;
1418 let peerConnectionRef : RTCPeerConnection | null = null ;
1519 let whepClientRef : WHEPClient | null = null ;
1620
21+ let statsInterval : ReturnType < typeof setInterval > | null = null ;
22+
1723 createEffect ( ( ) => {
1824 const params = new URLSearchParams ( location . search ) ;
1925 setStreamId ( params . get ( "id" ) ?? "" ) ;
@@ -99,17 +105,88 @@ export default () => {
99105
100106 onCleanup ( ( ) => {
101107 handleStop ( ) ;
108+ statsInterval && clearInterval ( statsInterval ) ;
109+ } ) ;
110+
111+ onMount ( ( ) => {
112+ videoRef ?. addEventListener ( "contextmenu" , startSyncStats ) ;
113+ } ) ;
114+
115+ onCleanup ( ( ) => {
116+ videoRef ?. removeEventListener ( "contextmenu" , startSyncStats ) ;
102117 } ) ;
103118
119+ function startSyncStats ( ) {
120+ statsInterval = setInterval ( async ( ) => {
121+ if ( ! peerConnectionRef ) return ;
122+
123+ let tmpStats : StatsNerds = {
124+ bytesReceived : 0 ,
125+ bytesSent : 0 ,
126+ currentRoundTripTime : 0 ,
127+ } ;
128+
129+ const stats = await peerConnectionRef ?. getStats ( ) ;
130+ stats . forEach ( ( report ) => {
131+ if ( report . type === "transport" ) {
132+ tmpStats . bytesReceived = report . bytesReceived ?? 0 ;
133+ tmpStats . bytesSent = report . bytesSent ?? 0 ;
134+ } else if ( report . type === "codec" ) {
135+ const [ kind , codec ] = report . mimeType
136+ . toLowerCase ( )
137+ . split ( "/" ) ;
138+ if ( kind === "video" ) {
139+ tmpStats . vcodec = `${ codec } @${ report . sdpFmtpLine ?? "" } ` ;
140+ } else if ( kind === "audio" ) {
141+ tmpStats . acodec = `${ codec } @${ report . sdpFmtpLine ?? "" } ` ;
142+ } else {
143+ console . log ( "Unknown mimeType" , report . mimeType ) ;
144+ }
145+ } else if (
146+ report . type === "candidate-pair" &&
147+ report . nominated
148+ ) {
149+ tmpStats . currentRoundTripTime =
150+ report . currentRoundTripTime ?? 0 ;
151+ }
152+
153+ if ( report . type === "inbound-rtp" && report . kind === "video" ) {
154+ tmpStats . frameWidth = report . frameWidth ;
155+ tmpStats . frameHeight = report . frameHeight ;
156+ tmpStats . framesPerSecond = report . framesPerSecond ;
157+ }
158+
159+ if ( report . type === "inbound-rtp" && report . kind === "audio" ) {
160+ tmpStats . audioLevel = report . audioLevel ;
161+ }
162+ } ) ;
163+ setStatsNerds ( tmpStats ) ;
164+ } , 1000 ) ;
165+ }
166+
167+ function stopSyncStats ( ) {
168+ if ( statsInterval ) {
169+ clearInterval ( statsInterval ) ;
170+ }
171+ setStatsNerds ( null ) ;
172+ }
173+
104174 return (
105- < div id = "player" >
175+ < div id = "player" class = "player-wrapper" >
106176 < video
107177 ref = { videoRef }
108178 autoplay = { autoPlay ( ) }
109179 muted = { muted ( ) }
110180 controls = { controls ( ) }
111181 onClick = { handleVideoClick }
112182 />
183+ < Show when = { statsNerds ( ) } >
184+ { ( stats ) => (
185+ < div class = "stats-container" id = "stats" >
186+ < Stats stats = { stats ( ) } onClose = { stopSyncStats } />
187+ </ div >
188+ ) }
189+ </ Show >
113190 </ div >
114191 ) ;
115192} ;
0 commit comments