style: auto-fix linting issues
This commit is contained in:
parent
c6f82bbac2
commit
d9878596e2
6 changed files with 135 additions and 82 deletions
|
|
@ -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 }],
|
||||||
[
|
[
|
||||||
|
|
|
||||||
71
js/api.js
71
js/api.js
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
106
js/player.js
106
js/player.js
|
|
@ -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] });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue