fix timeline

This commit is contained in:
lelo 2026-01-24 14:16:50 +00:00
parent 6b8f27ad19
commit f0346d82ad

View File

@ -23,7 +23,6 @@ class SimpleAudioPlayer {
this.isSeeking = false; this.isSeeking = false;
this.rafId = null; this.rafId = null;
this.abortCtrl = null; this.abortCtrl = null;
this._posInterval = null;
// Pre-compute icons once // Pre-compute icons once
const fill = getComputedStyle(document.documentElement) const fill = getComputedStyle(document.documentElement)
@ -53,6 +52,32 @@ class SimpleAudioPlayer {
this._initMediaSession(); this._initMediaSession();
} }
/**
* Push the current position to MediaSession so lockscreen / BT UIs stay in sync.
* Keeps values strict but avoids over-clamping that can confuse some platforms.
*/
_pushPositionState(force = false) {
if (!navigator.mediaSession?.setPositionState) return;
const duration = this.audio.duration;
const position = this.audio.currentTime;
if (!Number.isFinite(duration) || duration <= 0) return;
if (!Number.isFinite(position) || position < 0) return;
const playbackRate = this.audio.paused
? 0
: (Number.isFinite(this.audio.playbackRate) ? this.audio.playbackRate : 1);
try {
if ('playbackState' in navigator.mediaSession) {
navigator.mediaSession.playbackState = this.audio.paused ? 'paused' : 'playing';
}
navigator.mediaSession.setPositionState({ duration, playbackRate, position });
} catch (err) {
if (force) console.warn('setPositionState failed:', err);
}
}
// Minimal SVG helper // Minimal SVG helper
svg(pathD, fill) { svg(pathD, fill) {
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="${fill}"> return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="${fill}">
@ -71,36 +96,40 @@ class SimpleAudioPlayer {
this.timeline.max = this.audio.duration; this.timeline.max = this.audio.duration;
this.timeline.value = 0; this.timeline.value = 0;
this.timeline.style.backgroundSize = '0% 100%'; this.timeline.style.backgroundSize = '0% 100%';
this._pushPositionState(true);
}); });
this.audio.addEventListener('play', () => this._updatePlayState()); this.audio.addEventListener('play', () => {
this.audio.addEventListener('pause', () => this._updatePlayState()); this._updatePlayState();
this.audio.addEventListener('ended', () => this.playBtn.innerHTML = this.icons.play); this._pushPositionState(true);
});
this.audio.addEventListener('playing', () => this._pushPositionState(true));
this.audio.addEventListener('pause', () => {
this._updatePlayState();
this._pushPositionState(true);
});
this.audio.addEventListener('ratechange', () => this._pushPositionState(true));
this.audio.addEventListener('ended', () => {
this.playBtn.innerHTML = this.icons.play;
this._pushPositionState(true);
if ('mediaSession' in navigator) navigator.mediaSession.playbackState = 'paused';
});
// Native timeupdate => update timeline // Native timeupdate => update timeline
this.audio.addEventListener('timeupdate', () => this.updateTimeline()); this.audio.addEventListener('timeupdate', () => this.updateTimeline());
// Fallback interval for throttled mobile // Keep position in sync when page visibility changes
this._interval = setInterval(() => { document.addEventListener('visibilitychange', () => this._pushPositionState(true));
if (!this.audio.paused && !this.isSeeking) {
this.updateTimeline();
}
}, 500);
// Unified seek input // Unified seek input
this.timeline.addEventListener('input', () => { this.timeline.addEventListener('input', () => {
this.isSeeking = true; this.isSeeking = true;
this.audio.currentTime = this.timeline.value; this.audio.currentTime = this.timeline.value;
// immediate Android sync this._pushPositionState(true); // immediate Android sync
if (navigator.mediaSession?.setPositionState && Number.isFinite(this.audio.duration)) {
navigator.mediaSession.setPositionState({
duration: this.audio.duration,
playbackRate: this.audio.playbackRate,
position: this.audio.currentTime
});
}
this.updateTimeline(); this.updateTimeline();
this.isSeeking = false; this.isSeeking = false;
}); });
@ -112,9 +141,7 @@ class SimpleAudioPlayer {
_updatePlayState() { _updatePlayState() {
this.playBtn.innerHTML = this.audio.paused ? this.icons.play : this.icons.pause; this.playBtn.innerHTML = this.audio.paused ? this.icons.play : this.icons.pause;
if ('mediaSession' in navigator) { this._pushPositionState(true); // lockscreen refresh on play/pause toggle
navigator.mediaSession.playbackState = this.audio.paused ? 'paused' : 'playing';
}
} }
_formatTime(sec) { _formatTime(sec) {
@ -134,13 +161,7 @@ class SimpleAudioPlayer {
this.timeInfo.textContent = this.timeInfo.textContent =
`${this._formatTime(this.audio.currentTime)} / ${this._formatTime(this.audio.duration)}`; `${this._formatTime(this.audio.currentTime)} / ${this._formatTime(this.audio.duration)}`;
// 4) Push to Android widget // 4) Push to Android widget
if (navigator.mediaSession?.setPositionState && Number.isFinite(this.audio.duration)) { this._pushPositionState();
navigator.mediaSession.setPositionState({
duration: this.audio.duration,
playbackRate: this.audio.playbackRate,
position: this.audio.currentTime
});
}
} }
toggleCollapse() { toggleCollapse() {
@ -267,16 +288,6 @@ class SimpleAudioPlayer {
} }
})); }));
// Heartbeat for widget
this._posInterval = setInterval(() => {
if (!this.audio.paused && navigator.mediaSession?.setPositionState && Number.isFinite(this.audio.duration)) {
navigator.mediaSession.setPositionState({
duration: this.audio.duration,
playbackRate: this.audio.playbackRate,
position: this.audio.currentTime
});
}
}, 1000);
} }
} }