@flighthq/audio 0.3.0-next.906.07cea63 → 0.3.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/audioDecoderRegistry.d.ts +7 -0
- package/dist/audioDecoderRegistry.d.ts.map +1 -0
- package/dist/audioDecoderRegistry.js +28 -0
- package/dist/audioDecoderRegistry.js.map +1 -0
- package/dist/audioFormat.d.ts +2 -0
- package/dist/audioFormat.d.ts.map +1 -1
- package/dist/audioFormat.js +33 -0
- package/dist/audioFormat.js.map +1 -1
- package/dist/audioResourceFrom.d.ts.map +1 -1
- package/dist/audioResourceFrom.js +14 -0
- package/dist/audioResourceFrom.js.map +1 -1
- package/dist/audioResourceReference.d.ts +9 -0
- package/dist/audioResourceReference.d.ts.map +1 -0
- package/dist/audioResourceReference.js +113 -0
- package/dist/audioResourceReference.js.map +1 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +2 -0
- package/dist/contract.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/src/audioDecoderRegistry.test.ts +73 -0
- package/src/audioFormat.test.ts +43 -1
- package/src/audioResourceFrom.test.ts +99 -0
- package/src/audioResourceReference.test.ts +219 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AudioDecoder } from '@flighthq/types/contract';
|
|
2
|
+
export declare function getAudioDecoder(mimeType: string): AudioDecoder | null;
|
|
3
|
+
export declare function getAudioDecoderMimeTypes(): readonly string[];
|
|
4
|
+
export declare function hasAudioDecoder(mimeType: string): boolean;
|
|
5
|
+
export declare function registerAudioDecoder(mimeType: string, decoder: AudioDecoder): void;
|
|
6
|
+
export declare function unregisterAudioDecoder(mimeType: string): void;
|
|
7
|
+
//# sourceMappingURL=audioDecoderRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioDecoderRegistry.d.ts","sourceRoot":"","sources":["../src/audioDecoderRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAErE;AAID,wBAAgB,wBAAwB,IAAI,SAAS,MAAM,EAAE,CAE5D;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAElF;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE7D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getAudioMimeTypeEssence } from './audioFormat';
|
|
2
|
+
export function getAudioDecoder(mimeType) {
|
|
3
|
+
return decoders.get(getAudioMimeTypeEssence(mimeType)) ?? null;
|
|
4
|
+
}
|
|
5
|
+
// Returns an insertion-ordered snapshot of the MIME types with registered decoders. Mutating the returned
|
|
6
|
+
// array cannot change registry state.
|
|
7
|
+
export function getAudioDecoderMimeTypes() {
|
|
8
|
+
return Array.from(decoders.keys());
|
|
9
|
+
}
|
|
10
|
+
export function hasAudioDecoder(mimeType) {
|
|
11
|
+
return decoders.has(getAudioMimeTypeEssence(mimeType));
|
|
12
|
+
}
|
|
13
|
+
export function registerAudioDecoder(mimeType, decoder) {
|
|
14
|
+
decoders.set(getAudioMimeTypeEssence(mimeType), decoder);
|
|
15
|
+
}
|
|
16
|
+
export function unregisterAudioDecoder(mimeType) {
|
|
17
|
+
decoders.delete(getAudioMimeTypeEssence(mimeType));
|
|
18
|
+
}
|
|
19
|
+
// Global MIME-keyed decoder registry, mirroring the image one. Empty at import — only register* populates
|
|
20
|
+
// it, so importing this package has no side effects. Last-write-wins, so a host can override a decoder by
|
|
21
|
+
// registering after the default one.
|
|
22
|
+
//
|
|
23
|
+
// This registry is for formats the platform cannot decode on its own. Anything the browser understands
|
|
24
|
+
// (MP3, WAV, Ogg) needs no entry: the resolver falls through to the platform decoder, which content-sniffs.
|
|
25
|
+
// Registering here is how a SWF ADPCM or Nellymoser decoder becomes reachable without this package, or the
|
|
26
|
+
// resolver, knowing that those formats exist.
|
|
27
|
+
const decoders = new Map();
|
|
28
|
+
//# sourceMappingURL=audioDecoderRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioDecoderRegistry.js","sourceRoot":"","sources":["../src/audioDecoderRegistry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAExD,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,QAAQ,CAAC,GAAG,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC;AACjE,CAAC;AAED,0GAA0G;AAC1G,sCAAsC;AACtC,MAAM,UAAU,wBAAwB;IACtC,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,QAAQ,CAAC,GAAG,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAE,OAAqB;IAC1E,QAAQ,CAAC,GAAG,CAAC,uBAAuB,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACrD,QAAQ,CAAC,MAAM,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,0GAA0G;AAC1G,0GAA0G;AAC1G,qCAAqC;AACrC,EAAE;AACF,uGAAuG;AACvG,4GAA4G;AAC5G,2GAA2G;AAC3G,8CAA8C;AAC9C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC"}
|
package/dist/audioFormat.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare function canPlayAudioType(mimeType: string): boolean;
|
|
2
2
|
export declare function detectAudioMimeType(data: ArrayBuffer | Uint8Array): string | null;
|
|
3
|
+
export declare function getAudioMimeTypeEssence(mimeType: string): string;
|
|
4
|
+
export declare function getAudioMimeTypeParameter(mimeType: string, name: string): string | null;
|
|
3
5
|
export declare function inferAudioMimeType(url: string): string | null;
|
|
4
6
|
//# sourceMappingURL=audioFormat.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audioFormat.d.ts","sourceRoot":"","sources":["../src/audioFormat.ts"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG1D;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,CAmCjF;AAID,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoB7D"}
|
|
1
|
+
{"version":3,"file":"audioFormat.d.ts","sourceRoot":"","sources":["../src/audioFormat.ts"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG1D;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,CAmCjF;AAKD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGhE;AAID,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqBvF;AAID,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoB7D"}
|
package/dist/audioFormat.js
CHANGED
|
@@ -42,6 +42,39 @@ export function detectAudioMimeType(data) {
|
|
|
42
42
|
return 'audio/webm';
|
|
43
43
|
return null;
|
|
44
44
|
}
|
|
45
|
+
// Returns the type/subtype of a MIME string without its parameters, lowercased — `audio/vnd.adobe.swf-adpcm`
|
|
46
|
+
// for `audio/vnd.adobe.swf-adpcm; rate=22050`. Decoders register against the essence so one registration
|
|
47
|
+
// serves every parameter combination a container emits.
|
|
48
|
+
export function getAudioMimeTypeEssence(mimeType) {
|
|
49
|
+
const end = mimeType.indexOf(';');
|
|
50
|
+
return (end === -1 ? mimeType : mimeType.slice(0, end)).trim().toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
// Returns one MIME parameter's value, or null when the type does not carry it. This is how a decoder for a
|
|
53
|
+
// container-less format reads the sample rate and channel count its bitstream does not encode.
|
|
54
|
+
export function getAudioMimeTypeParameter(mimeType, name) {
|
|
55
|
+
const wanted = name.toLowerCase();
|
|
56
|
+
// Split on semicolons outside quotes: a quoted value may contain one, and treating that as a separator
|
|
57
|
+
// would truncate the value and invent a parameter out of its tail.
|
|
58
|
+
let start = -1;
|
|
59
|
+
let quoted = false;
|
|
60
|
+
for (let i = 0; i <= mimeType.length; i++) {
|
|
61
|
+
const character = i < mimeType.length ? mimeType[i] : ';';
|
|
62
|
+
if (character === '"')
|
|
63
|
+
quoted = !quoted;
|
|
64
|
+
if (quoted || character !== ';')
|
|
65
|
+
continue;
|
|
66
|
+
if (start >= 0) {
|
|
67
|
+
const part = mimeType.slice(start, i);
|
|
68
|
+
const separator = part.indexOf('=');
|
|
69
|
+
if (separator >= 0 && part.slice(0, separator).trim().toLowerCase() === wanted) {
|
|
70
|
+
const value = part.slice(separator + 1).trim();
|
|
71
|
+
return value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
start = i + 1;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
45
78
|
// Infers the MIME type from a URL's file extension, or null when unrecognized. Extension-based; use
|
|
46
79
|
// detectAudioMimeType when the bytes are in hand.
|
|
47
80
|
export function inferAudioMimeType(url) {
|
package/dist/audioFormat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audioFormat.js","sourceRoot":"","sources":["../src/audioFormat.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"audioFormat.js","sourceRoot":"","sources":["../src/audioFormat.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audioResourceFrom.d.ts","sourceRoot":"","sources":["../src/audioResourceFrom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAShF,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,CAUnH;AAID,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAKxB;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,IAAI,EACV,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAGxB;AAKD,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"audioResourceFrom.d.ts","sourceRoot":"","sources":["../src/audioResourceFrom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAShF,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,CAUnH;AAID,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAKxB;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,IAAI,EACV,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAGxB;AAKD,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAWxB;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAgBxB;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAIxB;AAID,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,GAAG,IAAI,CAM1F"}
|
|
@@ -36,10 +36,24 @@ export async function loadAudioResourceFromBytes(context, bytes, mimeType, signa
|
|
|
36
36
|
signal?.throwIfAborted();
|
|
37
37
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
38
38
|
const audioBuffer = await context.decodeAudioData(buffer);
|
|
39
|
+
// decodeAudioData cannot be cancelled, so an abort landing mid-decode cannot stop the work — but it
|
|
40
|
+
// must still stop the result, or an aborted load resolves with a resource the caller no longer wants
|
|
41
|
+
// and is indistinguishable from one it asked for. This is the whole family's abort barrier: every
|
|
42
|
+
// loader here funnels through this function, so checking after the await is what makes "an aborted
|
|
43
|
+
// load never resolves" true for all of them rather than only for the pre-abort fast path above.
|
|
44
|
+
signal?.throwIfAborted();
|
|
39
45
|
return createAudioResource(audioBuffer);
|
|
40
46
|
}
|
|
41
47
|
export async function loadAudioResourceFromUrl(context, url, signal) {
|
|
42
48
|
const response = await fetch(url, { signal });
|
|
49
|
+
// fetch only rejects on a network-level failure, so a 404/500 arrives here as a perfectly good
|
|
50
|
+
// response whose body is an error page. Decoding that reports "unable to decode audio data" — the
|
|
51
|
+
// wrong failure, blaming the codec for a transport problem, after paying for a decode that never had
|
|
52
|
+
// a chance. Reporting the status keeps this function's existing reject-on-failure contract; whether
|
|
53
|
+
// this family should reject at all or return the empty-resource sentinel is the separate parked
|
|
54
|
+
// question in the assessment Backlog.
|
|
55
|
+
if (!response.ok)
|
|
56
|
+
throw new Error(`Failed to load audio: ${url} (${response.status} ${response.statusText})`);
|
|
43
57
|
const arrayBuffer = await response.arrayBuffer();
|
|
44
58
|
return loadAudioResourceFromBytes(context, new Uint8Array(arrayBuffer), response.headers.get('content-type') ?? undefined, signal);
|
|
45
59
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audioResourceFrom.js","sourceRoot":"","sources":["../src/audioResourceFrom.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"audioResourceFrom.js","sourceRoot":"","sources":["../src/audioResourceFrom.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AudioResource, AudioResourceFailure, AudioResourceFetch, AudioResourceReference, AudioResourceReferenceResolutionExplanation, EmbeddedAudioResourceReference, ExternalAudioResourceReference } from '@flighthq/types/contract';
|
|
2
|
+
export declare function createAudioResourceFailure(cause: unknown): AudioResourceFailure;
|
|
3
|
+
export declare function createEmbeddedAudioResourceReference(bytes: Uint8Array, mimeType?: string | null, name?: string | null): EmbeddedAudioResourceReference;
|
|
4
|
+
export declare function createExternalAudioResourceReference(uri: string, basePath?: string | null, mimeType?: string | null, name?: string | null): ExternalAudioResourceReference;
|
|
5
|
+
export declare function explainAudioResourceReferenceResolution(ref: Readonly<AudioResourceReference>): AudioResourceReferenceResolutionExplanation;
|
|
6
|
+
export declare function findAudioResourceReferenceByName(references: readonly Readonly<AudioResourceReference>[], name: string): Readonly<AudioResourceReference> | null;
|
|
7
|
+
export declare function resetFailedAudioResourceReference(ref: AudioResourceReference): boolean;
|
|
8
|
+
export declare function resolveAudioResourceReference(ref: AudioResourceReference, context: AudioContext | null, fetch: AudioResourceFetch, signal: AbortSignal): Promise<AudioResource | null>;
|
|
9
|
+
//# sourceMappingURL=audioResourceReference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioResourceReference.d.ts","sourceRoot":"","sources":["../src/audioResourceReference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,2CAA2C,EAC3C,8BAA8B,EAC9B,8BAA8B,EAC/B,MAAM,0BAA0B,CAAC;AAalC,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAK/E;AAMD,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,UAAU,EACjB,QAAQ,GAAE,MAAM,GAAG,IAAW,EAC9B,IAAI,GAAE,MAAM,GAAG,IAAW,GACzB,8BAA8B,CAUhC;AAED,wBAAgB,oCAAoC,CAClD,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,GAAG,IAAW,EAC9B,QAAQ,GAAE,MAAM,GAAG,IAAW,EAC9B,IAAI,GAAE,MAAM,GAAG,IAAW,GACzB,8BAA8B,CAWhC;AAID,wBAAgB,uCAAuC,CACrD,GAAG,EAAE,QAAQ,CAAC,sBAAsB,CAAC,GACpC,2CAA2C,CAO7C;AAKD,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,SAAS,QAAQ,CAAC,sBAAsB,CAAC,EAAE,EACvD,IAAI,EAAE,MAAM,GACX,QAAQ,CAAC,sBAAsB,CAAC,GAAG,IAAI,CAKzC;AAID,wBAAgB,iCAAiC,CAAC,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAKtF;AAUD,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,sBAAsB,EAC3B,OAAO,EAAE,YAAY,GAAG,IAAI,EAC5B,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA2B/B"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { AudioResourceFailureKind, AudioResourceReferenceKind, ResourceResolutionState, } from '@flighthq/types/contract';
|
|
2
|
+
import { getAudioDecoder } from './audioDecoderRegistry';
|
|
3
|
+
import { createAudioResource } from './audioResource';
|
|
4
|
+
import { loadAudioResourceFromBytes } from './audioResourceFrom';
|
|
5
|
+
// Reduces a thrown value to the serialization-safe categories a reference retains. Raw Error objects and
|
|
6
|
+
// arbitrary thrown values stay inside the resolving operation; diagnostics get category, name, and message.
|
|
7
|
+
export function createAudioResourceFailure(cause) {
|
|
8
|
+
if (cause instanceof Error) {
|
|
9
|
+
return { kind: AudioResourceFailureKind.Error, message: cause.message, name: cause.name };
|
|
10
|
+
}
|
|
11
|
+
return { kind: AudioResourceFailureKind.Error, message: String(cause), name: null };
|
|
12
|
+
}
|
|
13
|
+
// `bytes` is retained as the view the container handed over, not a copy: a parser carves a sound payload
|
|
14
|
+
// out of its source and the reference borrows it, so a document that never resolves a sound never pays for
|
|
15
|
+
// its samples. The `resource` is allocated here rather than by the caller, because a trigger has to have
|
|
16
|
+
// something to bind to before anything decodes.
|
|
17
|
+
export function createEmbeddedAudioResourceReference(bytes, mimeType = null, name = null) {
|
|
18
|
+
return {
|
|
19
|
+
bytes,
|
|
20
|
+
failure: null,
|
|
21
|
+
kind: AudioResourceReferenceKind.Embedded,
|
|
22
|
+
mimeType,
|
|
23
|
+
name,
|
|
24
|
+
resource: createAudioResource(),
|
|
25
|
+
state: ResourceResolutionState.Unresolved,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function createExternalAudioResourceReference(uri, basePath = null, mimeType = null, name = null) {
|
|
29
|
+
return {
|
|
30
|
+
basePath,
|
|
31
|
+
failure: null,
|
|
32
|
+
kind: AudioResourceReferenceKind.External,
|
|
33
|
+
mimeType,
|
|
34
|
+
name,
|
|
35
|
+
resource: createAudioResource(),
|
|
36
|
+
state: ResourceResolutionState.Unresolved,
|
|
37
|
+
uri,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Returns a detached plain-data explanation suitable for logs, tools, and serialization. It never throws
|
|
41
|
+
// and exposes no resolver runtime or raw thrown value.
|
|
42
|
+
export function explainAudioResourceReferenceResolution(ref) {
|
|
43
|
+
return {
|
|
44
|
+
failure: ref.failure === null ? null : { ...ref.failure },
|
|
45
|
+
kind: ref.kind,
|
|
46
|
+
retryable: ref.state === ResourceResolutionState.Failed,
|
|
47
|
+
state: ref.state,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Returns the reference a caller asked for by authoring-time name, or null when the document carried no
|
|
51
|
+
// such sound. This is the only handle on a sound no timeline cues — a library sound published for code to
|
|
52
|
+
// start — so a miss is an ordinary outcome rather than an error.
|
|
53
|
+
export function findAudioResourceReferenceByName(references, name) {
|
|
54
|
+
for (let i = 0; i < references.length; i++) {
|
|
55
|
+
if (references[i].name === name)
|
|
56
|
+
return references[i];
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
// Returns a failed reference to the requestable state. Loading/resolved/unresolved references are unchanged
|
|
61
|
+
// so this atom cannot invalidate live work or a successfully bound resource accidentally.
|
|
62
|
+
export function resetFailedAudioResourceReference(ref) {
|
|
63
|
+
if (ref.state !== ResourceResolutionState.Failed)
|
|
64
|
+
return false;
|
|
65
|
+
ref.failure = null;
|
|
66
|
+
ref.state = ResourceResolutionState.Unresolved;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
// Advances one reference through its lifecycle and binds the decoded samples into the resource the
|
|
70
|
+
// reference already handed out, returning it or null for an expected failure. An abort is a cancel rather
|
|
71
|
+
// than a failure, so the reference reverts to Unresolved and the rejection propagates.
|
|
72
|
+
//
|
|
73
|
+
// Embedded bytes take a registered decoder when the MIME type has one and the platform decoder otherwise,
|
|
74
|
+
// in that order: a format the browser cannot parse is exactly what a registration is for, and a format it
|
|
75
|
+
// can parse needs no registration. `context` may be null when every reference in play resolves through a
|
|
76
|
+
// registered decoder or the fetch seam, which is what lets a non-web host avoid constructing one at all.
|
|
77
|
+
export async function resolveAudioResourceReference(ref, context, fetch, signal) {
|
|
78
|
+
ref.failure = null;
|
|
79
|
+
ref.state = ResourceResolutionState.Loading;
|
|
80
|
+
try {
|
|
81
|
+
const decoded = ref.kind === AudioResourceReferenceKind.Embedded
|
|
82
|
+
? await decodeAudioResourceBytes(ref, context, signal)
|
|
83
|
+
: await fetch(ref, signal);
|
|
84
|
+
if (decoded === null || decoded.buffer === null) {
|
|
85
|
+
ref.failure = { kind: AudioResourceFailureKind.Unavailable, message: 'Audio resource unavailable', name: null };
|
|
86
|
+
ref.state = ResourceResolutionState.Failed;
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
// The resource every cue already holds is the one that gains the samples, so a sound cued from forty
|
|
90
|
+
// frames costs one decode and all forty are live at once.
|
|
91
|
+
ref.resource.buffer = decoded.buffer;
|
|
92
|
+
ref.state = ResourceResolutionState.Resolved;
|
|
93
|
+
return ref.resource;
|
|
94
|
+
}
|
|
95
|
+
catch (cause) {
|
|
96
|
+
if (signal.aborted) {
|
|
97
|
+
ref.state = ResourceResolutionState.Unresolved;
|
|
98
|
+
throw cause;
|
|
99
|
+
}
|
|
100
|
+
ref.failure = createAudioResourceFailure(cause);
|
|
101
|
+
ref.state = ResourceResolutionState.Failed;
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function decodeAudioResourceBytes(ref, context, signal) {
|
|
106
|
+
const decoder = ref.mimeType === null ? null : getAudioDecoder(ref.mimeType);
|
|
107
|
+
if (decoder !== null)
|
|
108
|
+
return decoder(ref.bytes, ref.mimeType, signal);
|
|
109
|
+
if (context === null)
|
|
110
|
+
return null;
|
|
111
|
+
return loadAudioResourceFromBytes(context, ref.bytes, ref.mimeType ?? undefined, signal);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=audioResourceReference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioResourceReference.js","sourceRoot":"","sources":["../src/audioResourceReference.ts"],"names":[],"mappings":"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"}
|
package/dist/contract.d.ts
CHANGED
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC"}
|
package/dist/contract.js
CHANGED
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { canPlayAudioType, cloneAudioResource, createAudioResource, createAudioResourceFromSamples, detectAudioMimeType, disposeAudioResource, getAudioResourceByteSize, getAudioResourceChannelCount, getAudioResourceChannelData, getAudioResourceDuration, getAudioResourceSampleRate, hasAudioResourceBuffer, inferAudioMimeType, isAudioResourceEmpty, loadAudioResourceFromBase64, loadAudioResourceFromBlob, loadAudioResourceFromBytes, loadAudioResourceFromUrl, loadAudioResourceFromUrls, selectAudioResourceUrl, } from './contract';
|
|
1
|
+
export { findAudioResourceReferenceByName, unregisterAudioDecoder, resolveAudioResourceReference, resetFailedAudioResourceReference, registerAudioDecoder, hasAudioDecoder, getAudioMimeTypeParameter, getAudioMimeTypeEssence, getAudioDecoderMimeTypes, getAudioDecoder, explainAudioResourceReferenceResolution, createExternalAudioResourceReference, createAudioResourceFailure, canPlayAudioType, cloneAudioResource, createAudioResource, createAudioResourceFromSamples, createEmbeddedAudioResourceReference, detectAudioMimeType, disposeAudioResource, getAudioResourceByteSize, getAudioResourceChannelCount, getAudioResourceChannelData, getAudioResourceDuration, getAudioResourceSampleRate, hasAudioResourceBuffer, inferAudioMimeType, isAudioResourceEmpty, loadAudioResourceFromBase64, loadAudioResourceFromBlob, loadAudioResourceFromBytes, loadAudioResourceFromUrl, loadAudioResourceFromUrls, selectAudioResourceUrl, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,sBAAsB,EACtB,6BAA6B,EAC7B,iCAAiC,EACjC,oBAAoB,EACpB,eAAe,EACf,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,EACxB,eAAe,EACf,uCAAuC,EACvC,oCAAoC,EACpC,0BAA0B,EAC1B,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,8BAA8B,EAC9B,oCAAoC,EACpC,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { canPlayAudioType, cloneAudioResource, createAudioResource, createAudioResourceFromSamples, detectAudioMimeType, disposeAudioResource, getAudioResourceByteSize, getAudioResourceChannelCount, getAudioResourceChannelData, getAudioResourceDuration, getAudioResourceSampleRate, hasAudioResourceBuffer, inferAudioMimeType, isAudioResourceEmpty, loadAudioResourceFromBase64, loadAudioResourceFromBlob, loadAudioResourceFromBytes, loadAudioResourceFromUrl, loadAudioResourceFromUrls, selectAudioResourceUrl, } from './contract';
|
|
1
|
+
export { findAudioResourceReferenceByName, unregisterAudioDecoder, resolveAudioResourceReference, resetFailedAudioResourceReference, registerAudioDecoder, hasAudioDecoder, getAudioMimeTypeParameter, getAudioMimeTypeEssence, getAudioDecoderMimeTypes, getAudioDecoder, explainAudioResourceReferenceResolution, createExternalAudioResourceReference, createAudioResourceFailure, canPlayAudioType, cloneAudioResource, createAudioResource, createAudioResourceFromSamples, createEmbeddedAudioResourceReference, detectAudioMimeType, disposeAudioResource, getAudioResourceByteSize, getAudioResourceChannelCount, getAudioResourceChannelData, getAudioResourceDuration, getAudioResourceSampleRate, hasAudioResourceBuffer, inferAudioMimeType, isAudioResourceEmpty, loadAudioResourceFromBase64, loadAudioResourceFromBlob, loadAudioResourceFromBytes, loadAudioResourceFromUrl, loadAudioResourceFromUrls, selectAudioResourceUrl, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,sBAAsB,EACtB,6BAA6B,EAC7B,iCAAiC,EACjC,oBAAoB,EACpB,eAAe,EACf,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,EACxB,eAAe,EACf,uCAAuC,EACvC,oCAAoC,EACpC,0BAA0B,EAC1B,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,8BAA8B,EAC9B,oCAAoC,EACpC,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/audio",
|
|
3
|
-
"version": "0.3.0
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"author": "Joshua Granick and other contributors",
|
|
5
|
+
"license": "MIT",
|
|
4
6
|
"repository": {
|
|
5
7
|
"type": "git",
|
|
6
8
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
|
-
"@flighthq/types": "0.3.0
|
|
41
|
+
"@flighthq/types": "0.3.0"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"typescript": "^5.3.0"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { AudioDecoder } from '@flighthq/types/contract';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getAudioDecoder,
|
|
5
|
+
getAudioDecoderMimeTypes,
|
|
6
|
+
hasAudioDecoder,
|
|
7
|
+
registerAudioDecoder,
|
|
8
|
+
unregisterAudioDecoder,
|
|
9
|
+
} from './audioDecoderRegistry';
|
|
10
|
+
import { createAudioResource } from './audioResource';
|
|
11
|
+
|
|
12
|
+
const decoder: AudioDecoder = async () => createAudioResource();
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
for (const mimeType of [...getAudioDecoderMimeTypes()]) unregisterAudioDecoder(mimeType);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('getAudioDecoder', () => {
|
|
19
|
+
it('returns null for an unregistered type rather than throwing', () => {
|
|
20
|
+
expect(getAudioDecoder('audio/vnd.acme.nothing')).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('matches a registration regardless of the parameters the caller carries', () => {
|
|
24
|
+
registerAudioDecoder('audio/vnd.adobe.swf-adpcm', decoder);
|
|
25
|
+
// A container tags every sound with the parameters that sound needs, so a registry keyed on the whole
|
|
26
|
+
// string would miss every one of them.
|
|
27
|
+
expect(getAudioDecoder('audio/vnd.adobe.swf-adpcm; rate=22050; channels=1')).toBe(decoder);
|
|
28
|
+
expect(getAudioDecoder('AUDIO/VND.ADOBE.SWF-ADPCM')).toBe(decoder);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('getAudioDecoderMimeTypes', () => {
|
|
33
|
+
it('is empty until something registers', () => {
|
|
34
|
+
expect(getAudioDecoderMimeTypes()).toEqual([]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('snapshots the registered essences and cannot be mutated into the registry', () => {
|
|
38
|
+
registerAudioDecoder('audio/vnd.adobe.swf-adpcm; rate=8000', decoder);
|
|
39
|
+
const types = getAudioDecoderMimeTypes();
|
|
40
|
+
expect(types).toEqual(['audio/vnd.adobe.swf-adpcm']);
|
|
41
|
+
(types as string[]).push('audio/fake');
|
|
42
|
+
expect(getAudioDecoderMimeTypes()).toEqual(['audio/vnd.adobe.swf-adpcm']);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('hasAudioDecoder', () => {
|
|
47
|
+
it('reports registration by essence', () => {
|
|
48
|
+
expect(hasAudioDecoder('audio/vnd.acme.thing')).toBe(false);
|
|
49
|
+
registerAudioDecoder('audio/vnd.acme.thing', decoder);
|
|
50
|
+
expect(hasAudioDecoder('audio/vnd.acme.thing; rate=1')).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('registerAudioDecoder', () => {
|
|
55
|
+
it('lets a later registration win so a host can override a default', () => {
|
|
56
|
+
const replacement: AudioDecoder = async () => createAudioResource();
|
|
57
|
+
registerAudioDecoder('audio/vnd.acme.thing', decoder);
|
|
58
|
+
registerAudioDecoder('audio/vnd.acme.thing', replacement);
|
|
59
|
+
expect(getAudioDecoder('audio/vnd.acme.thing')).toBe(replacement);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('unregisterAudioDecoder', () => {
|
|
64
|
+
it('removes a registration by essence', () => {
|
|
65
|
+
registerAudioDecoder('audio/vnd.acme.thing', decoder);
|
|
66
|
+
unregisterAudioDecoder('audio/vnd.acme.thing; rate=2');
|
|
67
|
+
expect(hasAudioDecoder('audio/vnd.acme.thing')).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('is a no-op for a type that was never registered', () => {
|
|
71
|
+
expect(() => unregisterAudioDecoder('audio/missing')).not.toThrow();
|
|
72
|
+
});
|
|
73
|
+
});
|
package/src/audioFormat.test.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
canPlayAudioType,
|
|
3
|
+
detectAudioMimeType,
|
|
4
|
+
getAudioMimeTypeEssence,
|
|
5
|
+
getAudioMimeTypeParameter,
|
|
6
|
+
inferAudioMimeType,
|
|
7
|
+
} from './audioFormat';
|
|
2
8
|
|
|
3
9
|
describe('canPlayAudioType', () => {
|
|
4
10
|
beforeEach(() => {
|
|
@@ -72,6 +78,42 @@ describe('detectAudioMimeType', () => {
|
|
|
72
78
|
});
|
|
73
79
|
});
|
|
74
80
|
|
|
81
|
+
describe('getAudioMimeTypeEssence', () => {
|
|
82
|
+
it('strips parameters and lowercases the type', () => {
|
|
83
|
+
expect(getAudioMimeTypeEssence('audio/vnd.adobe.swf-adpcm; rate=22050; channels=1')).toBe(
|
|
84
|
+
'audio/vnd.adobe.swf-adpcm',
|
|
85
|
+
);
|
|
86
|
+
expect(getAudioMimeTypeEssence(' AUDIO/MPEG ')).toBe('audio/mpeg');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns a parameterless type unchanged', () => {
|
|
90
|
+
expect(getAudioMimeTypeEssence('audio/mpeg')).toBe('audio/mpeg');
|
|
91
|
+
expect(getAudioMimeTypeEssence('')).toBe('');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('getAudioMimeTypeParameter', () => {
|
|
96
|
+
it('reads a parameter a container-less format needs to decode', () => {
|
|
97
|
+
const mimeType = 'audio/vnd.adobe.swf-pcm; rate=22050; channels=2; bits=16';
|
|
98
|
+
expect(getAudioMimeTypeParameter(mimeType, 'rate')).toBe('22050');
|
|
99
|
+
expect(getAudioMimeTypeParameter(mimeType, 'channels')).toBe('2');
|
|
100
|
+
expect(getAudioMimeTypeParameter(mimeType, 'BITS')).toBe('16');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('unwraps a quoted value', () => {
|
|
104
|
+
expect(getAudioMimeTypeParameter('audio/x-thing; codec="a;b"', 'codec')).toBe('a;b');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('returns null for a parameter the type does not carry', () => {
|
|
108
|
+
expect(getAudioMimeTypeParameter('audio/mpeg', 'rate')).toBeNull();
|
|
109
|
+
expect(getAudioMimeTypeParameter('audio/mpeg; rate=1', 'channels')).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('never reads the type itself as a parameter', () => {
|
|
113
|
+
expect(getAudioMimeTypeParameter('audio/mpeg', 'audio/mpeg')).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
75
117
|
describe('inferAudioMimeType', () => {
|
|
76
118
|
it('returns "audio/mpeg" for .mp3 files', () => {
|
|
77
119
|
expect(inferAudioMimeType('track.mp3')).toBe('audio/mpeg');
|
|
@@ -14,6 +14,17 @@ const mockContext = {
|
|
|
14
14
|
decodeAudioData: vi.fn().mockResolvedValue(decodedBuffer),
|
|
15
15
|
} as unknown as AudioContext;
|
|
16
16
|
|
|
17
|
+
// A context whose decode is held open, so a test can land an abort while the decode is still in
|
|
18
|
+
// flight — the window the pre-abort fast path cannot see. `finishDecode` then completes it, modelling
|
|
19
|
+
// the real decodeAudioData, which has no cancellation and so always runs to completion.
|
|
20
|
+
function createPendingDecodeContext(): { context: AudioContext; finishDecode: () => void } {
|
|
21
|
+
let release: (buffer: AudioBuffer) => void = () => {};
|
|
22
|
+
const context = {
|
|
23
|
+
decodeAudioData: vi.fn(() => new Promise<AudioBuffer>((resolve) => (release = resolve))),
|
|
24
|
+
} as unknown as AudioContext;
|
|
25
|
+
return { context, finishDecode: () => release(decodedBuffer) };
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
// jsdom lacks the AudioBuffer constructor; this minimal stand-in honours the { length,
|
|
18
29
|
// numberOfChannels, sampleRate } constructor plus copyToChannel/getChannelData used by
|
|
19
30
|
// createAudioResourceFromSamples.
|
|
@@ -78,6 +89,15 @@ describe('loadAudioResourceFromBase64', () => {
|
|
|
78
89
|
expect(resource.buffer).toBe(decodedBuffer);
|
|
79
90
|
expect(mockContext.decodeAudioData).toHaveBeenCalledOnce();
|
|
80
91
|
});
|
|
92
|
+
|
|
93
|
+
it('rejects when the signal aborts while the decode is in flight', async () => {
|
|
94
|
+
const { context, finishDecode } = createPendingDecodeContext();
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
const promise = loadAudioResourceFromBase64(context, btoa('abc'), 'audio/mpeg', controller.signal);
|
|
97
|
+
controller.abort(new Error('cancelled'));
|
|
98
|
+
finishDecode();
|
|
99
|
+
await expect(promise).rejects.toThrow('cancelled');
|
|
100
|
+
});
|
|
81
101
|
});
|
|
82
102
|
|
|
83
103
|
describe('loadAudioResourceFromBlob', () => {
|
|
@@ -91,6 +111,20 @@ describe('loadAudioResourceFromBlob', () => {
|
|
|
91
111
|
expect(resource.buffer).toBe(decodedBuffer);
|
|
92
112
|
expect(mockContext.decodeAudioData).toHaveBeenCalledOnce();
|
|
93
113
|
});
|
|
114
|
+
|
|
115
|
+
it('rejects when the signal aborts while the decode is in flight', async () => {
|
|
116
|
+
const { context, finishDecode } = createPendingDecodeContext();
|
|
117
|
+
const blob = {
|
|
118
|
+
arrayBuffer: () => Promise.resolve(new Uint8Array([1, 2, 3, 4]).buffer),
|
|
119
|
+
type: 'audio/wav',
|
|
120
|
+
} as unknown as Blob;
|
|
121
|
+
const controller = new AbortController();
|
|
122
|
+
const promise = loadAudioResourceFromBlob(context, blob, controller.signal);
|
|
123
|
+
await Promise.resolve();
|
|
124
|
+
controller.abort(new Error('cancelled'));
|
|
125
|
+
finishDecode();
|
|
126
|
+
await expect(promise).rejects.toThrow('cancelled');
|
|
127
|
+
});
|
|
94
128
|
});
|
|
95
129
|
|
|
96
130
|
describe('loadAudioResourceFromBytes', () => {
|
|
@@ -112,6 +146,31 @@ describe('loadAudioResourceFromBytes', () => {
|
|
|
112
146
|
loadAudioResourceFromBytes(mockContext, new Uint8Array([1, 2, 3, 4]), undefined, controller.signal),
|
|
113
147
|
).rejects.toThrow('cancelled');
|
|
114
148
|
});
|
|
149
|
+
|
|
150
|
+
// Every loader in this family funnels through here, and each is covered separately, because the
|
|
151
|
+
// guarantee is per entry point: a barrier that only holds for direct callers still lets the wrappers
|
|
152
|
+
// resolve past an abort.
|
|
153
|
+
it('rejects when the signal aborts while the decode is in flight', async () => {
|
|
154
|
+
const { context, finishDecode } = createPendingDecodeContext();
|
|
155
|
+
const controller = new AbortController();
|
|
156
|
+
const promise = loadAudioResourceFromBytes(context, new Uint8Array([1, 2, 3, 4]), undefined, controller.signal);
|
|
157
|
+
controller.abort(new Error('cancelled'));
|
|
158
|
+
finishDecode();
|
|
159
|
+
await expect(promise).rejects.toThrow('cancelled');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('does not resolve with a decoded buffer after an abort', async () => {
|
|
163
|
+
const { context, finishDecode } = createPendingDecodeContext();
|
|
164
|
+
const controller = new AbortController();
|
|
165
|
+
const promise = loadAudioResourceFromBytes(context, new Uint8Array([1, 2, 3, 4]), undefined, controller.signal);
|
|
166
|
+
controller.abort(new Error('cancelled'));
|
|
167
|
+
finishDecode();
|
|
168
|
+
const settled = await promise.then(
|
|
169
|
+
(resource) => `resolved:${resource.buffer !== null}`,
|
|
170
|
+
() => 'rejected',
|
|
171
|
+
);
|
|
172
|
+
expect(settled).toBe('rejected');
|
|
173
|
+
});
|
|
115
174
|
});
|
|
116
175
|
|
|
117
176
|
describe('loadAudioResourceFromUrl', () => {
|
|
@@ -119,6 +178,7 @@ describe('loadAudioResourceFromUrl', () => {
|
|
|
119
178
|
vi.stubGlobal(
|
|
120
179
|
'fetch',
|
|
121
180
|
vi.fn().mockResolvedValue({
|
|
181
|
+
ok: true,
|
|
122
182
|
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
|
|
123
183
|
headers: new Headers({ 'content-type': 'audio/mpeg' }),
|
|
124
184
|
}),
|
|
@@ -126,6 +186,44 @@ describe('loadAudioResourceFromUrl', () => {
|
|
|
126
186
|
const resource = await loadAudioResourceFromUrl(mockContext, 'sound.mp3');
|
|
127
187
|
expect(resource.buffer).toBe(decodedBuffer);
|
|
128
188
|
});
|
|
189
|
+
|
|
190
|
+
// fetch resolves for 404/500, so without a status check the error page reaches the decoder and the
|
|
191
|
+
// caller is told the codec failed. The assertion pins the status, not merely that it rejected.
|
|
192
|
+
it('rejects with the HTTP status rather than decoding an error response', async () => {
|
|
193
|
+
const decodeAudioData = vi.fn().mockResolvedValue(decodedBuffer);
|
|
194
|
+
vi.stubGlobal(
|
|
195
|
+
'fetch',
|
|
196
|
+
vi.fn().mockResolvedValue({
|
|
197
|
+
ok: false,
|
|
198
|
+
status: 404,
|
|
199
|
+
statusText: 'Not Found',
|
|
200
|
+
arrayBuffer: () => Promise.resolve(new TextEncoder().encode('<html>404</html>').buffer),
|
|
201
|
+
headers: new Headers({ 'content-type': 'text/html' }),
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
await expect(
|
|
205
|
+
loadAudioResourceFromUrl({ decodeAudioData } as unknown as AudioContext, 'missing.mp3'),
|
|
206
|
+
).rejects.toThrow('Failed to load audio: missing.mp3 (404 Not Found)');
|
|
207
|
+
expect(decodeAudioData).not.toHaveBeenCalled();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('rejects when the signal aborts while the decode is in flight', async () => {
|
|
211
|
+
const { context, finishDecode } = createPendingDecodeContext();
|
|
212
|
+
vi.stubGlobal(
|
|
213
|
+
'fetch',
|
|
214
|
+
vi.fn().mockResolvedValue({
|
|
215
|
+
ok: true,
|
|
216
|
+
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
|
|
217
|
+
headers: new Headers({ 'content-type': 'audio/mpeg' }),
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
220
|
+
const controller = new AbortController();
|
|
221
|
+
const promise = loadAudioResourceFromUrl(context, 'sound.mp3', controller.signal);
|
|
222
|
+
await Promise.resolve();
|
|
223
|
+
controller.abort(new Error('cancelled'));
|
|
224
|
+
finishDecode();
|
|
225
|
+
await expect(promise).rejects.toThrow('cancelled');
|
|
226
|
+
});
|
|
129
227
|
});
|
|
130
228
|
|
|
131
229
|
describe('loadAudioResourceFromUrls', () => {
|
|
@@ -139,6 +237,7 @@ describe('loadAudioResourceFromUrls', () => {
|
|
|
139
237
|
type === 'audio/ogg' ? 'probably' : '',
|
|
140
238
|
);
|
|
141
239
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
240
|
+
ok: true,
|
|
142
241
|
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
|
|
143
242
|
headers: new Headers(),
|
|
144
243
|
});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { AudioDecoder, AudioResourceFetch } from '@flighthq/types/contract';
|
|
2
|
+
import { ResourceResolutionState } from '@flighthq/types/contract';
|
|
3
|
+
|
|
4
|
+
import { getAudioDecoderMimeTypes, registerAudioDecoder, unregisterAudioDecoder } from './audioDecoderRegistry';
|
|
5
|
+
import { createAudioResource } from './audioResource';
|
|
6
|
+
import {
|
|
7
|
+
createAudioResourceFailure,
|
|
8
|
+
createEmbeddedAudioResourceReference,
|
|
9
|
+
createExternalAudioResourceReference,
|
|
10
|
+
explainAudioResourceReferenceResolution,
|
|
11
|
+
findAudioResourceReferenceByName,
|
|
12
|
+
resetFailedAudioResourceReference,
|
|
13
|
+
resolveAudioResourceReference,
|
|
14
|
+
} from './audioResourceReference';
|
|
15
|
+
|
|
16
|
+
describe('createAudioResourceFailure', () => {
|
|
17
|
+
it('keeps an Error’s name and message without retaining the Error', () => {
|
|
18
|
+
const failure = createAudioResourceFailure(new TypeError('bad bytes'));
|
|
19
|
+
expect(failure).toEqual({ kind: 'Error', message: 'bad bytes', name: 'TypeError' });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('stringifies a thrown non-Error', () => {
|
|
23
|
+
expect(createAudioResourceFailure('nope')).toEqual({ kind: 'Error', message: 'nope', name: null });
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('createEmbeddedAudioResourceReference', () => {
|
|
28
|
+
it('starts unresolved with a resource waiting for its samples', () => {
|
|
29
|
+
const bytes = new Uint8Array([1, 2, 3]);
|
|
30
|
+
const reference = createEmbeddedAudioResourceReference(bytes, 'audio/mpeg', 'theme');
|
|
31
|
+
|
|
32
|
+
expect(reference.kind).toBe('Embedded');
|
|
33
|
+
expect(reference.bytes).toBe(bytes);
|
|
34
|
+
expect(reference.mimeType).toBe('audio/mpeg');
|
|
35
|
+
expect(reference.name).toBe('theme');
|
|
36
|
+
expect(reference.failure).toBeNull();
|
|
37
|
+
expect(reference.state).toBe(ResourceResolutionState.Unresolved);
|
|
38
|
+
// The resource exists before its samples do, so a cue can hold it while the bytes are undecoded.
|
|
39
|
+
expect(reference.resource.buffer).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('borrows the caller’s bytes rather than copying them', () => {
|
|
43
|
+
const bytes = new Uint8Array([9, 9]);
|
|
44
|
+
expect(createEmbeddedAudioResourceReference(bytes).bytes).toBe(bytes);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('leaves the mime type and name null when the container declared neither', () => {
|
|
48
|
+
const reference = createEmbeddedAudioResourceReference(new Uint8Array([0]));
|
|
49
|
+
expect(reference.mimeType).toBeNull();
|
|
50
|
+
expect(reference.name).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('gives each reference its own resource', () => {
|
|
54
|
+
const first = createEmbeddedAudioResourceReference(new Uint8Array([1]));
|
|
55
|
+
const second = createEmbeddedAudioResourceReference(new Uint8Array([2]));
|
|
56
|
+
expect(first.resource).not.toBe(second.resource);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('createExternalAudioResourceReference', () => {
|
|
61
|
+
it('names a uri that has not been fetched yet', () => {
|
|
62
|
+
const reference = createExternalAudioResourceReference('hit.mp3', '/sounds', 'audio/mpeg', 'hit');
|
|
63
|
+
expect(reference.kind).toBe('External');
|
|
64
|
+
expect(reference.uri).toBe('hit.mp3');
|
|
65
|
+
expect(reference.basePath).toBe('/sounds');
|
|
66
|
+
expect(reference.mimeType).toBe('audio/mpeg');
|
|
67
|
+
expect(reference.name).toBe('hit');
|
|
68
|
+
expect(reference.resource.buffer).toBeNull();
|
|
69
|
+
expect(reference.state).toBe(ResourceResolutionState.Unresolved);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('defaults the base path and the declared type to null', () => {
|
|
73
|
+
const reference = createExternalAudioResourceReference('hit.mp3');
|
|
74
|
+
expect(reference.basePath).toBeNull();
|
|
75
|
+
expect(reference.mimeType).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('explainAudioResourceReferenceResolution', () => {
|
|
80
|
+
it('reports an unresolved reference as not retryable', () => {
|
|
81
|
+
const reference = createEmbeddedAudioResourceReference(new Uint8Array([1]));
|
|
82
|
+
expect(explainAudioResourceReferenceResolution(reference)).toEqual({
|
|
83
|
+
failure: null,
|
|
84
|
+
kind: 'Embedded',
|
|
85
|
+
retryable: false,
|
|
86
|
+
state: ResourceResolutionState.Unresolved,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('detaches the failure so a caller cannot mutate the reference through it', () => {
|
|
91
|
+
const reference = createEmbeddedAudioResourceReference(new Uint8Array([1]));
|
|
92
|
+
reference.state = ResourceResolutionState.Failed;
|
|
93
|
+
reference.failure = { kind: 'Error', message: 'boom', name: null };
|
|
94
|
+
const explanation = explainAudioResourceReferenceResolution(reference);
|
|
95
|
+
expect(explanation.retryable).toBe(true);
|
|
96
|
+
expect(explanation.failure).not.toBe(reference.failure);
|
|
97
|
+
explanation.failure!.message = 'changed';
|
|
98
|
+
expect(reference.failure.message).toBe('boom');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('findAudioResourceReferenceByName', () => {
|
|
103
|
+
it('returns the reference carrying the name', () => {
|
|
104
|
+
const first = createEmbeddedAudioResourceReference(new Uint8Array([1]), 'audio/mpeg', 'intro');
|
|
105
|
+
const second = createEmbeddedAudioResourceReference(new Uint8Array([2]), 'audio/mpeg', 'loop');
|
|
106
|
+
expect(findAudioResourceReferenceByName([first, second], 'loop')).toBe(second);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('returns null for a name the document does not carry', () => {
|
|
110
|
+
const only = createEmbeddedAudioResourceReference(new Uint8Array([1]));
|
|
111
|
+
expect(findAudioResourceReferenceByName([only], 'missing')).toBeNull();
|
|
112
|
+
expect(findAudioResourceReferenceByName([], 'anything')).toBeNull();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('resetFailedAudioResourceReference', () => {
|
|
117
|
+
it('returns a failed reference to unresolved', () => {
|
|
118
|
+
const reference = createEmbeddedAudioResourceReference(new Uint8Array([1]));
|
|
119
|
+
reference.state = ResourceResolutionState.Failed;
|
|
120
|
+
reference.failure = { kind: 'Error', message: 'boom', name: null };
|
|
121
|
+
expect(resetFailedAudioResourceReference(reference)).toBe(true);
|
|
122
|
+
expect(reference.state).toBe(ResourceResolutionState.Unresolved);
|
|
123
|
+
expect(reference.failure).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('leaves a reference that never failed untouched', () => {
|
|
127
|
+
const reference = createEmbeddedAudioResourceReference(new Uint8Array([1]));
|
|
128
|
+
reference.state = ResourceResolutionState.Resolved;
|
|
129
|
+
expect(resetFailedAudioResourceReference(reference)).toBe(false);
|
|
130
|
+
expect(reference.state).toBe(ResourceResolutionState.Resolved);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('resolveAudioResourceReference', () => {
|
|
135
|
+
const decodedBuffer = { duration: 1 } as AudioBuffer;
|
|
136
|
+
const context = { decodeAudioData: vi.fn().mockResolvedValue(decodedBuffer) } as unknown as AudioContext;
|
|
137
|
+
const noFetch: AudioResourceFetch = async () => null;
|
|
138
|
+
|
|
139
|
+
afterEach(() => {
|
|
140
|
+
for (const mimeType of [...getAudioDecoderMimeTypes()]) unregisterAudioDecoder(mimeType);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('decodes embedded bytes through the platform and binds them into the reference’s own resource', async () => {
|
|
144
|
+
const reference = createEmbeddedAudioResourceReference(new Uint8Array([1, 2]), 'audio/mpeg');
|
|
145
|
+
const resource = reference.resource;
|
|
146
|
+
|
|
147
|
+
const resolved = await resolveAudioResourceReference(reference, context, noFetch, new AbortController().signal);
|
|
148
|
+
|
|
149
|
+
// The resource every cue already holds is the one that gains the samples; swapping in the decoder's
|
|
150
|
+
// own would leave every cue pointing at an empty resource.
|
|
151
|
+
expect(resolved).toBe(resource);
|
|
152
|
+
expect(resource.buffer).toBe(decodedBuffer);
|
|
153
|
+
expect(reference.state).toBe(ResourceResolutionState.Resolved);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('prefers a registered decoder over the platform for a format the platform cannot parse', async () => {
|
|
157
|
+
const swfBuffer = { duration: 2 } as AudioBuffer;
|
|
158
|
+
const decoder = vi.fn<AudioDecoder>(async () => createAudioResource(swfBuffer));
|
|
159
|
+
registerAudioDecoder('audio/vnd.adobe.swf-adpcm', decoder);
|
|
160
|
+
const reference = createEmbeddedAudioResourceReference(
|
|
161
|
+
new Uint8Array([7]),
|
|
162
|
+
'audio/vnd.adobe.swf-adpcm; rate=22050; channels=1',
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
await resolveAudioResourceReference(reference, context, noFetch, new AbortController().signal);
|
|
166
|
+
|
|
167
|
+
expect(decoder).toHaveBeenCalledOnce();
|
|
168
|
+
// The decoder gets the whole type, parameters included, because that is where its rate lives.
|
|
169
|
+
expect(decoder.mock.calls[0][1]).toBe('audio/vnd.adobe.swf-adpcm; rate=22050; channels=1');
|
|
170
|
+
expect(reference.resource.buffer).toBe(swfBuffer);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('fails rather than throwing when no decoder and no context can take the bytes', async () => {
|
|
174
|
+
const reference = createEmbeddedAudioResourceReference(new Uint8Array([7]), 'audio/vnd.adobe.swf-adpcm');
|
|
175
|
+
const resolved = await resolveAudioResourceReference(reference, null, noFetch, new AbortController().signal);
|
|
176
|
+
|
|
177
|
+
expect(resolved).toBeNull();
|
|
178
|
+
expect(reference.state).toBe(ResourceResolutionState.Failed);
|
|
179
|
+
expect(reference.failure).toEqual({ kind: 'Unavailable', message: 'Audio resource unavailable', name: null });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('routes an external reference through the fetch seam', async () => {
|
|
183
|
+
const fetched = { duration: 3 } as AudioBuffer;
|
|
184
|
+
const fetch = vi.fn(async () => createAudioResource(fetched));
|
|
185
|
+
const reference = createExternalAudioResourceReference('hit.mp3', '/sounds');
|
|
186
|
+
|
|
187
|
+
await resolveAudioResourceReference(reference, context, fetch, new AbortController().signal);
|
|
188
|
+
|
|
189
|
+
expect(fetch).toHaveBeenCalledOnce();
|
|
190
|
+
expect(reference.resource.buffer).toBe(fetched);
|
|
191
|
+
expect(reference.state).toBe(ResourceResolutionState.Resolved);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('records a thrown decode as a failure rather than propagating it', async () => {
|
|
195
|
+
const failing = {
|
|
196
|
+
decodeAudioData: vi.fn().mockRejectedValue(new TypeError('bad bytes')),
|
|
197
|
+
} as unknown as AudioContext;
|
|
198
|
+
const reference = createEmbeddedAudioResourceReference(new Uint8Array([1]), 'audio/mpeg');
|
|
199
|
+
|
|
200
|
+
const resolved = await resolveAudioResourceReference(reference, failing, noFetch, new AbortController().signal);
|
|
201
|
+
|
|
202
|
+
expect(resolved).toBeNull();
|
|
203
|
+
expect(reference.state).toBe(ResourceResolutionState.Failed);
|
|
204
|
+
expect(reference.failure).toEqual({ kind: 'Error', message: 'bad bytes', name: 'TypeError' });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('treats an abort as a cancel, leaving the reference requestable rather than failed', async () => {
|
|
208
|
+
const controller = new AbortController();
|
|
209
|
+
const failing = {
|
|
210
|
+
decodeAudioData: vi.fn().mockRejectedValue(new Error('cancelled')),
|
|
211
|
+
} as unknown as AudioContext;
|
|
212
|
+
controller.abort(new Error('cancelled'));
|
|
213
|
+
const reference = createEmbeddedAudioResourceReference(new Uint8Array([1]), 'audio/mpeg');
|
|
214
|
+
|
|
215
|
+
await expect(resolveAudioResourceReference(reference, failing, noFetch, controller.signal)).rejects.toThrow();
|
|
216
|
+
expect(reference.state).toBe(ResourceResolutionState.Unresolved);
|
|
217
|
+
expect(reference.failure).toBeNull();
|
|
218
|
+
});
|
|
219
|
+
});
|