Skip to content

Commit 15e0f0c

Browse files
committed
Tesla scubber fix
1 parent 07bdd4b commit 15e0f0c

File tree

1 file changed

+66
-62
lines changed

1 file changed

+66
-62
lines changed

static/app.js

Lines changed: 66 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -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 () {
428431
player.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

451438
player.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
475446
let lastPositionUpdate = 0;
476447
player.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
551540
function 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

Comments
 (0)