@flighthq/audio 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.638.d4dbd4e

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 CHANGED
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/audio`. The `@flight
13
13
  This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
14
14
 
15
15
  - [Flight project](https://github.com/flighthq/flight)
16
- - [Source for @flighthq/audio@0.5.0-next.1944.cd6b6e7](https://github.com/flighthq/flight/tree/cd6b6e71eb472b3a4249830a7743333f019fa7b7/packages/audio)
17
- - [License](https://github.com/flighthq/flight/blob/cd6b6e71eb472b3a4249830a7743333f019fa7b7/LICENSE.md)
16
+ - [Source for @flighthq/audio@0.5.1-edge.638.d4dbd4e](https://github.com/flighthq/flight/tree/d4dbd4e347dc8be10b180c85a766ab50333dcb51/packages/audio)
17
+ - [License](https://github.com/flighthq/flight/blob/d4dbd4e347dc8be10b180c85a766ab50333dcb51/LICENSE.md)
@@ -0,0 +1,9 @@
1
+ import type { AudioBackend, BackendExplanation } from '@flighthq/types/contract';
2
+ export declare function createWebAudioBackend(): AudioBackend;
3
+ export declare function explainAudioBackend(): BackendExplanation;
4
+ export declare function getAudioBackend(): AudioBackend;
5
+ export declare function installAudioHostBackend(backend: AudioBackend): void;
6
+ export declare function observeAudioHostResult(operation: string, succeeded: boolean): void;
7
+ export declare function resetAudioBackendForTest(): void;
8
+ export declare function setAudioBackend(backend: AudioBackend | null): void;
9
+ //# sourceMappingURL=audioBackend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioBackend.d.ts","sourceRoot":"","sources":["../src/audioBackend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEjF,wBAAgB,qBAAqB,IAAI,YAAY,CAMpD;AAED,wBAAgB,mBAAmB,IAAI,kBAAkB,CAaxD;AAED,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAMnE;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAKlF;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAElE"}
@@ -0,0 +1,57 @@
1
+ export function createWebAudioBackend() {
2
+ return {
3
+ canPlayType(mimeType) {
4
+ return new Audio().canPlayType(mimeType) !== '';
5
+ },
6
+ };
7
+ }
8
+ export function explainAudioBackend() {
9
+ if (_custom !== null) {
10
+ return { conflict: _hostConflict, layer: 'custom', operation: null, viability: 'unobserved' };
11
+ }
12
+ if (_host !== null) {
13
+ return {
14
+ conflict: _hostConflict,
15
+ layer: 'host',
16
+ operation: _hostObservation !== null ? _hostObservation.operation : null,
17
+ viability: _hostObservation !== null ? _hostObservation.viability : 'unobserved',
18
+ };
19
+ }
20
+ return { conflict: false, layer: 'host-not-enabled', operation: null, viability: 'unobserved' };
21
+ }
22
+ export function getAudioBackend() {
23
+ return _custom ?? _host ?? _sentinel;
24
+ }
25
+ export function installAudioHostBackend(backend) {
26
+ if (_host !== null) {
27
+ if (_host !== backend)
28
+ _hostConflict = true;
29
+ return;
30
+ }
31
+ _host = backend;
32
+ }
33
+ export function observeAudioHostResult(operation, succeeded) {
34
+ _hostObservation = {
35
+ operation,
36
+ viability: succeeded ? 'available' : 'runtime-api-unavailable',
37
+ };
38
+ }
39
+ export function resetAudioBackendForTest() {
40
+ _custom = null;
41
+ _host = null;
42
+ _hostConflict = false;
43
+ _hostObservation = null;
44
+ }
45
+ export function setAudioBackend(backend) {
46
+ _custom = backend;
47
+ }
48
+ let _custom = null;
49
+ let _host = null;
50
+ let _hostConflict = false;
51
+ let _hostObservation = null;
52
+ const _sentinel = {
53
+ canPlayType(_mimeType) {
54
+ return false;
55
+ },
56
+ };
57
+ //# sourceMappingURL=audioBackend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioBackend.js","sourceRoot":"","sources":["../src/audioBackend.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,qBAAqB;IACnC,OAAO;QACL,WAAW,CAAC,QAAgB;YAC1B,OAAO,IAAI,KAAK,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAChG,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO;YACL,QAAQ,EAAE,aAAa;YACvB,KAAK,EAAE,MAAM;YACb,SAAS,EAAE,gBAAgB,KAAK,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;YACxE,SAAS,EAAE,gBAAgB,KAAK,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY;SACjF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAClG,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,OAAO,IAAI,KAAK,IAAI,SAAS,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,OAAqB;IAC3D,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,IAAI,KAAK,KAAK,OAAO;YAAE,aAAa,GAAG,IAAI,CAAC;QAC5C,OAAO;IACT,CAAC;IACD,KAAK,GAAG,OAAO,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,SAAiB,EAAE,SAAkB;IAC1E,gBAAgB,GAAG;QACjB,SAAS;QACT,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,yBAAyB;KAC/D,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,OAAO,GAAG,IAAI,CAAC;IACf,KAAK,GAAG,IAAI,CAAC;IACb,aAAa,GAAG,KAAK,CAAC;IACtB,gBAAgB,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAA4B;IAC1D,OAAO,GAAG,OAAO,CAAC;AACpB,CAAC;AAED,IAAI,OAAO,GAAwB,IAAI,CAAC;AACxC,IAAI,KAAK,GAAwB,IAAI,CAAC;AACtC,IAAI,aAAa,GAAG,KAAK,CAAC;AAC1B,IAAI,gBAAgB,GAAqF,IAAI,CAAC;AAE9G,MAAM,SAAS,GAAiB;IAC9B,WAAW,CAAC,SAAiB;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;CACF,CAAC"}
@@ -1,9 +1,8 @@
1
- // Reports whether the current environment can play a given MIME type, by probing an <audio> element's
2
- // canPlayType. Returns false for the empty string and for any type the browser reports it cannot play.
1
+ import { getAudioBackend } from './audioBackend';
3
2
  export function canPlayAudioType(mimeType) {
4
3
  if (mimeType === '')
5
4
  return false;
6
- return new Audio().canPlayType(mimeType) !== '';
5
+ return getAudioBackend().canPlayType(mimeType);
7
6
  }
8
7
  // Sniffs the container MIME type from a buffer's leading magic bytes, or null when unrecognized.
9
8
  // Mirrors detectImageMimeType. Covers wav, flac, ogg, mp3 (ID3 or MPEG frame sync), m4a/aac
@@ -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,9 +1,9 @@
1
- import type { AudioResource, AudioResourceUrl } from '@flighthq/types/contract';
1
+ import type { AudioResource, AudioResourceUrl, HasNetHttp } from '@flighthq/types/contract';
2
2
  export declare function createAudioResourceFromSamples(channels: readonly Float32Array[], sampleRate: number): AudioResource;
3
3
  export declare function loadAudioResourceFromBase64(context: AudioContext, base64: string, mimeType: string, signal?: AbortSignal): Promise<AudioResource>;
4
4
  export declare function loadAudioResourceFromBlob(context: AudioContext, blob: Blob, signal?: AbortSignal): Promise<AudioResource>;
5
5
  export declare function loadAudioResourceFromBytes(context: AudioContext, bytes: Uint8Array, mimeType?: string, signal?: AbortSignal): Promise<AudioResource>;
6
- export declare function loadAudioResourceFromUrl(context: AudioContext, url: string, signal?: AbortSignal): Promise<AudioResource>;
7
- export declare function loadAudioResourceFromUrls(context: AudioContext, sources: readonly AudioResourceUrl[], signal?: AbortSignal): Promise<AudioResource>;
6
+ export declare function loadAudioResourceFromUrl(host: HasNetHttp, context: AudioContext, url: string, signal?: AbortSignal): Promise<AudioResource>;
7
+ export declare function loadAudioResourceFromUrls(host: HasNetHttp, context: AudioContext, sources: readonly AudioResourceUrl[], signal?: AbortSignal): Promise<AudioResource>;
8
8
  export declare function selectAudioResourceUrl(sources: readonly AudioResourceUrl[]): string | null;
9
9
  //# sourceMappingURL=audioResourceFrom.d.ts.map
@@ -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,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"}
1
+ {"version":3,"file":"audioResourceFrom.d.ts","sourceRoot":"","sources":["../src/audioResourceFrom.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAS5F,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,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAYxB;AAED,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,UAAU,EAChB,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"}
@@ -1,3 +1,4 @@
1
+ import { sendNetRequest } from '@flighthq/net/contract';
1
2
  import { canPlayAudioType, inferAudioMimeType } from './audioFormat';
2
3
  import { createAudioResource } from './audioResource';
3
4
  // Builds a resource from raw PCM channel data without needing an AudioContext. Each entry in
@@ -44,24 +45,22 @@ export async function loadAudioResourceFromBytes(context, bytes, mimeType, signa
44
45
  signal?.throwIfAborted();
45
46
  return createAudioResource(audioBuffer);
46
47
  }
47
- export async function loadAudioResourceFromUrl(context, url, signal) {
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.
48
+ export async function loadAudioResourceFromUrl(host, context, url, signal) {
49
+ const response = await sendNetRequest(host, { method: 'GET', responseType: 'arraybuffer', url }, signal === undefined ? undefined : { signal });
50
+ // The NetBackend reports network failures and non-2xx responses alike through the response, before
51
+ // the audio decoder can misdiagnose an HTTP error body as invalid audio. This function retains its
52
+ // existing reject-on-failure contract; the transport itself remains caller-replaceable.
55
53
  if (!response.ok)
56
54
  throw new Error(`Failed to load audio: ${url} (${response.status} ${response.statusText})`);
57
- const arrayBuffer = await response.arrayBuffer();
58
- return loadAudioResourceFromBytes(context, new Uint8Array(arrayBuffer), response.headers.get('content-type') ?? undefined, signal);
55
+ if (!(response.body instanceof ArrayBuffer))
56
+ throw new Error(`Failed to load audio: ${url} (invalid body)`);
57
+ return loadAudioResourceFromBytes(context, new Uint8Array(response.body), response.headers['content-type'], signal);
59
58
  }
60
- export async function loadAudioResourceFromUrls(context, sources, signal) {
59
+ export async function loadAudioResourceFromUrls(host, context, sources, signal) {
61
60
  const selected = selectAudioResourceUrl(sources);
62
61
  if (selected === null)
63
62
  return createAudioResource();
64
- return loadAudioResourceFromUrl(context, selected, signal);
63
+ return loadAudioResourceFromUrl(host, context, selected, signal);
65
64
  }
66
65
  // Picks the first source URL the environment can play, or null when none is playable. A source's
67
66
  // explicit `type` wins; otherwise the MIME type is inferred from the URL extension.
@@ -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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"}
@@ -1,3 +1,4 @@
1
+ export * from './audioBackend';
1
2
  export * from './audioDecoderRegistry';
2
3
  export * from './audioFormat';
3
4
  export * from './audioResource';
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC"}
package/dist/contract.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './audioBackend';
1
2
  export * from './audioDecoderRegistry';
2
3
  export * from './audioFormat';
3
4
  export * from './audioResource';
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/audio",
3
- "version": "0.5.0-next.1944.cd6b6e7",
3
+ "version": "0.5.1-edge.638.d4dbd4e",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,7 +38,8 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/types": "0.5.0-next.1944.cd6b6e7"
41
+ "@flighthq/net": "0.5.1-edge.638.d4dbd4e",
42
+ "@flighthq/types": "0.5.1-edge.638.d4dbd4e"
42
43
  },
43
44
  "devDependencies": {
44
45
  "typescript": "^5.3.0"
@@ -0,0 +1,136 @@
1
+ import type { AudioBackend } from '@flighthq/types/contract';
2
+ import { afterEach, describe, expect, it } from 'vitest';
3
+
4
+ import {
5
+ explainAudioBackend,
6
+ getAudioBackend,
7
+ installAudioHostBackend,
8
+ observeAudioHostResult,
9
+ resetAudioBackendForTest,
10
+ setAudioBackend,
11
+ } from './audioBackend';
12
+
13
+ afterEach(() => {
14
+ resetAudioBackendForTest();
15
+ });
16
+
17
+ describe('createWebAudioBackend', () => {
18
+ it('is tested via host-web', () => {
19
+ expect(true).toBe(true);
20
+ });
21
+ });
22
+
23
+ describe('explainAudioBackend', () => {
24
+ it('reports host-not-enabled when no backend is installed', () => {
25
+ const explanation = explainAudioBackend();
26
+ expect(explanation.layer).toBe('host-not-enabled');
27
+ expect(explanation.conflict).toBe(false);
28
+ expect(explanation.viability).toBe('unobserved');
29
+ });
30
+
31
+ it('reports host layer when host backend is installed', () => {
32
+ installAudioHostBackend({ canPlayType: () => false });
33
+ const explanation = explainAudioBackend();
34
+ expect(explanation.layer).toBe('host');
35
+ expect(explanation.viability).toBe('unobserved');
36
+ });
37
+
38
+ it('reports custom layer when custom backend is set', () => {
39
+ installAudioHostBackend({ canPlayType: () => false });
40
+ setAudioBackend({ canPlayType: () => true });
41
+ const explanation = explainAudioBackend();
42
+ expect(explanation.layer).toBe('custom');
43
+ });
44
+
45
+ it('reports observed viability after observeAudioHostResult', () => {
46
+ installAudioHostBackend({ canPlayType: () => true });
47
+ observeAudioHostResult('canPlayType', true);
48
+ const explanation = explainAudioBackend();
49
+ expect(explanation.viability).toBe('available');
50
+ expect(explanation.operation).toBe('canPlayType');
51
+ });
52
+
53
+ it('reports runtime-api-unavailable on failed observation', () => {
54
+ installAudioHostBackend({ canPlayType: () => false });
55
+ observeAudioHostResult('canPlayType', false);
56
+ const explanation = explainAudioBackend();
57
+ expect(explanation.viability).toBe('runtime-api-unavailable');
58
+ });
59
+ });
60
+
61
+ describe('getAudioBackend', () => {
62
+ it('returns sentinel when no backend is installed', () => {
63
+ const backend = getAudioBackend();
64
+ expect(backend.canPlayType('audio/mpeg')).toBe(false);
65
+ });
66
+
67
+ it('returns host backend when installed', () => {
68
+ const host: AudioBackend = { canPlayType: () => true };
69
+ installAudioHostBackend(host);
70
+ expect(getAudioBackend()).toBe(host);
71
+ });
72
+
73
+ it('returns custom backend over host', () => {
74
+ const host: AudioBackend = { canPlayType: () => false };
75
+ const custom: AudioBackend = { canPlayType: () => true };
76
+ installAudioHostBackend(host);
77
+ setAudioBackend(custom);
78
+ expect(getAudioBackend()).toBe(custom);
79
+ });
80
+ });
81
+
82
+ describe('installAudioHostBackend', () => {
83
+ it('installs the host backend', () => {
84
+ const host: AudioBackend = { canPlayType: () => true };
85
+ installAudioHostBackend(host);
86
+ expect(getAudioBackend()).toBe(host);
87
+ });
88
+
89
+ it('detects conflict on double install with different backend', () => {
90
+ installAudioHostBackend({ canPlayType: () => false });
91
+ installAudioHostBackend({ canPlayType: () => true });
92
+ expect(explainAudioBackend().conflict).toBe(true);
93
+ });
94
+
95
+ it('does not flag conflict on same backend', () => {
96
+ const host: AudioBackend = { canPlayType: () => false };
97
+ installAudioHostBackend(host);
98
+ installAudioHostBackend(host);
99
+ expect(explainAudioBackend().conflict).toBe(false);
100
+ });
101
+ });
102
+
103
+ describe('observeAudioHostResult', () => {
104
+ it('records the operation and viability', () => {
105
+ installAudioHostBackend({ canPlayType: () => true });
106
+ observeAudioHostResult('canPlayType', true);
107
+ const explanation = explainAudioBackend();
108
+ expect(explanation.operation).toBe('canPlayType');
109
+ expect(explanation.viability).toBe('available');
110
+ });
111
+ });
112
+
113
+ describe('resetAudioBackendForTest', () => {
114
+ it('clears all state', () => {
115
+ installAudioHostBackend({ canPlayType: () => true });
116
+ setAudioBackend({ canPlayType: () => true });
117
+ observeAudioHostResult('canPlayType', true);
118
+ resetAudioBackendForTest();
119
+ expect(explainAudioBackend().layer).toBe('host-not-enabled');
120
+ expect(getAudioBackend().canPlayType('audio/mpeg')).toBe(false);
121
+ });
122
+ });
123
+
124
+ describe('setAudioBackend', () => {
125
+ it('overrides the active backend', () => {
126
+ const custom: AudioBackend = { canPlayType: () => true };
127
+ setAudioBackend(custom);
128
+ expect(getAudioBackend()).toBe(custom);
129
+ });
130
+
131
+ it('clears the custom backend when null', () => {
132
+ setAudioBackend({ canPlayType: () => true });
133
+ setAudioBackend(null);
134
+ expect(getAudioBackend().canPlayType('audio/mpeg')).toBe(false);
135
+ });
136
+ });
@@ -1,3 +1,4 @@
1
+ import { resetAudioBackendForTest, setAudioBackend } from './audioBackend';
1
2
  import {
2
3
  canPlayAudioType,
3
4
  detectAudioMimeType,
@@ -8,26 +9,29 @@ import {
8
9
 
9
10
  describe('canPlayAudioType', () => {
10
11
  beforeEach(() => {
11
- vi.spyOn(HTMLMediaElement.prototype, 'canPlayType').mockImplementation((type: string) =>
12
- type === 'audio/mpeg' ? 'probably' : '',
13
- );
12
+ setAudioBackend({ canPlayType: (type: string) => type === 'audio/mpeg' });
14
13
  });
15
14
 
16
15
  afterEach(() => {
17
- vi.restoreAllMocks();
16
+ resetAudioBackendForTest();
18
17
  });
19
18
 
20
19
  it('returns false for the empty string without probing', () => {
21
20
  expect(canPlayAudioType('')).toBe(false);
22
21
  });
23
22
 
24
- it('returns true for a type the element reports it can play', () => {
23
+ it('returns true for a type the backend reports as playable', () => {
25
24
  expect(canPlayAudioType('audio/mpeg')).toBe(true);
26
25
  });
27
26
 
28
- it('returns false for a type the element cannot play', () => {
27
+ it('returns false for a type the backend cannot play', () => {
29
28
  expect(canPlayAudioType('audio/x-unknown')).toBe(false);
30
29
  });
30
+
31
+ it('returns false when no backend is installed (sentinel)', () => {
32
+ resetAudioBackendForTest();
33
+ expect(canPlayAudioType('audio/mpeg')).toBe(false);
34
+ });
31
35
  });
32
36
 
33
37
  describe('detectAudioMimeType', () => {
@@ -1,3 +1,6 @@
1
+ import type { HasNetHttp, NetBackend } from '@flighthq/types/contract';
2
+
3
+ import { resetAudioBackendForTest, setAudioBackend } from './audioBackend';
1
4
  import {
2
5
  createAudioResourceFromSamples,
3
6
  loadAudioResourceFromBase64,
@@ -8,6 +11,16 @@ import {
8
11
  selectAudioResourceUrl,
9
12
  } from './audioResourceFrom';
10
13
 
14
+ function fakeNetHost(backend?: NetBackend): HasNetHttp {
15
+ return {
16
+ net: {
17
+ http: backend ?? {
18
+ sendNetRequest: async () => ({ status: 200, statusText: 'OK', ok: true, headers: {}, body: null, url: '' }),
19
+ },
20
+ },
21
+ };
22
+ }
23
+
11
24
  const decodedBuffer = { duration: 1 } as AudioBuffer;
12
25
 
13
26
  const mockContext = {
@@ -55,6 +68,7 @@ beforeEach(() => {
55
68
  });
56
69
 
57
70
  afterEach(() => {
71
+ resetAudioBackendForTest();
58
72
  vi.unstubAllGlobals();
59
73
  vi.restoreAllMocks();
60
74
  (mockContext.decodeAudioData as ReturnType<typeof vi.fn>).mockClear();
@@ -174,51 +188,73 @@ describe('loadAudioResourceFromBytes', () => {
174
188
  });
175
189
 
176
190
  describe('loadAudioResourceFromUrl', () => {
177
- it('fetches, decodes, and returns a resource', async () => {
178
- vi.stubGlobal(
179
- 'fetch',
180
- vi.fn().mockResolvedValue({
191
+ it('routes URL loading through the host NetBackend', async () => {
192
+ const mockSendNetRequest = vi.fn().mockResolvedValue({
193
+ body: new ArrayBuffer(8),
194
+ headers: { 'content-type': 'audio/mpeg' },
195
+ ok: true,
196
+ status: 200,
197
+ statusText: 'OK',
198
+ url: 'sound.mp3',
199
+ });
200
+ const host = fakeNetHost({ sendNetRequest: mockSendNetRequest });
201
+
202
+ const resource = await loadAudioResourceFromUrl(host, mockContext, 'sound.mp3');
203
+
204
+ expect(resource.buffer).toBe(decodedBuffer);
205
+ expect(mockSendNetRequest).toHaveBeenCalledWith(
206
+ { method: 'GET', responseType: 'arraybuffer', url: 'sound.mp3' },
207
+ undefined,
208
+ );
209
+ });
210
+
211
+ it('decodes a successful arraybuffer response into a resource', async () => {
212
+ const host = fakeNetHost({
213
+ sendNetRequest: async () => ({
214
+ body: new ArrayBuffer(8),
215
+ headers: { 'content-type': 'audio/mpeg' },
181
216
  ok: true,
182
- arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
183
- headers: new Headers({ 'content-type': 'audio/mpeg' }),
217
+ status: 200,
218
+ statusText: 'OK',
219
+ url: 'sound.mp3',
184
220
  }),
185
- );
186
- const resource = await loadAudioResourceFromUrl(mockContext, 'sound.mp3');
221
+ });
222
+ const resource = await loadAudioResourceFromUrl(host, mockContext, 'sound.mp3');
187
223
  expect(resource.buffer).toBe(decodedBuffer);
188
224
  });
189
225
 
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
226
  it('rejects with the HTTP status rather than decoding an error response', async () => {
193
227
  const decodeAudioData = vi.fn().mockResolvedValue(decodedBuffer);
194
- vi.stubGlobal(
195
- 'fetch',
196
- vi.fn().mockResolvedValue({
228
+ const host = fakeNetHost({
229
+ sendNetRequest: async () => ({
230
+ body: null,
231
+ headers: {},
197
232
  ok: false,
198
233
  status: 404,
199
234
  statusText: 'Not Found',
200
- arrayBuffer: () => Promise.resolve(new TextEncoder().encode('<html>404</html>').buffer),
201
- headers: new Headers({ 'content-type': 'text/html' }),
235
+ url: 'missing.mp3',
202
236
  }),
203
- );
237
+ });
204
238
  await expect(
205
- loadAudioResourceFromUrl({ decodeAudioData } as unknown as AudioContext, 'missing.mp3'),
239
+ loadAudioResourceFromUrl(host, { decodeAudioData } as unknown as AudioContext, 'missing.mp3'),
206
240
  ).rejects.toThrow('Failed to load audio: missing.mp3 (404 Not Found)');
207
241
  expect(decodeAudioData).not.toHaveBeenCalled();
208
242
  });
209
243
 
210
244
  it('rejects when the signal aborts while the decode is in flight', async () => {
211
245
  const { context, finishDecode } = createPendingDecodeContext();
212
- vi.stubGlobal(
213
- 'fetch',
214
- vi.fn().mockResolvedValue({
246
+ const host = fakeNetHost({
247
+ sendNetRequest: async () => ({
248
+ body: new ArrayBuffer(8),
249
+ headers: { 'content-type': 'audio/mpeg' },
215
250
  ok: true,
216
- arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
217
- headers: new Headers({ 'content-type': 'audio/mpeg' }),
251
+ status: 200,
252
+ statusText: 'OK',
253
+ url: 'sound.mp3',
218
254
  }),
219
- );
255
+ });
220
256
  const controller = new AbortController();
221
- const promise = loadAudioResourceFromUrl(context, 'sound.mp3', controller.signal);
257
+ const promise = loadAudioResourceFromUrl(host, context, 'sound.mp3', controller.signal);
222
258
  await Promise.resolve();
223
259
  controller.abort(new Error('cancelled'));
224
260
  finishDecode();
@@ -228,33 +264,36 @@ describe('loadAudioResourceFromUrl', () => {
228
264
 
229
265
  describe('loadAudioResourceFromUrls', () => {
230
266
  it('resolves with a null-buffer resource when sources is empty', async () => {
231
- const resource = await loadAudioResourceFromUrls(mockContext, []);
267
+ const host = fakeNetHost();
268
+ const resource = await loadAudioResourceFromUrls(host, mockContext, []);
232
269
  expect(resource.buffer).toBeNull();
233
270
  });
234
271
 
235
272
  it('loads the first playable source', async () => {
236
- vi.spyOn(HTMLMediaElement.prototype, 'canPlayType').mockImplementation((type: string) =>
237
- type === 'audio/ogg' ? 'probably' : '',
238
- );
239
- const fetchMock = vi.fn().mockResolvedValue({
273
+ setAudioBackend({ canPlayType: (type: string) => type === 'audio/ogg' });
274
+ const mockSendNetRequest = vi.fn().mockResolvedValue({
275
+ body: new ArrayBuffer(8),
276
+ headers: { 'content-type': 'audio/ogg' },
240
277
  ok: true,
241
- arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
242
- headers: new Headers(),
278
+ status: 200,
279
+ statusText: 'OK',
280
+ url: 'sound.ogg',
243
281
  });
244
- vi.stubGlobal('fetch', fetchMock);
282
+ const host = fakeNetHost({ sendNetRequest: mockSendNetRequest });
245
283
 
246
- const resource = await loadAudioResourceFromUrls(mockContext, [{ url: 'sound.mp3' }, { url: 'sound.ogg' }]);
284
+ const resource = await loadAudioResourceFromUrls(host, mockContext, [{ url: 'sound.mp3' }, { url: 'sound.ogg' }]);
247
285
 
248
286
  expect(resource.buffer).toBe(decodedBuffer);
249
- expect(fetchMock).toHaveBeenCalledWith('sound.ogg', expect.anything());
287
+ expect(mockSendNetRequest).toHaveBeenCalledWith(
288
+ { method: 'GET', responseType: 'arraybuffer', url: 'sound.ogg' },
289
+ undefined,
290
+ );
250
291
  });
251
292
  });
252
293
 
253
294
  describe('selectAudioResourceUrl', () => {
254
295
  beforeEach(() => {
255
- vi.spyOn(HTMLMediaElement.prototype, 'canPlayType').mockImplementation((type: string) =>
256
- type === 'audio/ogg' ? 'maybe' : '',
257
- );
296
+ setAudioBackend({ canPlayType: (type: string) => type === 'audio/ogg' });
258
297
  });
259
298
 
260
299
  it('returns the first source whose inferred type is playable', () => {