@@ -387,6 +387,9 @@ player.addEventListener('playing', function () {
387387 navigator . mediaSession . playbackState = 'playing' ;
388388 }
389389
390+ // Update position state when playback starts (important for Tesla scrubber)
391+ updatePositionState ( ) ;
392+
390393 // Clear stalled timeout
391394 if ( stalledTimeout ) {
392395 clearTimeout ( stalledTimeout ) ;
@@ -428,77 +431,25 @@ player.addEventListener('suspend', function () {
428431player . addEventListener ( 'loadedmetadata' , function ( ) {
429432 remoteLog ( 'log' , '[MediaSession] Metadata loaded' , { duration : player . duration } ) ;
430433
431- // Immediately set position state when duration becomes available
432- if ( 'mediaSession' in navigator && navigator . mediaSession . setPositionState ) {
433- if ( player . duration && ! isNaN ( player . duration ) && isFinite ( player . duration ) ) {
434- try {
435- navigator . mediaSession . setPositionState ( {
436- duration : player . duration ,
437- playbackRate : player . playbackRate ,
438- position : player . currentTime
439- } ) ;
440- remoteLog ( 'log' , '[MediaSession] Initial position state set for car display' , {
441- duration : player . duration ,
442- position : player . currentTime
443- } ) ;
444- } catch ( e ) {
445- remoteLog ( 'warn' , '[MediaSession] Failed to set initial position state' , { error : e . message } ) ;
446- }
447- }
448- }
434+ // Set position state when duration becomes available (critical for Tesla scrubber)
435+ updatePositionState ( ) ;
449436} ) ;
450437
451438player . addEventListener ( 'durationchange' , function ( ) {
452439 remoteLog ( 'log' , '[MediaSession] Duration changed' , { duration : player . duration } ) ;
453440
454441 // Update position state when duration changes
455- if ( 'mediaSession' in navigator && navigator . mediaSession . setPositionState ) {
456- if ( player . duration && ! isNaN ( player . duration ) && isFinite ( player . duration ) ) {
457- try {
458- navigator . mediaSession . setPositionState ( {
459- duration : player . duration ,
460- playbackRate : player . playbackRate ,
461- position : player . currentTime
462- } ) ;
463- remoteLog ( 'log' , '[MediaSession] Position state updated after duration change' , {
464- duration : player . duration ,
465- position : player . currentTime
466- } ) ;
467- } catch ( e ) {
468- remoteLog ( 'warn' , '[MediaSession] Failed to update position state' , { error : e . message } ) ;
469- }
470- }
471- }
442+ updatePositionState ( ) ;
472443} ) ;
473444
474445// Prefetch next queue item when current track is nearing its end
475446let lastPositionUpdate = 0 ;
476447player . addEventListener ( 'timeupdate' , async function ( ) {
477- // Update MediaSession position state (throttled to once per second)
448+ // Update MediaSession position state (throttled to once per second for performance )
478449 const now = Date . now ( ) ;
479- if ( 'mediaSession' in navigator && navigator . mediaSession . setPositionState && now - lastPositionUpdate > 1000 ) {
480- if ( player . duration && ! isNaN ( player . duration ) && isFinite ( player . duration ) ) {
481- try {
482- navigator . mediaSession . setPositionState ( {
483- duration : player . duration ,
484- playbackRate : player . playbackRate ,
485- position : player . currentTime
486- } ) ;
487- lastPositionUpdate = now ;
488- } catch ( e ) {
489- // Some browsers don't support this or have restrictions
490- console . debug ( '[MediaSession] setPositionState not supported or failed:' , e . message ) ;
491- }
492- } else {
493- // Debug why position state isn't being set (only log once per 10 seconds)
494- if ( now - lastPositionUpdate > 10000 ) {
495- remoteLog ( 'warn' , '[MediaSession] Cannot set position state' , {
496- duration : player . duration ,
497- isNaN : isNaN ( player . duration ) ,
498- isFinite : isFinite ( player . duration )
499- } ) ;
500- }
501- }
450+ if ( now - lastPositionUpdate > 1000 ) {
451+ updatePositionState ( ) ;
452+ lastPositionUpdate = now ;
502453 }
503454
504455 // Update progress bar on currently playing queue item
@@ -547,6 +498,44 @@ function updateWindowTitle(title) {
547498 }
548499}
549500
501+ // Helper function to safely update position state with validation
502+ function updatePositionState ( ) {
503+ if ( ! ( 'mediaSession' in navigator ) || ! navigator . mediaSession . setPositionState ) {
504+ return ;
505+ }
506+
507+ // Validate that we have a valid duration
508+ if ( ! player . duration || isNaN ( player . duration ) || ! isFinite ( player . duration ) || player . duration <= 0 ) {
509+ return ;
510+ }
511+
512+ try {
513+ // Clamp position to be within valid range [0, duration]
514+ const position = Math . min ( Math . max ( 0 , player . currentTime || 0 ) , player . duration ) ;
515+ const playbackRate = player . playbackRate || 1.0 ;
516+
517+ // All parameters must be valid: duration > 0, position >= 0 && position <= duration, playbackRate != 0
518+ navigator . mediaSession . setPositionState ( {
519+ duration : player . duration ,
520+ playbackRate : playbackRate ,
521+ position : position
522+ } ) ;
523+
524+ remoteLog ( 'log' , '[MediaSession] Position state updated' , {
525+ duration : player . duration ,
526+ position : position ,
527+ playbackRate : playbackRate
528+ } ) ;
529+ } catch ( e ) {
530+ remoteLog ( 'warn' , '[MediaSession] Failed to set position state' , {
531+ error : e . message ,
532+ duration : player . duration ,
533+ position : player . currentTime ,
534+ playbackRate : player . playbackRate
535+ } ) ;
536+ }
537+ }
538+
550539// Set up MediaSession for lock screen and car display controls
551540function setupMediaSession ( trackInfo ) {
552541 if ( ! ( 'mediaSession' in navigator ) ) {
@@ -631,6 +620,9 @@ function setupMediaSession(trackInfo) {
631620 }
632621 }
633622
623+ // Try to set position state after metadata (Tesla requires this early)
624+ updatePositionState ( ) ;
625+
634626 // Set up action handlers for background playback
635627 navigator . mediaSession . setActionHandler ( 'play' , ( ) => {
636628 player . play ( ) ;
@@ -645,17 +637,29 @@ function setupMediaSession(trackInfo) {
645637 rewind ( ) ;
646638 } ) ;
647639 navigator . mediaSession . setActionHandler ( 'seekbackward' , ( details ) => {
648- player . currentTime = Math . max ( 0 , player . currentTime - ( details . seekOffset || 15 ) ) ;
640+ const skipTime = details . seekOffset || 15 ;
641+ player . currentTime = Math . max ( 0 , player . currentTime - skipTime ) ;
642+ updatePositionState ( ) ;
649643 } ) ;
650644 navigator . mediaSession . setActionHandler ( 'seekforward' , ( details ) => {
651- player . currentTime = Math . max ( 0 , player . currentTime + ( details . seekOffset || 15 ) ) ;
645+ const skipTime = details . seekOffset || 15 ;
646+ player . currentTime = Math . min ( player . currentTime + skipTime , player . duration || player . currentTime + skipTime ) ;
647+ updatePositionState ( ) ;
652648 } ) ;
653649
654650 // Handle seek to specific position (for scrubber/progress bar in car displays)
655651 navigator . mediaSession . setActionHandler ( 'seekto' , ( details ) => {
656652 if ( details . seekTime !== null && ! isNaN ( details . seekTime ) ) {
657- player . currentTime = details . seekTime ;
653+ // Use fastSeek if available for better performance
654+ if ( details . fastSeek && 'fastSeek' in player ) {
655+ player . fastSeek ( details . seekTime ) ;
656+ } else {
657+ player . currentTime = details . seekTime ;
658+ }
658659 remoteLog ( 'log' , '[MediaSession] Seeked to position' , { seekTime : details . seekTime } ) ;
660+
661+ // Update position state immediately after seeking (critical for Tesla)
662+ updatePositionState ( ) ;
659663 }
660664 } ) ;
661665}
0 commit comments