Skip to main content

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:

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
}
}
}