style: auto-fix linting issues

This commit is contained in:
binimum 2026-03-22 20:13:27 +00:00 committed by github-actions[bot]
parent c6f82bbac2
commit d9878596e2
6 changed files with 135 additions and 82 deletions

View file

@ -563,7 +563,13 @@ export class HiFiClient {
[ [
q, q,
'https://api.tidal.com/v1/search', 'https://api.tidal.com/v1/search',
{ query: q, limit, offset, types: 'ARTISTS,ALBUMS,TRACKS,VIDEOS,PLAYLISTS', countryCode: this.countryCode }, {
query: q,
limit,
offset,
types: 'ARTISTS,ALBUMS,TRACKS,VIDEOS,PLAYLISTS',
countryCode: this.countryCode,
},
], ],
[s, 'https://api.tidal.com/v1/search/tracks', { query: s, limit, offset, countryCode: this.countryCode }], [s, 'https://api.tidal.com/v1/search/tracks', { query: s, limit, offset, countryCode: this.countryCode }],
[ [

View file

@ -429,45 +429,47 @@ export class LosslessAPI {
try { try {
const response = await this.fetchWithRetry(`/search/?q=${encodeURIComponent(query)}`, options); const response = await this.fetchWithRetry(`/search/?q=${encodeURIComponent(query)}`, options);
const data = await response.json(); const data = await response.json();
// Check if backend returned an error or if this looks like individual fallback // Check if backend returned an error or if this looks like individual fallback
if (data.error || (!data.tracks && !data.artists && !data.albums && (!data.data || !data.data.tracks))) { if (data.error || (!data.tracks && !data.artists && !data.albums && (!data.data || !data.data.tracks))) {
throw new Error('Fallback to individual searches'); throw new Error('Fallback to individual searches');
} }
const extractSection = (key) => this.normalizeSearchResponse(data, key); const extractSection = (key) => this.normalizeSearchResponse(data, key);
const tracksData = extractSection('tracks'); const tracksData = extractSection('tracks');
const artistsData = extractSection('artists'); const artistsData = extractSection('artists');
const albumsData = extractSection('albums'); const albumsData = extractSection('albums');
const playlistsData = extractSection('playlists'); const playlistsData = extractSection('playlists');
const videosData = extractSection('videos'); const videosData = extractSection('videos');
const results = { const results = {
tracks: { tracks: {
...tracksData, ...tracksData,
items: tracksData.items.map(t => this.prepareTrack(t)) items: tracksData.items.map((t) => this.prepareTrack(t)),
}, },
artists: { artists: {
...artistsData, ...artistsData,
items: artistsData.items.map(a => this.prepareArtist(a)) items: artistsData.items.map((a) => this.prepareArtist(a)),
}, },
albums: { albums: {
...albumsData, ...albumsData,
items: albumsData.items.map(a => this.prepareAlbum(a)) items: albumsData.items.map((a) => this.prepareAlbum(a)),
}, },
playlists: playlistsData ? { playlists: playlistsData
...playlistsData, ? {
items: playlistsData.items.map(p => this.preparePlaylist(p)) ...playlistsData,
} : { items: [], limit: 0, offset: 0, totalNumberOfItems: 0 }, items: playlistsData.items.map((p) => this.preparePlaylist(p)),
}
: { items: [], limit: 0, offset: 0, totalNumberOfItems: 0 },
videos: { videos: {
...videosData, ...videosData,
items: videosData.items.map(v => this.prepareTrack(v)) items: videosData.items.map((v) => this.prepareTrack(v)),
} },
}; };
await this.cache.set('search_all', query, results); await this.cache.set('search_all', query, results);
return results; return results;
} catch (error) { } catch (error) {
// Fallback to individual searches if the backend proxy doesn't support ?q= or throws // Fallback to individual searches if the backend proxy doesn't support ?q= or throws
@ -476,11 +478,15 @@ export class LosslessAPI {
this.searchVideos(query, options).catch(() => ({ items: [] })), this.searchVideos(query, options).catch(() => ({ items: [] })),
this.searchArtists(query, options).catch(() => ({ items: [] })), this.searchArtists(query, options).catch(() => ({ items: [] })),
this.searchAlbums(query, options).catch(() => ({ items: [] })), this.searchAlbums(query, options).catch(() => ({ items: [] })),
this.searchPlaylists(query, options).catch(() => ({ items: [] })) this.searchPlaylists(query, options).catch(() => ({ items: [] })),
]); ]);
return { return {
tracks, videos, artists, albums, playlists tracks,
videos,
artists,
albums,
playlists,
}; };
} }
} }
@ -1432,21 +1438,25 @@ export class LosslessAPI {
let isUsingManifestEndpoint = false; let isUsingManifestEndpoint = false;
try { try {
const manifestType = (isIos || isSafari) ? 'HLS' : 'MPEG_DASH'; const manifestType = isIos || isSafari ? 'HLS' : 'MPEG_DASH';
let canPlayAtmos = false; let canPlayAtmos = false;
try { try {
if (window.MediaSource && typeof window.MediaSource.isTypeSupported === 'function') { if (window.MediaSource && typeof window.MediaSource.isTypeSupported === 'function') {
canPlayAtmos = MediaSource.isTypeSupported('audio/mp4; codecs="ec-3"') || MediaSource.isTypeSupported('audio/mp4; codecs="eac3"'); canPlayAtmos =
MediaSource.isTypeSupported('audio/mp4; codecs="ec-3"') ||
MediaSource.isTypeSupported('audio/mp4; codecs="eac3"');
} }
if (!canPlayAtmos && typeof document !== 'undefined') { if (!canPlayAtmos && typeof document !== 'undefined') {
const a = document.createElement('audio'); const a = document.createElement('audio');
canPlayAtmos = !!(a.canPlayType('audio/mp4; codecs="ec-3"') || a.canPlayType('audio/mp4; codecs="eac3"')); canPlayAtmos = !!(
a.canPlayType('audio/mp4; codecs="ec-3"') || a.canPlayType('audio/mp4; codecs="eac3"')
);
} }
} catch (e) {} } catch (e) {}
const paramsArray = []; const paramsArray = [];
if (quality === 'LOW') { if (quality === 'LOW') {
paramsArray.push(['formats', 'HEAACV1']); paramsArray.push(['formats', 'HEAACV1']);
} else if (quality === 'HIGH') { } else if (quality === 'HIGH') {
@ -1480,16 +1490,21 @@ export class LosslessAPI {
const params = new URLSearchParams(paramsArray); const params = new URLSearchParams(paramsArray);
const response = await this.fetchWithRetry(`/trackManifests/?id=${id}&${params.toString()}`, { type: 'streaming', minVersion: '2.7' }); const response = await this.fetchWithRetry(`/trackManifests/?id=${id}&${params.toString()}`, {
type: 'streaming',
minVersion: '2.7',
});
const jsonResponse = await response.json(); const jsonResponse = await response.json();
const url = jsonResponse?.data?.data?.attributes?.uri; const url = jsonResponse?.data?.data?.attributes?.uri;
if (url) { if (url) {
streamUrl = url; streamUrl = url;
manifestRgInfo = { manifestRgInfo = {
trackReplayGain: jsonResponse?.data?.data?.attributes?.trackAudioNormalizationData?.replayGain, trackReplayGain: jsonResponse?.data?.data?.attributes?.trackAudioNormalizationData?.replayGain,
trackPeakAmplitude: jsonResponse?.data?.data?.attributes?.trackAudioNormalizationData?.peakAmplitude, trackPeakAmplitude:
jsonResponse?.data?.data?.attributes?.trackAudioNormalizationData?.peakAmplitude,
albumReplayGain: jsonResponse?.data?.data?.attributes?.albumAudioNormalizationData?.replayGain, albumReplayGain: jsonResponse?.data?.data?.attributes?.albumAudioNormalizationData?.replayGain,
albumPeakAmplitude: jsonResponse?.data?.data?.attributes?.albumAudioNormalizationData?.peakAmplitude albumPeakAmplitude:
jsonResponse?.data?.data?.attributes?.albumAudioNormalizationData?.peakAmplitude,
}; };
isUsingManifestEndpoint = true; isUsingManifestEndpoint = true;
} else { } else {
@ -1515,14 +1530,14 @@ export class LosslessAPI {
trackReplayGain: lookup.info.trackReplayGain || lookup.info.replayGain, trackReplayGain: lookup.info.trackReplayGain || lookup.info.replayGain,
trackPeakAmplitude: lookup.info.trackPeakAmplitude || lookup.info.peakAmplitude, trackPeakAmplitude: lookup.info.trackPeakAmplitude || lookup.info.peakAmplitude,
albumReplayGain: lookup.info.albumReplayGain, albumReplayGain: lookup.info.albumReplayGain,
albumPeakAmplitude: lookup.info.albumPeakAmplitude albumPeakAmplitude: lookup.info.albumPeakAmplitude,
}; };
} }
} }
const result = { url: streamUrl, rgInfo: manifestRgInfo }; const result = { url: streamUrl, rgInfo: manifestRgInfo };
this.streamCache.set(cacheKey, result); this.streamCache.set(cacheKey, result);
return result; return result;
} }

View file

@ -1196,18 +1196,24 @@ class CommandPalette {
} }
async setQuality(quality) { async setQuality(quality) {
const qualityNames = { auto: 'Auto', LOW: 'Low', HIGH: 'High', LOSSLESS: 'Lossless', HI_RES_LOSSLESS: 'Hi-Res' }; const qualityNames = {
auto: 'Auto',
LOW: 'Low',
HIGH: 'High',
LOSSLESS: 'Lossless',
HI_RES_LOSSLESS: 'Hi-Res',
};
if (Player.instance) { if (Player.instance) {
// Set fallback API quality (Auto maps back to Hi-Res) // Set fallback API quality (Auto maps back to Hi-Res)
const apiQuality = quality === 'auto' ? 'HI_RES_LOSSLESS' : quality; const apiQuality = quality === 'auto' ? 'HI_RES_LOSSLESS' : quality;
Player.instance.setQuality(apiQuality); Player.instance.setQuality(apiQuality);
localStorage.setItem('playback-quality', apiQuality); localStorage.setItem('playback-quality', apiQuality);
// Set adaptive streaming quality // Set adaptive streaming quality
localStorage.setItem('adaptive-playback-quality', quality); localStorage.setItem('adaptive-playback-quality', quality);
if (Player.instance.forceQuality) Player.instance.forceQuality(quality); if (Player.instance.forceQuality) Player.instance.forceQuality(quality);
const streamingSelect = document.getElementById('streaming-quality-setting'); const streamingSelect = document.getElementById('streaming-quality-setting');
if (streamingSelect) streamingSelect.value = quality; if (streamingSelect) streamingSelect.value = quality;
} }

View file

@ -50,22 +50,22 @@ export class MusicAPI {
if (typeof api.search === 'function') { if (typeof api.search === 'function') {
return api.search(query, options); return api.search(query, options);
} }
// Fallback for providers that don't implement unified search // Fallback for providers that don't implement unified search
const [tracksResult, videosResult, artistsResult, albumsResult, playlistsResult] = await Promise.all([ const [tracksResult, videosResult, artistsResult, albumsResult, playlistsResult] = await Promise.all([
api.searchTracks(query, options), api.searchTracks(query, options),
api.searchVideos ? api.searchVideos(query, options) : Promise.resolve({ items: [] }), api.searchVideos ? api.searchVideos(query, options) : Promise.resolve({ items: [] }),
api.searchArtists(query, options), api.searchArtists(query, options),
api.searchAlbums(query, options), api.searchAlbums(query, options),
api.searchPlaylists ? api.searchPlaylists(query, options) : Promise.resolve({ items: [] }) api.searchPlaylists ? api.searchPlaylists(query, options) : Promise.resolve({ items: [] }),
]); ]);
return { return {
tracks: tracksResult, tracks: tracksResult,
videos: videosResult, videos: videosResult,
artists: artistsResult, artists: artistsResult,
albums: albumsResult, albums: albumsResult,
playlists: playlistsResult playlists: playlistsResult,
}; };
} }

View file

@ -106,7 +106,7 @@ export class Player {
bufferingGoal: 30, bufferingGoal: 30,
rebufferingGoal: 2, rebufferingGoal: 2,
bufferBehind: 30, bufferBehind: 30,
jumpLargeGaps: true jumpLargeGaps: true,
}, },
abr: { abr: {
enabled: true, enabled: true,
@ -115,11 +115,11 @@ export class Player {
defaultBandwidthEstimate: 100000, defaultBandwidthEstimate: 100000,
switchInterval: 1, // Check more frequently switchInterval: 1, // Check more frequently
bandwidthDowngradeTarget: 0.8, // Downgrade more aggressively if bandwidth drops bandwidthDowngradeTarget: 0.8, // Downgrade more aggressively if bandwidth drops
restrictToElementSize: false restrictToElementSize: false,
}, },
mediaSource: { mediaSource: {
codecSwitchingStrategy: 'smooth' codecSwitchingStrategy: 'smooth',
} },
}); });
this.shakaPlayer.addEventListener('adaptation', this.updateAdaptiveQualityBadge.bind(this)); this.shakaPlayer.addEventListener('adaptation', this.updateAdaptiveQualityBadge.bind(this));
this.shakaPlayer.addEventListener('variantchanged', this.updateAdaptiveQualityBadge.bind(this)); this.shakaPlayer.addEventListener('variantchanged', this.updateAdaptiveQualityBadge.bind(this));
@ -501,7 +501,9 @@ export class Player {
// Warm connection/cache // Warm connection/cache
// For Blob URLs (DASH), this head request is not needed and can cause errors. // For Blob URLs (DASH), this head request is not needed and can cause errors.
if (!streamInfo.url.startsWith('blob:')) { if (!streamInfo.url.startsWith('blob:')) {
fetch(streamInfo.url, { method: 'HEAD', signal: this.preloadAbortController.signal }).catch(() => {}); fetch(streamInfo.url, { method: 'HEAD', signal: this.preloadAbortController.signal }).catch(
() => {}
);
} }
} catch (error) { } catch (error) {
if (error.name !== 'AbortError') { if (error.name !== 'AbortError') {
@ -921,10 +923,10 @@ export class Player {
await this.shakaPlayer.attach(activeElement); await this.shakaPlayer.attach(activeElement);
await this.shakaPlayer.load(streamUrl); await this.shakaPlayer.load(streamUrl);
this.shakaInitialized = true; this.shakaInitialized = true;
const savedAdaptiveQuality = localStorage.getItem('adaptive-playback-quality') || 'auto'; const savedAdaptiveQuality = localStorage.getItem('adaptive-playback-quality') || 'auto';
this.forceQuality(savedAdaptiveQuality); this.forceQuality(savedAdaptiveQuality);
this.updateAdaptiveQualityBadge(); this.updateAdaptiveQualityBadge();
} else { } else {
activeElement.src = streamUrl; activeElement.src = streamUrl;
@ -963,7 +965,7 @@ export class Player {
// We only need the legacy track info if we missed getting ReplayGain from the manifest endpoint // We only need the legacy track info if we missed getting ReplayGain from the manifest endpoint
const resolvedStreamInfo = await streamInfoPromise; const resolvedStreamInfo = await streamInfoPromise;
if (this.playbackSequence !== currentSequence) return; if (this.playbackSequence !== currentSequence) return;
streamUrl = resolvedStreamInfo.url; streamUrl = resolvedStreamInfo.url;
if (resolvedStreamInfo.rgInfo) { if (resolvedStreamInfo.rgInfo) {
@ -1001,10 +1003,10 @@ export class Player {
} }
this.shakaInitialized = true; this.shakaInitialized = true;
this.applyAudioEffects(); this.applyAudioEffects();
const savedAdaptiveQuality = localStorage.getItem('adaptive-playback-quality') || 'auto'; const savedAdaptiveQuality = localStorage.getItem('adaptive-playback-quality') || 'auto';
this.forceQuality(savedAdaptiveQuality); this.forceQuality(savedAdaptiveQuality);
this.updateAdaptiveQualityBadge(); this.updateAdaptiveQualityBadge();
const canPlay = await this.waitForCanPlayOrTimeout(activeElement); const canPlay = await this.waitForCanPlayOrTimeout(activeElement);
@ -1330,7 +1332,7 @@ export class Player {
handlePlayPause() { handlePlayPause() {
const el = this.activeElement; const el = this.activeElement;
const hasSource = el.src || el.currentSrc || el.srcObject || this.shakaInitialized; const hasSource = el.src || el.currentSrc || el.srcObject || this.shakaInitialized;
if (!hasSource || el.error) { if (!hasSource || el.error) {
if (this.currentTrack) { if (this.currentTrack) {
this.playTrackFromQueue(0, 0); this.playTrackFromQueue(0, 0);
@ -1653,7 +1655,7 @@ export class Player {
updateAdaptiveQualityBadge() { updateAdaptiveQualityBadge() {
if (!this.currentTrack) return; if (!this.currentTrack) return;
try { try {
const titleEl = document.querySelector('.now-playing-bar .title'); const titleEl = document.querySelector('.now-playing-bar .title');
if (!titleEl) return; if (!titleEl) return;
@ -1662,11 +1664,12 @@ export class Player {
// Determine if the track is inherently an Atmos track based on metadata // Determine if the track is inherently an Atmos track based on metadata
const trackBaseQuality = deriveTrackQuality(this.currentTrack); const trackBaseQuality = deriveTrackQuality(this.currentTrack);
const isTrackAtmos = trackBaseQuality === 'DOLBY_ATMOS' || this.currentTrack?.audioQuality === 'DOLBY_ATMOS'; const isTrackAtmos =
trackBaseQuality === 'DOLBY_ATMOS' || this.currentTrack?.audioQuality === 'DOLBY_ATMOS';
if (this.shakaInitialized) { if (this.shakaInitialized) {
const variants = this.shakaPlayer.getVariantTracks(); const variants = this.shakaPlayer.getVariantTracks();
const activeVariant = variants.find(t => t.active); const activeVariant = variants.find((t) => t.active);
if (activeVariant) { if (activeVariant) {
if (!badgeEl) { if (!badgeEl) {
badgeEl = document.createElement('span'); badgeEl = document.createElement('span');
@ -1676,16 +1679,18 @@ export class Player {
const staticBadge = titleEl.querySelector('.quality-badge:not(.shaka-quality-badge)'); const staticBadge = titleEl.querySelector('.quality-badge:not(.shaka-quality-badge)');
if (staticBadge) staticBadge.style.display = 'none'; if (staticBadge) staticBadge.style.display = 'none';
} }
let text = ''; let text = '';
let isAtmosPlaying = false; let isAtmosPlaying = false;
if (activeVariant.videoBandwidth && activeVariant.height) { if (activeVariant.videoBandwidth && activeVariant.height) {
text = `${activeVariant.height}p`; text = `${activeVariant.height}p`;
} else if (activeVariant.audioCodec) { } else if (activeVariant.audioCodec) {
const codec = activeVariant.audioCodec.toLowerCase(); const codec = activeVariant.audioCodec.toLowerCase();
if (codec.includes('flac')) { if (codec.includes('flac')) {
const sampleRate = activeVariant.audioSamplingRate ? activeVariant.audioSamplingRate / 1000 : 44.1; const sampleRate = activeVariant.audioSamplingRate
? activeVariant.audioSamplingRate / 1000
: 44.1;
if (sampleRate > 48 || activeVariant.audioBandwidth > 1200000) { if (sampleRate > 48 || activeVariant.audioBandwidth > 1200000) {
text = `HD 24/${sampleRate}`; text = `HD 24/${sampleRate}`;
} else { } else {
@ -1702,13 +1707,18 @@ export class Player {
} else { } else {
text = activeVariant.audioCodec; text = activeVariant.audioCodec;
} }
if (activeVariant.audioBandwidth && !text.includes('FLAC') && !text.includes('HD') && !isAtmosPlaying) { if (
activeVariant.audioBandwidth &&
!text.includes('FLAC') &&
!text.includes('HD') &&
!isAtmosPlaying
) {
text += ` ${Math.round(activeVariant.audioBandwidth / 1000)}k`; text += ` ${Math.round(activeVariant.audioBandwidth / 1000)}k`;
} }
} else { } else {
text = 'Auto'; text = 'Auto';
} }
if (isAtmosPlaying) { if (isAtmosPlaying) {
badgeEl.className = 'quality-badge quality-atmos shaka-quality-badge'; badgeEl.className = 'quality-badge quality-atmos shaka-quality-badge';
badgeEl.innerHTML = SVG_ATMOS(20); badgeEl.innerHTML = SVG_ATMOS(20);
@ -1716,9 +1726,14 @@ export class Player {
badgeEl.className = 'quality-badge quality-hires shaka-quality-badge'; badgeEl.className = 'quality-badge quality-hires shaka-quality-badge';
badgeEl.textContent = text; badgeEl.textContent = text;
} }
badgeEl.style.display = (text || isAtmosPlaying) ? 'inline-flex' : 'none'; badgeEl.style.display = text || isAtmosPlaying ? 'inline-flex' : 'none';
} }
} else if ((isIos || isSafari) && this.activeElement && this.activeElement.src && (this.activeElement.src.includes('.m3u8') || this.currentTrack)) { } else if (
(isIos || isSafari) &&
this.activeElement &&
this.activeElement.src &&
(this.activeElement.src.includes('.m3u8') || this.currentTrack)
) {
if (!badgeEl) { if (!badgeEl) {
badgeEl = document.createElement('span'); badgeEl = document.createElement('span');
badgeEl.className = 'quality-badge quality-hires shaka-quality-badge'; badgeEl.className = 'quality-badge quality-hires shaka-quality-badge';
@ -1727,24 +1742,28 @@ export class Player {
const staticBadge = titleEl.querySelector('.quality-badge:not(.shaka-quality-badge)'); const staticBadge = titleEl.querySelector('.quality-badge:not(.shaka-quality-badge)');
if (staticBadge) staticBadge.style.display = 'none'; if (staticBadge) staticBadge.style.display = 'none';
} }
let text = ''; let text = '';
// Ensure device can actually decode Atmos before rendering logo for HLS // Ensure device can actually decode Atmos before rendering logo for HLS
let deviceSupportsAtmos = false; let deviceSupportsAtmos = false;
try { try {
if (window.MediaSource && typeof window.MediaSource.isTypeSupported === 'function') { if (window.MediaSource && typeof window.MediaSource.isTypeSupported === 'function') {
deviceSupportsAtmos = MediaSource.isTypeSupported('audio/mp4; codecs="ec-3"') || MediaSource.isTypeSupported('audio/mp4; codecs="eac3"'); deviceSupportsAtmos =
MediaSource.isTypeSupported('audio/mp4; codecs="ec-3"') ||
MediaSource.isTypeSupported('audio/mp4; codecs="eac3"');
} }
if (!deviceSupportsAtmos && typeof document !== 'undefined') { if (!deviceSupportsAtmos && typeof document !== 'undefined') {
const a = document.createElement('audio'); const a = document.createElement('audio');
deviceSupportsAtmos = !!(a.canPlayType('audio/mp4; codecs="ec-3"') || a.canPlayType('audio/mp4; codecs="eac3"')); deviceSupportsAtmos = !!(
a.canPlayType('audio/mp4; codecs="ec-3"') || a.canPlayType('audio/mp4; codecs="eac3"')
);
} }
} catch (e) {} } catch (e) {}
let isAtmosPlaying = isTrackAtmos && deviceSupportsAtmos; let isAtmosPlaying = isTrackAtmos && deviceSupportsAtmos;
const q = this.quality || localStorage.getItem('adaptive-playback-quality') || 'auto'; const q = this.quality || localStorage.getItem('adaptive-playback-quality') || 'auto';
if (!isAtmosPlaying) { if (!isAtmosPlaying) {
if (q === 'HI_RES_LOSSLESS') text = 'HD FLAC'; if (q === 'HI_RES_LOSSLESS') text = 'HD FLAC';
else if (q === 'LOSSLESS') text = 'FLAC'; else if (q === 'LOSSLESS') text = 'FLAC';
@ -1753,7 +1772,7 @@ export class Player {
else if (q === 'auto') text = 'HLS Auto'; else if (q === 'auto') text = 'HLS Auto';
else text = 'HLS'; else text = 'HLS';
} }
if (isAtmosPlaying) { if (isAtmosPlaying) {
badgeEl.innerHTML = SVG_ATMOS(20); badgeEl.innerHTML = SVG_ATMOS(20);
badgeEl.className = 'quality-badge quality-atmos shaka-quality-badge'; badgeEl.className = 'quality-badge quality-atmos shaka-quality-badge';
@ -1771,7 +1790,8 @@ export class Player {
} }
evaluateCrossCodecAbr() { evaluateCrossCodecAbr() {
if (!this.shakaInitialized || !this.shakaPlayer || this.shakaPlayer.isBuffering() || this.activeElement.paused) return; if (!this.shakaInitialized || !this.shakaPlayer || this.shakaPlayer.isBuffering() || this.activeElement.paused)
return;
try { try {
const stats = this.shakaPlayer.getStats(); const stats = this.shakaPlayer.getStats();
@ -1781,13 +1801,13 @@ export class Player {
const variants = this.shakaPlayer.getVariantTracks(); const variants = this.shakaPlayer.getVariantTracks();
if (variants.length < 2) return; if (variants.length < 2) return;
const activeVariant = variants.find(v => v.active); const activeVariant = variants.find((v) => v.active);
if (!activeVariant) return; if (!activeVariant) return;
// Sort variants by bandwidth descending // Sort variants by bandwidth descending
const sortedVariants = [...variants].sort((a, b) => b.bandwidth - a.bandwidth); const sortedVariants = [...variants].sort((a, b) => b.bandwidth - a.bandwidth);
const safeUpBandwidth = estimatedBandwidth * 0.85; const safeUpBandwidth = estimatedBandwidth * 0.85;
let bestVariant = sortedVariants[0]; let bestVariant = sortedVariants[0];
for (const variant of sortedVariants) { for (const variant of sortedVariants) {
if (variant.bandwidth <= safeUpBandwidth) { if (variant.bandwidth <= safeUpBandwidth) {
@ -1795,7 +1815,7 @@ export class Player {
break; break;
} }
} }
if (sortedVariants[sortedVariants.length - 1].bandwidth > safeUpBandwidth) { if (sortedVariants[sortedVariants.length - 1].bandwidth > safeUpBandwidth) {
bestVariant = sortedVariants[sortedVariants.length - 1]; bestVariant = sortedVariants[sortedVariants.length - 1];
} }
@ -1817,9 +1837,9 @@ export class Player {
try { try {
if (quality === 'auto') { if (quality === 'auto') {
this.shakaPlayer.configure({ this.shakaPlayer.configure({
abr: { enabled: true }, abr: { enabled: true },
preferredAudioCodecs: [] preferredAudioCodecs: [],
}); });
return; return;
} }
@ -1828,12 +1848,12 @@ export class Player {
if (variants.length === 0) return; if (variants.length === 0) return;
let bestVariant = variants[0]; let bestVariant = variants[0];
if (quality === 'LOW' || quality === 'HIGH') { if (quality === 'LOW' || quality === 'HIGH') {
const targetBandwidth = quality === 'LOW' ? 96000 : 320000; const targetBandwidth = quality === 'LOW' ? 96000 : 320000;
const aacVariants = variants.filter(v => v.audioCodec && v.audioCodec.toLowerCase().includes('mp4a')); const aacVariants = variants.filter((v) => v.audioCodec && v.audioCodec.toLowerCase().includes('mp4a'));
const searchVariants = aacVariants.length > 0 ? aacVariants : variants; const searchVariants = aacVariants.length > 0 ? aacVariants : variants;
let minDiff = Infinity; let minDiff = Infinity;
for (const variant of searchVariants) { for (const variant of searchVariants) {
const bw = variant.audioBandwidth || variant.bandwidth; const bw = variant.audioBandwidth || variant.bandwidth;
@ -1844,22 +1864,24 @@ export class Player {
} }
} }
} else if (quality === 'LOSSLESS' || quality === 'HI_RES_LOSSLESS') { } else if (quality === 'LOSSLESS' || quality === 'HI_RES_LOSSLESS') {
const flacVariants = variants.filter(v => v.audioCodec && v.audioCodec.toLowerCase().includes('flac')); const flacVariants = variants.filter(
(v) => v.audioCodec && v.audioCodec.toLowerCase().includes('flac')
);
if (flacVariants.length > 0) { if (flacVariants.length > 0) {
if (quality === 'HI_RES_LOSSLESS') { if (quality === 'HI_RES_LOSSLESS') {
// Find highest quality FLAC // Find highest quality FLAC
bestVariant = flacVariants.reduce((prev, current) => { bestVariant = flacVariants.reduce((prev, current) => {
const prevBw = prev.audioBandwidth || prev.bandwidth || 0; const prevBw = prev.audioBandwidth || prev.bandwidth || 0;
const currBw = current.audioBandwidth || current.bandwidth || 0; const currBw = current.audioBandwidth || current.bandwidth || 0;
return (currBw > prevBw) ? current : prev; return currBw > prevBw ? current : prev;
}, flacVariants[0]); }, flacVariants[0]);
} else { } else {
// Find standard lossless (lowest bandwidth FLAC, usually 16-bit 44.1kHz) // Find standard lossless (lowest bandwidth FLAC, usually 16-bit 44.1kHz)
bestVariant = flacVariants.reduce((prev, current) => { bestVariant = flacVariants.reduce((prev, current) => {
const prevBw = prev.audioBandwidth || prev.bandwidth || 0; const prevBw = prev.audioBandwidth || prev.bandwidth || 0;
const currBw = current.audioBandwidth || current.bandwidth || 0; const currBw = current.audioBandwidth || current.bandwidth || 0;
return (currBw < prevBw) ? current : prev; return currBw < prevBw ? current : prev;
}, flacVariants[0]); }, flacVariants[0]);
} }
} else { } else {
@ -1867,13 +1889,13 @@ export class Player {
bestVariant = variants.reduce((prev, current) => { bestVariant = variants.reduce((prev, current) => {
const prevBw = prev.audioBandwidth || prev.bandwidth || 0; const prevBw = prev.audioBandwidth || prev.bandwidth || 0;
const currBw = current.audioBandwidth || current.bandwidth || 0; const currBw = current.audioBandwidth || current.bandwidth || 0;
return (currBw > prevBw) ? current : prev; return currBw > prevBw ? current : prev;
}, variants[0]); }, variants[0]);
} }
} }
this.shakaPlayer.configure({ abr: { enabled: false } }); this.shakaPlayer.configure({ abr: { enabled: false } });
if (bestVariant.audioCodec) { if (bestVariant.audioCodec) {
this.shakaPlayer.configure({ preferredAudioCodecs: [bestVariant.audioCodec] }); this.shakaPlayer.configure({ preferredAudioCodecs: [bestVariant.audioCodec] });
} }

View file

@ -801,10 +801,14 @@ export async function initializeSettings(scrobbler, player, api, ui) {
const streamingQualitySetting = document.getElementById('streaming-quality-setting'); const streamingQualitySetting = document.getElementById('streaming-quality-setting');
if (streamingQualitySetting) { if (streamingQualitySetting) {
const savedAdaptiveQuality = localStorage.getItem('adaptive-playback-quality') || 'auto'; const savedAdaptiveQuality = localStorage.getItem('adaptive-playback-quality') || 'auto';
// Map the stored auto state to the dropdown, or if it doesn't match an option, use the playback-quality value // Map the stored auto state to the dropdown, or if it doesn't match an option, use the playback-quality value
const optionExists = Array.from(streamingQualitySetting.options).some(opt => opt.value === savedAdaptiveQuality); const optionExists = Array.from(streamingQualitySetting.options).some(
streamingQualitySetting.value = optionExists ? savedAdaptiveQuality : (localStorage.getItem('playback-quality') || 'auto'); (opt) => opt.value === savedAdaptiveQuality
);
streamingQualitySetting.value = optionExists
? savedAdaptiveQuality
: localStorage.getItem('playback-quality') || 'auto';
// Apply initially // Apply initially
if (player.forceQuality) player.forceQuality(streamingQualitySetting.value); if (player.forceQuality) player.forceQuality(streamingQualitySetting.value);
@ -813,7 +817,7 @@ export async function initializeSettings(scrobbler, player, api, ui) {
streamingQualitySetting.addEventListener('change', (e) => { streamingQualitySetting.addEventListener('change', (e) => {
const val = e.target.value; const val = e.target.value;
// Set adaptive DASH quality // Set adaptive DASH quality
localStorage.setItem('adaptive-playback-quality', val); localStorage.setItem('adaptive-playback-quality', val);
if (player.forceQuality) player.forceQuality(val); if (player.forceQuality) player.forceQuality(val);