@antha/audio 0.4.3 → 0.6.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/dist/audio-file.d.ts +20 -2
- package/dist/audio-file.js +58 -15
- package/dist/audio-player.d.ts +12 -0
- package/dist/audio-player.js +34 -0
- package/package.json +3 -3
package/dist/audio-file.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type PartialWithUndefined, type SelectFrom } from '@augment-vir/common';
|
|
1
|
+
import { DeferredPromise, type PartialWithUndefined, type SelectFrom } from '@augment-vir/common';
|
|
2
2
|
import { ListenTarget } from 'typed-event-target';
|
|
3
3
|
import { type Codec } from './codecs.js';
|
|
4
4
|
/**
|
|
@@ -166,6 +166,17 @@ export type AudioFileCache = {
|
|
|
166
166
|
buffer: AudioBuffer;
|
|
167
167
|
}>;
|
|
168
168
|
};
|
|
169
|
+
/**
|
|
170
|
+
* State tracked for an active or paused audio playback.
|
|
171
|
+
*
|
|
172
|
+
* @category Internal
|
|
173
|
+
*/
|
|
174
|
+
export type AudioPlayback = {
|
|
175
|
+
audioBuffer: AudioBuffer;
|
|
176
|
+
deferredPlayPromise: DeferredPromise<boolean>;
|
|
177
|
+
offsetSeconds: number;
|
|
178
|
+
startedAt: number;
|
|
179
|
+
};
|
|
169
180
|
/**
|
|
170
181
|
* Allows creating an array of `AudioNode` instances ("effects") by passing the given `AudioContext`
|
|
171
182
|
* to `createEffects`. The effects created by `createEffects`, if any, are then sequentially
|
|
@@ -241,7 +252,8 @@ export declare class AudioFile extends ListenTarget<AllAudioFileEvents> {
|
|
|
241
252
|
readonly isDestroyed: boolean;
|
|
242
253
|
readonly gainNode: GainNode;
|
|
243
254
|
readonly sourceKey: string;
|
|
244
|
-
protected readonly activeBufferSources:
|
|
255
|
+
protected readonly activeBufferSources: Map<AudioBufferSourceNode, AudioPlayback>;
|
|
256
|
+
protected readonly pausedPlaybacks: Set<AudioPlayback>;
|
|
245
257
|
constructor(params: AudioFileParams);
|
|
246
258
|
/**
|
|
247
259
|
* Load the audio file so it's ready to play. This will automatically be called on the first
|
|
@@ -260,6 +272,10 @@ export declare class AudioFile extends ListenTarget<AllAudioFileEvents> {
|
|
|
260
272
|
play(): Promise<boolean>;
|
|
261
273
|
/** Stops every active playback while preserving the loaded audio buffer. */
|
|
262
274
|
stop(): void;
|
|
275
|
+
/** Pauses every active playback while preserving its current position. */
|
|
276
|
+
pause(): void;
|
|
277
|
+
/** Resumes every playback paused by {@link pause}. */
|
|
278
|
+
resume(): void;
|
|
263
279
|
/** Destroys this audio file entirely; it cannot be used anymore. */
|
|
264
280
|
destroy(): Promise<void>;
|
|
265
281
|
/**
|
|
@@ -276,5 +292,7 @@ export declare class AudioFile extends ListenTarget<AllAudioFileEvents> {
|
|
|
276
292
|
protected loadBase64(): ArrayBuffer;
|
|
277
293
|
/** Load the audio file's `ArrayBuffer` from its source URL. */
|
|
278
294
|
protected loadFromUrl(): Promise<ArrayBuffer>;
|
|
295
|
+
/** Starts an audio source for the supplied playback state. */
|
|
296
|
+
protected startPlayback(playback: AudioPlayback): void;
|
|
279
297
|
}
|
|
280
298
|
export {};
|
package/dist/audio-file.js
CHANGED
|
@@ -159,7 +159,8 @@ export class AudioFile extends ListenTarget {
|
|
|
159
159
|
isDestroyed = false;
|
|
160
160
|
gainNode;
|
|
161
161
|
sourceKey;
|
|
162
|
-
activeBufferSources = new
|
|
162
|
+
activeBufferSources = new Map();
|
|
163
|
+
pausedPlaybacks = new Set();
|
|
163
164
|
constructor(params) {
|
|
164
165
|
super();
|
|
165
166
|
this.params = params;
|
|
@@ -228,26 +229,51 @@ export class AudioFile extends ListenTarget {
|
|
|
228
229
|
return false;
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this.dispatch(new AudioFilePlayEndEvent());
|
|
240
|
-
});
|
|
241
|
-
this.dispatch(new AudioFilePlayStartEvent());
|
|
242
|
-
bufferSource.start();
|
|
243
|
-
return deferredPlayPromise.promise;
|
|
232
|
+
const playback = {
|
|
233
|
+
audioBuffer,
|
|
234
|
+
deferredPlayPromise: new DeferredPromise(),
|
|
235
|
+
offsetSeconds: 0,
|
|
236
|
+
startedAt: this.audioContext.currentTime,
|
|
237
|
+
};
|
|
238
|
+
this.startPlayback(playback);
|
|
239
|
+
return playback.deferredPlayPromise.promise;
|
|
244
240
|
}
|
|
245
241
|
/** Stops every active playback while preserving the loaded audio buffer. */
|
|
246
242
|
stop() {
|
|
243
|
+
const pausedPlaybacks = [...this.pausedPlaybacks];
|
|
247
244
|
const activeBufferSources = [...this.activeBufferSources];
|
|
245
|
+
this.pausedPlaybacks.clear();
|
|
248
246
|
this.activeBufferSources.clear();
|
|
249
|
-
|
|
247
|
+
pausedPlaybacks.forEach((playback) => {
|
|
248
|
+
playback.deferredPlayPromise.resolve(true);
|
|
249
|
+
this.dispatch(new AudioFilePlayEndEvent());
|
|
250
|
+
});
|
|
251
|
+
activeBufferSources.forEach(([bufferSource, playback,]) => {
|
|
250
252
|
bufferSource.stop();
|
|
253
|
+
playback.deferredPlayPromise.resolve(true);
|
|
254
|
+
this.dispatch(new AudioFilePlayEndEvent());
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/** Pauses every active playback while preserving its current position. */
|
|
258
|
+
pause() {
|
|
259
|
+
[...this.activeBufferSources].forEach(([bufferSource, playback,]) => {
|
|
260
|
+
const offsetSeconds = Math.min(playback.audioBuffer.duration, playback.offsetSeconds + this.audioContext.currentTime - playback.startedAt);
|
|
261
|
+
if (offsetSeconds >= playback.audioBuffer.duration) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
this.activeBufferSources.delete(bufferSource);
|
|
265
|
+
this.pausedPlaybacks.add({
|
|
266
|
+
...playback,
|
|
267
|
+
offsetSeconds,
|
|
268
|
+
});
|
|
269
|
+
bufferSource.stop();
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/** Resumes every playback paused by {@link pause}. */
|
|
273
|
+
resume() {
|
|
274
|
+
[...this.pausedPlaybacks].forEach((playback) => {
|
|
275
|
+
this.pausedPlaybacks.delete(playback);
|
|
276
|
+
this.startPlayback(playback);
|
|
251
277
|
});
|
|
252
278
|
}
|
|
253
279
|
/** Destroys this audio file entirely; it cannot be used anymore. */
|
|
@@ -327,4 +353,21 @@ export class AudioFile extends ListenTarget {
|
|
|
327
353
|
async loadFromUrl() {
|
|
328
354
|
return await (await this.fetch(this.urlOrBase64)).arrayBuffer();
|
|
329
355
|
}
|
|
356
|
+
/** Starts an audio source for the supplied playback state. */
|
|
357
|
+
startPlayback(playback) {
|
|
358
|
+
const bufferSource = this.audioContext.createBufferSource();
|
|
359
|
+
bufferSource.buffer = playback.audioBuffer;
|
|
360
|
+
bufferSource.connect(this.outputNode);
|
|
361
|
+
playback.startedAt = this.audioContext.currentTime;
|
|
362
|
+
this.activeBufferSources.set(bufferSource, playback);
|
|
363
|
+
bufferSource.addEventListener('ended', () => {
|
|
364
|
+
if (!this.activeBufferSources.delete(bufferSource)) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
playback.deferredPlayPromise.resolve(true);
|
|
368
|
+
this.dispatch(new AudioFilePlayEndEvent());
|
|
369
|
+
});
|
|
370
|
+
this.dispatch(new AudioFilePlayStartEvent());
|
|
371
|
+
bufferSource.start(0, playback.offsetSeconds);
|
|
372
|
+
}
|
|
330
373
|
}
|
package/dist/audio-player.d.ts
CHANGED
|
@@ -64,6 +64,18 @@ export declare class AudioPlayer extends ListenTarget<AllAudioFileEvents> {
|
|
|
64
64
|
stopFiles(files: ReadonlyArray<Readonly<AudioSetupParams>>): void;
|
|
65
65
|
/** Stops all active playback without unloading any audio files. */
|
|
66
66
|
stopAllFiles(): void;
|
|
67
|
+
/** Pauses all active playback for an already-loaded audio file. */
|
|
68
|
+
pauseFile(file: Readonly<AudioSetupParams>): void;
|
|
69
|
+
/** Pauses all active playback for the given already-loaded audio files. */
|
|
70
|
+
pauseFiles(files: ReadonlyArray<Readonly<AudioSetupParams>>): void;
|
|
71
|
+
/** Pauses all active playback without unloading any audio files. */
|
|
72
|
+
pauseAllFiles(): void;
|
|
73
|
+
/** Resumes playback paused for an already-loaded audio file. */
|
|
74
|
+
resumeFile(file: Readonly<AudioSetupParams>): void;
|
|
75
|
+
/** Resumes playback paused for the given already-loaded audio files. */
|
|
76
|
+
resumeFiles(files: ReadonlyArray<Readonly<AudioSetupParams>>): void;
|
|
77
|
+
/** Resumes all playback paused through this {@link AudioPlayer}. */
|
|
78
|
+
resumeAllFiles(): void;
|
|
67
79
|
/** Load a batch of audio files. */
|
|
68
80
|
loadFiles(files: ReadonlyArray<Readonly<AudioSetupParams>>, options?: Readonly<PartialWithUndefined<{
|
|
69
81
|
progressCallback: AudioLoadProgressCallback;
|
package/dist/audio-player.js
CHANGED
|
@@ -89,6 +89,40 @@ export class AudioPlayer extends ListenTarget {
|
|
|
89
89
|
audioFile.stop();
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
+
/** Pauses all active playback for an already-loaded audio file. */
|
|
93
|
+
pauseFile(file) {
|
|
94
|
+
const sourceKey = createAudioSourceKey(file);
|
|
95
|
+
this.audioFiles[sourceKey]?.pause();
|
|
96
|
+
}
|
|
97
|
+
/** Pauses all active playback for the given already-loaded audio files. */
|
|
98
|
+
pauseFiles(files) {
|
|
99
|
+
files.forEach((file) => {
|
|
100
|
+
this.pauseFile(file);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/** Pauses all active playback without unloading any audio files. */
|
|
104
|
+
pauseAllFiles() {
|
|
105
|
+
getObjectTypedValues(this.audioFiles).forEach((audioFile) => {
|
|
106
|
+
audioFile.pause();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/** Resumes playback paused for an already-loaded audio file. */
|
|
110
|
+
resumeFile(file) {
|
|
111
|
+
const sourceKey = createAudioSourceKey(file);
|
|
112
|
+
this.audioFiles[sourceKey]?.resume();
|
|
113
|
+
}
|
|
114
|
+
/** Resumes playback paused for the given already-loaded audio files. */
|
|
115
|
+
resumeFiles(files) {
|
|
116
|
+
files.forEach((file) => {
|
|
117
|
+
this.resumeFile(file);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** Resumes all playback paused through this {@link AudioPlayer}. */
|
|
121
|
+
resumeAllFiles() {
|
|
122
|
+
getObjectTypedValues(this.audioFiles).forEach((audioFile) => {
|
|
123
|
+
audioFile.resume();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
92
126
|
/** Load a batch of audio files. */
|
|
93
127
|
async loadFiles(files, options = {}) {
|
|
94
128
|
let loadedCount = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antha/audio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "An Antha mod for handling audio.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vir",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@augment-vir/common": "^32.2.3"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@antha/engine": "^0.
|
|
40
|
+
"@antha/engine": "^0.6.0",
|
|
41
41
|
"@augment-vir/test": "^32.2.3",
|
|
42
42
|
"@web/dev-server-esbuild": "^2.0.0",
|
|
43
43
|
"@web/test-runner": "^1.0.0",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"typed-event-target": "^4.3.3"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@antha/engine": "^0.
|
|
49
|
+
"@antha/engine": "^0.6.0",
|
|
50
50
|
"element-vir": ">=26",
|
|
51
51
|
"typed-event-target": ">=4.3"
|
|
52
52
|
},
|