expo-audio 55.0.6 → 55.0.8

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 (110) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/android/build.gradle +4 -2
  3. package/android/src/main/java/expo/modules/audio/AudioModule.kt +274 -45
  4. package/android/src/main/java/expo/modules/audio/AudioPlayer.kt +78 -67
  5. package/android/src/main/java/expo/modules/audio/AudioPlaylist.kt +347 -0
  6. package/android/src/main/java/expo/modules/audio/AudioPreloadManager.kt +55 -0
  7. package/android/src/main/java/expo/modules/audio/AudioRecords.kt +8 -1
  8. package/android/src/main/java/expo/modules/audio/Playable.kt +48 -0
  9. package/build/Audio.types.d.ts +123 -2
  10. package/build/Audio.types.d.ts.map +1 -1
  11. package/build/Audio.types.js.map +1 -1
  12. package/build/AudioEventKeys.d.ts +2 -0
  13. package/build/AudioEventKeys.d.ts.map +1 -1
  14. package/build/AudioEventKeys.js +2 -0
  15. package/build/AudioEventKeys.js.map +1 -1
  16. package/build/AudioModule.types.d.ts +142 -4
  17. package/build/AudioModule.types.d.ts.map +1 -1
  18. package/build/AudioModule.types.js.map +1 -1
  19. package/build/AudioModule.web.d.ts +8 -1
  20. package/build/AudioModule.web.d.ts.map +1 -1
  21. package/build/AudioModule.web.js +48 -2
  22. package/build/AudioModule.web.js.map +1 -1
  23. package/build/AudioPlayer.web.d.ts +6 -1
  24. package/build/AudioPlayer.web.d.ts.map +1 -1
  25. package/build/AudioPlayer.web.js +64 -27
  26. package/build/AudioPlayer.web.js.map +1 -1
  27. package/build/AudioPlaylist.web.d.ts +58 -0
  28. package/build/AudioPlaylist.web.d.ts.map +1 -0
  29. package/build/AudioPlaylist.web.js +449 -0
  30. package/build/AudioPlaylist.web.js.map +1 -0
  31. package/build/AudioRecorder.web.d.ts +1 -1
  32. package/build/AudioUtils.web.d.ts +7 -2
  33. package/build/AudioUtils.web.d.ts.map +1 -1
  34. package/build/AudioUtils.web.js +7 -2
  35. package/build/AudioUtils.web.js.map +1 -1
  36. package/build/ExpoAudio.d.ts +121 -2
  37. package/build/ExpoAudio.d.ts.map +1 -1
  38. package/build/ExpoAudio.js +157 -6
  39. package/build/ExpoAudio.js.map +1 -1
  40. package/build/ExpoAudio.web.d.ts +8 -1
  41. package/build/ExpoAudio.web.d.ts.map +1 -1
  42. package/build/ExpoAudio.web.js +52 -57
  43. package/build/ExpoAudio.web.js.map +1 -1
  44. package/build/utils/resolveSource.d.ts +1 -0
  45. package/build/utils/resolveSource.d.ts.map +1 -1
  46. package/build/utils/resolveSource.js +9 -11
  47. package/build/utils/resolveSource.js.map +1 -1
  48. package/expo-module.config.json +1 -1
  49. package/ios/AudioComponentRegistry.swift +61 -0
  50. package/ios/AudioModule.swift +209 -4
  51. package/ios/AudioPlayer.swift +21 -2
  52. package/ios/AudioPlaylist.swift +374 -0
  53. package/ios/AudioRecords.swift +6 -0
  54. package/ios/AudioSource.swift +3 -0
  55. package/ios/AudioUtils.swift +2 -15
  56. package/ios/MediaController.swift +9 -9
  57. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8-sources.jar +0 -0
  58. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8-sources.jar.md5 +1 -0
  59. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8-sources.jar.sha1 +1 -0
  60. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8-sources.jar.sha256 +1 -0
  61. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8-sources.jar.sha512 +1 -0
  62. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.aar +0 -0
  63. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.aar.md5 +1 -0
  64. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.aar.sha1 +1 -0
  65. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.aar.sha256 +1 -0
  66. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.aar.sha512 +1 -0
  67. package/local-maven-repo/expo/modules/audio/expo.modules.audio/{55.0.6/expo.modules.audio-55.0.6.module → 55.0.8/expo.modules.audio-55.0.8.module} +36 -22
  68. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.module.md5 +1 -0
  69. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.module.sha1 +1 -0
  70. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.module.sha256 +1 -0
  71. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.module.sha512 +1 -0
  72. package/local-maven-repo/expo/modules/audio/expo.modules.audio/{55.0.6/expo.modules.audio-55.0.6.pom → 55.0.8/expo.modules.audio-55.0.8.pom} +25 -1
  73. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.pom.md5 +1 -0
  74. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.pom.sha1 +1 -0
  75. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.pom.sha256 +1 -0
  76. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.8/expo.modules.audio-55.0.8.pom.sha512 +1 -0
  77. package/local-maven-repo/expo/modules/audio/expo.modules.audio/maven-metadata.xml +4 -4
  78. package/local-maven-repo/expo/modules/audio/expo.modules.audio/maven-metadata.xml.md5 +1 -1
  79. package/local-maven-repo/expo/modules/audio/expo.modules.audio/maven-metadata.xml.sha1 +1 -1
  80. package/local-maven-repo/expo/modules/audio/expo.modules.audio/maven-metadata.xml.sha256 +1 -1
  81. package/local-maven-repo/expo/modules/audio/expo.modules.audio/maven-metadata.xml.sha512 +1 -1
  82. package/package.json +2 -2
  83. package/src/Audio.types.ts +131 -2
  84. package/src/AudioEventKeys.ts +2 -0
  85. package/src/AudioModule.types.ts +173 -3
  86. package/src/AudioModule.web.ts +57 -3
  87. package/src/AudioPlayer.web.ts +79 -31
  88. package/src/AudioPlaylist.web.ts +529 -0
  89. package/src/AudioUtils.web.ts +10 -3
  90. package/src/ExpoAudio.ts +194 -7
  91. package/src/ExpoAudio.web.ts +71 -64
  92. package/src/utils/resolveSource.ts +10 -12
  93. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6-sources.jar +0 -0
  94. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6-sources.jar.md5 +0 -1
  95. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6-sources.jar.sha1 +0 -1
  96. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6-sources.jar.sha256 +0 -1
  97. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6-sources.jar.sha512 +0 -1
  98. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.aar +0 -0
  99. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.aar.md5 +0 -1
  100. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.aar.sha1 +0 -1
  101. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.aar.sha256 +0 -1
  102. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.aar.sha512 +0 -1
  103. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.module.md5 +0 -1
  104. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.module.sha1 +0 -1
  105. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.module.sha256 +0 -1
  106. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.module.sha512 +0 -1
  107. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.pom.md5 +0 -1
  108. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.pom.sha1 +0 -1
  109. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.pom.sha256 +0 -1
  110. package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.6/expo.modules.audio-55.0.6.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,20 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 55.0.8 — 2026-02-25
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 55.0.7 — 2026-02-20
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - [Web] Fix potential `nan` duration. ([#43268](https://github.com/expo/expo/pull/43268) by [@alanjhughes](https://github.com/alanjhughes))
22
+
23
+ ### 💡 Others
24
+
25
+ - [android] Make in-memory preload cache. ([#43293](https://github.com/expo/expo/pull/43293) by [@alanjhughes](https://github.com/alanjhughes))
26
+
13
27
  ## 55.0.6 — 2026-02-16
14
28
 
15
29
  ### 🎉 New features
@@ -20,6 +34,8 @@
20
34
  - [Web] Add support for media controls. ([#43150](https://github.com/expo/expo/pull/43150) by [@alanjhughes](https://github.com/alanjhughes))
21
35
  - [Web] Add support for selecting recording inputs. ([#43151](https://github.com/expo/expo/pull/43151) by [@alanjhughes](https://github.com/alanjhughes))
22
36
  - [Web] Add support for recording metering. ([#43152](https://github.com/expo/expo/pull/43152) by [@alanjhughes](https://github.com/alanjhughes))
37
+ - [Web] Enable `setIsAudioActiveAsync`. ([#43142](https://github.com/expo/expo/pull/43142) by [@alanjhughes](https://github.com/alanjhughes))
38
+ - Add support for preloading audio sources. ([#43063](https://github.com/expo/expo/pull/43063) by [@alanjhughes](https://github.com/alanjhughes))
23
39
 
24
40
  ### 🐛 Bug fixes
25
41
 
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'expo.modules.audio'
7
- version = '55.0.6'
7
+ version = '55.0.8'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.audio"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName "55.0.6"
13
+ versionName "55.0.8"
14
14
  }
15
15
 
16
16
  compileOptions {
@@ -24,12 +24,14 @@ repositories {
24
24
 
25
25
  dependencies {
26
26
  implementation 'androidx.appcompat:appcompat:1.7.1'
27
+ implementation 'androidx.core:core-ktx:1.15.0'
27
28
 
28
29
  def androidxMedia3Version = "1.8.0"
29
30
  implementation "androidx.media3:media3-session:$androidxMedia3Version"
30
31
  implementation "androidx.media3:media3-ui:$androidxMedia3Version"
31
32
  implementation "androidx.media3:media3-exoplayer:$androidxMedia3Version"
32
33
  implementation "androidx.media3:media3-exoplayer-dash:$androidxMedia3Version"
34
+ implementation "androidx.media3:media3-datasource:$androidxMedia3Version"
33
35
  implementation "androidx.media3:media3-datasource-okhttp:$androidxMedia3Version"
34
36
  implementation "androidx.media3:media3-exoplayer-hls:$androidxMedia3Version"
35
37
  implementation "androidx.media3:media3-exoplayer-smoothstreaming:$androidxMedia3Version"
@@ -17,7 +17,6 @@ import androidx.media3.common.C.CONTENT_TYPE_HLS
17
17
  import androidx.media3.common.C.CONTENT_TYPE_OTHER
18
18
  import androidx.media3.common.C.CONTENT_TYPE_SS
19
19
  import androidx.media3.common.MediaItem
20
- import androidx.media3.common.PlaybackParameters
21
20
  import androidx.media3.common.Player
22
21
  import androidx.media3.common.util.Util
23
22
  import androidx.media3.datasource.DataSource
@@ -35,13 +34,16 @@ import expo.modules.kotlin.functions.Coroutine
35
34
  import expo.modules.kotlin.functions.Queues
36
35
  import expo.modules.kotlin.modules.Module
37
36
  import expo.modules.kotlin.modules.ModuleDefinition
37
+ import kotlinx.coroutines.DelicateCoroutinesApi
38
+ import kotlinx.coroutines.Dispatchers
39
+ import kotlinx.coroutines.GlobalScope
38
40
  import kotlinx.coroutines.launch
39
41
  import kotlinx.coroutines.runBlocking
40
42
  import okhttp3.OkHttpClient
41
43
  import java.io.File
42
44
  import java.util.concurrent.ConcurrentHashMap
43
- import kotlin.math.min
44
45
 
46
+ @DelicateCoroutinesApi
45
47
  @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
46
48
  class AudioModule : Module() {
47
49
  private lateinit var audioManager: AudioManager
@@ -51,6 +53,7 @@ class AudioModule : Module() {
51
53
 
52
54
  private val players = ConcurrentHashMap<String, AudioPlayer>()
53
55
  private val recorders = ConcurrentHashMap<String, AudioRecorder>()
56
+ private val playlists = ConcurrentHashMap<String, AudioPlaylist>()
54
57
  private var shouldPlayInBackground = false
55
58
  private var audioEnabled = true
56
59
  private var shouldRouteThroughEarpiece = false
@@ -58,40 +61,41 @@ class AudioModule : Module() {
58
61
  private var interruptionMode: InterruptionMode? = null
59
62
  private var allowsBackgroundRecording = false
60
63
 
64
+ private val allPlayables: Sequence<Playable>
65
+ get() = players.values.asSequence() + playlists.values.asSequence()
66
+
61
67
  private var audioFocusRequest: AudioFocusRequest? = null
62
68
  private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
63
69
  appContext.mainQueue.launch {
64
70
  when (focusChange) {
65
71
  AudioManager.AUDIOFOCUS_LOSS -> {
66
72
  focusAcquired = false
67
- players.values.forEach { player ->
68
- player.ref.pause()
69
- }
73
+ allPlayables.forEach { it.pause() }
70
74
  }
71
75
 
72
76
  AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
73
77
  focusAcquired = false
74
- players.values.forEach { player ->
75
- if (player.ref.isPlaying) {
76
- player.isPaused = true
77
- player.ref.pause()
78
+ allPlayables.forEach { playable ->
79
+ if (playable.isPlaying) {
80
+ playable.isPaused = true
81
+ playable.pause()
78
82
  }
79
83
  }
80
84
  }
81
85
 
82
86
  AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
83
87
  if (interruptionMode == InterruptionMode.DUCK_OTHERS) {
84
- players.values.forEach { player ->
85
- if (player.previousVolume != player.ref.volume) {
86
- player.previousVolume = player.ref.volume
88
+ allPlayables.forEach { playable ->
89
+ if (playable.previousVolume != playable.volume) {
90
+ playable.previousVolume = playable.volume
87
91
  }
88
- player.ref.volume = player.previousVolume * 0.5f
92
+ playable.setVolume(playable.previousVolume * 0.5f)
89
93
  }
90
94
  } else {
91
- players.values.forEach { player ->
92
- if (player.ref.isPlaying) {
93
- player.isPaused = true
94
- player.ref.pause()
95
+ allPlayables.forEach { playable ->
96
+ if (playable.isPlaying) {
97
+ playable.isPaused = true
98
+ playable.pause()
95
99
  }
96
100
  }
97
101
  }
@@ -99,11 +103,11 @@ class AudioModule : Module() {
99
103
 
100
104
  AudioManager.AUDIOFOCUS_GAIN -> {
101
105
  focusAcquired = true
102
- players.values.forEach { player ->
103
- player.setVolume(player.previousVolume)
104
- if (player.isPaused) {
105
- player.isPaused = false
106
- player.ref.play()
106
+ allPlayables.forEach { playable ->
107
+ playable.setVolume(playable.previousVolume)
108
+ if (playable.isPaused) {
109
+ playable.isPaused = false
110
+ playable.play()
107
111
  }
108
112
  }
109
113
  }
@@ -112,7 +116,7 @@ class AudioModule : Module() {
112
116
  }
113
117
 
114
118
  private fun shouldReleaseFocus(): Boolean {
115
- return players.values.none { it.ref.isPlaying }
119
+ return allPlayables.none { it.isPlaying }
116
120
  }
117
121
 
118
122
  private fun requestAudioFocus() {
@@ -197,9 +201,9 @@ class AudioModule : Module() {
197
201
  if (!enabled) {
198
202
  releaseAudioFocus()
199
203
  runOnMain {
200
- players.values.forEach {
201
- if (it.ref.isPlaying) {
202
- it.ref.pause()
204
+ allPlayables.forEach {
205
+ if (it.isPlaying) {
206
+ it.pause()
203
207
  }
204
208
  }
205
209
  }
@@ -218,13 +222,35 @@ class AudioModule : Module() {
218
222
  Permissions.getPermissionsWithPermissionsManager(appContext.permissions, promise, Manifest.permission.RECORD_AUDIO)
219
223
  }
220
224
 
225
+ AsyncFunction("preload") Coroutine { source: AudioSource, _: Double ->
226
+ val uri = source.uri ?: return@Coroutine
227
+ val factory = when (uri.toUri().scheme) {
228
+ "http", "https" -> httpDataSourceFactory(source.headers)
229
+ else -> DefaultDataSource.Factory(context)
230
+ }
231
+ AudioPreloadManager.preload(uri, factory)
232
+ }
233
+
234
+ AsyncFunction("clearPreloadedSource") Coroutine { source: AudioSource ->
235
+ val uri = source.uri ?: return@Coroutine
236
+ AudioPreloadManager.clearSource(uri)
237
+ }
238
+
239
+ AsyncFunction("clearAllPreloadedSources") Coroutine { ->
240
+ AudioPreloadManager.clearAll()
241
+ }
242
+
243
+ AsyncFunction("getPreloadedSources") {
244
+ AudioPreloadManager.getPreloadedSources()
245
+ }
246
+
221
247
  OnActivityEntersBackground {
222
248
  if (!shouldPlayInBackground) {
223
249
  releaseAudioFocus()
224
- players.values.forEach { player ->
225
- if (player.ref.isPlaying) {
226
- player.isPaused = true
227
- player.ref.pause()
250
+ allPlayables.forEach { playable ->
251
+ if (playable.isPlaying) {
252
+ playable.isPaused = true
253
+ playable.pause()
228
254
  }
229
255
  }
230
256
  }
@@ -239,15 +265,14 @@ class AudioModule : Module() {
239
265
 
240
266
  OnActivityEntersForeground {
241
267
  if (!shouldPlayInBackground) {
242
- val hasPlayersToResume = players.values.any { it.isPaused }
243
- if (hasPlayersToResume) {
268
+ if (allPlayables.any { it.isPaused }) {
244
269
  requestAudioFocus()
245
270
  }
246
271
 
247
- players.values.forEach { player ->
248
- if (player.isPaused) {
249
- player.isPaused = false
250
- player.ref.play()
272
+ allPlayables.forEach { playable ->
273
+ if (playable.isPaused) {
274
+ playable.isPaused = false
275
+ playable.play()
251
276
  }
252
277
  }
253
278
  }
@@ -264,27 +289,34 @@ class AudioModule : Module() {
264
289
  }
265
290
 
266
291
  OnDestroy {
267
- appContext.mainQueue.launch {
292
+ GlobalScope.launch(Dispatchers.Main) {
268
293
  releaseAudioFocus()
269
294
  players.values.forEach {
270
295
  it.ref.stop()
271
296
  }
272
297
 
298
+ playlists.values.forEach {
299
+ it.ref.stop()
300
+ }
301
+
273
302
  recorders.values.forEach {
274
303
  it.stopRecording()
275
304
  }
305
+ AudioPreloadManager.clearAll()
276
306
  }
277
307
  }
278
308
 
279
309
  Class(AudioPlayer::class) {
280
- Constructor { source: AudioSource?, updateInterval: Double, keepAudioSessionActive: Boolean ->
310
+ Constructor { source: AudioSource?, updateInterval: Double, keepAudioSessionActive: Boolean, preferredForwardBufferDuration: Double ->
281
311
  val mediaSource = createMediaItem(source)
312
+ val bufferDurationMs = (preferredForwardBufferDuration * 1000).toLong()
282
313
  runOnMain {
283
314
  val player = AudioPlayer(
284
315
  context,
285
316
  appContext,
286
317
  mediaSource,
287
- updateInterval
318
+ updateInterval,
319
+ bufferDurationMs
288
320
  )
289
321
  player.onPlaybackStateChange = { isPlaying ->
290
322
  if (!isPlaying && shouldReleaseFocus()) {
@@ -455,9 +487,7 @@ class AudioModule : Module() {
455
487
 
456
488
  Function("setPlaybackRate") { player: AudioPlayer, rate: Float ->
457
489
  appContext.mainQueue.launch {
458
- val playbackRate = if (rate <= 0) 0.1f else min(rate, 2.0f)
459
- val pitch = if (player.preservesPitch) 1f else playbackRate
460
- player.ref.playbackParameters = PlaybackParameters(playbackRate, pitch)
490
+ player.setPlaybackRate(rate)
461
491
  }
462
492
  }
463
493
 
@@ -552,6 +582,200 @@ class AudioModule : Module() {
552
582
  }
553
583
  }
554
584
  }
585
+
586
+ Class(AudioPlaylist::class) {
587
+ Constructor { sources: List<AudioSource>, updateInterval: Double, loop: LoopMode ->
588
+ runOnMain {
589
+ val playlist = AudioPlaylist(
590
+ context,
591
+ appContext,
592
+ sources,
593
+ updateInterval,
594
+ DefaultDataSource.Factory(context)
595
+ )
596
+ playlist.loopMode = loop
597
+ playlist.setMediaItemCreator { source ->
598
+ source.uri?.let { uriString ->
599
+ val uri = uriString.toUri()
600
+ when {
601
+ isRawResource(uri) -> {
602
+ val file = getResourceName(uri, uriString)
603
+ MediaItem.fromUri(getRawResourceURI(file))
604
+ }
605
+ else -> MediaItem.fromUri(uri)
606
+ }
607
+ }
608
+ }
609
+ playlist.loadInitialPlaylist()
610
+ playlist.onPlaybackStateChange = { isPlaying ->
611
+ if (!isPlaying && shouldReleaseFocus()) {
612
+ releaseAudioFocus()
613
+ }
614
+ }
615
+ playlists[playlist.id] = playlist
616
+ playlist
617
+ }
618
+ }
619
+
620
+ Property("id") { playlist ->
621
+ playlist.id
622
+ }
623
+
624
+ Property("currentIndex") { playlist ->
625
+ runOnMain {
626
+ playlist.currentTrackIndex
627
+ }
628
+ }
629
+
630
+ Property("trackCount") { playlist ->
631
+ runOnMain {
632
+ playlist.trackCount
633
+ }
634
+ }
635
+
636
+ Property("sources") { playlist ->
637
+ playlist.getSources()
638
+ }
639
+
640
+ Property("playing") { playlist ->
641
+ runOnMain {
642
+ playlist.ref.isPlaying
643
+ }
644
+ }
645
+
646
+ Property("muted") { playlist ->
647
+ playlist.isMuted
648
+ }.set { playlist, muted: Boolean? ->
649
+ val newMuted = muted ?: false
650
+ playlist.isMuted = newMuted
651
+ playlist.setVolume(if (newMuted) 0f else playlist.previousVolume)
652
+ }
653
+
654
+ Property("isLoaded") { playlist ->
655
+ runOnMain {
656
+ playlist.ref.playbackState == Player.STATE_READY
657
+ }
658
+ }
659
+
660
+ Property("isBuffering") { playlist ->
661
+ runOnMain {
662
+ playlist.ref.playbackState == Player.STATE_BUFFERING
663
+ }
664
+ }
665
+
666
+ Property("currentTime") { playlist ->
667
+ runOnMain {
668
+ playlist.currentTime
669
+ }
670
+ }
671
+
672
+ Property("duration") { playlist ->
673
+ runOnMain {
674
+ playlist.duration
675
+ }
676
+ }
677
+
678
+ Property("volume") { playlist ->
679
+ runOnMain {
680
+ playlist.ref.volume
681
+ }
682
+ }.set { playlist, volume: Float? ->
683
+ playlist.setVolume(volume)
684
+ }
685
+
686
+ Property("playbackRate") { playlist ->
687
+ runOnMain {
688
+ playlist.ref.playbackParameters.speed
689
+ }
690
+ }.set { playlist, rate: Float ->
691
+ appContext.mainQueue.launch {
692
+ playlist.setPlaybackRate(rate)
693
+ }
694
+ }
695
+
696
+ Property("loop") { playlist ->
697
+ playlist.loopMode.value
698
+ }.set { playlist, mode: LoopMode ->
699
+ runOnMain {
700
+ playlist.loopMode = mode
701
+ }
702
+ }
703
+
704
+ Property("currentStatus") { playlist ->
705
+ runOnMain {
706
+ playlist.currentStatus()
707
+ }
708
+ }
709
+
710
+ Function("play") { playlist: AudioPlaylist ->
711
+ if (!audioEnabled) {
712
+ Log.e(TAG, "Audio has been disabled. Re-enable to start playing")
713
+ return@Function
714
+ }
715
+ runOnMain {
716
+ if (!focusAcquired) {
717
+ requestAudioFocus()
718
+ }
719
+ playlist.ref.play()
720
+ }
721
+ }
722
+
723
+ Function("pause") { playlist: AudioPlaylist ->
724
+ runOnMain {
725
+ playlist.ref.pause()
726
+ }
727
+ }
728
+
729
+ Function("next") { playlist: AudioPlaylist ->
730
+ runOnMain {
731
+ playlist.next()
732
+ }
733
+ }
734
+
735
+ Function("previous") { playlist: AudioPlaylist ->
736
+ runOnMain {
737
+ playlist.previous()
738
+ }
739
+ }
740
+
741
+ Function("skipTo") { playlist: AudioPlaylist, index: Int ->
742
+ runOnMain {
743
+ playlist.skipTo(index)
744
+ }
745
+ }
746
+
747
+ AsyncFunction("seekTo") { playlist: AudioPlaylist, seconds: Double ->
748
+ playlist.seekTo(seconds)
749
+ }.runOnQueue(Queues.MAIN)
750
+
751
+ Function("add") { playlist: AudioPlaylist, source: AudioSource ->
752
+ runOnMain {
753
+ playlist.add(source)
754
+ }
755
+ }
756
+
757
+ Function("insert") { playlist: AudioPlaylist, source: AudioSource, index: Int ->
758
+ runOnMain {
759
+ playlist.insert(source, index)
760
+ }
761
+ }
762
+
763
+ Function("remove") { playlist: AudioPlaylist, index: Int ->
764
+ runOnMain {
765
+ playlist.remove(index)
766
+ }
767
+ }
768
+
769
+ Function("clear") { playlist: AudioPlaylist ->
770
+ runOnMain {
771
+ playlist.clear()
772
+ }
773
+ }
774
+
775
+ Function("destroy") { playlist: AudioPlaylist ->
776
+ playlists.remove(playlist.id)
777
+ }
778
+ }
555
779
  }
556
780
 
557
781
  private fun createMediaItem(source: AudioSource?): MediaSource? = source?.uri?.let { uriString ->
@@ -564,9 +788,14 @@ class AudioModule : Module() {
564
788
  else -> MediaItem.fromUri(uri)
565
789
  }
566
790
 
567
- val factory = when (uri.scheme) {
568
- "http", "https" -> httpDataSourceFactory(source.headers)
569
- else -> DefaultDataSource.Factory(context)
791
+ val preloadedBytes = AudioPreloadManager.get(uriString)
792
+ val factory: DataSource.Factory = if (preloadedBytes != null) {
793
+ InMemoryDataSourceFactory(preloadedBytes)
794
+ } else {
795
+ when (uri.scheme) {
796
+ "http", "https" -> httpDataSourceFactory(source.headers)
797
+ else -> DefaultDataSource.Factory(context)
798
+ }
570
799
  }
571
800
  return buildMediaSourceFactory(factory, mediaItem)
572
801
  }