chore(player): log preload load errors

This commit is contained in:
binimum 2026-04-04 20:36:41 +00:00 committed by GitHub
parent f461aee942
commit 8a377d5332
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 121 additions and 37 deletions

View file

@ -10,7 +10,7 @@
"@capacitor/core": "^8.2.0",
"@capacitor/haptics": "^8.0.1",
"@capacitor/ios": "^8.2.0",
"@dantheman827/taglib-ts": "https://github.com/DanTheMan827/taglib-ts/archive/ebd0e369b706c127a280d4ad631977f8d12ff88f.tar.gz",
"@dantheman827/taglib-ts": "^0.1.5",
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
@ -69,7 +69,7 @@
"overrides": {
"serialize-javascript": "^7.0.3",
"source-map": "^0.7.4",
"sourcemap-codec": "npm:@jridgewell/sourcemap-codec@^1.4.14",
"sourcemap-codec": "^1.4.14",
},
"packages": {
"@apideck/better-ajv-errors": ["@apideck/better-ajv-errors@0.3.7", "", { "dependencies": { "jsonpointer": "^5.0.1", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw=="],
@ -300,7 +300,7 @@
"@csstools/selector-specificity": ["@csstools/selector-specificity@6.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.1.1" } }, "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA=="],
"@dantheman827/taglib-ts": ["@dantheman827/taglib-ts@https://github.com/DanTheMan827/taglib-ts/archive/ebd0e369b706c127a280d4ad631977f8d12ff88f.tar.gz", {}, "sha512-QPl5eWhFqP716VKIX5t7x39eRswRHG+5RpQ9wW6cNbDh1QSa/gWpPyvwP55O+/qCV7VjLepZI1/XFoxMO8jK7w=="],
"@dantheman827/taglib-ts": ["@dantheman827/taglib-ts@0.1.5", "", {}, "sha512-uOMzKccEE0AUsMZ6rTPSUB0ZqAvuRxih9ECn8N62lMNi0UsXQumuKN86F3CxNaF4y3LOkvtp45vplTjwEEUOlQ=="],
"@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],

View file

@ -47,6 +47,8 @@ export class Player {
this.shuffleActive = false;
this.repeatMode = REPEAT_MODE.OFF;
this.preloadCache = new Map();
this._pendingPreload = false;
setInterval(this.checkPreloadConditions.bind(this), 2000);
this.preloadAbortController = null;
this.currentTrack = null;
this.currentRgValues = null;
@ -473,7 +475,27 @@ export class Player {
this.quality = quality;
}
async preloadNextTracks() {
preloadNextTracks() {
this._pendingPreload = true;
}
async checkPreloadConditions() {
if (!this._pendingPreload || !this.activeElement || this.activeElement.paused) return;
const currentTime = this.activeElement.currentTime || 0;
const duration = this.activeElement.duration || 0;
const timeRemaining = duration - currentTime;
// Preload if we are in last 30 seconds of song
const shouldPreload = (duration > 0 && timeRemaining <= 30);
if (shouldPreload) {
this._pendingPreload = false;
void this._executePreloadNextTracks().catch(console.error);
}
}
async _executePreloadNextTracks() {
if (this.preloadAbortController) {
this.preloadAbortController.abort();
}
@ -482,7 +504,8 @@ export class Player {
const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue;
const tracksToPreload = [];
for (let i = 1; i <= 2; i++) {
// Only preload the next 1 song to prevent data waste
for (let i = 1; i <= 1; i++) {
const nextIndex = this.currentQueueIndex + i;
if (nextIndex < currentQueue.length) {
tracksToPreload.push({ track: currentQueue[nextIndex], index: nextIndex });
@ -502,13 +525,52 @@ export class Player {
if (this.preloadAbortController.signal.aborted) break;
// Also preload ReplayGain legacy metadata if the fast manifest endpoint failed to provide it
if (track.type !== 'video' && !streamInfo.rgInfo) {
try {
const trackData = await this.api.getTrack(track.id, this.quality);
if (trackData && trackData.info) {
streamInfo.rgInfoFallback = {
trackReplayGain: trackData.info.trackReplayGain,
trackPeakAmplitude: trackData.info.trackPeakAmplitude,
albumReplayGain: trackData.info.albumReplayGain,
albumPeakAmplitude: trackData.info.albumPeakAmplitude,
};
}
} catch(e) {} // Fail silently
}
this.preloadCache.set(track.id, streamInfo);
// Warm connection/cache
// For Blob URLs (DASH), this head request is not needed and can cause errors.
if (!streamInfo.url.startsWith('blob:')) {
fetch(streamInfo.url, { method: 'HEAD', signal: this.preloadAbortController.signal }).catch(
() => {}
);
const streamUrl = streamInfo.url;
// Warm connection and pre-fetch
if (!streamUrl.startsWith('blob:')) {
if (streamUrl.includes('.mpd') || streamUrl.includes('.m3u8')) {
if (this.shakaInitialized && this.shakaPlayer && typeof this.shakaPlayer.preload === 'function') {
try {
const preloadManager = await this.shakaPlayer.preload(streamUrl);
streamInfo.preloadManager = preloadManager;
} catch (e) {
// Ignore preload errors, will just load fresh
}
} else {
fetch(streamUrl, { method: 'GET', signal: this.preloadAbortController.signal }).catch(
() => {}
);
}
} else {
// For static files (FLAC, MP3), standard fetch of the first ~5MB completely primes the cache.
const preloader = new Audio();
preloader.preload = 'auto';
preloader.muted = true;
preloader.src = streamUrl;
streamInfo.preloader = preloader; // Hold reference
fetch(streamUrl, {
headers: { Range: 'bytes=0-5242880' },
signal: this.preloadAbortController.signal,
}).catch(() => {});
}
}
} catch (error) {
if (error.name !== 'AbortError') {
@ -720,9 +782,13 @@ export class Player {
this.hls.destroy();
this.hls = null;
}
// Retain the initialized Shaka player if we are remaining on the same HTMLMediaElement
if (this.shakaInitialized && this.shakaPlayer) {
this.shakaPlayer.unload();
this.shakaInitialized = false;
if (this.shakaPlayer.getMediaElement() !== activeElement) {
this.shakaPlayer.unload();
this.shakaInitialized = false;
}
}
if (inactiveElement) {
@ -736,9 +802,13 @@ export class Player {
}
if (activeElement) {
activeElement.pause();
activeElement.src = '';
activeElement.removeAttribute('src');
// Let Shaka overwrite the activeElement's decoder pipeline gracefully if we're carrying it over.
// It manages its own buffering teardown implicitly when `load()` is executed.
if (!this.shakaInitialized) {
activeElement.pause();
activeElement.src = '';
activeElement.removeAttribute('src');
}
}
audioContextManager.changeSource(activeElement);
@ -925,7 +995,17 @@ export class Player {
await this.setupHlsVideo(activeElement, streamUrl, null);
} else if (streamUrl.startsWith('blob:') || streamUrl.includes('.mpd')) {
await this.shakaPlayer.attach(activeElement);
await this.shakaPlayer.load(streamUrl);
const loadTarget = track.type == 'video' && this.preloadCache.has(track.id) ?
(this.preloadCache.get(track.id).preloadManager || streamUrl) : streamUrl;
try {
await this.shakaPlayer.load(loadTarget);
} catch (e) {
console.error("PreloadManager load Error:", e); if (loadTarget !== streamUrl) await this.shakaPlayer.load(streamUrl);
else throw e;
}
this.shakaInitialized = true;
const savedAdaptiveQuality = localStorage.getItem('adaptive-playback-quality') || 'auto';
@ -938,9 +1018,6 @@ export class Player {
this.applyAudioEffects();
const canPlay = await this.waitForCanPlayOrTimeout(activeElement);
if (!canPlay || this.playbackSequence !== currentSequence) return;
if (startTime > 0) {
activeElement.currentTime = startTime;
}
@ -961,6 +1038,9 @@ export class Player {
if (resolvedStreamInfo.rgInfo) {
this.currentRgValues = resolvedStreamInfo.rgInfo;
this.applyReplayGain();
} else if (resolvedStreamInfo.rgInfoFallback) {
this.currentRgValues = resolvedStreamInfo.rgInfoFallback;
this.applyReplayGain();
} else {
// Fallback to legacy metadata if manifest lacked normalization data
const trackData = await this.api.getTrack(track.id, this.quality).catch(() => null);
@ -984,12 +1064,24 @@ export class Player {
// Handle playback
if (streamUrl && (streamUrl.startsWith('blob:') || streamUrl.includes('.mpd')) && !track.isLocal) {
// It's likely a DASH manifest URL
await this.shakaPlayer.attach(activeElement);
if (startTime > 0) {
await this.shakaPlayer.load(streamUrl, startTime);
} else {
await this.shakaPlayer.load(streamUrl);
if (this.shakaPlayer.getMediaElement() !== activeElement) {
await this.shakaPlayer.attach(activeElement);
this.shakaInitialized = true;
}
const loadTarget = resolvedStreamInfo.preloadManager || streamUrl;
try {
if (startTime > 0) {
await this.shakaPlayer.load(loadTarget, startTime);
} else {
await this.shakaPlayer.load(loadTarget);
}
} catch (e) {
console.error("PreloadManager load Error:", e); if (loadTarget !== streamUrl) await this.shakaPlayer.load(streamUrl);
else throw e;
}
this.shakaInitialized = true;
this.applyAudioEffects();
@ -998,18 +1090,14 @@ export class Player {
this.updateAdaptiveQualityBadge();
const canPlay = await this.waitForCanPlayOrTimeout(activeElement);
if (!canPlay || this.playbackSequence !== currentSequence) return;
// Instantly trigger playback rather than explicitly waiting for 'canplay'
// which delays the event loop and natively adds gap/latency
await this.safePlay(activeElement);
} else {
activeElement.src = streamUrl;
this.applyAudioEffects();
this.updateAdaptiveQualityBadge();
// Wait for audio to be ready before playing
const canPlay = await this.waitForCanPlayOrTimeout(activeElement);
if (!canPlay || this.playbackSequence !== currentSequence) return;
if (startTime > 0) {
activeElement.currentTime = startTime;
}
@ -1046,10 +1134,6 @@ export class Player {
}
console.error(`Could not play track: ${trackTitle}`, error);
// Skip to next track on unexpected error
if (recursiveCount < currentQueue.length) {
setTimeout(() => this.playNext(recursiveCount + 1), 1000);
}
}
}

View file

@ -53,7 +53,7 @@
"vite-plugin-pwa": "^1.2.0"
},
"overrides": {
"sourcemap-codec": "npm:@jridgewell/sourcemap-codec@^1.4.14",
"sourcemap-codec": "^1.4.14",
"source-map": "^0.7.4",
"serialize-javascript": "^7.0.3"
},
@ -63,7 +63,7 @@
"@capacitor/core": "^8.2.0",
"@capacitor/haptics": "^8.0.1",
"@capacitor/ios": "^8.2.0",
"@dantheman827/taglib-ts": "https://github.com/DanTheMan827/taglib-ts/archive/ebd0e369b706c127a280d4ad631977f8d12ff88f.tar.gz",
"@dantheman827/taglib-ts": "^0.1.5",
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",