This commit is contained in:
2026-01-03 14:02:07 +00:00
parent c08ee70fe8
commit 111f4b347e
2 changed files with 86 additions and 30 deletions

View File

@@ -2180,20 +2180,49 @@ function initListenerMode() {
// AudioContext will be created when user enables audio to avoid suspension // AudioContext will be created when user enables audio to avoid suspension
// Create or reuse audio element to handle the MediaSource // ALWAYS create a fresh audio element to avoid MediaSource/MediaElementSource conflicts
// This is critical for page refreshes - you can only create MediaElementSource once per element
let audio; let audio;
if (window.listenerAudio) {
// Reuse existing audio element from previous initialization
audio = window.listenerAudio;
console.log('♻️ Reusing existing audio element');
// Clean up old MediaSource if it exists // Clean up old audio element if it exists
if (audio.src) { if (window.listenerAudio) {
URL.revokeObjectURL(audio.src); console.log('🧹 Cleaning up old audio element and AudioContext nodes');
audio.removeAttribute('src'); try {
audio.load(); // Reset the element window.listenerAudio.pause();
if (window.listenerAudio.src) {
URL.revokeObjectURL(window.listenerAudio.src);
} }
} else { window.listenerAudio.removeAttribute('src');
window.listenerAudio.remove(); // Remove from DOM
} catch (e) {
console.warn('Error cleaning up old audio:', e);
}
// Reset all AudioContext-related nodes
if (listenerMediaElementSourceNode) {
try {
listenerMediaElementSourceNode.disconnect();
} catch (e) { }
listenerMediaElementSourceNode = null;
}
if (listenerAnalyserNode) {
try {
listenerAnalyserNode.disconnect();
} catch (e) { }
listenerAnalyserNode = null;
}
if (listenerGainNode) {
try {
listenerGainNode.disconnect();
} catch (e) { }
listenerGainNode = null;
}
window.listenerAudio = null;
window.listenerMediaSource = null;
window.listenerAudioEnabled = false;
}
// Create a new hidden media element. // Create a new hidden media element.
// Note: MSE (MediaSource) support is often more reliable on <video> than <audio>. // Note: MSE (MediaSource) support is often more reliable on <video> than <audio>.
audio = document.createElement('video'); audio = document.createElement('video');
@@ -2204,10 +2233,7 @@ function initListenerMode() {
audio.setAttribute('playsinline', ''); audio.setAttribute('playsinline', '');
audio.style.display = 'none'; audio.style.display = 'none';
document.body.appendChild(audio); document.body.appendChild(audio);
console.log('🆕 Created new media element (video) for listener'); console.log('🆕 Created fresh media element (video) for listener');
// AudioContext will be created later on user interaction
}
// Initialize MediaSource for streaming binary chunks // Initialize MediaSource for streaming binary chunks
const mediaSource = new MediaSource(); const mediaSource = new MediaSource();
@@ -2470,8 +2496,38 @@ async function enableListenerAudio() {
return window.listenerAudio.buffered && window.listenerAudio.buffered.length > 0; return window.listenerAudio.buffered && window.listenerAudio.buffered.length > 0;
}; };
// Attempt playback IMMEDIATELY to capture user gesture // CRITICAL: Wait for buffered data before calling play()
// We do this before waiting for data so we don't lose the "user interaction" token // This prevents NotSupportedError when buffer is empty
if (!hasBufferedData()) {
console.log('⏳ Waiting for audio data to buffer before playback...');
if (audioText) audioText.textContent = 'BUFFERING...';
// Wait for data with timeout (max 5 seconds)
const waitForData = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
clearInterval(checkInterval);
reject(new Error('Timeout waiting for audio data'));
}, 5000);
const checkInterval = setInterval(() => {
if (hasBufferedData()) {
clearInterval(checkInterval);
clearTimeout(timeout);
console.log('✅ Audio data buffered, ready to play');
resolve();
}
}, 100);
});
try {
await waitForData;
} catch (e) {
console.warn('⚠️ Timeout waiting for buffer data:', e.message);
}
} else {
console.log('✅ Audio already has buffered data');
}
console.log('▶️ Attempting to play audio...'); console.log('▶️ Attempting to play audio...');
const playPromise = window.listenerAudio.play(); const playPromise = window.listenerAudio.play();