@symbiote-native/audio 0.1.1 → 0.1.3

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.
Files changed (280) hide show
  1. package/README.md +96 -52
  2. package/build/angular/index.d.ts +8 -0
  3. package/build/angular/index.js +8 -3
  4. package/build/angular/inject-audio-player-status.d.ts +4 -0
  5. package/build/angular/inject-audio-player-status.js +4 -0
  6. package/build/angular/inject-audio-player.d.ts +4 -0
  7. package/build/angular/inject-audio-player.js +8 -0
  8. package/build/angular/inject-audio-playlist-status.d.ts +3 -0
  9. package/build/angular/inject-audio-playlist-status.js +4 -0
  10. package/build/angular/inject-audio-playlist.d.ts +5 -0
  11. package/build/angular/inject-audio-playlist.js +7 -0
  12. package/build/angular/inject-audio-recorder-state.d.ts +5 -0
  13. package/build/angular/inject-audio-recorder-state.js +10 -0
  14. package/build/angular/inject-audio-recorder.d.ts +5 -0
  15. package/build/angular/inject-audio-recorder.js +13 -0
  16. package/build/angular/inject-audio-sample-listener.d.ts +4 -0
  17. package/build/angular/inject-audio-sample-listener.js +7 -0
  18. package/build/angular/inject-audio-stream.d.ts +4 -0
  19. package/build/angular/inject-audio-stream.js +17 -0
  20. package/build/core/audio-module.js +1 -1
  21. package/build/core/audio-player-controller.d.ts +7 -0
  22. package/build/core/audio-player-controller.js +10 -0
  23. package/build/core/audio-player.js +16 -4
  24. package/build/core/audio-playlist-controller.d.ts +4 -0
  25. package/build/core/audio-playlist-controller.js +5 -0
  26. package/build/core/audio-recorder-controller.d.ts +4 -0
  27. package/build/core/audio-recorder-controller.js +5 -0
  28. package/build/core/audio-recorder-polling.d.ts +5 -0
  29. package/build/core/audio-recorder-polling.js +10 -0
  30. package/build/core/audio-recorder-state.d.ts +2 -0
  31. package/build/core/audio-recorder-state.js +18 -0
  32. package/build/core/audio-recorder-status.d.ts +8 -0
  33. package/build/core/audio-recorder-status.js +6 -0
  34. package/build/core/audio-recorder.d.ts +2 -0
  35. package/build/core/audio-recorder.js +4 -0
  36. package/build/core/audio-sample-listener.d.ts +11 -0
  37. package/build/core/audio-sample-listener.js +9 -0
  38. package/build/core/audio-stream-controller.d.ts +4 -0
  39. package/build/core/audio-stream-controller.js +5 -0
  40. package/build/core/audio-stream-hooks.d.ts +8 -0
  41. package/build/core/audio-stream-hooks.js +13 -0
  42. package/build/core/audio-stream-lifecycle.d.ts +3 -0
  43. package/build/core/audio-stream-lifecycle.js +9 -0
  44. package/build/core/audio-stream-subscription.d.ts +10 -0
  45. package/build/core/audio-stream-subscription.js +12 -0
  46. package/build/core/audio-stream.d.ts +9 -10
  47. package/build/core/audio-stream.js +1 -9
  48. package/build/core/index.d.ts +14 -2
  49. package/build/core/index.js +9 -1
  50. package/build/core/json-keyed-resource-controller.d.ts +2 -0
  51. package/build/core/json-keyed-resource-controller.js +5 -0
  52. package/build/core/resolve-source.d.ts +2 -7
  53. package/build/core/resolve-source.js +69 -15
  54. package/build/core/subscribe-single-event.d.ts +5 -0
  55. package/build/core/subscribe-single-event.js +8 -0
  56. package/build/core/types.d.ts +11 -5
  57. package/build/react/index.d.ts +9 -0
  58. package/build/react/index.js +9 -0
  59. package/build/react/use-audio-player-status.d.ts +4 -0
  60. package/build/react/use-audio-player-status.js +4 -0
  61. package/build/react/use-audio-player.d.ts +3 -0
  62. package/build/react/use-audio-player.js +7 -0
  63. package/build/react/use-audio-playlist-status.d.ts +3 -0
  64. package/build/react/use-audio-playlist-status.js +4 -0
  65. package/build/react/use-audio-playlist.d.ts +4 -0
  66. package/build/react/use-audio-playlist.js +7 -0
  67. package/build/react/use-audio-recorder-state.d.ts +4 -0
  68. package/build/react/use-audio-recorder-state.js +9 -0
  69. package/build/react/use-audio-recorder.d.ts +4 -0
  70. package/build/react/use-audio-recorder.js +11 -0
  71. package/build/react/use-audio-sample-listener.d.ts +4 -0
  72. package/build/react/use-audio-sample-listener.js +6 -0
  73. package/build/react/use-audio-stream.d.ts +3 -0
  74. package/build/react/use-audio-stream.js +14 -0
  75. package/build/solid/index.d.ts +9 -0
  76. package/build/solid/index.js +9 -0
  77. package/build/solid/use-audio-player-status.d.ts +4 -0
  78. package/build/solid/use-audio-player-status.js +4 -0
  79. package/build/solid/use-audio-player.d.ts +5 -0
  80. package/build/solid/use-audio-player.js +7 -0
  81. package/build/solid/use-audio-playlist-status.d.ts +3 -0
  82. package/build/solid/use-audio-playlist-status.js +4 -0
  83. package/build/solid/use-audio-playlist.d.ts +5 -0
  84. package/build/solid/use-audio-playlist.js +7 -0
  85. package/build/solid/use-audio-recorder-state.d.ts +5 -0
  86. package/build/solid/use-audio-recorder-state.js +9 -0
  87. package/build/solid/use-audio-recorder.d.ts +5 -0
  88. package/build/solid/use-audio-recorder.js +13 -0
  89. package/build/solid/use-audio-sample-listener.d.ts +4 -0
  90. package/build/solid/use-audio-sample-listener.js +8 -0
  91. package/build/solid/use-audio-stream.d.ts +4 -0
  92. package/build/solid/use-audio-stream.js +16 -0
  93. package/build/svelte/index.d.ts +9 -0
  94. package/build/svelte/index.js +9 -0
  95. package/build/svelte/use-audio-player-status.svelte.d.ts +6 -0
  96. package/build/svelte/use-audio-player-status.svelte.js +4 -0
  97. package/build/svelte/use-audio-player.svelte.d.ts +6 -0
  98. package/build/svelte/use-audio-player.svelte.js +8 -0
  99. package/build/svelte/use-audio-playlist-status.svelte.d.ts +5 -0
  100. package/build/svelte/use-audio-playlist-status.svelte.js +4 -0
  101. package/build/svelte/use-audio-playlist.svelte.d.ts +6 -0
  102. package/build/svelte/use-audio-playlist.svelte.js +8 -0
  103. package/build/svelte/use-audio-recorder-state.svelte.d.ts +7 -0
  104. package/build/svelte/use-audio-recorder-state.svelte.js +14 -0
  105. package/build/svelte/use-audio-recorder.svelte.d.ts +6 -0
  106. package/build/svelte/use-audio-recorder.svelte.js +11 -0
  107. package/build/svelte/use-audio-sample-listener.svelte.d.ts +4 -0
  108. package/build/svelte/use-audio-sample-listener.svelte.js +5 -0
  109. package/build/svelte/use-audio-stream.svelte.d.ts +4 -0
  110. package/build/svelte/use-audio-stream.svelte.js +29 -0
  111. package/build/vue/index.d.ts +9 -0
  112. package/build/vue/index.js +9 -0
  113. package/build/vue/use-audio-player-status.d.ts +4 -0
  114. package/build/vue/use-audio-player-status.js +4 -0
  115. package/build/vue/use-audio-player.d.ts +5 -0
  116. package/build/vue/use-audio-player.js +8 -0
  117. package/build/vue/use-audio-playlist-status.d.ts +3 -0
  118. package/build/vue/use-audio-playlist-status.js +4 -0
  119. package/build/vue/use-audio-playlist.d.ts +5 -0
  120. package/build/vue/use-audio-playlist.js +8 -0
  121. package/build/vue/use-audio-recorder-state.d.ts +5 -0
  122. package/build/vue/use-audio-recorder-state.js +12 -0
  123. package/build/vue/use-audio-recorder.d.ts +5 -0
  124. package/build/vue/use-audio-recorder.js +11 -0
  125. package/build/vue/use-audio-sample-listener.d.ts +4 -0
  126. package/build/vue/use-audio-sample-listener.js +11 -0
  127. package/build/vue/use-audio-stream.d.ts +4 -0
  128. package/build/vue/use-audio-stream.js +18 -0
  129. package/build-ngc/angular/index.d.ts +8 -0
  130. package/build-ngc/angular/index.js +8 -3
  131. package/build-ngc/angular/index.js.map +1 -1
  132. package/build-ngc/angular/inject-audio-player-status.d.ts +4 -0
  133. package/build-ngc/angular/inject-audio-player-status.js +5 -0
  134. package/build-ngc/angular/inject-audio-player-status.js.map +1 -0
  135. package/build-ngc/angular/inject-audio-player.d.ts +4 -0
  136. package/build-ngc/angular/inject-audio-player.js +9 -0
  137. package/build-ngc/angular/inject-audio-player.js.map +1 -0
  138. package/build-ngc/angular/inject-audio-playlist-status.d.ts +3 -0
  139. package/build-ngc/angular/inject-audio-playlist-status.js +5 -0
  140. package/build-ngc/angular/inject-audio-playlist-status.js.map +1 -0
  141. package/build-ngc/angular/inject-audio-playlist.d.ts +5 -0
  142. package/build-ngc/angular/inject-audio-playlist.js +8 -0
  143. package/build-ngc/angular/inject-audio-playlist.js.map +1 -0
  144. package/build-ngc/angular/inject-audio-recorder-state.d.ts +5 -0
  145. package/build-ngc/angular/inject-audio-recorder-state.js +12 -0
  146. package/build-ngc/angular/inject-audio-recorder-state.js.map +1 -0
  147. package/build-ngc/angular/inject-audio-recorder.d.ts +5 -0
  148. package/build-ngc/angular/inject-audio-recorder.js +14 -0
  149. package/build-ngc/angular/inject-audio-recorder.js.map +1 -0
  150. package/build-ngc/angular/inject-audio-sample-listener.d.ts +4 -0
  151. package/build-ngc/angular/inject-audio-sample-listener.js +8 -0
  152. package/build-ngc/angular/inject-audio-sample-listener.js.map +1 -0
  153. package/build-ngc/angular/inject-audio-stream.d.ts +4 -0
  154. package/build-ngc/angular/inject-audio-stream.js +18 -0
  155. package/build-ngc/angular/inject-audio-stream.js.map +1 -0
  156. package/build-ngc/core/audio-module.js +1 -1
  157. package/build-ngc/core/audio-module.js.map +1 -1
  158. package/build-ngc/core/audio-player-controller.d.ts +7 -0
  159. package/build-ngc/core/audio-player-controller.js +11 -0
  160. package/build-ngc/core/audio-player-controller.js.map +1 -0
  161. package/build-ngc/core/audio-player.js +16 -4
  162. package/build-ngc/core/audio-player.js.map +1 -1
  163. package/build-ngc/core/audio-playlist-controller.d.ts +4 -0
  164. package/build-ngc/core/audio-playlist-controller.js +6 -0
  165. package/build-ngc/core/audio-playlist-controller.js.map +1 -0
  166. package/build-ngc/core/audio-recorder-controller.d.ts +4 -0
  167. package/build-ngc/core/audio-recorder-controller.js +6 -0
  168. package/build-ngc/core/audio-recorder-controller.js.map +1 -0
  169. package/build-ngc/core/audio-recorder-polling.d.ts +5 -0
  170. package/build-ngc/core/audio-recorder-polling.js +11 -0
  171. package/build-ngc/core/audio-recorder-polling.js.map +1 -0
  172. package/build-ngc/core/audio-recorder-state.d.ts +2 -0
  173. package/build-ngc/core/audio-recorder-state.js +19 -0
  174. package/build-ngc/core/audio-recorder-state.js.map +1 -0
  175. package/build-ngc/core/audio-recorder-status.d.ts +8 -0
  176. package/build-ngc/core/audio-recorder-status.js +7 -0
  177. package/build-ngc/core/audio-recorder-status.js.map +1 -0
  178. package/build-ngc/core/audio-recorder.d.ts +2 -0
  179. package/build-ngc/core/audio-recorder.js +4 -0
  180. package/build-ngc/core/audio-recorder.js.map +1 -1
  181. package/build-ngc/core/audio-sample-listener.d.ts +11 -0
  182. package/build-ngc/core/audio-sample-listener.js +10 -0
  183. package/build-ngc/core/audio-sample-listener.js.map +1 -0
  184. package/build-ngc/core/audio-stream-controller.d.ts +4 -0
  185. package/build-ngc/core/audio-stream-controller.js +6 -0
  186. package/build-ngc/core/audio-stream-controller.js.map +1 -0
  187. package/build-ngc/core/audio-stream-hooks.d.ts +8 -0
  188. package/build-ngc/core/audio-stream-hooks.js +14 -0
  189. package/build-ngc/core/audio-stream-hooks.js.map +1 -0
  190. package/build-ngc/core/audio-stream-lifecycle.d.ts +3 -0
  191. package/build-ngc/core/audio-stream-lifecycle.js +10 -0
  192. package/build-ngc/core/audio-stream-lifecycle.js.map +1 -0
  193. package/build-ngc/core/audio-stream-subscription.d.ts +10 -0
  194. package/build-ngc/core/audio-stream-subscription.js +13 -0
  195. package/build-ngc/core/audio-stream-subscription.js.map +1 -0
  196. package/build-ngc/core/audio-stream.d.ts +9 -10
  197. package/build-ngc/core/audio-stream.js +1 -9
  198. package/build-ngc/core/audio-stream.js.map +1 -1
  199. package/build-ngc/core/index.d.ts +14 -2
  200. package/build-ngc/core/index.js +9 -1
  201. package/build-ngc/core/index.js.map +1 -1
  202. package/build-ngc/core/json-keyed-resource-controller.d.ts +2 -0
  203. package/build-ngc/core/json-keyed-resource-controller.js +6 -0
  204. package/build-ngc/core/json-keyed-resource-controller.js.map +1 -0
  205. package/build-ngc/core/resolve-source.d.ts +2 -7
  206. package/build-ngc/core/resolve-source.js +69 -15
  207. package/build-ngc/core/resolve-source.js.map +1 -1
  208. package/build-ngc/core/subscribe-single-event.d.ts +5 -0
  209. package/build-ngc/core/subscribe-single-event.js +9 -0
  210. package/build-ngc/core/subscribe-single-event.js.map +1 -0
  211. package/build-ngc/core/types.d.ts +11 -5
  212. package/build-ngc/core/types.js.map +1 -1
  213. package/package.json +24 -23
  214. package/src/angular/index.ts +8 -3
  215. package/src/angular/inject-audio-player-status.ts +11 -0
  216. package/src/angular/inject-audio-player.ts +17 -0
  217. package/src/angular/inject-audio-playlist-status.ts +11 -0
  218. package/src/angular/inject-audio-playlist.ts +16 -0
  219. package/src/angular/inject-audio-recorder-state.ts +21 -0
  220. package/src/angular/inject-audio-recorder.ts +24 -0
  221. package/src/angular/inject-audio-sample-listener.ts +13 -0
  222. package/src/angular/inject-audio-stream.ts +41 -0
  223. package/src/core/audio-module.ts +2 -1
  224. package/src/core/audio-player-controller.ts +25 -0
  225. package/src/core/audio-player.ts +17 -4
  226. package/src/core/audio-playlist-controller.ts +14 -0
  227. package/src/core/audio-recorder-controller.ts +14 -0
  228. package/src/core/audio-recorder-polling.ts +20 -0
  229. package/src/core/audio-recorder-state.ts +26 -0
  230. package/src/core/audio-recorder-status.ts +22 -0
  231. package/src/core/audio-recorder.ts +5 -0
  232. package/src/core/audio-sample-listener.ts +23 -0
  233. package/src/core/audio-stream-controller.ts +14 -0
  234. package/src/core/audio-stream-hooks.ts +31 -0
  235. package/src/core/audio-stream-lifecycle.ts +19 -0
  236. package/src/core/audio-stream-subscription.ts +28 -0
  237. package/src/core/audio-stream.ts +11 -10
  238. package/src/core/index.ts +20 -1
  239. package/src/core/json-keyed-resource-controller.ts +16 -0
  240. package/src/core/resolve-source.ts +80 -18
  241. package/src/core/subscribe-single-event.ts +17 -0
  242. package/src/core/types.ts +13 -14
  243. package/src/react/index.ts +9 -0
  244. package/src/react/use-audio-player-status.ts +11 -0
  245. package/src/react/use-audio-player.ts +14 -0
  246. package/src/react/use-audio-playlist-status.ts +11 -0
  247. package/src/react/use-audio-playlist.ts +13 -0
  248. package/src/react/use-audio-recorder-state.ts +23 -0
  249. package/src/react/use-audio-recorder.ts +23 -0
  250. package/src/react/use-audio-sample-listener.ts +15 -0
  251. package/src/react/use-audio-stream.ts +39 -0
  252. package/src/solid/index.ts +9 -0
  253. package/src/solid/use-audio-player-status.ts +11 -0
  254. package/src/solid/use-audio-player.ts +15 -0
  255. package/src/solid/use-audio-playlist-status.ts +11 -0
  256. package/src/solid/use-audio-playlist.ts +14 -0
  257. package/src/solid/use-audio-recorder-state.ts +18 -0
  258. package/src/solid/use-audio-recorder.ts +22 -0
  259. package/src/solid/use-audio-sample-listener.ts +14 -0
  260. package/src/solid/use-audio-stream.ts +34 -0
  261. package/src/svelte/index.ts +9 -0
  262. package/src/svelte/svelte-ambient.d.ts +3 -0
  263. package/src/svelte/svelte-compile.test-helper.ts +7 -0
  264. package/src/svelte/use-audio-player-status.svelte.ts +11 -0
  265. package/src/svelte/use-audio-player.svelte.ts +20 -0
  266. package/src/svelte/use-audio-playlist-status.svelte.ts +11 -0
  267. package/src/svelte/use-audio-playlist.svelte.ts +19 -0
  268. package/src/svelte/use-audio-recorder-state.svelte.ts +29 -0
  269. package/src/svelte/use-audio-recorder.svelte.ts +25 -0
  270. package/src/svelte/use-audio-sample-listener.svelte.ts +11 -0
  271. package/src/svelte/use-audio-stream.svelte.ts +44 -0
  272. package/src/vue/index.ts +9 -0
  273. package/src/vue/use-audio-player-status.ts +11 -0
  274. package/src/vue/use-audio-player.ts +19 -0
  275. package/src/vue/use-audio-playlist-status.ts +11 -0
  276. package/src/vue/use-audio-playlist.ts +15 -0
  277. package/src/vue/use-audio-recorder-state.ts +21 -0
  278. package/src/vue/use-audio-recorder.ts +30 -0
  279. package/src/vue/use-audio-sample-listener.ts +22 -0
  280. 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
- A wrapper package for [SymbioteNative](../../README.md) that makes
4
- [`expo-audio`](https://github.com/expo/expo/tree/main/packages/expo-audio) usable from **every**
5
- adapter — React, Vue, Svelte, Solid, and Angular. `AudioPlayer`, `AudioRecorder`, `AudioPlaylist`,
6
- and `AudioStream` are JSI-backed `SharedObject` instances — per-instance native objects with real
7
- state and methods, not one-shot functions — so there is no hook/composable/service wrapper here
8
- either: the React, Vue, Svelte, Solid, and Angular entry points are plain re-exports of the same
9
- `core`, and each adapter's own lifecycle code (the equivalent of upstream's `useAudioPlayer`)
10
- decides when to create one and when to call `.remove()` / `.destroy()`.
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 — since
27
- this package has an optional, policy-sensitive Android bundle — asks at the end whether to grant
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 — installing and wiring native autolinking by hand)</summary>
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 — never install either yourself, and never add the `expo` meta-package to your project
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 — see
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 — `NSMicrophoneUsageDescription` in `Info.plist`, wired automatically by
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 — override the default text by adding the key yourself first. Same aggregator
52
- also adds `UIBackgroundModes: audio` (`ios.infoPlistArrayKeys`) — upstream's own `withAudio.ts`
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 — `RECORD_AUDIO` / `MODIFY_AUDIO_SETTINGS` already ship in `expo-audio`'s own
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
- — matching upstream's own `withAudio.ts` config plugin, whose `enableBackgroundPlayback` defaults
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` — requesting the microphone
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 — opt-in, asked for you.** `new --audio`/`add --audio` above already ask,
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 — safe to run again even if already granted.
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 — source resolution on `replace()`, the
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 — export * from '../core'
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 and real-time PCM streaming follow the same `create*` + `addListener` shape:
148
- `createAudioPlaylist({ sources: [...], loop: 'all' })` and `createAudioStream()` — the stream
149
- factory takes no `onBuffer` callback (unlike upstream's `useAudioStream` hook); subscribe with
150
- `stream.addListener(AUDIO_STREAM_BUFFER, ...)` after `stream.start()`, same as every other event
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 — `@symbiote-native/audio/react`, `/vue`, `/svelte`,
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 — see `src/core/types.ts`.
220
-
221
- ## Deliberately not ported
222
-
223
- - **`useAudioPlayer` / `useAudioPlayerStatus` / `useAudioSampleListener` / `useAudioRecorder` /
224
- `useAudioRecorderState` / `useAudioPlaylist` / `useAudioPlaylistStatus` / `useAudioStream`** —
225
- React hooks, framework-specific by construction. `createAudioPlayer` / `createAudioPlaylist` /
226
- `createAudioStream` are the framework-agnostic equivalents each already exposes (upstream's own
227
- hooks are thin wrappers over these plus `useReleasingSharedObject` for cleanup-on-unmount); each
228
- adapter's own lifecycle wrapper is where a hook/composable/service belongs, per
229
- `<components_split_logic_view_lifecycle>` in the root project CLAUDE.md. None ship yet.
230
- - **The `Asset`-instance form of `AudioSource`, and the `downloadFirst` player option.** Upstream
231
- resolves both through `expo-asset` (`Asset.fromModule` / `Asset.downloadAsync`), which depends
232
- on `expo-constants` and peers on the `expo` meta-package — this project never depends on `expo`
233
- (root CLAUDE.md's dependency-scope invariant, and the `symbiote-expo-native-module` skill §1).
234
- The plain `number` form (`require('./song.mp3')`) **is** supported — `resolveSource`/
235
- `resolveSources` resolve it through RN's own generic `resolveAssetSource`, wired by
236
- `bootstrapHost`, no `expo-asset` needed.
237
- - **`interruptionModeAndroid`** on `IAudioMode` — upstream marks it `@deprecated`, superseded by
238
- the cross-platform `interruptionMode`.
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`) — this project targets iOS + Android only.
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 — every class here is a `SharedObject` with no visual
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`) — no `installFabric()`, no ViewConfig.
293
+ resolve-source.test.ts`) - no `installFabric()`, no ViewConfig.
@@ -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';
@@ -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 resolvedSource = resolveSource(source);
30
- return new AudioPlayer(resolvedSource, updateInterval, keepAudioSessionActive, preferredForwardBufferDuration);
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,2 @@
1
+ import type { IRecorderState } from './types';
2
+ export declare function shouldUpdateRecorderState(prev: IRecorderState, next: IRecorderState): boolean;
@@ -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
+ };