@@ -7,12 +7,18 @@ type PlayerConfig = Omit<YoutubePlayerConfig, 'source'> & {
77 videoId : string ;
88} ;
99
10+ const MUTED_TRACKING_INTERVAL_MS = 250 ;
11+
1012class WebYoutubePlayerController {
1113 private player : YouTubePlayer | null = null ;
1214 private progressInterval : NodeJS . Timeout | null = null ;
1315 private callbacks : PlayerEvents = { } ;
1416 private progressIntervalMs = 1000 ;
1517 private seekTimeout : NodeJS . Timeout | null = null ;
18+ private mutedTrackingInterval : NodeJS . Timeout | null = null ;
19+ private isMutedSyncing = false ;
20+ private lastKnownMuted : boolean | null = null ;
21+ private mutedTrackingEnabled = false ;
1622 private desiredMuted = false ;
1723
1824 static createInstance ( ) : WebYoutubePlayerController {
@@ -83,6 +89,8 @@ class WebYoutubePlayerController {
8389 }
8490
8591 this . desiredMuted = Boolean ( config . playerVars ?. muted ) ;
92+ this . lastKnownMuted = null ;
93+ this . stopMutedTracking ( ) ;
8694
8795 if ( this . player ) {
8896 try {
@@ -111,29 +119,38 @@ class WebYoutubePlayerController {
111119 events : {
112120 onReady : ( event ) => {
113121 const { playerInfo } = event . target ;
122+ const readyMuted =
123+ typeof playerInfo . muted === 'boolean' ? playerInfo . muted : this . desiredMuted ;
114124
115125 this . callbacks . onReady ?.( {
116126 availablePlaybackRates : playerInfo . availablePlaybackRates ,
117127 availableQualityLevels : playerInfo . availableQualityLevels ,
118128 currentTime : playerInfo . currentTime ,
119129 duration : playerInfo . duration ,
120- muted : playerInfo . muted ,
130+ muted : readyMuted ,
121131 playbackQuality : playerInfo . playbackQuality ,
122132 playbackRate : playerInfo . playbackRate ,
123133 playerState : playerInfo . playerState ,
124134 size : playerInfo . size ,
125135 volume : playerInfo . volume ,
126136 } ) ;
127137
138+ this . updateMutedState ( readyMuted , true ) ;
128139 this . applyDesiredMutedState ( ) ;
129140 this . startProgressTracking ( ) ;
141+ this . startMutedTracking ( ) ;
130142 } ,
131143 onStateChange : ( event ) => {
132144 const state = event . data ;
133145 const mutedState = event . target ?. playerInfo ?. muted ;
146+
147+ if ( typeof mutedState === 'boolean' ) {
148+ this . updateMutedState ( mutedState , true ) ;
149+ }
150+
134151 this . callbacks . onStateChange ?.( state ) ;
135152
136- this . handleStateChange ( state , mutedState ) ;
153+ this . handleStateChange ( state ) ;
137154 } ,
138155 onError : ( event ) => {
139156 console . error ( 'YouTube player error:' , event . data ) ;
@@ -163,11 +180,7 @@ class WebYoutubePlayerController {
163180 } ) ;
164181 }
165182
166- private handleStateChange ( state : number , mutedState ?: boolean ) : void {
167- if ( state !== PlayerState . PLAYING && typeof mutedState === 'boolean' ) {
168- this . desiredMuted = mutedState ;
169- }
170-
183+ private handleStateChange ( state : number ) : void {
171184 if ( state === PlayerState . ENDED ) {
172185 this . stopProgressTracking ( ) ;
173186 this . sendProgress ( ) ;
@@ -283,12 +296,22 @@ class WebYoutubePlayerController {
283296
284297 mute ( ) : void {
285298 this . desiredMuted = true ;
286- this . player ?. mute ( ) ;
299+ if ( ! this . player ) {
300+ return ;
301+ }
302+
303+ this . player . mute ( ) ;
304+ this . updateMutedState ( true , true ) ;
287305 }
288306
289307 unMute ( ) : void {
290308 this . desiredMuted = false ;
291- this . player ?. unMute ( ) ;
309+ if ( ! this . player ) {
310+ return ;
311+ }
312+
313+ this . player . unMute ( ) ;
314+ this . updateMutedState ( false , true ) ;
292315 }
293316
294317 async isMuted ( ) : Promise < boolean > {
@@ -370,20 +393,85 @@ class WebYoutubePlayerController {
370393 this . callbacks = { ...this . callbacks , ...newCallbacks } ;
371394 }
372395
396+ setMutedTrackingEnabled ( enabled : boolean ) : void {
397+ this . mutedTrackingEnabled = enabled ;
398+
399+ if ( enabled ) {
400+ this . lastKnownMuted = null ;
401+ this . startMutedTracking ( ) ;
402+ void this . syncMutedStateFromPlayer ( ) ;
403+ return ;
404+ }
405+
406+ this . stopMutedTracking ( ) ;
407+ }
408+
409+ private startMutedTracking ( ) : void {
410+ if ( ! this . mutedTrackingEnabled || ! this . player ) {
411+ return ;
412+ }
413+
414+ this . stopMutedTracking ( ) ;
415+ this . mutedTrackingInterval = setInterval ( ( ) => {
416+ void this . syncMutedStateFromPlayer ( ) ;
417+ } , MUTED_TRACKING_INTERVAL_MS ) ;
418+ }
419+
420+ private stopMutedTracking ( ) : void {
421+ if ( this . mutedTrackingInterval ) {
422+ clearInterval ( this . mutedTrackingInterval ) ;
423+ this . mutedTrackingInterval = null ;
424+ }
425+ this . isMutedSyncing = false ;
426+ }
427+
428+ private async syncMutedStateFromPlayer ( ) : Promise < void > {
429+ if ( this . isMutedSyncing || ! this . player || ! this . player . isMuted ) {
430+ return ;
431+ }
432+
433+ this . isMutedSyncing = true ;
434+
435+ try {
436+ const muted = await this . player . isMuted ( ) ;
437+ this . updateMutedState ( Boolean ( muted ) , true ) ;
438+ } catch {
439+ // ignore polling errors while player is transitioning
440+ } finally {
441+ this . isMutedSyncing = false ;
442+ }
443+ }
444+
445+ private updateMutedState ( muted : boolean , emitEvent = true ) : void {
446+ this . desiredMuted = muted ;
447+
448+ if ( this . lastKnownMuted === muted ) {
449+ return ;
450+ }
451+
452+ this . lastKnownMuted = muted ;
453+
454+ if ( emitEvent ) {
455+ this . callbacks . onMuteChange ?.( muted ) ;
456+ }
457+ }
458+
373459 private applyDesiredMutedState ( ) : void {
374460 if ( ! this . desiredMuted ) {
375461 return ;
376462 }
377463
378464 try {
379465 this . player ?. mute ( ) ;
466+ this . updateMutedState ( true , true ) ;
380467 } catch ( error ) {
381468 console . warn ( 'Failed to apply muted state:' , error ) ;
382469 }
383470 }
384471
385472 destroy ( ) : void {
386473 this . stopProgressTracking ( ) ;
474+ this . stopMutedTracking ( ) ;
387475
388476 if ( this . seekTimeout ) {
389477 clearTimeout ( this . seekTimeout ) ;
@@ -398,6 +486,8 @@ class WebYoutubePlayerController {
398486 }
399487 this . player = null ;
400488 }
489+
490+ this . lastKnownMuted = null ;
401491 }
402492}
403493
0 commit comments