Revert "style: auto-fix linting issues"

This reverts commit e1b0b403fa.
This commit is contained in:
uimaxbai 2026-04-22 21:28:43 +01:00
parent 464e4e3e29
commit fa8fc6be15
6 changed files with 89 additions and 275 deletions

View file

@ -115,8 +115,8 @@
</head> </head>
<body> <body>
<audio id="audio-player" crossorigin="anonymous" style="display: none"></audio> <audio id="audio-player" style="display: none"></audio>
<video id="video-player" crossorigin="anonymous" style="display: none"></video> <video id="video-player" style="display: none"></video>
<div id="context-menu"> <div id="context-menu">
<ul> <ul>
<li data-action="shuffle-play-card" data-type-filter="album,playlist,mix,user-playlist"> <li data-action="shuffle-play-card" data-type-filter="album,playlist,mix,user-playlist">

View file

@ -1590,10 +1590,10 @@ class HiFiClient {
const attr = inc.attributes ?? ({} as JsonApiIncludeAttributes); const attr = inc.attributes ?? ({} as JsonApiIncludeAttributes);
let pic_id: string | null = null; let pic_id: string | null = null;
const art_refs_artist = (() => { const art_data = inc.relationships?.profileArt?.data;
const d = inc.relationships?.profileArt?.data; if (Array.isArray(art_data) && art_data.length > 0) {
return Array.isArray(d) ? d : d ? [d as JsonApiRef] : []; const artwork = artworks_map[art_data[0].id];
})(); const art_refs_artist = (() => { const d = inc.relationships?.profileArt?.data; return Array.isArray(d) ? d : d ? [d as JsonApiRef] : []; })();
if (art_refs_artist.length > 0) { if (art_refs_artist.length > 0) {
const artwork = artworks_map[art_refs_artist[0].id]; const artwork = artworks_map[art_refs_artist[0].id];
const files = artwork?.attributes?.files; const files = artwork?.attributes?.files;
@ -1653,21 +1653,15 @@ class HiFiClient {
if (i.type === 'artists') artists_map[i.id] = i; if (i.type === 'artists') artists_map[i.id] = i;
} }
const toArray = (data: unknown): JsonApiRef[] => {
if (Array.isArray(data)) return data as JsonApiRef[];
if (data && typeof data === 'object') return [data as JsonApiRef];
return [];
};
const resolveAlbum = (entry: JsonApiRef): TidalSimilarAlbum => { const resolveAlbum = (entry: JsonApiRef): TidalSimilarAlbum => {
const aid = entry.id; const aid = entry.id;
const inc = albums_map[aid] ?? ({} as JsonApiInclude); const inc = albums_map[aid] ?? ({} as JsonApiInclude);
const attr = inc.attributes ?? ({} as JsonApiIncludeAttributes); const attr = inc.attributes ?? ({} as JsonApiIncludeAttributes);
let cover_id: string | null = null; let cover_id: string | null = null;
const art_refs = toArray(inc.relationships?.coverArt?.data); const art_data = inc.relationships?.coverArt?.data;
if (art_refs.length > 0) { if (Array.isArray(art_data) && art_data.length > 0) {
const artwork = artworks_map[art_refs[0].id]; const artwork = artworks_map[art_data[0].id];
const files = artwork?.attributes?.files; const files = artwork?.attributes?.files;
if (Array.isArray(files) && files[0]?.href) { if (Array.isArray(files) && files[0]?.href) {
cover_id = HiFiClient.#extractUuidFromTidalUrl(files[0].href); cover_id = HiFiClient.#extractUuidFromTidalUrl(files[0].href);
@ -1675,14 +1669,16 @@ class HiFiClient {
} }
const artist_list: Array<{ id: number; name: string }> = []; const artist_list: Array<{ id: number; name: string }> = [];
const artist_refs = toArray(inc.relationships?.artists?.data); const artists_data = inc.relationships?.artists?.data;
for (const a_entry of artist_refs) { if (Array.isArray(artists_data)) {
const a_obj = artists_map[a_entry.id]; for (const a_entry of artists_data) {
if (a_obj) { const a_obj = artists_map[a_entry.id];
artist_list.push({ if (a_obj) {
id: Number(a_obj.id), artist_list.push({
name: a_obj.attributes?.name ?? '', id: Number(a_obj.id),
}); name: a_obj.attributes?.name ?? '',
});
}
} }
} }
@ -1729,27 +1725,15 @@ class HiFiClient {
if (id) { if (id) {
const artist_url = `https://openapi.tidal.com/v2/artists/${id}`; const artist_url = `https://openapi.tidal.com/v2/artists/${id}`;
const payload = await this.#fetchJson<any>(
// Fetch v2 artist metadata and v1 top tracks in parallel. artist_url,
// The v2 endpoint gives us profile art and biography but does NOT include {
// track details or album cover art in its `included` array even when requested, countryCode: this.#countryCode,
// so we use the v1 toptracks endpoint for complete track/cover data. include: 'albums,albums.coverArt,tracks,tracks.albums,biography,profileArt',
const [payload, topTracksPayload] = await Promise.all([ collapseBy: 'FINGERPRINT',
this.#fetchJson<any>( },
artist_url, signal
{ );
countryCode: this.#countryCode,
include: 'biography,profileArt',
collapseBy: 'FINGERPRINT',
},
signal
),
this.#fetchJson<TidalListResponse<TidalTrack>>(
`https://api.tidal.com/v1/artists/${id}/toptracks`,
{ countryCode: this.#countryCode, limit: '15' },
signal
).catch(() => ({ items: [] as TidalTrack[] })),
]);
const includedMap = new Map<string, any>(); const includedMap = new Map<string, any>();
if (Array.isArray(payload?.included)) { if (Array.isArray(payload?.included)) {
@ -1759,13 +1743,14 @@ class HiFiClient {
} }
const getPic = (item: any, relName: string) => { const getPic = (item: any, relName: string) => {
const relData = item?.relationships?.[relName]?.data; if (item?.relationships?.[relName]?.data?.[0]) {
const picRef = Array.isArray(relData) ? relData[0] : relData; const picRef = item.relationships[relName].data[0];
if (!picRef) return null; const pic = includedMap.get(`artworks:${picRef.id}`);
const pic = includedMap.get(`artworks:${picRef.id}`); return pic?.attributes?.files?.[0]?.href
return pic?.attributes?.files?.[0]?.href ? HiFiClient.#extractUuidFromTidalUrl(pic.attributes.files[0].href)
? HiFiClient.#extractUuidFromTidalUrl(pic.attributes.files[0].href) : null;
: null; }
return null;
}; };
const data = payload?.data; const data = payload?.data;
@ -1773,9 +1758,7 @@ class HiFiClient {
if (data?.relationships?.biography?.data) { if (data?.relationships?.biography?.data) {
const bioRef = data.relationships.biography.data; const bioRef = data.relationships.biography.data;
const bioItem = const bioItem =
includedMap.get(`biographies:${bioRef.id}`) || includedMap.get(`biographies:${bioRef.id}`) || includedMap.get(`biography:${bioRef.id}`);
includedMap.get(`biography:${bioRef.id}`) ||
includedMap.get(`artistBiographies:${bioRef.id}`);
if (bioItem) { if (bioItem) {
biography = { text: bioItem.attributes?.text, source: bioItem.attributes?.source }; biography = { text: bioItem.attributes?.text, source: bioItem.attributes?.source };
} }
@ -1799,15 +1782,59 @@ class HiFiClient {
}; };
} }
// v1 toptracks have complete TidalTrack objects with album.cover UUIDs, const albums: any[] = [];
// proper artist/artists arrays, and real duration values. const tracks: any[] = [];
const tracks: TidalTrack[] = topTracksPayload?.items ?? [];
if (data?.relationships?.albums?.data) {
for (const ref of data.relationships.albums.data) {
const al = includedMap.get(`albums:${ref.id}`);
if (al) {
albums.push({
id: Number(al.id),
title: al.attributes?.title,
duration: al.attributes?.duration ? 100 : undefined,
numberOfTracks: al.attributes?.numberOfItems,
releaseDate: al.attributes?.releaseDate,
type: al.attributes?.albumType,
cover: getPic(al, 'coverArt'),
artist: { id: artist_data.id, name: artist_data.name },
});
}
}
}
if (data?.relationships?.tracks?.data) {
for (const ref of data.relationships.tracks.data) {
const tr = includedMap.get(`tracks:${ref.id}`);
if (tr) {
let albumInfo = undefined;
if (tr.relationships?.albums?.data?.[0]) {
const aRef = tr.relationships.albums.data[0];
const aItem = includedMap.get(`albums:${aRef.id}`);
if (aItem) {
albumInfo = {
id: Number(aItem.id),
title: aItem.attributes?.title,
cover: getPic(aItem, 'coverArt'),
};
}
}
tracks.push({
id: Number(tr.id),
title: tr.attributes?.title,
duration: tr.attributes?.duration ? 100 : undefined,
album: albumInfo,
artist: { id: artist_data.id, name: artist_data.name },
});
}
}
}
return HiFiClient.#jsonResponse({ return HiFiClient.#jsonResponse({
version: HiFiClient.API_VERSION, version: HiFiClient.API_VERSION,
artist: artist_data, artist: artist_data,
cover, cover,
albums: { items: [] }, albums: { items: albums },
tracks, tracks,
}); });
} }

View file

@ -284,10 +284,6 @@ export class LosslessAPI {
normalized = { ...normalized, artist: track.artists[0] }; normalized = { ...normalized, artist: track.artists[0] };
} }
if (normalized.artist && (!Array.isArray(normalized.artists) || normalized.artists.length === 0)) {
normalized = { ...normalized, artists: [normalized.artist] };
}
const derivedQuality = deriveTrackQuality(normalized); const derivedQuality = deriveTrackQuality(normalized);
if (derivedQuality && normalized.audioQuality !== derivedQuality) { if (derivedQuality && normalized.audioQuality !== derivedQuality) {
normalized = { ...normalized, audioQuality: derivedQuality }; normalized = { ...normalized, audioQuality: derivedQuality };

View file

@ -2,9 +2,7 @@
// Shared Audio Context Manager - handles EQ and provides context for visualizer // Shared Audio Context Manager - handles EQ and provides context for visualizer
// Supports 3-32 parametric EQ bands // Supports 3-32 parametric EQ bands
import { isIos } from './platform-detection.js';
import { equalizerSettings, monoAudioSettings, binauralDspSettings } from './storage.js'; import { equalizerSettings, monoAudioSettings, binauralDspSettings } from './storage.js';
import { BinauralDSP } from './binaural-dsp.js';
// Generate frequency array for given number of bands using logarithmic spacing // Generate frequency array for given number of bands using logarithmic spacing
function generateFrequencies(bandCount, minFreq = 20, maxFreq = 20000) { function generateFrequencies(bandCount, minFreq = 20, maxFreq = 20000) {
@ -475,11 +473,6 @@ class AudioContextManager {
this.audio = audioElement; this.audio = audioElement;
if (isIos) {
console.log('[AudioContext] Skipping Web Audio initialization on iOS for lock screen compatibility');
return;
}
try { try {
const AudioContext = window.AudioContext || window.webkitAudioContext; const AudioContext = window.AudioContext || window.webkitAudioContext;
@ -490,23 +483,6 @@ class AudioContextManager {
this.audioContext = new AudioContext(); this.audioContext = new AudioContext();
} }
if (!this.sources.has(audioElement)) {
const src = this.audioContext.createMediaElementSource(audioElement);
this.sources.set(audioElement, src);
}
this.source = this.sources.get(audioElement);
try {
this.audioContext.destination.channelCount = Math.min(this.audioContext.destination.maxChannelCount, 8);
this.audioContext.destination.channelCountMode = 'explicit';
this.audioContext.destination.channelInterpretation = 'discrete';
} catch {
// Some browsers may not support changing destination channel count
}
this.binauralDsp = new BinauralDSP(this.audioContext);
void this._loadBinauralSettings();
this.analyser = this.audioContext.createAnalyser(); this.analyser = this.audioContext.createAnalyser();
this.analyser.fftSize = 1024; this.analyser.fftSize = 1024;
this.analyser.smoothingTimeConstant = 0.7; this.analyser.smoothingTimeConstant = 0.7;
@ -526,8 +502,6 @@ class AudioContextManager {
this.monoMergerNode = this.audioContext.createChannelMerger(2); this.monoMergerNode = this.audioContext.createChannelMerger(2);
this._connectGraph();
// Auto-recover from unexpected suspensions (e.g. background throttling) // Auto-recover from unexpected suspensions (e.g. background throttling)
this.audioContext.addEventListener('statechange', () => { this.audioContext.addEventListener('statechange', () => {
if (this.audioContext.state === 'interrupted' || this.audioContext.state === 'suspended') { if (this.audioContext.state === 'interrupted' || this.audioContext.state === 'suspended') {
@ -555,198 +529,16 @@ class AudioContextManager {
} }
if (this.audio === audioElement) return; if (this.audio === audioElement) return;
try { this.audio = audioElement;
if (this.source) {
try {
this.source.disconnect();
} catch {
// node may already be disconnected
}
}
this.audio = audioElement;
if (!this.sources.has(audioElement)) {
this.sources.set(audioElement, this.audioContext.createMediaElementSource(audioElement));
}
this.source = this.sources.get(audioElement);
if (this.isInitialized) {
this._connectGraph();
}
} catch (e) {
console.warn('changeSource failed:', e);
}
} }
/** /**
* Connect the audio graph based on EQ and mono audio state. * Connect the audio graph based on EQ and mono audio state.
* Uses connect-before-disconnect ordering to avoid audio dropouts: * Uses connect-before-disconnect ordering to avoid audio dropouts:
* the new chain is wired up first, then the old connections are torn down. * the new chain is wired up first, then the old connections are torn down.
* Only functional when the extension is active (crossorigin required for createMediaElementSource).
*/ */
_connectGraph() { _connectGraph() {
if (!this.isInitialized || !this.source || !this.audioContext) return; if (!this.isInitialized || !this.audioContext) return;
// Ensure graphic EQ nodes exist
if (this.geqFilters.length === 0 && this.isGraphicEQEnabled) {
this._createGraphicEQ();
}
// Helper: connect a chain segment from lastNode through graphic EQ (if enabled) to analyser -> volume -> dest
const connectTail = (lastNode) => {
if (this.isGraphicEQEnabled && this.geqFilters.length > 0) {
lastNode.connect(this.geqPreampNode);
this.geqPreampNode.connect(this.geqFilters[0]);
for (let i = 0; i < this.geqFilters.length - 1; i++) {
this.geqFilters[i].connect(this.geqFilters[i + 1]);
}
this.geqFilters[this.geqFilters.length - 1].connect(this.geqOutputNode);
this.geqOutputNode.connect(this.analyser);
} else {
lastNode.connect(this.analyser);
}
this.analyser.connect(this.volumeNode);
this.volumeNode.connect(this.audioContext.destination);
};
try {
// Ensure mono gain node exists if needed
if (this.isMonoAudioEnabled && this.monoMergerNode && !this.monoGainNode) {
this.monoGainNode = this.audioContext.createGain();
this.monoGainNode.gain.value = 0.5;
}
// --- 1. Disconnect all existing connections ---
const safeDisconnect = (node) => {
try {
node?.disconnect();
} catch {
/* */
}
};
safeDisconnect(this.source);
safeDisconnect(this.monoGainNode);
safeDisconnect(this.monoMergerNode);
if (this.binauralDsp) {
const { input, output } = this.binauralDsp.getNodes();
safeDisconnect(input);
safeDisconnect(output);
}
safeDisconnect(this.preampNode);
this.filters.forEach(safeDisconnect);
safeDisconnect(this.outputNode);
safeDisconnect(this.msSplitter);
safeDisconnect(this.msEncoderMidL);
safeDisconnect(this.msEncoderMidR);
safeDisconnect(this.msEncoderSideL);
safeDisconnect(this.msEncoderSideR);
safeDisconnect(this.msMidInput);
safeDisconnect(this.msSideInput);
this.midFilters.forEach(safeDisconnect);
this.sideFilters.forEach(safeDisconnect);
safeDisconnect(this.midOutputNode);
safeDisconnect(this.sideOutputNode);
safeDisconnect(this.msDecoderMidToL);
safeDisconnect(this.msDecoderSideToL);
safeDisconnect(this.msDecoderMidToR);
safeDisconnect(this.msDecoderSideToR);
safeDisconnect(this.msLMix);
safeDisconnect(this.msRMix);
safeDisconnect(this.msMerger);
safeDisconnect(this.msOutputNode);
safeDisconnect(this.geqPreampNode);
this.geqFilters.forEach(safeDisconnect);
safeDisconnect(this.geqOutputNode);
safeDisconnect(this.analyser);
safeDisconnect(this.volumeNode);
// --- 2. Reconnect the graph ---
let lastNode = this.source;
if (this.isMonoAudioEnabled && this.monoMergerNode) {
this.source.connect(this.monoGainNode);
this.monoGainNode.connect(this.monoMergerNode, 0, 0);
this.monoGainNode.connect(this.monoMergerNode, 0, 1);
lastNode = this.monoMergerNode;
}
if (this.isBinauralEnabled && this.binauralDsp) {
const { input, output } = this.binauralDsp.getNodes();
lastNode.connect(input);
this.binauralDsp.reconnect();
lastNode = output;
}
if (this.isEQEnabled && this.filters.length > 0) {
const useMS = this.msEnabled && this.midFilters.length > 0 && this.sideFilters.length > 0;
if (this.preampNode) {
lastNode.connect(this.preampNode);
lastNode = this.preampNode;
}
if (useMS) {
lastNode.connect(this.msSplitter);
this.msSplitter.connect(this.msEncoderMidL, 0);
this.msSplitter.connect(this.msEncoderMidR, 1);
this.msEncoderMidL.connect(this.msMidInput);
this.msEncoderMidR.connect(this.msMidInput);
this.msSplitter.connect(this.msEncoderSideL, 0);
this.msSplitter.connect(this.msEncoderSideR, 1);
this.msEncoderSideL.connect(this.msSideInput);
this.msEncoderSideR.connect(this.msSideInput);
this.msMidInput.connect(this.midFilters[0]);
for (let i = 0; i < this.midFilters.length - 1; i++) {
this.midFilters[i].connect(this.midFilters[i + 1]);
}
this.midFilters[this.midFilters.length - 1].connect(this.midOutputNode);
this.msSideInput.connect(this.sideFilters[0]);
for (let i = 0; i < this.sideFilters.length - 1; i++) {
this.sideFilters[i].connect(this.sideFilters[i + 1]);
}
this.sideFilters[this.sideFilters.length - 1].connect(this.sideOutputNode);
this.midOutputNode.connect(this.msDecoderMidToL);
this.sideOutputNode.connect(this.msDecoderSideToL);
this.msDecoderMidToL.connect(this.msLMix);
this.msDecoderSideToL.connect(this.msLMix);
this.midOutputNode.connect(this.msDecoderMidToR);
this.sideOutputNode.connect(this.msDecoderSideToR);
this.msDecoderMidToR.connect(this.msRMix);
this.msDecoderSideToR.connect(this.msRMix);
this.msLMix.connect(this.msMerger, 0, 0);
this.msRMix.connect(this.msMerger, 0, 1);
this.msMerger.connect(this.msOutputNode);
connectTail(this.msOutputNode);
} else {
lastNode.connect(this.filters[0]);
for (let i = 0; i < this.filters.length - 1; i++) {
this.filters[i].connect(this.filters[i + 1]);
}
this.filters[this.filters.length - 1].connect(this.outputNode);
connectTail(this.outputNode);
}
} else {
connectTail(lastNode);
}
this._notifyGraphChange();
} catch (e) {
console.warn('[AudioContext] Failed to connect graph:', e);
try {
this.source.connect(this.audioContext.destination);
} catch {
/* ignore */
}
}
} }
/** /**

View file

@ -663,7 +663,7 @@ export class Player {
const preloader = new Audio(); const preloader = new Audio();
preloader.preload = 'auto'; preloader.preload = 'auto';
preloader.muted = true; preloader.muted = true;
preloader.src = getProxyUrl(streamUrl); preloader.src = streamUrl;
streamInfo.preloader = preloader; // Hold reference streamInfo.preloader = preloader; // Hold reference
} }
} }
@ -1282,7 +1282,7 @@ export class Player {
} catch {} } catch {}
this.shakaInitialized = false; this.shakaInitialized = false;
} }
activeElement.src = getProxyUrl(streamUrl); activeElement.src = streamUrl;
this.applyAudioEffects(); this.applyAudioEffects();
this.updateAdaptiveQualityBadge(); this.updateAdaptiveQualityBadge();

View file

@ -1,5 +1,4 @@
export const getProxyUrl = (url) => { export const getProxyUrl = (url) => {
if (window.__tidalOriginExtension) return url; if (window.__tidalOriginExtension) return url;
if (url.startsWith('https://audio-proxy.binimum.org/')) return url;
return `https://audio-proxy.binimum.org/proxy-audio?url=${url}`; return `https://audio-proxy.binimum.org/proxy-audio?url=${url}`;
}; };