Enable AudioContext and proxy for all users, not just extension users

- Set crossorigin="anonymous" as a static HTML attribute on audio/video elements
  so all users can use createMediaElementSource (required for Web Audio API)
- Remove window.__tidalOriginExtension guards from AudioContext init/changeSource:
  binaural DSP, EQ, M/S processing, and audio graph now active for everyone
- Route direct (non-DASH) stream URLs through getProxyUrl in player.js so
  non-extension users get CORS headers from the proxy for lossless/MP4 audio
- Proxy preloader src as well so browser cache warms with the proxied URL
- Add idempotency guard in getProxyUrl to prevent double-proxying when Shaka
  retries segment requests (proxied URL still contains "tidal.com" in params)
- Extension users: still bypass proxy and get CDN URLs directly (unchanged)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
uimaxbai 2026-04-22 21:07:39 +01:00
parent 9138ec5289
commit 7b8a6e6cab
4 changed files with 41 additions and 54 deletions

View file

@ -115,14 +115,8 @@
</head>
<body>
<audio id="audio-player" style="display: none"></audio>
<video id="video-player" style="display: none"></video>
<script>
if (window.__tidalOriginExtension) {
document.getElementById('audio-player').crossOrigin = 'anonymous';
document.getElementById('video-player').crossOrigin = 'anonymous';
}
</script>
<audio id="audio-player" crossorigin="anonymous" style="display: none"></audio>
<video id="video-player" crossorigin="anonymous" style="display: none"></video>
<div id="context-menu">
<ul>
<li data-action="shuffle-play-card" data-type-filter="album,playlist,mix,user-playlist">

View file

@ -475,7 +475,7 @@ class AudioContextManager {
this.audio = audioElement;
if (isIos && !window.__tidalOriginExtension) {
if (isIos) {
console.log('[AudioContext] Skipping Web Audio initialization on iOS for lock screen compatibility');
return;
}
@ -490,24 +490,22 @@ class AudioContextManager {
this.audioContext = new AudioContext();
}
if (window.__tidalOriginExtension) {
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();
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.fftSize = 1024;
@ -528,16 +526,14 @@ class AudioContextManager {
this.monoMergerNode = this.audioContext.createChannelMerger(2);
if (window.__tidalOriginExtension) {
this._connectGraph();
}
this._connectGraph();
// Auto-recover from unexpected suspensions (e.g. background throttling)
this.audioContext.addEventListener('statechange', () => {
if (this.audioContext.state === 'interrupted' || this.audioContext.state === 'suspended') {
console.log(`[AudioContext] State changed to ${this.audioContext.state}, attempting resume`);
setTimeout(() => {
if (this.audioContext && this.audioContext.state !== 'running' && (window.__tidalOriginExtension ? this.source : true)) {
if (this.audioContext && this.audioContext.state !== 'running') {
this.audioContext.resume().catch((e) => {
console.warn('[AudioContext] Auto-resume failed:', e);
});
@ -559,31 +555,27 @@ class AudioContextManager {
}
if (this.audio === audioElement) return;
if (window.__tidalOriginExtension) {
try {
if (this.source) {
try {
this.source.disconnect();
} catch {
// node may already be disconnected
}
try {
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);
}
} else {
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);
}
}

View file

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

View file

@ -1,4 +1,5 @@
export const getProxyUrl = (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}`;
};