Event System
Understanding the comprehensive event system that powers real-time audio monitoring and reactive programming patterns.
What is the Event System?
The audio-channel-queue package provides a rich event system that allows you to react to audio playback changes, queue modifications, and progress updates in real-time. Events are channel-specific and fire automatically during audio operations.
import {
onAudioStart,
onAudioComplete,
onAudioProgress,
onQueueChange
} from 'audio-channel-queue';
// React to audio starting
onAudioStart(0, (info) => {
console.log(`Started playing: ${info.fileName}`);
});
// React to audio completing
onAudioComplete(0, (info) => {
console.log(`Finished: ${info.fileName}`);
console.log(`Remaining in queue: ${info.remainingInQueue}`);
});
// Track playback progress
onAudioProgress(0, (info) => {
console.log(`Progress: ${Math.round(info.progress * 100)}%`);
});
// Monitor queue changes
onQueueChange(0, (snapshot) => {
console.log(`Queue now has ${snapshot.totalItems} items`);
});
Core Event Types
Audio Start Events
Fired when audio begins playing on a channel:
import { onAudioStart, AudioStartInfo } from 'audio-channel-queue';
onAudioStart(0, (info: AudioStartInfo) => {
console.log('Audio started:', {
fileName: info.fileName,
duration: `${Math.round(info.duration / 1000)} seconds`,
volume: `${Math.round(info.volume * 100)}%`,
currentTime: info.currentTime
});
// Example: Update UI
updateNowPlaying(info.fileName);
setProgressBarMax(info.duration);
});
function updateNowPlaying(fileName: string): void {
const element = document.getElementById('now-playing');
if (element) {
element.textContent = `Now Playing: ${fileName}`;
}
}
function setProgressBarMax(duration: number): void {
const progressBar = document.getElementById('progress') as HTMLProgressElement;
if (progressBar) {
progressBar.max = duration;
}
}
Audio Complete Events
Fired when audio finishes playing (naturally or interrupted):
import { onAudioComplete, AudioCompleteInfo } from 'audio-channel-queue';
onAudioComplete(0, (info: AudioCompleteInfo) => {
console.log('Audio completed:', {
fileName: info.fileName,
playbackDuration: `${Math.round(info.playbackDuration / 1000)} seconds`,
remainingInQueue: info.remainingInQueue,
wasInterrupted: info.wasInterrupted
});
// Handle different completion scenarios
if (info.wasInterrupted) {
console.log('Playback was stopped early');
logInterruption(info.fileName, info.playbackDuration);
} else {
console.log('Playback completed naturally');
logSuccessfulPlayback(info.fileName);
}
// Auto-play next or handle end of queue
if (info.remainingInQueue === 0) {
onQueueFinished();
}
});
function logInterruption(fileName: string, duration: number): void {
console.log(`${fileName} was interrupted after ${duration}ms`);
}
function logSuccessfulPlayback(fileName: string): void {
console.log(`${fileName} played to completion`);
}
function onQueueFinished(): void {
console.log('Queue is empty - playback session ended');
// Maybe start background music or show completion UI
}
Audio Progress Events
Fired continuously during audio playback for real-time progress tracking:
import { onAudioProgress, AudioProgressInfo } from 'audio-channel-queue';
onAudioProgress(0, (info: AudioProgressInfo) => {
const percentage = Math.round(info.progress * 100);
const currentSeconds = Math.floor(info.currentTime / 1000);
const totalSeconds = Math.floor(info.duration / 1000);
// Update progress display
updateProgressDisplay(percentage, currentSeconds, totalSeconds);
// Trigger events at specific progress points
if (info.progress >= 0.25 && info.progress < 0.26) {
console.log('25% complete');
onQuarterProgress(info.fileName);
}
if (info.progress >= 0.5 && info.progress < 0.51) {
console.log('50% complete');
onHalfwayProgress(info.fileName);
}
if (info.progress >= 0.75 && info.progress < 0.76) {
console.log('75% complete');
onThreeQuarterProgress(info.fileName);
}
});
function updateProgressDisplay(percentage: number, current: number, total: number): void {
// Update progress bar
const progressBar = document.getElementById('progress') as HTMLProgressElement;
if (progressBar) {
progressBar.value = percentage;
}
// Update time display
const timeDisplay = document.getElementById('time-display');
if (timeDisplay) {
timeDisplay.textContent = `${current}s / ${total}s (${percentage}%)`;
}
}
function onQuarterProgress(fileName: string): void {
console.log(`${fileName} is 25% complete`);
}
function onHalfwayProgress(fileName: string): void {
console.log(`${fileName} is 50% complete - halfway point reached`);
}
function onThreeQuarterProgress(fileName: string): void {
console.log(`${fileName} is 75% complete - almost finished`);
}
Queue Change Events
Fired when the queue is modified (items added, removed, or reordered):
import { onQueueChange, QueueSnapshot } from 'audio-channel-queue';
onQueueChange(0, (snapshot: QueueSnapshot) => {
console.log('Queue changed:', {
totalItems: snapshot.totalItems,
currentlyPlaying: snapshot.currentlyPlaying,
isChannelActive: snapshot.isChannelActive
});
// Update queue display
updateQueueDisplay(snapshot);
// Handle queue state changes
if (snapshot.totalItems === 0) {
onQueueEmpty();
} else if (snapshot.totalItems === 1) {
onLastItem(snapshot.currentlyPlaying);
} else if (snapshot.totalItems > 10) {
onQueueOverloaded(snapshot.totalItems);
}
});
function updateQueueDisplay(snapshot: QueueSnapshot): void {
const queueList = document.getElementById('queue-list');
if (!queueList) return;
queueList.innerHTML = '';
snapshot.items.forEach((item, index) => {
const listItem = document.createElement('li');
listItem.className = item.isCurrentlyPlaying ? 'playing' : 'queued';
listItem.textContent = `${item.position}. ${item.fileName}`;
queueList.appendChild(listItem);
});
}
function onQueueEmpty(): void {
console.log('Queue is now empty');
// Maybe show "Add music" prompt
}
function onLastItem(fileName: string | null): void {
console.log(`Last item in queue: ${fileName}`);
// Maybe start preloading next playlist
}
function onQueueOverloaded(count: number): void {
console.warn(`Queue has ${count} items - may impact performance`);
}
Event Management Patterns
Event Handler Classes
Organize event handling with dedicated classes:
class AudioEventManager {
private channel: number;
private isSetup: boolean = false;
constructor(channel: number = 0) {
this.channel = channel;
}
setupAllEventHandlers(): void {
if (this.isSetup) return;
this.setupStartHandler();
this.setupCompleteHandler();
this.setupProgressHandler();
this.setupQueueHandler();
this.isSetup = true;
console.log(`Event handlers setup for channel ${this.channel}`);
}
private setupStartHandler(): void {
onAudioStart(this.channel, (info) => {
console.log(`[${this.channel}] Started: ${info.fileName}`);
this.onAudioStarted(info);
});
}
private setupCompleteHandler(): void {
onAudioComplete(this.channel, (info) => {
console.log(`[${this.channel}] Completed: ${info.fileName}`);
this.onAudioCompleted(info);
});
}
private setupProgressHandler(): void {
onAudioProgress(this.channel, (info) => {
this.onAudioProgress(info);
});
}
private setupQueueHandler(): void {
onQueueChange(this.channel, (snapshot) => {
console.log(`[${this.channel}] Queue changed: ${snapshot.totalItems} items`);
this.onQueueChanged(snapshot);
});
}
// Override these methods in subclasses
protected onAudioStarted(info: AudioStartInfo): void {
// Default implementation
}
protected onAudioCompleted(info: AudioCompleteInfo): void {
// Default implementation
}
protected onAudioProgress(info: AudioProgressInfo): void {
// Default implementation - called frequently
}
protected onQueueChanged(snapshot: QueueSnapshot): void {
// Default implementation
}
}
// Gaming-specific event handler
class GameAudioEventManager extends AudioEventManager {
protected onAudioStarted(info: AudioStartInfo): void {
// Update game UI
this.updateGameUI(`Playing: ${info.fileName}`);
// Log for analytics
this.logGameAudioEvent('start', info.fileName);
}
protected onAudioCompleted(info: AudioCompleteInfo): void {
if (info.wasInterrupted) {
console.log('Game audio was interrupted - might be important dialog');
}
this.logGameAudioEvent('complete', info.fileName);
}
private updateGameUI(message: string): void {
// Update game interface
console.log(`Game UI: ${message}`);
}
private logGameAudioEvent(type: string, fileName: string): void {
// Send to analytics
console.log(`Analytics: ${type} - ${fileName}`);
}
}
Cross-Channel Event Coordination
Coordinate events across multiple channels:
class MultiChannelEventCoordinator {
private musicChannel: number = 0;
private sfxChannel: number = 1;
private voiceChannel: number = 2;
setupCrossChannelCoordination(): void {
this.setupVoiceDucking();
this.setupMusicTransitions();
this.setupSfxLimiting();
}
private setupVoiceDucking(): void {
// Duck music and SFX when voice starts
onAudioStart(this.voiceChannel, () => {
console.log('Voice started - ducking other channels');
duckChannelVolume(this.musicChannel, 0.2);
duckChannelVolume(this.sfxChannel, 0.3);
});
// Restore volume when voice ends
onAudioComplete(this.voiceChannel, () => {
console.log('Voice ended - restoring channel volumes');
restoreChannelVolume(this.musicChannel);
restoreChannelVolume(this.sfxChannel);
});
}
private setupMusicTransitions(): void {
onAudioComplete(this.musicChannel, (info) => {
if (!info.wasInterrupted) {
console.log('Music track completed naturally');
this.considerNextTrack();
}
});
}
private setupSfxLimiting(): void {
onQueueChange(this.sfxChannel, (snapshot) => {
if (snapshot.totalItems > 5) {
console.warn('Too many SFX queued - consider throttling');
this.throttleSfx();
}
});
}
private considerNextTrack(): void {
// Logic to decide next music track
console.log('Considering next music track...');
}
private throttleSfx(): void {
// Logic to limit SFX queue
console.log('Throttling SFX to prevent overload');
}
}
State Machine with Events
Use events to drive complex state machines:
enum AudioState {
IDLE = 'idle',
LOADING = 'loading',
PLAYING = 'playing',
PAUSED = 'paused',
COMPLETING = 'completing'
}
class AudioStateMachine {
private state: AudioState = AudioState.IDLE;
private channel: number;
constructor(channel: number = 0) {
this.channel = channel;
this.setupStateMachine();
}
private setupStateMachine(): void {
onAudioStart(this.channel, () => {
this.transitionTo(AudioState.PLAYING);
});
onAudioComplete(this.channel, (info) => {
if (info.remainingInQueue > 0) {
this.transitionTo(AudioState.LOADING);
} else {
this.transitionTo(AudioState.IDLE);
}
});
onAudioProgress(this.channel, (info) => {
if (this.state === AudioState.PLAYING) {
if (info.progress > 0.9) {
this.transitionTo(AudioState.COMPLETING);
}
}
});
}
private transitionTo(newState: AudioState): void {
const oldState = this.state;
this.state = newState;
console.log(`State transition: ${oldState} → ${newState}`);
// Handle state-specific logic
switch (newState) {
case AudioState.IDLE:
this.onIdle();
break;
case AudioState.PLAYING:
this.onPlaying();
break;
case AudioState.COMPLETING:
this.onCompleting();
break;
}
}
private onIdle(): void {
console.log('Audio system is idle');
// Maybe start background music
}
private onPlaying(): void {
console.log('Audio is playing');
// Update UI to show play state
}
private onCompleting(): void {
console.log('Audio is near completion');
// Maybe preload next track or show completion animation
}
getCurrentState(): AudioState {
return this.state;
}
}
Advanced Event Patterns
Event Aggregation
Collect and analyze events across time periods:
class AudioEventAggregator {
private events: { type: string; timestamp: number; data: any }[] = [];
private maxEvents: number = 1000;
constructor() {
this.setupEventCollection();
}
private setupEventCollection(): void {
// Collect all events from channel 0
onAudioStart(0, (info) => {
this.recordEvent('audioStart', info);
});
onAudioComplete(0, (info) => {
this.recordEvent('audioComplete', info);
});
onQueueChange(0, (snapshot) => {
this.recordEvent('queueChange', { totalItems: snapshot.totalItems });
});
}
private recordEvent(type: string, data: any): void {
this.events.push({
type,
timestamp: Date.now(),
data
});
// Keep only recent events
if (this.events.length > this.maxEvents) {
this.events = this.events.slice(-this.maxEvents);
}
}
getEventsSince(timestampMs: number): any[] {
return this.events.filter(event => event.timestamp >= timestampMs);
}
getEventStats(periodMs: number = 60000): {
starts: number;
completions: number;
queueChanges: number;
avgPlaybackDuration: number;
} {
const since = Date.now() - periodMs;
const recentEvents = this.getEventsSince(since);
const starts = recentEvents.filter(e => e.type === 'audioStart').length;
const completions = recentEvents.filter(e => e.type === 'audioComplete').length;
const queueChanges = recentEvents.filter(e => e.type === 'queueChange').length;
// Calculate average playback duration
const durations = recentEvents
.filter(e => e.type === 'audioComplete')
.map(e => e.data.playbackDuration);
const avgPlaybackDuration = durations.length > 0
? durations.reduce((a, b) => a + b, 0) / durations.length
: 0;
return { starts, completions, queueChanges, avgPlaybackDuration };
}
}
Event-Driven Analytics
class AudioAnalytics {
private sessionStart: number = Date.now();
private playbackStats: Map<string, { count: number; totalDuration: number }> = new Map();
constructor() {
this.setupAnalytics();
}
private setupAnalytics(): void {
onAudioStart(0, (info) => {
this.trackAudioStart(info);
});
onAudioComplete(0, (info) => {
this.trackAudioComplete(info);
});
onAudioProgress(0, (info) => {
this.trackProgress(info);
});
}
private trackAudioStart(info: AudioStartInfo): void {
console.log(`📊 Analytics: Started ${info.fileName}`);
// Initialize or increment play count
const stats = this.playbackStats.get(info.fileName) || { count: 0, totalDuration: 0 };
stats.count++;
this.playbackStats.set(info.fileName, stats);
}
private trackAudioComplete(info: AudioCompleteInfo): void {
console.log(`📊 Analytics: Completed ${info.fileName} (${info.playbackDuration}ms)`);
// Update duration stats
const stats = this.playbackStats.get(info.fileName);
if (stats) {
stats.totalDuration += info.playbackDuration;
this.playbackStats.set(info.fileName, stats);
}
// Track completion rate
if (!info.wasInterrupted) {
console.log(`✅ ${info.fileName} played to completion`);
} else {
console.log(`⏹️ ${info.fileName} was interrupted`);
}
}
private trackProgress(info: AudioProgressInfo): void {
// Track milestone progress (only log once per milestone)
const milestones = [0.25, 0.5, 0.75];
for (const milestone of milestones) {
if (info.progress >= milestone && info.progress < milestone + 0.01) {
console.log(`📊 Analytics: ${info.fileName} reached ${milestone * 100}%`);
}
}
}
getSessionReport(): {
sessionDuration: number;
filesPlayed: string[];
mostPlayedFile: string | null;
totalPlaybackTime: number;
} {
const sessionDuration = Date.now() - this.sessionStart;
const filesPlayed = Array.from(this.playbackStats.keys());
let mostPlayedFile: string | null = null;
let maxCount = 0;
let totalPlaybackTime = 0;
for (const [fileName, stats] of this.playbackStats) {
if (stats.count > maxCount) {
maxCount = stats.count;
mostPlayedFile = fileName;
}
totalPlaybackTime += stats.totalDuration;
}
return {
sessionDuration,
filesPlayed,
mostPlayedFile,
totalPlaybackTime
};
}
}
Event Cleanup and Management
Event Handler Cleanup
Properly manage event handler lifecycle:
class EventHandlerManager {
private cleanupFunctions: Array<() => void> = [];
addEventHandler(
channel: number,
eventType: 'start' | 'complete' | 'progress' | 'queueChange',
handler: any
): void {
let cleanup: () => void;
switch (eventType) {
case 'start':
cleanup = onAudioStart(channel, handler);
break;
case 'complete':
cleanup = onAudioComplete(channel, handler);
break;
case 'progress':
cleanup = onAudioProgress(channel, handler);
break;
case 'queueChange':
cleanup = onQueueChange(channel, handler);
break;
default:
throw new Error(`Unknown event type: ${eventType}`);
}
this.cleanupFunctions.push(cleanup);
}
removeAllEventHandlers(): void {
console.log(`Cleaning up ${this.cleanupFunctions.length} event handlers`);
for (const cleanup of this.cleanupFunctions) {
cleanup();
}
this.cleanupFunctions = [];
}
getHandlerCount(): number {
return this.cleanupFunctions.length;
}
}
// Usage
const eventManager = new EventHandlerManager();
// Add handlers
eventManager.addEventHandler(0, 'start', (info) => {
console.log(`Started: ${info.fileName}`);
});
eventManager.addEventHandler(0, 'complete', (info) => {
console.log(`Completed: ${info.fileName}`);
});
// Later, clean up when component unmounts or page unloads
window.addEventListener('beforeunload', () => {
eventManager.removeAllEventHandlers();
});
Performance-Aware Event Handling
Handle high-frequency events efficiently:
class PerformantEventHandler {
private lastProgressUpdate: number = 0;
private progressThrottle: number = 100; // Update every 100ms max
setupOptimizedEventHandlers(channel: number): void {
// Standard frequency events
onAudioStart(channel, (info) => {
this.handleAudioStart(info);
});
onAudioComplete(channel, (info) => {
this.handleAudioComplete(info);
});
// Throttled high-frequency events
onAudioProgress(channel, (info) => {
this.handleThrottledProgress(info);
});
onQueueChange(channel, (snapshot) => {
this.handleQueueChange(snapshot);
});
}
private handleAudioStart(info: AudioStartInfo): void {
// Immediate response for start events
console.log(`Quick start: ${info.fileName}`);
}
private handleAudioComplete(info: AudioCompleteInfo): void {
// Immediate response for completion events
console.log(`Quick complete: ${info.fileName}`);
}
private handleThrottledProgress(info: AudioProgressInfo): void {
const now = Date.now();
if (now - this.lastProgressUpdate >= this.progressThrottle) {
this.lastProgressUpdate = now;
// Only update UI at reasonable intervals
this.updateProgressUI(info);
}
}
private updateProgressUI(info: AudioProgressInfo): void {
// Expensive UI updates only when throttled
const percentage = Math.round(info.progress * 100);
console.log(`Progress: ${percentage}%`);
}
private handleQueueChange(snapshot: QueueSnapshot): void {
// Queue changes are less frequent but important
console.log(`Queue: ${snapshot.totalItems} items`);
}
}
Next Steps
Now that you understand the event system, explore:
- Audio Lifecycle - Complete audio playback flow with events
- Performance & Memory - Optimization strategies for events
- API Reference - Detailed event documentation
- Examples - Real-world event handling patterns
Real-time Queue Monitoring
import { onQueueChange } from 'audio-channel-queue';
class QueueMonitor {
setupQueueTracking(channel: number): void {
onQueueChange(channel, (snapshot) => {
console.log(`Queue changed on channel ${channel}:`);
console.log(`- Items: ${snapshot.totalItems}`);
console.log(`- Playing: ${snapshot.currentlyPlaying}`);
// React to queue events
if (snapshot.totalItems === 0) {
this.onQueueEmpty(channel);
} else if (snapshot.totalItems > 10) {
this.onQueueOverloaded(channel);
}
});
}
onQueueEmpty(channel: number): void {
console.log(`Channel ${channel} queue is empty - maybe start background music?`);
}
onQueueOverloaded(channel: number): void {
console.log(`Channel ${channel} queue is getting long - consider optimization`);
}
}
Playlist Management
class PlaylistManager {
private playlist: string[] = [];
private currentIndex: number = 0;
private channel: number = 0;
constructor(channel: number = 0) {
this.channel = channel;
this.setupEventHandlers();
}
loadPlaylist(audioFiles: string[]): void {
this.playlist = [...audioFiles];
this.currentIndex = 0;
}
async startPlaylist(): Promise<void> {
if (this.playlist.length === 0) return;
// Queue all tracks
for (const track of this.playlist) {
if (this.channel === 0) {
await queueAudio(track);
} else {
await queueAudio(track, this.channel);
}
}
}
async skipToNext(): Promise<void> {
// Stop current and play next
if (this.channel === 0) {
stopCurrentAudioInChannel();
} else {
stopCurrentAudioInChannel(this.channel);
}
this.currentIndex++;
if (this.currentIndex < this.playlist.length) {
if (this.channel === 0) {
await queueAudioPriority(this.playlist[this.currentIndex]);
} else {
await queueAudioPriority(this.playlist[this.currentIndex], this.channel);
}
}
}
async skipToPrevious(): Promise<void> {
this.currentIndex = Math.max(0, this.currentIndex - 1);
if (this.channel === 0) {
stopCurrentAudioInChannel();
await queueAudioPriority(this.playlist[this.currentIndex]);
} else {
stopCurrentAudioInChannel(this.channel);
await queueAudioPriority(this.playlist[this.currentIndex], this.channel);
}
}
private setupEventHandlers(): void {
onAudioComplete(this.channel, (info) => {
console.log(`Completed: ${info.fileName}`);
// Auto-advance playlist
if (info.remainingInQueue === 0 && this.currentIndex < this.playlist.length - 1) {
this.skipToNext();
}
});
}
}
// Usage
const musicPlayer = new PlaylistManager(0);
musicPlayer.loadPlaylist([
'./music/track1.mp3',
'./music/track2.mp3',
'./music/track3.mp3'
]);
await musicPlayer.startPlaylist();
Dynamic Content Insertion
class DynamicContentManager {
private baseChannel: number = 0;
private adChannel: number = 1;
async startMainContent(contentUrl: string): Promise<void> {
await queueAudio(contentUrl); // Using default channel 0
}
async insertAd(adUrl: string, returnToContent: boolean = true): Promise<void> {
// Pause main content
pauseChannel(this.baseChannel);
// Play ad on separate channel
await queueAudioPriority(adUrl, this.adChannel);
if (returnToContent) {
// Resume main content when ad finishes
onAudioComplete(this.adChannel, () => {
resumeChannel(this.baseChannel);
});
}
}
async insertBreakingNews(newsUrl: string): Promise<void> {
// Interrupt everything with priority news
await queueAudioPriority(newsUrl); // Using default channel 0
}
}
Cross-Channel Synchronization
class SynchronizedQueueManager {
async syncChannelPlayback(channel1: number, channel2: number): Promise<void> {
// Start audio on both channels simultaneously
const promises = [
channel1 === 0 ? queueAudio('./audio/left-channel.mp3') : queueAudio('./audio/left-channel.mp3', channel1),
channel2 === 0 ? queueAudio('./audio/right-channel.mp3') : queueAudio('./audio/right-channel.mp3', channel2)
];
await Promise.all(promises);
console.log('Synchronized playback started on both channels');
}
async createCallAndResponse(callTrack: string, responseTrack: string): Promise<void> {
// Play call on channel 0
await queueAudio(callTrack); // Using default channel 0
// When call finishes, play response on channel 1
onAudioComplete(0, async () => {
await queueAudio(responseTrack, 1);
});
}
async orchestrateMultiChannelSequence(): Promise<void> {
// Complex multi-channel coordination
// Start background music (using default channel 0)
await queueAudio('./music/background.mp3', 0, { loop: true, volume: 0.3 });
// Layer in sound effects
setTimeout(() => queueAudio('./sfx/wind.mp3', 1), 2000);
setTimeout(() => queueAudio('./sfx/birds.mp3', 2), 4000);
// Add narration that ducks other audio
setTimeout(async () => {
// Duck background channels
setChannelVolume(0, 0.1);
setChannelVolume(1, 0.2);
setChannelVolume(2, 0.2);
// Play narration
await queueAudio('./voice/narration.mp3', 3);
// Restore volumes when narration ends
onAudioComplete(3, () => {
setChannelVolume(0, 0.3);
setChannelVolume(1, 1.0);
setChannelVolume(2, 1.0);
});
}, 6000);
}
}
Efficient Queue Operations
class EfficientQueueManager {
private queueCache: Map<number, QueueSnapshot> = new Map();
private cacheTimeout: number = 1000; // 1 second cache
getCachedQueueSnapshot(channel: number): QueueSnapshot {
const cached = this.queueCache.get(channel);
if (cached) {
return cached;
}
const snapshot = channel === 0 ? getQueueSnapshot() : getQueueSnapshot(channel);
this.queueCache.set(channel, snapshot);
// Clear cache after timeout
setTimeout(() => {
this.queueCache.delete(channel);
}, this.cacheTimeout);
return snapshot;
}
async batchQueueAudio(audioFiles: string[], channel: number): Promise<void> {
// Queue multiple files efficiently
const promises = audioFiles.map(file =>
channel === 0 ? queueAudio(file) : queueAudio(file, channel)
);
await Promise.all(promises);
console.log(`Batch queued ${audioFiles.length} files on channel ${channel}`);
}
optimizeQueueSize(channel: number, maxItems: number = 5): void {
const snapshot = this.getCachedQueueSnapshot(channel);
if (snapshot.totalItems > maxItems) {
console.warn(`Channel ${channel} queue has ${snapshot.totalItems} items, above optimal size of ${maxItems}`);
// Consider stopping current audio to clear queue
}
}
}