murmuraba
Version:
Real-time audio noise reduction with advanced chunked processing for web applications
119 lines (118 loc) • 4.21 kB
JavaScript
import { LOG_PREFIX } from './constants';
export class PlaybackManager {
constructor() {
this.audioElements = new Map();
}
/**
* Toggle chunk playback
*/
async toggleChunkPlayback(chunk, audioType, onPlayStateChange) {
// Store the callback for global state management
this.stateChangeCallback = onPlayStateChange;
const audioUrl = audioType === 'processed' ? chunk.processedAudioUrl : chunk.originalAudioUrl;
if (!audioUrl) {
console.error(`❌ ${LOG_PREFIX.ERROR} No ${audioType} audio URL for chunk ${chunk.id}`);
return;
}
// Get or create audio element
const audioKey = `${chunk.id}-${audioType}`;
let audio = this.audioElements.get(audioKey);
if (!audio) {
audio = new Audio(audioUrl);
this.audioElements.set(audioKey, audio);
// Set up event listeners
audio.addEventListener('ended', () => {
onPlayStateChange(chunk.id, false);
});
audio.addEventListener('error', (e) => {
console.error(`❌ ${LOG_PREFIX.ERROR} Audio playback error:`, e);
onPlayStateChange(chunk.id, false);
});
}
// Check if this specific audio is already playing
const isThisAudioPlaying = !audio.paused;
if (isThisAudioPlaying) {
// Stop this specific audio
audio.pause();
audio.currentTime = 0;
onPlayStateChange(chunk.id, false);
}
else {
// Stop all audio from OTHER chunks, but not from this chunk
this.stopAllAudioExceptChunk(chunk.id);
// Stop the other audio type of the same chunk
const otherAudioType = audioType === 'processed' ? 'original' : 'processed';
const otherAudioKey = `${chunk.id}-${otherAudioType}`;
const otherAudio = this.audioElements.get(otherAudioKey);
if (otherAudio && !otherAudio.paused) {
otherAudio.pause();
otherAudio.currentTime = 0;
}
// Start playback
try {
await audio.play();
onPlayStateChange(chunk.id, true);
}
catch (error) {
console.error(`❌ ${LOG_PREFIX.ERROR} Failed to play audio:`, error);
onPlayStateChange(chunk.id, false);
}
}
}
/**
* Stop all audio playback
*/
stopAllAudio() {
this.audioElements.forEach((audio, key) => {
if (!audio.paused) {
audio.pause();
audio.currentTime = 0;
// Extract chunk ID from key and update state
const keyChunkId = key.split('-')[0];
if (this.stateChangeCallback) {
this.stateChangeCallback(keyChunkId, false);
}
}
});
}
/**
* Stop all audio except for a specific chunk
*/
stopAllAudioExceptChunk(chunkId) {
this.audioElements.forEach((audio, key) => {
if (!key.startsWith(chunkId) && !audio.paused) {
audio.pause();
audio.currentTime = 0;
// Extract chunk ID from key (format: "chunkId-audioType")
const keyChunkId = key.split('-')[0];
if (this.stateChangeCallback) {
this.stateChangeCallback(keyChunkId, false);
}
}
});
}
/**
* Clean up audio elements for a chunk
*/
cleanupChunk(chunkId) {
const keysToRemove = [];
this.audioElements.forEach((audio, key) => {
if (key.startsWith(chunkId)) {
audio.pause();
audio.src = '';
keysToRemove.push(key);
}
});
keysToRemove.forEach(key => this.audioElements.delete(key));
}
/**
* Clean up all audio elements
*/
cleanup() {
this.audioElements.forEach(audio => {
audio.pause();
audio.src = '';
});
this.audioElements.clear();
}
}