@symbiote-native/audio 0.1.2 → 0.1.4
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 +96 -52
- package/build/angular/index.d.ts +8 -0
- package/build/angular/index.js +8 -3
- package/build/angular/inject-audio-player-status.d.ts +4 -0
- package/build/angular/inject-audio-player-status.js +4 -0
- package/build/angular/inject-audio-player.d.ts +4 -0
- package/build/angular/inject-audio-player.js +8 -0
- package/build/angular/inject-audio-playlist-status.d.ts +3 -0
- package/build/angular/inject-audio-playlist-status.js +4 -0
- package/build/angular/inject-audio-playlist.d.ts +5 -0
- package/build/angular/inject-audio-playlist.js +7 -0
- package/build/angular/inject-audio-recorder-state.d.ts +5 -0
- package/build/angular/inject-audio-recorder-state.js +10 -0
- package/build/angular/inject-audio-recorder.d.ts +5 -0
- package/build/angular/inject-audio-recorder.js +13 -0
- package/build/angular/inject-audio-sample-listener.d.ts +4 -0
- package/build/angular/inject-audio-sample-listener.js +7 -0
- package/build/angular/inject-audio-stream.d.ts +4 -0
- package/build/angular/inject-audio-stream.js +17 -0
- package/build/core/audio-module.js +1 -1
- package/build/core/audio-player-controller.d.ts +7 -0
- package/build/core/audio-player-controller.js +10 -0
- package/build/core/audio-player.js +16 -4
- package/build/core/audio-playlist-controller.d.ts +4 -0
- package/build/core/audio-playlist-controller.js +5 -0
- package/build/core/audio-recorder-controller.d.ts +4 -0
- package/build/core/audio-recorder-controller.js +5 -0
- package/build/core/audio-recorder-polling.d.ts +5 -0
- package/build/core/audio-recorder-polling.js +10 -0
- package/build/core/audio-recorder-state.d.ts +2 -0
- package/build/core/audio-recorder-state.js +18 -0
- package/build/core/audio-recorder-status.d.ts +8 -0
- package/build/core/audio-recorder-status.js +6 -0
- package/build/core/audio-recorder.d.ts +2 -0
- package/build/core/audio-recorder.js +4 -0
- package/build/core/audio-sample-listener.d.ts +11 -0
- package/build/core/audio-sample-listener.js +9 -0
- package/build/core/audio-stream-controller.d.ts +4 -0
- package/build/core/audio-stream-controller.js +5 -0
- package/build/core/audio-stream-hooks.d.ts +8 -0
- package/build/core/audio-stream-hooks.js +13 -0
- package/build/core/audio-stream-lifecycle.d.ts +3 -0
- package/build/core/audio-stream-lifecycle.js +9 -0
- package/build/core/audio-stream-subscription.d.ts +10 -0
- package/build/core/audio-stream-subscription.js +12 -0
- package/build/core/audio-stream.d.ts +9 -10
- package/build/core/audio-stream.js +1 -9
- package/build/core/index.d.ts +14 -2
- package/build/core/index.js +9 -1
- package/build/core/json-keyed-resource-controller.d.ts +1 -0
- package/build/core/json-keyed-resource-controller.js +1 -0
- package/build/core/resolve-source.d.ts +2 -7
- package/build/core/resolve-source.js +69 -15
- package/build/core/subscribe-single-event.d.ts +5 -0
- package/build/core/subscribe-single-event.js +8 -0
- package/build/core/types.d.ts +11 -5
- package/build/react/index.d.ts +9 -0
- package/build/react/index.js +9 -0
- package/build/react/use-audio-player-status.d.ts +4 -0
- package/build/react/use-audio-player-status.js +4 -0
- package/build/react/use-audio-player.d.ts +3 -0
- package/build/react/use-audio-player.js +7 -0
- package/build/react/use-audio-playlist-status.d.ts +3 -0
- package/build/react/use-audio-playlist-status.js +4 -0
- package/build/react/use-audio-playlist.d.ts +4 -0
- package/build/react/use-audio-playlist.js +7 -0
- package/build/react/use-audio-recorder-state.d.ts +4 -0
- package/build/react/use-audio-recorder-state.js +9 -0
- package/build/react/use-audio-recorder.d.ts +4 -0
- package/build/react/use-audio-recorder.js +11 -0
- package/build/react/use-audio-sample-listener.d.ts +4 -0
- package/build/react/use-audio-sample-listener.js +6 -0
- package/build/react/use-audio-stream.d.ts +3 -0
- package/build/react/use-audio-stream.js +14 -0
- package/build/solid/index.d.ts +9 -0
- package/build/solid/index.js +9 -0
- package/build/solid/use-audio-player-status.d.ts +4 -0
- package/build/solid/use-audio-player-status.js +4 -0
- package/build/solid/use-audio-player.d.ts +5 -0
- package/build/solid/use-audio-player.js +7 -0
- package/build/solid/use-audio-playlist-status.d.ts +3 -0
- package/build/solid/use-audio-playlist-status.js +4 -0
- package/build/solid/use-audio-playlist.d.ts +5 -0
- package/build/solid/use-audio-playlist.js +7 -0
- package/build/solid/use-audio-recorder-state.d.ts +5 -0
- package/build/solid/use-audio-recorder-state.js +9 -0
- package/build/solid/use-audio-recorder.d.ts +5 -0
- package/build/solid/use-audio-recorder.js +13 -0
- package/build/solid/use-audio-sample-listener.d.ts +4 -0
- package/build/solid/use-audio-sample-listener.js +8 -0
- package/build/solid/use-audio-stream.d.ts +4 -0
- package/build/solid/use-audio-stream.js +16 -0
- package/build/svelte/index.d.ts +9 -0
- package/build/svelte/index.js +9 -0
- package/build/svelte/use-audio-player-status.svelte.d.ts +6 -0
- package/build/svelte/use-audio-player-status.svelte.js +4 -0
- package/build/svelte/use-audio-player.svelte.d.ts +6 -0
- package/build/svelte/use-audio-player.svelte.js +8 -0
- package/build/svelte/use-audio-playlist-status.svelte.d.ts +5 -0
- package/build/svelte/use-audio-playlist-status.svelte.js +4 -0
- package/build/svelte/use-audio-playlist.svelte.d.ts +6 -0
- package/build/svelte/use-audio-playlist.svelte.js +8 -0
- package/build/svelte/use-audio-recorder-state.svelte.d.ts +7 -0
- package/build/svelte/use-audio-recorder-state.svelte.js +14 -0
- package/build/svelte/use-audio-recorder.svelte.d.ts +6 -0
- package/build/svelte/use-audio-recorder.svelte.js +11 -0
- package/build/svelte/use-audio-sample-listener.svelte.d.ts +4 -0
- package/build/svelte/use-audio-sample-listener.svelte.js +5 -0
- package/build/svelte/use-audio-stream.svelte.d.ts +4 -0
- package/build/svelte/use-audio-stream.svelte.js +29 -0
- package/build/vue/index.d.ts +9 -0
- package/build/vue/index.js +9 -0
- package/build/vue/use-audio-player-status.d.ts +4 -0
- package/build/vue/use-audio-player-status.js +4 -0
- package/build/vue/use-audio-player.d.ts +5 -0
- package/build/vue/use-audio-player.js +8 -0
- package/build/vue/use-audio-playlist-status.d.ts +3 -0
- package/build/vue/use-audio-playlist-status.js +4 -0
- package/build/vue/use-audio-playlist.d.ts +5 -0
- package/build/vue/use-audio-playlist.js +8 -0
- package/build/vue/use-audio-recorder-state.d.ts +5 -0
- package/build/vue/use-audio-recorder-state.js +12 -0
- package/build/vue/use-audio-recorder.d.ts +5 -0
- package/build/vue/use-audio-recorder.js +11 -0
- package/build/vue/use-audio-sample-listener.d.ts +4 -0
- package/build/vue/use-audio-sample-listener.js +11 -0
- package/build/vue/use-audio-stream.d.ts +4 -0
- package/build/vue/use-audio-stream.js +18 -0
- package/build-ngc/angular/index.d.ts +8 -0
- package/build-ngc/angular/index.js +8 -3
- package/build-ngc/angular/index.js.map +1 -1
- package/build-ngc/angular/inject-audio-player-status.d.ts +4 -0
- package/build-ngc/angular/inject-audio-player-status.js +5 -0
- package/build-ngc/angular/inject-audio-player-status.js.map +1 -0
- package/build-ngc/angular/inject-audio-player.d.ts +4 -0
- package/build-ngc/angular/inject-audio-player.js +9 -0
- package/build-ngc/angular/inject-audio-player.js.map +1 -0
- package/build-ngc/angular/inject-audio-playlist-status.d.ts +3 -0
- package/build-ngc/angular/inject-audio-playlist-status.js +5 -0
- package/build-ngc/angular/inject-audio-playlist-status.js.map +1 -0
- package/build-ngc/angular/inject-audio-playlist.d.ts +5 -0
- package/build-ngc/angular/inject-audio-playlist.js +8 -0
- package/build-ngc/angular/inject-audio-playlist.js.map +1 -0
- package/build-ngc/angular/inject-audio-recorder-state.d.ts +5 -0
- package/build-ngc/angular/inject-audio-recorder-state.js +12 -0
- package/build-ngc/angular/inject-audio-recorder-state.js.map +1 -0
- package/build-ngc/angular/inject-audio-recorder.d.ts +5 -0
- package/build-ngc/angular/inject-audio-recorder.js +14 -0
- package/build-ngc/angular/inject-audio-recorder.js.map +1 -0
- package/build-ngc/angular/inject-audio-sample-listener.d.ts +4 -0
- package/build-ngc/angular/inject-audio-sample-listener.js +8 -0
- package/build-ngc/angular/inject-audio-sample-listener.js.map +1 -0
- package/build-ngc/angular/inject-audio-stream.d.ts +4 -0
- package/build-ngc/angular/inject-audio-stream.js +18 -0
- package/build-ngc/angular/inject-audio-stream.js.map +1 -0
- package/build-ngc/core/audio-module.js +1 -1
- package/build-ngc/core/audio-module.js.map +1 -1
- package/build-ngc/core/audio-player-controller.d.ts +7 -0
- package/build-ngc/core/audio-player-controller.js +11 -0
- package/build-ngc/core/audio-player-controller.js.map +1 -0
- package/build-ngc/core/audio-player.js +16 -4
- package/build-ngc/core/audio-player.js.map +1 -1
- package/build-ngc/core/audio-playlist-controller.d.ts +4 -0
- package/build-ngc/core/audio-playlist-controller.js +6 -0
- package/build-ngc/core/audio-playlist-controller.js.map +1 -0
- package/build-ngc/core/audio-recorder-controller.d.ts +4 -0
- package/build-ngc/core/audio-recorder-controller.js +6 -0
- package/build-ngc/core/audio-recorder-controller.js.map +1 -0
- package/build-ngc/core/audio-recorder-polling.d.ts +5 -0
- package/build-ngc/core/audio-recorder-polling.js +11 -0
- package/build-ngc/core/audio-recorder-polling.js.map +1 -0
- package/build-ngc/core/audio-recorder-state.d.ts +2 -0
- package/build-ngc/core/audio-recorder-state.js +19 -0
- package/build-ngc/core/audio-recorder-state.js.map +1 -0
- package/build-ngc/core/audio-recorder-status.d.ts +8 -0
- package/build-ngc/core/audio-recorder-status.js +7 -0
- package/build-ngc/core/audio-recorder-status.js.map +1 -0
- package/build-ngc/core/audio-recorder.d.ts +2 -0
- package/build-ngc/core/audio-recorder.js +4 -0
- package/build-ngc/core/audio-recorder.js.map +1 -1
- package/build-ngc/core/audio-sample-listener.d.ts +11 -0
- package/build-ngc/core/audio-sample-listener.js +10 -0
- package/build-ngc/core/audio-sample-listener.js.map +1 -0
- package/build-ngc/core/audio-stream-controller.d.ts +4 -0
- package/build-ngc/core/audio-stream-controller.js +6 -0
- package/build-ngc/core/audio-stream-controller.js.map +1 -0
- package/build-ngc/core/audio-stream-hooks.d.ts +8 -0
- package/build-ngc/core/audio-stream-hooks.js +14 -0
- package/build-ngc/core/audio-stream-hooks.js.map +1 -0
- package/build-ngc/core/audio-stream-lifecycle.d.ts +3 -0
- package/build-ngc/core/audio-stream-lifecycle.js +10 -0
- package/build-ngc/core/audio-stream-lifecycle.js.map +1 -0
- package/build-ngc/core/audio-stream-subscription.d.ts +10 -0
- package/build-ngc/core/audio-stream-subscription.js +13 -0
- package/build-ngc/core/audio-stream-subscription.js.map +1 -0
- package/build-ngc/core/audio-stream.d.ts +9 -10
- package/build-ngc/core/audio-stream.js +1 -9
- package/build-ngc/core/audio-stream.js.map +1 -1
- package/build-ngc/core/index.d.ts +14 -2
- package/build-ngc/core/index.js +9 -1
- package/build-ngc/core/index.js.map +1 -1
- package/build-ngc/core/json-keyed-resource-controller.d.ts +1 -0
- package/build-ngc/core/json-keyed-resource-controller.js +2 -0
- package/build-ngc/core/json-keyed-resource-controller.js.map +1 -0
- package/build-ngc/core/resolve-source.d.ts +2 -7
- package/build-ngc/core/resolve-source.js +69 -15
- package/build-ngc/core/resolve-source.js.map +1 -1
- package/build-ngc/core/subscribe-single-event.d.ts +5 -0
- package/build-ngc/core/subscribe-single-event.js +9 -0
- package/build-ngc/core/subscribe-single-event.js.map +1 -0
- package/build-ngc/core/types.d.ts +11 -5
- package/build-ngc/core/types.js.map +1 -1
- package/package.json +24 -23
- package/src/angular/index.ts +8 -3
- package/src/angular/inject-audio-player-status.ts +11 -0
- package/src/angular/inject-audio-player.ts +17 -0
- package/src/angular/inject-audio-playlist-status.ts +11 -0
- package/src/angular/inject-audio-playlist.ts +16 -0
- package/src/angular/inject-audio-recorder-state.ts +21 -0
- package/src/angular/inject-audio-recorder.ts +24 -0
- package/src/angular/inject-audio-sample-listener.ts +13 -0
- package/src/angular/inject-audio-stream.ts +41 -0
- package/src/core/audio-module.ts +2 -1
- package/src/core/audio-player-controller.ts +25 -0
- package/src/core/audio-player.ts +17 -4
- package/src/core/audio-playlist-controller.ts +14 -0
- package/src/core/audio-recorder-controller.ts +14 -0
- package/src/core/audio-recorder-polling.ts +20 -0
- package/src/core/audio-recorder-state.ts +26 -0
- package/src/core/audio-recorder-status.ts +22 -0
- package/src/core/audio-recorder.ts +5 -0
- package/src/core/audio-sample-listener.ts +23 -0
- package/src/core/audio-stream-controller.ts +14 -0
- package/src/core/audio-stream-hooks.ts +31 -0
- package/src/core/audio-stream-lifecycle.ts +19 -0
- package/src/core/audio-stream-subscription.ts +28 -0
- package/src/core/audio-stream.ts +11 -10
- package/src/core/index.ts +20 -1
- package/src/core/json-keyed-resource-controller.ts +1 -0
- package/src/core/resolve-source.ts +80 -18
- package/src/core/subscribe-single-event.ts +17 -0
- package/src/core/types.ts +13 -14
- package/src/react/index.ts +9 -0
- package/src/react/use-audio-player-status.ts +11 -0
- package/src/react/use-audio-player.ts +14 -0
- package/src/react/use-audio-playlist-status.ts +11 -0
- package/src/react/use-audio-playlist.ts +13 -0
- package/src/react/use-audio-recorder-state.ts +23 -0
- package/src/react/use-audio-recorder.ts +23 -0
- package/src/react/use-audio-sample-listener.ts +15 -0
- package/src/react/use-audio-stream.ts +39 -0
- package/src/solid/index.ts +9 -0
- package/src/solid/use-audio-player-status.ts +11 -0
- package/src/solid/use-audio-player.ts +15 -0
- package/src/solid/use-audio-playlist-status.ts +11 -0
- package/src/solid/use-audio-playlist.ts +14 -0
- package/src/solid/use-audio-recorder-state.ts +18 -0
- package/src/solid/use-audio-recorder.ts +22 -0
- package/src/solid/use-audio-sample-listener.ts +14 -0
- package/src/solid/use-audio-stream.ts +34 -0
- package/src/svelte/index.ts +9 -0
- package/src/svelte/svelte-ambient.d.ts +3 -0
- package/src/svelte/svelte-compile.test-helper.ts +7 -0
- package/src/svelte/use-audio-player-status.svelte.ts +11 -0
- package/src/svelte/use-audio-player.svelte.ts +20 -0
- package/src/svelte/use-audio-playlist-status.svelte.ts +11 -0
- package/src/svelte/use-audio-playlist.svelte.ts +19 -0
- package/src/svelte/use-audio-recorder-state.svelte.ts +29 -0
- package/src/svelte/use-audio-recorder.svelte.ts +25 -0
- package/src/svelte/use-audio-sample-listener.svelte.ts +11 -0
- package/src/svelte/use-audio-stream.svelte.ts +44 -0
- package/src/vue/index.ts +9 -0
- package/src/vue/use-audio-player-status.ts +11 -0
- package/src/vue/use-audio-player.ts +19 -0
- package/src/vue/use-audio-playlist-status.ts +11 -0
- package/src/vue/use-audio-playlist.ts +15 -0
- package/src/vue/use-audio-recorder-state.ts +21 -0
- package/src/vue/use-audio-recorder.ts +30 -0
- package/src/vue/use-audio-sample-listener.ts +22 -0
- package/src/vue/use-audio-stream.ts +40 -0
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# @symbiote-native/audio
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
Play a track, record the microphone, queue a playlist or read raw PCM samples, and keep playing when
|
|
4
|
+
the app goes to the background. One API for every [SymbioteNative](../../README.md) adapter (React,
|
|
5
|
+
Vue, Svelte, Solid and Angular).
|
|
6
|
+
|
|
7
|
+
It wraps [`expo-audio`](https://github.com/expo/expo/tree/main/packages/expo-audio).
|
|
8
|
+
`AudioPlayer`, `AudioRecorder`, `AudioPlaylist` and `AudioStream` are JSI-backed `SharedObject`
|
|
9
|
+
instances: per-instance native objects with real state and methods, not one-shot functions. Every
|
|
10
|
+
upstream lifecycle hook, including `useAudioStream`, is ported to every adapter (see "Hooks"
|
|
11
|
+
below), so a player is created, replaced and released with its component.
|
|
11
12
|
|
|
12
13
|
## Install
|
|
13
14
|
|
|
@@ -23,49 +24,49 @@ npx @symbiote-native/cli new my-app --audio
|
|
|
23
24
|
npx @symbiote-native/cli add --audio
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
Either way: installs `@symbiote-native/audio`, wires the native autolinking automatically, and
|
|
27
|
-
this package has an optional, policy-sensitive Android bundle
|
|
27
|
+
Either way: installs `@symbiote-native/audio`, wires the native autolinking automatically, and - since
|
|
28
|
+
this package has an optional, policy-sensitive Android bundle - asks at the end whether to grant
|
|
28
29
|
background recording too (see the note below the manual-install block). See
|
|
29
30
|
[`@symbiote-native/cli`](../cli).
|
|
30
31
|
|
|
31
32
|
<details>
|
|
32
|
-
<summary>Manual install (no CLI
|
|
33
|
+
<summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
|
|
33
34
|
|
|
34
35
|
```bash
|
|
35
36
|
npm install @symbiote-native/audio
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
`expo-audio` and `expo-modules-core` come along as regular dependencies, pinned to exact
|
|
39
|
-
versions
|
|
40
|
+
versions - never install either yourself, and never add the `expo` meta-package to your project
|
|
40
41
|
(it bundles its own Metro/Babel pipeline, which conflicts with this project's own).
|
|
41
42
|
|
|
42
43
|
## Required one-time step: native autolinking wiring
|
|
43
44
|
|
|
44
|
-
Same one-time step as every other `expo-modules-core` package this project ships
|
|
45
|
+
Same one-time step as every other `expo-modules-core` package this project ships - see
|
|
45
46
|
[`@symbiote-native/local-auth`'s README](../local-auth/README.md#required-one-time-step-native-autolinking-wiring)
|
|
46
47
|
and the `symbiote-expo-native-module` project skill. Nothing package-specific beyond one Info.plist
|
|
47
48
|
string:
|
|
48
49
|
|
|
49
|
-
- iOS
|
|
50
|
+
- iOS - `NSMicrophoneUsageDescription` in `Info.plist`, wired automatically by
|
|
50
51
|
`@symbiote-native/expo-modules-link`'s aggregator (`native-link.json`'s `ios.infoPlistKeys`) the
|
|
51
|
-
next time it runs
|
|
52
|
-
also adds `UIBackgroundModes: audio` (`ios.infoPlistArrayKeys`)
|
|
52
|
+
next time it runs - override the default text by adding the key yourself first. Same aggregator
|
|
53
|
+
also adds `UIBackgroundModes: audio` (`ios.infoPlistArrayKeys`) - upstream's own `withAudio.ts`
|
|
53
54
|
config plugin defaults `enableBackgroundPlayback` to `true`, and without this key iOS suspends
|
|
54
55
|
playback the moment the app backgrounds.
|
|
55
|
-
- Android
|
|
56
|
+
- Android - `RECORD_AUDIO` / `MODIFY_AUDIO_SETTINGS` already ship in `expo-audio`'s own
|
|
56
57
|
`AndroidManifest.xml` and merge automatically, same as every other autolinked permission.
|
|
57
58
|
Background **playback** is wired the same way as iOS: `native-link.json`'s
|
|
58
59
|
`android.manifestPermissions` adds `FOREGROUND_SERVICE`/`FOREGROUND_SERVICE_MEDIA_PLAYBACK`, and
|
|
59
60
|
`android.manifestServices` declares `AudioControlsService` (`foregroundServiceType="mediaPlayback"`)
|
|
60
|
-
|
|
61
|
+
- matching upstream's own `withAudio.ts` config plugin, whose `enableBackgroundPlayback` defaults
|
|
61
62
|
to `true`. Background **recording** (`allowsBackgroundRecording: true`) is opt-in, matching
|
|
62
|
-
upstream's own `enableBackgroundRecording` default of `false`
|
|
63
|
+
upstream's own `enableBackgroundRecording` default of `false` - requesting the microphone
|
|
63
64
|
foreground-service type is exactly the kind of thing that should be a deliberate app choice, not
|
|
64
65
|
a package side effect (same reasoning as `@symbiote-native/location`'s background permission).
|
|
65
66
|
|
|
66
67
|
</details>
|
|
67
68
|
|
|
68
|
-
**Background recording
|
|
69
|
+
**Background recording - opt-in, asked for you.** `new --audio`/`add --audio` above already ask,
|
|
69
70
|
interactively, whether to grant it; say yes and `FOREGROUND_SERVICE_MICROPHONE`/
|
|
70
71
|
`POST_NOTIFICATIONS` plus the `AudioRecordingService` (`foregroundServiceType="microphone"`)
|
|
71
72
|
`<service>` land in your `AndroidManifest.xml` for you, along with the `allowsBackgroundRecording`
|
|
@@ -76,21 +77,21 @@ Run it any time after:
|
|
|
76
77
|
npx @symbiote-native/cli grant audio
|
|
77
78
|
```
|
|
78
79
|
|
|
79
|
-
Idempotent
|
|
80
|
+
Idempotent - safe to run again even if already granted.
|
|
80
81
|
|
|
81
82
|
## Shape
|
|
82
83
|
|
|
83
84
|
```
|
|
84
85
|
src/core/ AudioPlayer / AudioRecorder / AudioPlaylist / AudioStream classes (each a thin
|
|
85
86
|
subclass of the native SharedObject, adding only the JS-side logic upstream's own
|
|
86
|
-
ExpoAudio.ts shims onto the prototype
|
|
87
|
+
ExpoAudio.ts shims onto the prototype - source resolution on `replace()`, the
|
|
87
88
|
Android arg-count fix for `setPlaybackRate()`, per-platform option processing for
|
|
88
89
|
`prepareToRecordAsync()`), the createAudioPlayer / createAudioPlaylist /
|
|
89
90
|
createAudioStream factories, the audio-session/permission/preload module functions,
|
|
90
91
|
RecordingPresets, and the event-name constants `addListener` accepts.
|
|
91
92
|
native-module.ts resolves the native module via expo-modules-core's
|
|
92
93
|
requireNativeModule.
|
|
93
|
-
src/angular/ @symbiote-native/audio/angular
|
|
94
|
+
src/angular/ @symbiote-native/audio/angular - export * from '../core'
|
|
94
95
|
```
|
|
95
96
|
|
|
96
97
|
`./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto
|
|
@@ -144,13 +145,12 @@ recorder.addListener(RECORDING_STATUS_UPDATE, status => console.log(status));
|
|
|
144
145
|
await recorder.stop();
|
|
145
146
|
```
|
|
146
147
|
|
|
147
|
-
Playlist
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
`
|
|
151
|
-
on these classes.
|
|
148
|
+
Playlist follows the same `create*` + `addListener` shape: `createAudioPlaylist({ sources: [...],
|
|
149
|
+
loop: 'all' })`. Real-time PCM streaming has both a bare factory (`createAudioStream()` plus
|
|
150
|
+
`stream.addListener(AUDIO_STREAM_BUFFER, ...)` after `stream.start()`) and the full
|
|
151
|
+
`useAudioStream(options)` hook on every adapter, see "Hooks" below.
|
|
152
152
|
|
|
153
|
-
Identical import surface on every adapter
|
|
153
|
+
Identical import surface on every adapter - `@symbiote-native/audio/react`, `/vue`, `/svelte`,
|
|
154
154
|
`/solid`, `/angular` all re-export the same classes and functions.
|
|
155
155
|
|
|
156
156
|
## API
|
|
@@ -216,34 +216,78 @@ TRACK_CHANGED, AUDIO_STREAM_BUFFER, AUDIO_STREAM_STATUS // event names for .add
|
|
|
216
216
|
|
|
217
217
|
Plus the full `I`-prefixed type surface ported from upstream's `Audio.types.ts` /
|
|
218
218
|
`AudioModule.types.ts` / `AudioStream.types.ts` / `AudioConstants.ts`, re-exported from the
|
|
219
|
-
barrel
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
`
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
219
|
+
barrel - see `src/core/types.ts`.
|
|
220
|
+
|
|
221
|
+
`IAudioSource` accepts a URI string, a `require('./song.mp3')` module id, an `@symbiote-native/asset`
|
|
222
|
+
`Asset` instance, or a `{ uri | assetId, headers?, name? }` object - matching upstream's full
|
|
223
|
+
`AudioSource` union now that `@symbiote-native/asset` ships. `createAudioPlayer`'s `downloadFirst`
|
|
224
|
+
option is ported too: the player starts with no source and `replace()`s it once
|
|
225
|
+
`Asset.downloadAsync()` resolves a local cache file, same as upstream's `useAudioPlayer`.
|
|
226
|
+
|
|
227
|
+
## Hooks
|
|
228
|
+
|
|
229
|
+
Every upstream hook is ported to all five adapters (Angular names each `injectX` per its own
|
|
230
|
+
`injectX` convention). Framework-name suffix is dropped below; read `useAudioPlayer` as
|
|
231
|
+
`useAudioPlayer`/`injectAudioPlayer` depending on adapter.
|
|
232
|
+
|
|
233
|
+
- **`useAudioPlayer(source?, options?)`** - resource lifecycle (recreate-on-source-change, dispose
|
|
234
|
+
the stale one), via `createResourceController`/`createResourceHook` in
|
|
235
|
+
`core/audio-player-controller.ts`.
|
|
236
|
+
- **`useAudioPlayerStatus(player)`** - subscribes to `playbackStatusUpdate`, via the shared
|
|
237
|
+
`createEventValueHook` factory (`core/index.ts`'s `IEventValueSource` contract).
|
|
238
|
+
- **`useAudioSampleListener(player, listener)`** - enables sampling and subscribes to
|
|
239
|
+
`audioSampleUpdate`, via `subscribeAudioSampleListener` in `core/audio-sample-listener.ts`.
|
|
240
|
+
- **`useAudioPlaylist(options?)`** - same resource-lifecycle shape as `useAudioPlayer`, via
|
|
241
|
+
`core/audio-playlist-controller.ts`.
|
|
242
|
+
- **`useAudioPlaylistStatus(playlist)`** - subscribes to `playlistStatusUpdate`, same
|
|
243
|
+
`createEventValueHook` factory.
|
|
244
|
+
- **`useAudioRecorder(options, statusListener?)`** - resource lifecycle plus a
|
|
245
|
+
`recordingStatusUpdate` subscription, via `core/audio-recorder-controller.ts` and
|
|
246
|
+
`subscribeRecordingStatus` in `core/audio-recorder-status.ts`.
|
|
247
|
+
- **`useAudioRecorderState(recorder, interval?)`** - polls `recorder.getStatus()` and only writes
|
|
248
|
+
a meaningful change, via `pollRecorderState`/`shouldUpdateRecorderState` in
|
|
249
|
+
`core/audio-recorder-polling.ts`.
|
|
250
|
+
- **`useAudioStream(options)`** - resource lifecycle keyed on `{sampleRate, channels, encoding}`
|
|
251
|
+
plus an `audioStreamStatus`/`audioStreamBuffer` subscription, via `createAudioStreamHooks`/
|
|
252
|
+
`runAudioStreamBufferEffect` in `core/audio-stream-hooks.ts`/`core/audio-stream-lifecycle.ts`.
|
|
253
|
+
|
|
254
|
+
Every adapter shares the framework-agnostic diffing/subscription logic from `core/`; only the
|
|
255
|
+
lifecycle primitive differs (React/Solid/Vue/Angular's own `createResourceHook`/
|
|
256
|
+
`createEventValueHook`, Svelte's from `@symbiote-native/svelte/runes/*`).
|
|
257
|
+
|
|
258
|
+
## Notes
|
|
259
|
+
|
|
260
|
+
- **Silent mode on iOS.** Call `setAudioModeAsync({ playsInSilentMode: true })` or playback is
|
|
261
|
+
muted by the ring/silent switch.
|
|
262
|
+
- **Release what you create by hand.** `createAudioPlayer` and the other factories leave cleanup to
|
|
263
|
+
you (`remove()`); the hooks do it automatically.
|
|
264
|
+
- **Android background playback needs lock-screen controls.** Without `setActiveForLockScreen`,
|
|
265
|
+
Android stops background audio after about three minutes (an OS limit).
|
|
266
|
+
|
|
267
|
+
## Common questions
|
|
268
|
+
|
|
269
|
+
- **Silent on iPhone.** `setAudioModeAsync({ playsInSilentMode: true })`.
|
|
270
|
+
- **Stops in the background.** `shouldPlayInBackground: true`; on Android also
|
|
271
|
+
`setActiveForLockScreen`, or the OS stops it after about three minutes.
|
|
272
|
+
- **Recording permission error.** `requestRecordingPermissionsAsync()` first; background recording
|
|
273
|
+
is opt-in (`npx @symbiote-native/cli grant audio`).
|
|
274
|
+
- **Change the track.** `player.replace(source)`; `useAudioPlayer` recreates on a changed source.
|
|
275
|
+
- **Leaking players.** Factory-made players need `remove()`; the hooks do it for you.
|
|
276
|
+
|
|
277
|
+
Sources: [Expo docs: Audio](https://docs.expo.dev/versions/latest/sdk/audio/),
|
|
278
|
+
[expo/expo#24484](https://github.com/expo/expo/issues/24484),
|
|
279
|
+
[expo/expo#43086](https://github.com/expo/expo/issues/43086).
|
|
280
|
+
|
|
281
|
+
## Scope exclusions
|
|
282
|
+
|
|
239
283
|
- **Web-only surfaces** (`ExpoAudio.web.ts`, `AudioPlayer.web.ts`, `AudioStream.web.ts`,
|
|
240
|
-
`MediaSessionController.web.ts`)
|
|
284
|
+
`MediaSessionController.web.ts`) - this project targets iOS + Android only.
|
|
241
285
|
|
|
242
286
|
## Test it
|
|
243
287
|
|
|
244
|
-
No Fabric/Descriptor angle at all
|
|
288
|
+
No Fabric/Descriptor angle at all - every class here is a `SharedObject` with no visual
|
|
245
289
|
component. Tests fake the native `AudioPlayer`/`AudioRecorder`/`AudioPlaylist`/`AudioStream`
|
|
246
290
|
classes `expoAudio` exposes (real prototype methods, so the subclass `super.*()` calls in
|
|
247
291
|
`audio-player.ts`/`audio-recorder.ts` resolve correctly) and the plain async module functions
|
|
248
292
|
(`src/core/audio-classes.test.ts`, `src/core/audio-module.test.ts`, `src/core/
|
|
249
|
-
resolve-source.test.ts`)
|
|
293
|
+
resolve-source.test.ts`) - no `installFabric()`, no ViewConfig.
|
package/build/angular/index.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
1
|
export * from '../core';
|
|
2
|
+
export { injectAudioPlayer } from './inject-audio-player';
|
|
3
|
+
export { injectAudioPlayerStatus } from './inject-audio-player-status';
|
|
4
|
+
export { injectAudioSampleListener } from './inject-audio-sample-listener';
|
|
5
|
+
export { injectAudioPlaylist } from './inject-audio-playlist';
|
|
6
|
+
export { injectAudioPlaylistStatus } from './inject-audio-playlist-status';
|
|
7
|
+
export { injectAudioRecorder } from './inject-audio-recorder';
|
|
8
|
+
export { injectAudioRecorderState } from './inject-audio-recorder-state';
|
|
9
|
+
export { injectAudioStream } from './inject-audio-stream';
|
package/build/angular/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
// @symbiote-native/audio/angular: the Angular entry over the framework-agnostic core. Same
|
|
2
|
-
// reasoning as the React/Vue entries — every export here is a plain class/function with no
|
|
3
|
-
// framework-specific lifecycle to wrap, so this is a plain re-export.
|
|
4
1
|
export * from '../core/index.js';
|
|
2
|
+
export { injectAudioPlayer } from './inject-audio-player.js';
|
|
3
|
+
export { injectAudioPlayerStatus } from './inject-audio-player-status.js';
|
|
4
|
+
export { injectAudioSampleListener } from './inject-audio-sample-listener.js';
|
|
5
|
+
export { injectAudioPlaylist } from './inject-audio-playlist.js';
|
|
6
|
+
export { injectAudioPlaylistStatus } from './inject-audio-playlist-status.js';
|
|
7
|
+
export { injectAudioRecorder } from './inject-audio-recorder.js';
|
|
8
|
+
export { injectAudioRecorderState } from './inject-audio-recorder-state.js';
|
|
9
|
+
export { injectAudioStream } from './inject-audio-stream.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AudioPlayer } from '../core';
|
|
2
|
+
import type { IAudioStatus } from '../core';
|
|
3
|
+
/** Angular twin of `expo-audio`'s `useAudioPlayerStatus` */
|
|
4
|
+
export declare const injectAudioPlayerStatus: (getSource: () => AudioPlayer) => import("@angular/core").Signal<IAudioStatus>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { createEventValueHook } from '@symbiote-native/angular';
|
|
2
|
+
import { PLAYBACK_STATUS_UPDATE } from '../core/types.js';
|
|
3
|
+
/** Angular twin of `expo-audio`'s `useAudioPlayerStatus` */
|
|
4
|
+
export const injectAudioPlayerStatus = createEventValueHook(PLAYBACK_STATUS_UPDATE, player => player.currentStatus);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Signal } from '@angular/core';
|
|
2
|
+
import type { AudioPlayer } from '../core';
|
|
3
|
+
import type { IAudioPlayerOptions, IAudioSource } from '../core';
|
|
4
|
+
export declare function injectAudioPlayer(source: () => IAudioSource, options?: () => IAudioPlayerOptions): Signal<AudioPlayer>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Angular twin of `useAudioPlayer`, `injectX` shape matching
|
|
2
|
+
// `@symbiote-native/navigation`'s `injectLinkingIntegration`
|
|
3
|
+
import { createResourceHook } from '@symbiote-native/angular';
|
|
4
|
+
import { createAudioPlayerController } from '../core/audio-player-controller.js';
|
|
5
|
+
const injectPlayerResource = createResourceHook(createAudioPlayerController);
|
|
6
|
+
export function injectAudioPlayer(source, options = () => ({})) {
|
|
7
|
+
return injectPlayerResource(() => [source(), options()]);
|
|
8
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { IAudioPlaylistStatus } from '../core';
|
|
2
|
+
/** Angular twin of `expo-audio`'s `useAudioPlaylistStatus` */
|
|
3
|
+
export declare const injectAudioPlaylistStatus: (getSource: () => import("../core/native-module").NativeAudioPlaylist) => import("@angular/core").Signal<IAudioPlaylistStatus>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { createEventValueHook } from '@symbiote-native/angular';
|
|
2
|
+
import { PLAYLIST_STATUS_UPDATE } from '../core/types.js';
|
|
3
|
+
/** Angular twin of `expo-audio`'s `useAudioPlaylistStatus` */
|
|
4
|
+
export const injectAudioPlaylistStatus = createEventValueHook(PLAYLIST_STATUS_UPDATE, playlist => playlist.currentStatus);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Signal } from '@angular/core';
|
|
2
|
+
import type { AudioPlaylist } from '../core';
|
|
3
|
+
import type { IAudioPlaylistOptions } from '../core';
|
|
4
|
+
/** Angular twin of `expo-audio`'s `useAudioPlaylist` */
|
|
5
|
+
export declare function injectAudioPlaylist(getOptions: () => IAudioPlaylistOptions): Signal<AudioPlaylist>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createResourceHook } from '@symbiote-native/angular';
|
|
2
|
+
import { createAudioPlaylistController } from '../core/audio-playlist-controller.js';
|
|
3
|
+
const injectPlaylistResource = createResourceHook(createAudioPlaylistController);
|
|
4
|
+
/** Angular twin of `expo-audio`'s `useAudioPlaylist` */
|
|
5
|
+
export function injectAudioPlaylist(getOptions) {
|
|
6
|
+
return injectPlaylistResource(() => [getOptions()]);
|
|
7
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Signal } from '@angular/core';
|
|
2
|
+
import type { AudioRecorder } from '../core';
|
|
3
|
+
import type { IRecorderState } from '../core';
|
|
4
|
+
/** Angular twin of `expo-audio`'s `useAudioRecorderState` */
|
|
5
|
+
export declare function injectAudioRecorderState(recorder: AudioRecorder, interval?: number): Signal<IRecorderState>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DestroyRef, inject, signal } from '@angular/core';
|
|
2
|
+
import { pollRecorderState } from '../core/audio-recorder-polling.js';
|
|
3
|
+
const DEFAULT_POLL_INTERVAL_MS = 500;
|
|
4
|
+
/** Angular twin of `expo-audio`'s `useAudioRecorderState` */
|
|
5
|
+
export function injectAudioRecorderState(recorder, interval = DEFAULT_POLL_INTERVAL_MS) {
|
|
6
|
+
const state = signal(recorder.getStatus());
|
|
7
|
+
const stop = pollRecorderState(recorder, interval, updater => state.set(updater(state())));
|
|
8
|
+
inject(DestroyRef).onDestroy(stop);
|
|
9
|
+
return state.asReadonly();
|
|
10
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Signal } from '@angular/core';
|
|
2
|
+
import type { AudioRecorder } from '../core';
|
|
3
|
+
import type { IRecordingOptions, IRecordingStatus } from '../core';
|
|
4
|
+
/** Angular twin of `expo-audio`'s `useAudioRecorder` */
|
|
5
|
+
export declare function injectAudioRecorder(getOptions: () => IRecordingOptions, statusListener?: (status: IRecordingStatus) => void): Signal<AudioRecorder>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { effect } from '@angular/core';
|
|
2
|
+
import { createResourceHook } from '@symbiote-native/angular';
|
|
3
|
+
import { createAudioRecorderController } from '../core/audio-recorder-controller.js';
|
|
4
|
+
import { subscribeRecordingStatus } from '../core/audio-recorder-status.js';
|
|
5
|
+
const injectRecorderResource = createResourceHook(createAudioRecorderController);
|
|
6
|
+
/** Angular twin of `expo-audio`'s `useAudioRecorder` */
|
|
7
|
+
export function injectAudioRecorder(getOptions, statusListener) {
|
|
8
|
+
const recorder = injectRecorderResource(() => [getOptions()]);
|
|
9
|
+
effect(onCleanup => {
|
|
10
|
+
onCleanup(subscribeRecordingStatus(recorder(), statusListener));
|
|
11
|
+
});
|
|
12
|
+
return recorder;
|
|
13
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IAudioSamplePlayer } from '../core/audio-sample-listener';
|
|
2
|
+
import type { IAudioSample } from '../core/types';
|
|
3
|
+
/** Angular twin of `expo-audio`'s `useAudioSampleListener` */
|
|
4
|
+
export declare function injectAudioSampleListener(player: IAudioSamplePlayer, listener: (data: IAudioSample) => void): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DestroyRef, inject } from '@angular/core';
|
|
2
|
+
import { subscribeAudioSampleListener } from '../core/audio-sample-listener.js';
|
|
3
|
+
/** Angular twin of `expo-audio`'s `useAudioSampleListener` */
|
|
4
|
+
export function injectAudioSampleListener(player, listener) {
|
|
5
|
+
const unsubscribe = subscribeAudioSampleListener(player, listener);
|
|
6
|
+
inject(DestroyRef).onDestroy(unsubscribe);
|
|
7
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Signal } from '@angular/core';
|
|
2
|
+
import type { IAudioStreamResult, IUseAudioStreamOptions } from '../core';
|
|
3
|
+
/** Angular twin of `expo-audio`'s `useAudioStream` */
|
|
4
|
+
export declare function injectAudioStream(getOptions: () => IUseAudioStreamOptions): Signal<IAudioStreamResult>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { computed, DestroyRef, effect, inject, } from '@angular/core';
|
|
2
|
+
import { createResourceHook, createEventValueHook, } from '@symbiote-native/angular';
|
|
3
|
+
import { createAudioStreamHooks } from '../core/audio-stream-hooks.js';
|
|
4
|
+
import { toAudioStreamKey } from '../core/audio-stream-subscription.js';
|
|
5
|
+
import { runAudioStreamBufferEffect } from '../core/audio-stream-lifecycle.js';
|
|
6
|
+
const { useStreamResource, useStreamStatus } = createAudioStreamHooks(createResourceHook, createEventValueHook);
|
|
7
|
+
/** Angular twin of `expo-audio`'s `useAudioStream` */
|
|
8
|
+
export function injectAudioStream(getOptions) {
|
|
9
|
+
const destroyRef = inject(DestroyRef);
|
|
10
|
+
const stream = useStreamResource(() => [toAudioStreamKey(getOptions())]);
|
|
11
|
+
const status = useStreamStatus(stream);
|
|
12
|
+
runAudioStreamBufferEffect(stream, () => getOptions().onBuffer, effect, cleanup => destroyRef.onDestroy(cleanup));
|
|
13
|
+
return computed(() => ({
|
|
14
|
+
stream: stream(),
|
|
15
|
+
isStreaming: status().isStreaming,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
@@ -12,7 +12,7 @@ export async function setAudioModeAsync(mode) {
|
|
|
12
12
|
: {
|
|
13
13
|
shouldPlayInBackground: mode.shouldPlayInBackground,
|
|
14
14
|
shouldRouteThroughEarpiece: mode.shouldRouteThroughEarpiece,
|
|
15
|
-
interruptionMode: mode.interruptionMode,
|
|
15
|
+
interruptionMode: mode.interruptionMode ?? mode.interruptionModeAndroid,
|
|
16
16
|
allowsBackgroundRecording: mode.allowsBackgroundRecording,
|
|
17
17
|
playsInSilentMode: mode.playsInSilentMode,
|
|
18
18
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type AudioPlayer } from './audio-player';
|
|
2
|
+
import type { IAudioPlayerOptions, IAudioSource } from './types';
|
|
3
|
+
export declare function createAudioPlayerController(): {
|
|
4
|
+
resolve: (source: IAudioSource, options: IAudioPlayerOptions) => AudioPlayer;
|
|
5
|
+
flushDispose: () => void;
|
|
6
|
+
dispose: () => void;
|
|
7
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createJsonKeyedResourceController } from './json-keyed-resource-controller.js';
|
|
2
|
+
import { createAudioPlayer } from './audio-player.js';
|
|
3
|
+
export function createAudioPlayerController() {
|
|
4
|
+
const controller = createJsonKeyedResourceController(key => createAudioPlayer(key.source, key.options), player => player.remove());
|
|
5
|
+
return {
|
|
6
|
+
resolve: (source, options) => controller.resolve({ source, options }),
|
|
7
|
+
flushDispose: controller.flushDispose,
|
|
8
|
+
dispose: controller.dispose,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Platform } from 'expo-modules-core';
|
|
2
2
|
import { expoAudio } from './native-module.js';
|
|
3
|
-
import { resolveSource } from './resolve-source.js';
|
|
3
|
+
import { resolveSource, resolveSourceWithDownload } from './resolve-source.js';
|
|
4
4
|
// Upstream shims these two methods onto `AudioModule.AudioPlayer.prototype` at module load
|
|
5
5
|
// (ExpoAudio.ts). We do the equivalent by subclassing instead of monkey-patching the native
|
|
6
6
|
// class's prototype — same shape packages/media-library/src/next/asset.ts uses for its own
|
|
@@ -25,7 +25,19 @@ export class AudioPlayer extends expoAudio.AudioPlayer {
|
|
|
25
25
|
* upstream's `useReleasingSharedObject`.
|
|
26
26
|
*/
|
|
27
27
|
export function createAudioPlayer(source = null, options = {}) {
|
|
28
|
-
const { updateInterval = 500, keepAudioSessionActive = false, preferredForwardBufferDuration = 0, } = options;
|
|
29
|
-
const
|
|
30
|
-
|
|
28
|
+
const { updateInterval = 500, downloadFirst = false, keepAudioSessionActive = false, preferredForwardBufferDuration = 0, } = options;
|
|
29
|
+
const player = new AudioPlayer(downloadFirst ? null : resolveSource(source), updateInterval, keepAudioSessionActive, preferredForwardBufferDuration);
|
|
30
|
+
if (downloadFirst && source) {
|
|
31
|
+
resolveSourceWithDownload(source)
|
|
32
|
+
.then(resolved => {
|
|
33
|
+
if (resolved)
|
|
34
|
+
player.replace(resolved);
|
|
35
|
+
})
|
|
36
|
+
.catch(() => {
|
|
37
|
+
const fallback = resolveSource(source);
|
|
38
|
+
if (fallback)
|
|
39
|
+
player.replace(fallback);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return player;
|
|
31
43
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IResourceController } from '@symbiote-native/engine';
|
|
2
|
+
import { type AudioPlaylist } from './audio-playlist';
|
|
3
|
+
import type { IAudioPlaylistOptions } from './types';
|
|
4
|
+
export declare function createAudioPlaylistController(): IResourceController<IAudioPlaylistOptions, AudioPlaylist>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createJsonKeyedResourceController } from './json-keyed-resource-controller.js';
|
|
2
|
+
import { createAudioPlaylist } from './audio-playlist.js';
|
|
3
|
+
export function createAudioPlaylistController() {
|
|
4
|
+
return createJsonKeyedResourceController(createAudioPlaylist, playlist => playlist.destroy());
|
|
5
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IResourceController } from '@symbiote-native/engine';
|
|
2
|
+
import { type AudioRecorder } from './audio-recorder';
|
|
3
|
+
import type { IRecordingOptions } from './types';
|
|
4
|
+
export declare function createAudioRecorderController(): IResourceController<IRecordingOptions, AudioRecorder>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createJsonKeyedResourceController } from './json-keyed-resource-controller.js';
|
|
2
|
+
import { createAudioRecorder } from './audio-recorder.js';
|
|
3
|
+
export function createAudioRecorderController() {
|
|
4
|
+
return createJsonKeyedResourceController(createAudioRecorder, recorder => recorder.release());
|
|
5
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IRecorderState } from './types';
|
|
2
|
+
export type IPollableRecorder = {
|
|
3
|
+
getStatus: () => IRecorderState;
|
|
4
|
+
};
|
|
5
|
+
export declare function pollRecorderState(recorder: IPollableRecorder, interval: number, update: (updater: (prev: IRecorderState) => IRecorderState) => void): () => void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { shouldUpdateRecorderState } from './audio-recorder-state.js';
|
|
2
|
+
// Ported from `useAudioRecorderState`'s `setInterval` body; `update` matches every adapter's
|
|
3
|
+
// own state setter (`useState`, Solid's `createSignal`, a plain `(prev) => next` writer, ...)
|
|
4
|
+
export function pollRecorderState(recorder, interval, update) {
|
|
5
|
+
const id = setInterval(() => {
|
|
6
|
+
const next = recorder.getStatus();
|
|
7
|
+
update(prev => (shouldUpdateRecorderState(prev, next) ? next : prev));
|
|
8
|
+
}, interval);
|
|
9
|
+
return () => clearInterval(id);
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const METERING_EPSILON = 0.1;
|
|
2
|
+
const DURATION_EPSILON_MS = 50;
|
|
3
|
+
function meteringChanged(prev, next) {
|
|
4
|
+
if ((prev.metering === undefined) !== (next.metering === undefined))
|
|
5
|
+
return true;
|
|
6
|
+
if (prev.metering === undefined || next.metering === undefined)
|
|
7
|
+
return false;
|
|
8
|
+
return Math.abs(prev.metering - next.metering) > METERING_EPSILON;
|
|
9
|
+
}
|
|
10
|
+
// Ported from `useAudioRecorderState`'s poll-diff `setState` body
|
|
11
|
+
export function shouldUpdateRecorderState(prev, next) {
|
|
12
|
+
return (prev.canRecord !== next.canRecord ||
|
|
13
|
+
prev.isRecording !== next.isRecording ||
|
|
14
|
+
prev.mediaServicesDidReset !== next.mediaServicesDidReset ||
|
|
15
|
+
prev.url !== next.url ||
|
|
16
|
+
Math.abs(prev.durationMillis - next.durationMillis) > DURATION_EPSILON_MS ||
|
|
17
|
+
meteringChanged(prev, next));
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RECORDING_STATUS_UPDATE } from './types';
|
|
2
|
+
import type { IRecordingStatus } from './types';
|
|
3
|
+
export type IStatusRecorder = {
|
|
4
|
+
addListener: (event: typeof RECORDING_STATUS_UPDATE, listener: (status: IRecordingStatus) => void) => {
|
|
5
|
+
remove: () => void;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export declare function subscribeRecordingStatus(recorder: IStatusRecorder, statusListener: ((status: IRecordingStatus) => void) | undefined): () => void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { RECORDING_STATUS_UPDATE } from './types.js';
|
|
2
|
+
import { subscribeSingleEvent } from './subscribe-single-event.js';
|
|
3
|
+
// Ported from `useAudioRecorder`'s status-listener `useEffect` body
|
|
4
|
+
export function subscribeRecordingStatus(recorder, statusListener) {
|
|
5
|
+
return subscribeSingleEvent(recorder, RECORDING_STATUS_UPDATE, statusListener);
|
|
6
|
+
}
|
|
@@ -3,3 +3,5 @@ import type { IRecordingOptions } from './types';
|
|
|
3
3
|
export declare class AudioRecorder extends expoAudio.AudioRecorder {
|
|
4
4
|
prepareToRecordAsync(options?: Partial<IRecordingOptions>): Promise<void>;
|
|
5
5
|
}
|
|
6
|
+
/** Does not release automatically, same lifecycle note as `createAudioPlayer` */
|
|
7
|
+
export declare function createAudioRecorder(options: IRecordingOptions): AudioRecorder;
|
|
@@ -38,3 +38,7 @@ export class AudioRecorder extends expoAudio.AudioRecorder {
|
|
|
38
38
|
return super.prepareToRecordAsync(processedOptions);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
/** Does not release automatically, same lifecycle note as `createAudioPlayer` */
|
|
42
|
+
export function createAudioRecorder(options) {
|
|
43
|
+
return new AudioRecorder(createRecordingOptions(options));
|
|
44
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AUDIO_SAMPLE_UPDATE } from './types';
|
|
2
|
+
import type { IAudioSample } from './types';
|
|
3
|
+
export type IAudioSamplePlayer = {
|
|
4
|
+
isAudioSamplingSupported: boolean;
|
|
5
|
+
setAudioSamplingEnabled: (enabled: boolean) => void;
|
|
6
|
+
addListener: (event: typeof AUDIO_SAMPLE_UPDATE, listener: (data: IAudioSample) => void) => {
|
|
7
|
+
remove: () => void;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
/** Ported from `expo-audio`'s `useAudioSampleListener` (its `useEffect` body factored out) */
|
|
11
|
+
export declare function subscribeAudioSampleListener(player: IAudioSamplePlayer, listener: (data: IAudioSample) => void): () => void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AUDIO_SAMPLE_UPDATE } from './types.js';
|
|
2
|
+
/** Ported from `expo-audio`'s `useAudioSampleListener` (its `useEffect` body factored out) */
|
|
3
|
+
export function subscribeAudioSampleListener(player, listener) {
|
|
4
|
+
if (!player.isAudioSamplingSupported)
|
|
5
|
+
return () => { };
|
|
6
|
+
player.setAudioSamplingEnabled(true);
|
|
7
|
+
const subscription = player.addListener(AUDIO_SAMPLE_UPDATE, listener);
|
|
8
|
+
return () => subscription.remove();
|
|
9
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IResourceController } from '@symbiote-native/engine';
|
|
2
|
+
import { type AudioStream } from './audio-stream';
|
|
3
|
+
import type { IAudioStreamOptions } from './types';
|
|
4
|
+
export declare function createAudioStreamController(): IResourceController<IAudioStreamOptions, AudioStream>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createJsonKeyedResourceController } from './json-keyed-resource-controller.js';
|
|
2
|
+
import { createAudioStream } from './audio-stream.js';
|
|
3
|
+
export function createAudioStreamController() {
|
|
4
|
+
return createJsonKeyedResourceController(createAudioStream, stream => stream.release());
|
|
5
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IResourceController } from '@symbiote-native/engine';
|
|
2
|
+
import { AUDIO_STREAM_STATUS } from './types';
|
|
3
|
+
import type { AudioStream } from './audio-stream';
|
|
4
|
+
import type { IAudioStreamOptions, IAudioStreamStatus } from './types';
|
|
5
|
+
export declare function createAudioStreamHooks<TResourceBox, TStatusBox>(createResourceHook: (createController: () => IResourceController<IAudioStreamOptions, AudioStream>) => (getArgs: () => [IAudioStreamOptions]) => TResourceBox, createEventValueHook: (event: typeof AUDIO_STREAM_STATUS, getValue: (source: AudioStream) => IAudioStreamStatus) => (getSource: () => AudioStream) => TStatusBox): {
|
|
6
|
+
useStreamResource: (getArgs: () => [IAudioStreamOptions]) => TResourceBox;
|
|
7
|
+
useStreamStatus: (getSource: () => AudioStream) => TStatusBox;
|
|
8
|
+
};
|