audio-channel-queue 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +249 -13
- package/dist/core.d.ts +53 -0
- package/dist/core.js +189 -0
- package/dist/events.d.ts +59 -0
- package/dist/events.js +162 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +47 -0
- package/dist/info.d.ts +122 -0
- package/dist/info.js +240 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.js +5 -0
- package/dist/utils.d.ts +52 -0
- package/dist/utils.js +106 -0
- package/package.json +7 -4
- package/src/core.ts +209 -0
- package/src/events.ts +189 -0
- package/src/index.ts +66 -0
- package/src/info.ts +262 -0
- package/src/types.ts +127 -0
- package/src/utils.ts +109 -0
- package/dist/audio.d.ts +0 -10
- package/dist/audio.js +0 -66
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Type definitions for the audio-channel-queue package
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Array of HTMLAudioElement objects representing an audio queue
|
|
6
|
+
*/
|
|
7
|
+
export type AudioQueue = HTMLAudioElement[];
|
|
8
|
+
/**
|
|
9
|
+
* Basic audio queue channel structure
|
|
10
|
+
*/
|
|
11
|
+
export type AudioQueueChannel = {
|
|
12
|
+
queue: AudioQueue;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Comprehensive audio information interface providing metadata about currently playing audio
|
|
16
|
+
*/
|
|
17
|
+
export interface AudioInfo {
|
|
18
|
+
/** Current playback position in milliseconds */
|
|
19
|
+
currentTime: number;
|
|
20
|
+
/** Total audio duration in milliseconds */
|
|
21
|
+
duration: number;
|
|
22
|
+
/** Extracted filename from the source URL */
|
|
23
|
+
fileName: string;
|
|
24
|
+
/** Whether the audio is currently playing */
|
|
25
|
+
isPlaying: boolean;
|
|
26
|
+
/** Playback progress as a decimal (0-1) */
|
|
27
|
+
progress: number;
|
|
28
|
+
/** Audio file source URL */
|
|
29
|
+
src: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Information provided when an audio file completes playback
|
|
33
|
+
*/
|
|
34
|
+
export interface AudioCompleteInfo {
|
|
35
|
+
/** Channel number where the audio completed */
|
|
36
|
+
channelNumber: number;
|
|
37
|
+
/** Extracted filename from the source URL */
|
|
38
|
+
fileName: string;
|
|
39
|
+
/** Number of audio files remaining in the queue after completion */
|
|
40
|
+
remainingInQueue: number;
|
|
41
|
+
/** Audio file source URL */
|
|
42
|
+
src: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Information provided when an audio file starts playing
|
|
46
|
+
*/
|
|
47
|
+
export interface AudioStartInfo {
|
|
48
|
+
/** Channel number where the audio is starting */
|
|
49
|
+
channelNumber: number;
|
|
50
|
+
/** Total audio duration in milliseconds */
|
|
51
|
+
duration: number;
|
|
52
|
+
/** Extracted filename from the source URL */
|
|
53
|
+
fileName: string;
|
|
54
|
+
/** Audio file source URL */
|
|
55
|
+
src: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Information about a single item in an audio queue
|
|
59
|
+
*/
|
|
60
|
+
export interface QueueItem {
|
|
61
|
+
/** Total audio duration in milliseconds */
|
|
62
|
+
duration: number;
|
|
63
|
+
/** Extracted filename from the source URL */
|
|
64
|
+
fileName: string;
|
|
65
|
+
/** Whether this item is currently playing */
|
|
66
|
+
isCurrentlyPlaying: boolean;
|
|
67
|
+
/** Audio file source URL */
|
|
68
|
+
src: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Complete snapshot of a queue's current state
|
|
72
|
+
*/
|
|
73
|
+
export interface QueueSnapshot {
|
|
74
|
+
/** Channel number this snapshot represents */
|
|
75
|
+
channelNumber: number;
|
|
76
|
+
/** Zero-based index of the currently playing item */
|
|
77
|
+
currentIndex: number;
|
|
78
|
+
/** Array of audio items in the queue with their metadata */
|
|
79
|
+
items: QueueItem[];
|
|
80
|
+
/** Total number of items in the queue */
|
|
81
|
+
totalItems: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Callback function type for audio progress updates
|
|
85
|
+
* @param info Current audio information
|
|
86
|
+
*/
|
|
87
|
+
export type ProgressCallback = (info: AudioInfo) => void;
|
|
88
|
+
/**
|
|
89
|
+
* Callback function type for queue change notifications
|
|
90
|
+
* @param queueSnapshot Current state of the queue
|
|
91
|
+
*/
|
|
92
|
+
export type QueueChangeCallback = (queueSnapshot: QueueSnapshot) => void;
|
|
93
|
+
/**
|
|
94
|
+
* Callback function type for audio start notifications
|
|
95
|
+
* @param audioInfo Information about the audio that started
|
|
96
|
+
*/
|
|
97
|
+
export type AudioStartCallback = (audioInfo: AudioStartInfo) => void;
|
|
98
|
+
/**
|
|
99
|
+
* Callback function type for audio complete notifications
|
|
100
|
+
* @param audioInfo Information about the audio that completed
|
|
101
|
+
*/
|
|
102
|
+
export type AudioCompleteCallback = (audioInfo: AudioCompleteInfo) => void;
|
|
103
|
+
/**
|
|
104
|
+
* Extended audio queue channel with event callback management
|
|
105
|
+
*/
|
|
106
|
+
export type ExtendedAudioQueueChannel = AudioQueueChannel & {
|
|
107
|
+
/** Set of callbacks for audio completion events */
|
|
108
|
+
audioCompleteCallbacks?: Set<AudioCompleteCallback>;
|
|
109
|
+
/** Set of callbacks for audio start events */
|
|
110
|
+
audioStartCallbacks?: Set<AudioStartCallback>;
|
|
111
|
+
/** Map of audio elements to their progress callback sets */
|
|
112
|
+
progressCallbacks?: Map<HTMLAudioElement, Set<ProgressCallback>>;
|
|
113
|
+
/** Set of callbacks for queue change events */
|
|
114
|
+
queueChangeCallbacks?: Set<QueueChangeCallback>;
|
|
115
|
+
};
|
package/dist/types.js
ADDED
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utility functions for the audio-channel-queue package
|
|
3
|
+
*/
|
|
4
|
+
import { AudioInfo, QueueSnapshot, ExtendedAudioQueueChannel } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the filename from a URL string
|
|
7
|
+
* @param url - The URL to extract the filename from
|
|
8
|
+
* @returns The extracted filename or 'unknown' if extraction fails
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* extractFileName('https://example.com/audio/song.mp3') // Returns: 'song.mp3'
|
|
12
|
+
* extractFileName('/path/to/audio.wav') // Returns: 'audio.wav'
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare const extractFileName: (url: string) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Extracts comprehensive audio information from an HTMLAudioElement
|
|
18
|
+
* @param audio - The HTML audio element to extract information from
|
|
19
|
+
* @returns AudioInfo object with current playback state or null if audio is invalid
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const audioElement = new Audio('song.mp3');
|
|
23
|
+
* const info = getAudioInfoFromElement(audioElement);
|
|
24
|
+
* console.log(info?.progress); // Current progress as decimal (0-1)
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare const getAudioInfoFromElement: (audio: HTMLAudioElement) => AudioInfo | null;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a complete snapshot of a queue's current state
|
|
30
|
+
* @param channelNumber - The channel number to create a snapshot for
|
|
31
|
+
* @param audioChannels - Array of audio channels
|
|
32
|
+
* @returns QueueSnapshot object or null if channel doesn't exist
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const snapshot = createQueueSnapshot(0, audioChannels);
|
|
36
|
+
* console.log(`Queue has ${snapshot?.totalItems} items`);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare const createQueueSnapshot: (channelNumber: number, audioChannels: ExtendedAudioQueueChannel[]) => QueueSnapshot | null;
|
|
40
|
+
/**
|
|
41
|
+
* Removes webpack hash patterns from filenames to get clean, readable names
|
|
42
|
+
* @param fileName - The filename that may contain webpack hashes
|
|
43
|
+
* @returns The cleaned filename with webpack hashes removed
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* cleanWebpackFilename('song.a1b2c3d4.mp3') // Returns: 'song.mp3'
|
|
47
|
+
* cleanWebpackFilename('notification.1a2b3c4d5e6f7890.wav') // Returns: 'notification.wav'
|
|
48
|
+
* cleanWebpackFilename('music.12345678.ogg') // Returns: 'music.ogg'
|
|
49
|
+
* cleanWebpackFilename('clean-file.mp3') // Returns: 'clean-file.mp3' (unchanged)
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare const cleanWebpackFilename: (fileName: string) => string;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Utility functions for the audio-channel-queue package
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cleanWebpackFilename = exports.createQueueSnapshot = exports.getAudioInfoFromElement = exports.extractFileName = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Extracts the filename from a URL string
|
|
9
|
+
* @param url - The URL to extract the filename from
|
|
10
|
+
* @returns The extracted filename or 'unknown' if extraction fails
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* extractFileName('https://example.com/audio/song.mp3') // Returns: 'song.mp3'
|
|
14
|
+
* extractFileName('/path/to/audio.wav') // Returns: 'audio.wav'
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
const extractFileName = (url) => {
|
|
18
|
+
try {
|
|
19
|
+
const urlObj = new URL(url);
|
|
20
|
+
const pathname = urlObj.pathname;
|
|
21
|
+
const segments = pathname.split('/');
|
|
22
|
+
const fileName = segments[segments.length - 1];
|
|
23
|
+
return fileName || 'unknown';
|
|
24
|
+
}
|
|
25
|
+
catch (_a) {
|
|
26
|
+
// If URL parsing fails, try simple string manipulation
|
|
27
|
+
const segments = url.split('/');
|
|
28
|
+
const fileName = segments[segments.length - 1];
|
|
29
|
+
return fileName || 'unknown';
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
exports.extractFileName = extractFileName;
|
|
33
|
+
/**
|
|
34
|
+
* Extracts comprehensive audio information from an HTMLAudioElement
|
|
35
|
+
* @param audio - The HTML audio element to extract information from
|
|
36
|
+
* @returns AudioInfo object with current playback state or null if audio is invalid
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const audioElement = new Audio('song.mp3');
|
|
40
|
+
* const info = getAudioInfoFromElement(audioElement);
|
|
41
|
+
* console.log(info?.progress); // Current progress as decimal (0-1)
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
const getAudioInfoFromElement = (audio) => {
|
|
45
|
+
if (!audio)
|
|
46
|
+
return null;
|
|
47
|
+
const duration = isNaN(audio.duration) ? 0 : audio.duration * 1000; // Convert to milliseconds
|
|
48
|
+
const currentTime = isNaN(audio.currentTime) ? 0 : audio.currentTime * 1000; // Convert to milliseconds
|
|
49
|
+
const progress = duration > 0 ? Math.min(currentTime / duration, 1) : 0;
|
|
50
|
+
const isPlaying = !audio.paused && !audio.ended && audio.readyState > 2;
|
|
51
|
+
return {
|
|
52
|
+
currentTime,
|
|
53
|
+
duration,
|
|
54
|
+
fileName: (0, exports.extractFileName)(audio.src),
|
|
55
|
+
isPlaying,
|
|
56
|
+
progress,
|
|
57
|
+
src: audio.src
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
exports.getAudioInfoFromElement = getAudioInfoFromElement;
|
|
61
|
+
/**
|
|
62
|
+
* Creates a complete snapshot of a queue's current state
|
|
63
|
+
* @param channelNumber - The channel number to create a snapshot for
|
|
64
|
+
* @param audioChannels - Array of audio channels
|
|
65
|
+
* @returns QueueSnapshot object or null if channel doesn't exist
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const snapshot = createQueueSnapshot(0, audioChannels);
|
|
69
|
+
* console.log(`Queue has ${snapshot?.totalItems} items`);
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
const createQueueSnapshot = (channelNumber, audioChannels) => {
|
|
73
|
+
const channel = audioChannels[channelNumber];
|
|
74
|
+
if (!channel)
|
|
75
|
+
return null;
|
|
76
|
+
const items = channel.queue.map((audio, index) => ({
|
|
77
|
+
duration: isNaN(audio.duration) ? 0 : audio.duration * 1000,
|
|
78
|
+
fileName: (0, exports.extractFileName)(audio.src),
|
|
79
|
+
isCurrentlyPlaying: index === 0 && !audio.paused && !audio.ended,
|
|
80
|
+
src: audio.src
|
|
81
|
+
}));
|
|
82
|
+
return {
|
|
83
|
+
channelNumber,
|
|
84
|
+
currentIndex: 0, // Current playing is always index 0 in our queue structure
|
|
85
|
+
items,
|
|
86
|
+
totalItems: channel.queue.length
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
exports.createQueueSnapshot = createQueueSnapshot;
|
|
90
|
+
/**
|
|
91
|
+
* Removes webpack hash patterns from filenames to get clean, readable names
|
|
92
|
+
* @param fileName - The filename that may contain webpack hashes
|
|
93
|
+
* @returns The cleaned filename with webpack hashes removed
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* cleanWebpackFilename('song.a1b2c3d4.mp3') // Returns: 'song.mp3'
|
|
97
|
+
* cleanWebpackFilename('notification.1a2b3c4d5e6f7890.wav') // Returns: 'notification.wav'
|
|
98
|
+
* cleanWebpackFilename('music.12345678.ogg') // Returns: 'music.ogg'
|
|
99
|
+
* cleanWebpackFilename('clean-file.mp3') // Returns: 'clean-file.mp3' (unchanged)
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
const cleanWebpackFilename = (fileName) => {
|
|
103
|
+
// Remove webpack hash pattern: filename.hash.ext → filename.ext
|
|
104
|
+
return fileName.replace(/\.[a-f0-9]{8,}\./i, '.');
|
|
105
|
+
};
|
|
106
|
+
exports.cleanWebpackFilename = cleanWebpackFilename;
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audio-channel-queue",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Allows you to queue audio files to different playback channels.",
|
|
5
|
-
"main": "dist/
|
|
6
|
-
"types": "dist/
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
9
|
"src"
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"prepare": "npm run build",
|
|
14
|
-
"test": "jest"
|
|
14
|
+
"test": "jest",
|
|
15
|
+
"test:watch": "jest --watch",
|
|
16
|
+
"test:coverage": "jest --coverage"
|
|
15
17
|
},
|
|
16
18
|
"repository": {
|
|
17
19
|
"type": "git",
|
|
@@ -32,6 +34,7 @@
|
|
|
32
34
|
"devDependencies": {
|
|
33
35
|
"@types/jest": "^29.5.13",
|
|
34
36
|
"jest": "^29.7.0",
|
|
37
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
35
38
|
"ts-jest": "^29.2.5",
|
|
36
39
|
"typescript": "^5.6.2"
|
|
37
40
|
}
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Core queue management functions for the audio-channel-queue package
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ExtendedAudioQueueChannel } from './types';
|
|
6
|
+
import { audioChannels } from './info';
|
|
7
|
+
import { extractFileName } from './utils';
|
|
8
|
+
import {
|
|
9
|
+
emitQueueChange,
|
|
10
|
+
emitAudioStart,
|
|
11
|
+
emitAudioComplete,
|
|
12
|
+
setupProgressTracking,
|
|
13
|
+
cleanupProgressTracking
|
|
14
|
+
} from './events';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Queues an audio file to a specific channel and starts playing if it's the first in queue
|
|
18
|
+
* @param audioUrl - The URL of the audio file to queue
|
|
19
|
+
* @param channelNumber - The channel number to queue the audio to (defaults to 0)
|
|
20
|
+
* @returns Promise that resolves when the audio is queued and starts playing (if first in queue)
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* await queueAudio('https://example.com/song.mp3', 0);
|
|
24
|
+
* await queueAudio('./sounds/notification.wav'); // Uses default channel 0
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const queueAudio = async (audioUrl: string, channelNumber: number = 0): Promise<void> => {
|
|
28
|
+
if (!audioChannels[channelNumber]) {
|
|
29
|
+
audioChannels[channelNumber] = {
|
|
30
|
+
audioCompleteCallbacks: new Set(),
|
|
31
|
+
audioStartCallbacks: new Set(),
|
|
32
|
+
progressCallbacks: new Map(),
|
|
33
|
+
queue: [],
|
|
34
|
+
queueChangeCallbacks: new Set()
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const audio: HTMLAudioElement = new Audio(audioUrl);
|
|
39
|
+
audioChannels[channelNumber].queue.push(audio);
|
|
40
|
+
|
|
41
|
+
emitQueueChange(channelNumber, audioChannels);
|
|
42
|
+
|
|
43
|
+
if (audioChannels[channelNumber].queue.length === 1) {
|
|
44
|
+
playAudioQueue(channelNumber);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Plays the audio queue for a specific channel
|
|
50
|
+
* @param channelNumber - The channel number to play
|
|
51
|
+
* @returns Promise that resolves when the current audio finishes playing
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* await playAudioQueue(0); // Play queue for channel 0
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export const playAudioQueue = async (channelNumber: number): Promise<void> => {
|
|
58
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
59
|
+
|
|
60
|
+
if (channel.queue.length === 0) return;
|
|
61
|
+
|
|
62
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
63
|
+
|
|
64
|
+
setupProgressTracking(currentAudio, channelNumber, audioChannels);
|
|
65
|
+
|
|
66
|
+
return new Promise<void>((resolve) => {
|
|
67
|
+
let hasStarted: boolean = false;
|
|
68
|
+
let metadataLoaded: boolean = false;
|
|
69
|
+
let playStarted: boolean = false;
|
|
70
|
+
|
|
71
|
+
// Check if we should fire onAudioStart (both conditions met)
|
|
72
|
+
const tryFireAudioStart = (): void => {
|
|
73
|
+
if (!hasStarted && metadataLoaded && playStarted) {
|
|
74
|
+
hasStarted = true;
|
|
75
|
+
emitAudioStart(channelNumber, {
|
|
76
|
+
channelNumber,
|
|
77
|
+
duration: currentAudio.duration * 1000, // Now guaranteed to have valid duration
|
|
78
|
+
fileName: extractFileName(currentAudio.src),
|
|
79
|
+
src: currentAudio.src
|
|
80
|
+
}, audioChannels);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Event handler for when metadata loads (duration becomes available)
|
|
85
|
+
const handleLoadedMetadata = (): void => {
|
|
86
|
+
metadataLoaded = true;
|
|
87
|
+
tryFireAudioStart();
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Event handler for when audio actually starts playing
|
|
91
|
+
const handlePlay = (): void => {
|
|
92
|
+
playStarted = true;
|
|
93
|
+
tryFireAudioStart();
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Event handler for when audio ends
|
|
97
|
+
const handleEnded = async (): Promise<void> => {
|
|
98
|
+
emitAudioComplete(channelNumber, {
|
|
99
|
+
channelNumber,
|
|
100
|
+
fileName: extractFileName(currentAudio.src),
|
|
101
|
+
remainingInQueue: channel.queue.length - 1,
|
|
102
|
+
src: currentAudio.src
|
|
103
|
+
}, audioChannels);
|
|
104
|
+
|
|
105
|
+
// Clean up event listeners
|
|
106
|
+
currentAudio.removeEventListener('loadedmetadata', handleLoadedMetadata);
|
|
107
|
+
currentAudio.removeEventListener('play', handlePlay);
|
|
108
|
+
currentAudio.removeEventListener('ended', handleEnded);
|
|
109
|
+
|
|
110
|
+
cleanupProgressTracking(currentAudio, channelNumber, audioChannels);
|
|
111
|
+
channel.queue.shift();
|
|
112
|
+
|
|
113
|
+
// Emit queue change after completion
|
|
114
|
+
setTimeout(() => emitQueueChange(channelNumber, audioChannels), 10);
|
|
115
|
+
|
|
116
|
+
await playAudioQueue(channelNumber);
|
|
117
|
+
resolve();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Add event listeners
|
|
121
|
+
currentAudio.addEventListener('loadedmetadata', handleLoadedMetadata);
|
|
122
|
+
currentAudio.addEventListener('play', handlePlay);
|
|
123
|
+
currentAudio.addEventListener('ended', handleEnded);
|
|
124
|
+
|
|
125
|
+
// Check if metadata is already loaded (in case it loads before we add the listener)
|
|
126
|
+
if (currentAudio.readyState >= 1) { // HAVE_METADATA or higher
|
|
127
|
+
metadataLoaded = true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
currentAudio.play();
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Stops the currently playing audio in a specific channel and plays the next audio in queue
|
|
136
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* stopCurrentAudioInChannel(0); // Stop current audio in channel 0
|
|
140
|
+
* stopCurrentAudioInChannel(); // Stop current audio in default channel
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export const stopCurrentAudioInChannel = (channelNumber: number = 0): void => {
|
|
144
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
145
|
+
if (channel && channel.queue.length > 0) {
|
|
146
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
147
|
+
|
|
148
|
+
emitAudioComplete(channelNumber, {
|
|
149
|
+
channelNumber,
|
|
150
|
+
fileName: extractFileName(currentAudio.src),
|
|
151
|
+
remainingInQueue: channel.queue.length - 1,
|
|
152
|
+
src: currentAudio.src
|
|
153
|
+
}, audioChannels);
|
|
154
|
+
|
|
155
|
+
currentAudio.pause();
|
|
156
|
+
cleanupProgressTracking(currentAudio, channelNumber, audioChannels);
|
|
157
|
+
channel.queue.shift();
|
|
158
|
+
|
|
159
|
+
emitQueueChange(channelNumber, audioChannels);
|
|
160
|
+
|
|
161
|
+
playAudioQueue(channelNumber);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Stops all audio in a specific channel and clears the entire queue
|
|
167
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* stopAllAudioInChannel(0); // Clear all audio in channel 0
|
|
171
|
+
* stopAllAudioInChannel(); // Clear all audio in default channel
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export const stopAllAudioInChannel = (channelNumber: number = 0): void => {
|
|
175
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
176
|
+
if (channel) {
|
|
177
|
+
if (channel.queue.length > 0) {
|
|
178
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
179
|
+
|
|
180
|
+
emitAudioComplete(channelNumber, {
|
|
181
|
+
channelNumber,
|
|
182
|
+
fileName: extractFileName(currentAudio.src),
|
|
183
|
+
remainingInQueue: 0, // Will be 0 since we're clearing the queue
|
|
184
|
+
src: currentAudio.src
|
|
185
|
+
}, audioChannels);
|
|
186
|
+
|
|
187
|
+
currentAudio.pause();
|
|
188
|
+
cleanupProgressTracking(currentAudio, channelNumber, audioChannels);
|
|
189
|
+
}
|
|
190
|
+
// Clean up all progress tracking for this channel
|
|
191
|
+
channel.queue.forEach(audio => cleanupProgressTracking(audio, channelNumber, audioChannels));
|
|
192
|
+
channel.queue = [];
|
|
193
|
+
|
|
194
|
+
emitQueueChange(channelNumber, audioChannels);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Stops all audio across all channels and clears all queues
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* stopAllAudio(); // Emergency stop - clears everything
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export const stopAllAudio = (): void => {
|
|
206
|
+
audioChannels.forEach((_channel: ExtendedAudioQueueChannel, index: number) => {
|
|
207
|
+
stopAllAudioInChannel(index);
|
|
208
|
+
});
|
|
209
|
+
};
|