expo-audio 55.0.5 → 55.0.7
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/CHANGELOG.md +33 -0
- package/android/build.gradle +4 -2
- package/android/src/main/AndroidManifest.xml +0 -17
- package/android/src/main/java/expo/modules/audio/AudioExceptions.kt +35 -0
- package/android/src/main/java/expo/modules/audio/AudioModule.kt +280 -49
- package/android/src/main/java/expo/modules/audio/AudioPlayer.kt +125 -80
- package/android/src/main/java/expo/modules/audio/AudioPlaylist.kt +347 -0
- package/android/src/main/java/expo/modules/audio/AudioPreloadManager.kt +55 -0
- package/android/src/main/java/expo/modules/audio/AudioRecorder.kt +56 -19
- package/android/src/main/java/expo/modules/audio/AudioRecords.kt +8 -1
- package/android/src/main/java/expo/modules/audio/AudioUtils.kt +9 -0
- package/android/src/main/java/expo/modules/audio/Playable.kt +48 -0
- package/android/src/main/java/expo/modules/audio/service/AudioControlsService.kt +146 -124
- package/android/src/main/java/expo/modules/audio/service/AudioPlaybackServiceConnection.kt +100 -0
- package/android/src/main/java/expo/modules/audio/service/AudioRecordingService.kt +64 -63
- package/android/src/main/java/expo/modules/audio/service/AudioRecordingServiceConnection.kt +153 -0
- package/android/src/main/java/expo/modules/audio/service/BaseServiceConnection.kt +141 -0
- package/build/Audio.types.d.ts +142 -3
- package/build/Audio.types.d.ts.map +1 -1
- package/build/Audio.types.js.map +1 -1
- package/build/AudioEventKeys.d.ts +2 -0
- package/build/AudioEventKeys.d.ts.map +1 -1
- package/build/AudioEventKeys.js +2 -0
- package/build/AudioEventKeys.js.map +1 -1
- package/build/AudioModule.types.d.ts +147 -4
- package/build/AudioModule.types.d.ts.map +1 -1
- package/build/AudioModule.types.js.map +1 -1
- package/build/AudioModule.web.d.ts +10 -67
- package/build/AudioModule.web.d.ts.map +1 -1
- package/build/AudioModule.web.js +43 -372
- package/build/AudioModule.web.js.map +1 -1
- package/build/AudioPlayer.web.d.ts +52 -0
- package/build/AudioPlayer.web.d.ts.map +1 -0
- package/build/AudioPlayer.web.js +304 -0
- package/build/AudioPlayer.web.js.map +1 -0
- package/build/AudioPlaylist.web.d.ts +58 -0
- package/build/AudioPlaylist.web.d.ts.map +1 -0
- package/build/AudioPlaylist.web.js +449 -0
- package/build/AudioPlaylist.web.js.map +1 -0
- package/build/AudioRecorder.web.d.ts +40 -0
- package/build/AudioRecorder.web.d.ts.map +1 -0
- package/build/AudioRecorder.web.js +239 -0
- package/build/AudioRecorder.web.js.map +1 -0
- package/build/AudioUtils.web.d.ts +12 -0
- package/build/AudioUtils.web.d.ts.map +1 -0
- package/build/AudioUtils.web.js +81 -0
- package/build/AudioUtils.web.js.map +1 -0
- package/build/ExpoAudio.d.ts +146 -2
- package/build/ExpoAudio.d.ts.map +1 -1
- package/build/ExpoAudio.js +187 -6
- package/build/ExpoAudio.js.map +1 -1
- package/build/ExpoAudio.web.d.ts +8 -1
- package/build/ExpoAudio.web.d.ts.map +1 -1
- package/build/ExpoAudio.web.js +52 -57
- package/build/ExpoAudio.web.js.map +1 -1
- package/build/MediaSessionController.web.d.ts +32 -0
- package/build/MediaSessionController.web.d.ts.map +1 -0
- package/build/MediaSessionController.web.js +156 -0
- package/build/MediaSessionController.web.js.map +1 -0
- package/build/utils/resolveSource.d.ts +1 -0
- package/build/utils/resolveSource.d.ts.map +1 -1
- package/build/utils/resolveSource.js +9 -11
- package/build/utils/resolveSource.js.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/AudioComponentRegistry.swift +61 -0
- package/ios/AudioModule.swift +254 -6
- package/ios/AudioPlayer.swift +101 -22
- package/ios/AudioPlaylist.swift +374 -0
- package/ios/AudioRecorder.swift +44 -4
- package/ios/AudioRecords.swift +7 -0
- package/ios/AudioSource.swift +3 -0
- package/ios/AudioUtils.swift +2 -15
- package/ios/MediaController.swift +9 -9
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7-sources.jar +0 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.aar +0 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.aar.sha512 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/{55.0.5/expo.modules.audio-55.0.5.module → 55.0.7/expo.modules.audio-55.0.7.module} +36 -22
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.module.md5 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/{55.0.5/expo.modules.audio-55.0.5.pom → 55.0.7/expo.modules.audio-55.0.7.pom} +25 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.7/expo.modules.audio-55.0.7.pom.sha512 +1 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/maven-metadata.xml +4 -4
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/maven-metadata.xml.sha512 +1 -1
- package/package.json +2 -2
- package/plugin/build/withAudio.d.ts +1 -0
- package/plugin/build/withAudio.js +54 -4
- package/plugin/src/withAudio.ts +73 -4
- package/src/Audio.types.ts +150 -3
- package/src/AudioEventKeys.ts +2 -0
- package/src/AudioModule.types.ts +178 -3
- package/src/AudioModule.web.ts +44 -486
- package/src/AudioPlayer.web.ts +392 -0
- package/src/AudioPlaylist.web.ts +529 -0
- package/src/AudioRecorder.web.ts +310 -0
- package/src/AudioUtils.web.ts +98 -0
- package/src/ExpoAudio.ts +227 -7
- package/src/ExpoAudio.web.ts +71 -64
- package/src/MediaSessionController.web.ts +191 -0
- package/src/utils/resolveSource.ts +10 -12
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5-sources.jar +0 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.aar +0 -0
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.module.md5 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/audio/expo.modules.audio/55.0.5/expo.modules.audio-55.0.5.pom.sha512 +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,39 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 55.0.7 — 2026-02-20
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [Web] Fix potential `nan` duration. ([#43268](https://github.com/expo/expo/pull/43268) by [@alanjhughes](https://github.com/alanjhughes))
|
|
18
|
+
|
|
19
|
+
### 💡 Others
|
|
20
|
+
|
|
21
|
+
- [android] Make in-memory preload cache. ([#43293](https://github.com/expo/expo/pull/43293) by [@alanjhughes](https://github.com/alanjhughes))
|
|
22
|
+
|
|
23
|
+
## 55.0.6 — 2026-02-16
|
|
24
|
+
|
|
25
|
+
### 🎉 New features
|
|
26
|
+
|
|
27
|
+
- [iOS] Add support for `shouldRouteThroughEarpiece`. ([#43089](https://github.com/expo/expo/pull/43089) by [@alanjhughes](https://github.com/alanjhughes))
|
|
28
|
+
- [Android] Make it possible to add/remove the foreground service and foreground service permissions with a config plugin. ([#43014](https://github.com/expo/expo/pull/43014) by [@behenate](https://github.com/behenate))
|
|
29
|
+
- [Web] Add support for audio sampling. ([#43149](https://github.com/expo/expo/pull/43149) by [@alanjhughes](https://github.com/alanjhughes))
|
|
30
|
+
- [Web] Add support for media controls. ([#43150](https://github.com/expo/expo/pull/43150) by [@alanjhughes](https://github.com/alanjhughes))
|
|
31
|
+
- [Web] Add support for selecting recording inputs. ([#43151](https://github.com/expo/expo/pull/43151) by [@alanjhughes](https://github.com/alanjhughes))
|
|
32
|
+
- [Web] Add support for recording metering. ([#43152](https://github.com/expo/expo/pull/43152) by [@alanjhughes](https://github.com/alanjhughes))
|
|
33
|
+
- [Web] Enable `setIsAudioActiveAsync`. ([#43142](https://github.com/expo/expo/pull/43142) by [@alanjhughes](https://github.com/alanjhughes))
|
|
34
|
+
- Add support for preloading audio sources. ([#43063](https://github.com/expo/expo/pull/43063) by [@alanjhughes](https://github.com/alanjhughes))
|
|
35
|
+
|
|
36
|
+
### 🐛 Bug fixes
|
|
37
|
+
|
|
38
|
+
- [Android] Fix memory leaks when refreshing the app. ([#42785](https://github.com/expo/expo/pull/42785) by [@behenate](https://github.com/behenate))
|
|
39
|
+
- [iOS] Fixes `mediaServicesDidReset` not being correctly implemented. ([#42898](https://github.com/expo/expo/pull/42898) by [@alanjhughes](https://github.com/alanjhughes))
|
|
40
|
+
|
|
41
|
+
### 💡 Others
|
|
42
|
+
|
|
43
|
+
- [Android] Improve event handling. ([#43121](https://github.com/expo/expo/pull/43121) by [@alanjhughes](https://github.com/alanjhughes))
|
|
44
|
+
- [Android] Rework native audio service handling. ([#43015](https://github.com/expo/expo/pull/43015) by [@behenate](https://github.com/behenate))
|
|
45
|
+
|
|
13
46
|
## 55.0.5 — 2026-02-08
|
|
14
47
|
|
|
15
48
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -4,13 +4,13 @@ plugins {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
group = 'expo.modules.audio'
|
|
7
|
-
version = '55.0.
|
|
7
|
+
version = '55.0.7'
|
|
8
8
|
|
|
9
9
|
android {
|
|
10
10
|
namespace "expo.modules.audio"
|
|
11
11
|
defaultConfig {
|
|
12
12
|
versionCode 1
|
|
13
|
-
versionName "55.0.
|
|
13
|
+
versionName "55.0.7"
|
|
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"
|
|
@@ -1,21 +1,4 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
2
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
3
3
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
|
4
|
-
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
5
|
-
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
|
6
|
-
|
|
7
|
-
<application>
|
|
8
|
-
<service
|
|
9
|
-
android:name=".service.AudioControlsService"
|
|
10
|
-
android:exported="false"
|
|
11
|
-
android:foregroundServiceType="mediaPlayback">
|
|
12
|
-
<intent-filter>
|
|
13
|
-
<action android:name="androidx.media3.session.MediaSessionService" />
|
|
14
|
-
</intent-filter>
|
|
15
|
-
</service>
|
|
16
|
-
<service
|
|
17
|
-
android:name=".service.AudioRecordingService"
|
|
18
|
-
android:exported="false"
|
|
19
|
-
android:foregroundServiceType="microphone" />
|
|
20
|
-
</application>
|
|
21
4
|
</manifest>
|
|
@@ -2,9 +2,24 @@ package expo.modules.audio
|
|
|
2
2
|
|
|
3
3
|
import expo.modules.kotlin.exception.CodedException
|
|
4
4
|
|
|
5
|
+
private const val defaultPlaybackServiceTip =
|
|
6
|
+
"Make sure that the expo-audio config plugin is properly configured with " +
|
|
7
|
+
"'enableBackgroundPlayback: true' to avoid issues with lock screen controls " +
|
|
8
|
+
"and background audio playback."
|
|
9
|
+
|
|
10
|
+
private const val defaultRecordingServiceTip =
|
|
11
|
+
"Make sure that the expo-audio config plugin is properly configured with " +
|
|
12
|
+
"'enableBackgroundRecording: true' to avoid issues with background recording."
|
|
13
|
+
|
|
5
14
|
internal class AudioPermissionsException :
|
|
6
15
|
CodedException("RECORD_AUDIO permission has not been granted")
|
|
7
16
|
|
|
17
|
+
internal class NotificationPermissionsException :
|
|
18
|
+
CodedException(
|
|
19
|
+
"POST_NOTIFICATIONS permission has not been granted. This permission is required when using background recording (allowsBackgroundRecording: true). " +
|
|
20
|
+
"Request notification permissions using AudioModule.requestNotificationPermissionsAsync() before calling prepareRecording()."
|
|
21
|
+
)
|
|
22
|
+
|
|
8
23
|
internal class GetAudioInputNotSupportedException :
|
|
9
24
|
CodedException("Getting current audio input is not supported on devices running Android version lower than Android 9.0")
|
|
10
25
|
|
|
@@ -22,3 +37,23 @@ internal class AudioRecorderPrepareException(cause: Throwable) :
|
|
|
22
37
|
|
|
23
38
|
internal class AudioRecorderAlreadyPreparedException :
|
|
24
39
|
CodedException("AudioRecorder has already been prepared. Stop or release the current session before preparing again.")
|
|
40
|
+
|
|
41
|
+
internal fun getPlaybackServiceErrorMessage(
|
|
42
|
+
message: String?,
|
|
43
|
+
tip: String = defaultPlaybackServiceTip
|
|
44
|
+
): String {
|
|
45
|
+
return (message ?: "expo-audio playback service error") + ". $tip"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
internal fun getRecordingServiceErrorMessage(
|
|
49
|
+
message: String?,
|
|
50
|
+
tip: String = defaultRecordingServiceTip
|
|
51
|
+
): String {
|
|
52
|
+
return (message ?: "expo-audio recording service error") + ". $tip"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
internal class AudioRecordingServiceException(message: String?, cause: Throwable? = null) :
|
|
56
|
+
CodedException(getRecordingServiceErrorMessage(message), cause)
|
|
57
|
+
|
|
58
|
+
internal class AudioPlaybackServiceException(message: String?, cause: Throwable? = null) :
|
|
59
|
+
CodedException(getPlaybackServiceErrorMessage(message), cause)
|
|
@@ -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
|
|
@@ -28,20 +27,23 @@ import androidx.media3.exoplayer.hls.HlsMediaSource
|
|
|
28
27
|
import androidx.media3.exoplayer.smoothstreaming.SsMediaSource
|
|
29
28
|
import androidx.media3.exoplayer.source.MediaSource
|
|
30
29
|
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
|
31
|
-
import expo.modules.audio.service.AudioControlsService
|
|
32
30
|
import expo.modules.interfaces.permissions.Permissions
|
|
33
31
|
import expo.modules.kotlin.Promise
|
|
34
32
|
import expo.modules.kotlin.exception.Exceptions
|
|
33
|
+
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
|
-
|
|
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
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
88
|
+
allPlayables.forEach { playable ->
|
|
89
|
+
if (playable.previousVolume != playable.volume) {
|
|
90
|
+
playable.previousVolume = playable.volume
|
|
87
91
|
}
|
|
88
|
-
|
|
92
|
+
playable.setVolume(playable.previousVolume * 0.5f)
|
|
89
93
|
}
|
|
90
94
|
} else {
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
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
|
-
|
|
201
|
-
if (it.
|
|
202
|
-
it.
|
|
204
|
+
allPlayables.forEach {
|
|
205
|
+
if (it.isPlaying) {
|
|
206
|
+
it.pause()
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
209
|
}
|
|
@@ -210,17 +214,43 @@ class AudioModule : Module() {
|
|
|
210
214
|
Permissions.askForPermissionsWithPermissionsManager(appContext.permissions, promise, Manifest.permission.RECORD_AUDIO)
|
|
211
215
|
}
|
|
212
216
|
|
|
217
|
+
AsyncFunction("requestNotificationPermissionsAsync") { promise: Promise ->
|
|
218
|
+
Permissions.askForPermissionsWithPermissionsManager(appContext.permissions, promise, Manifest.permission.POST_NOTIFICATIONS)
|
|
219
|
+
}
|
|
220
|
+
|
|
213
221
|
AsyncFunction("getRecordingPermissionsAsync") { promise: Promise ->
|
|
214
222
|
Permissions.getPermissionsWithPermissionsManager(appContext.permissions, promise, Manifest.permission.RECORD_AUDIO)
|
|
215
223
|
}
|
|
216
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
|
+
|
|
217
247
|
OnActivityEntersBackground {
|
|
218
248
|
if (!shouldPlayInBackground) {
|
|
219
249
|
releaseAudioFocus()
|
|
220
|
-
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
|
|
250
|
+
allPlayables.forEach { playable ->
|
|
251
|
+
if (playable.isPlaying) {
|
|
252
|
+
playable.isPaused = true
|
|
253
|
+
playable.pause()
|
|
224
254
|
}
|
|
225
255
|
}
|
|
226
256
|
}
|
|
@@ -235,15 +265,14 @@ class AudioModule : Module() {
|
|
|
235
265
|
|
|
236
266
|
OnActivityEntersForeground {
|
|
237
267
|
if (!shouldPlayInBackground) {
|
|
238
|
-
|
|
239
|
-
if (hasPlayersToResume) {
|
|
268
|
+
if (allPlayables.any { it.isPaused }) {
|
|
240
269
|
requestAudioFocus()
|
|
241
270
|
}
|
|
242
271
|
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
|
|
246
|
-
|
|
272
|
+
allPlayables.forEach { playable ->
|
|
273
|
+
if (playable.isPaused) {
|
|
274
|
+
playable.isPaused = false
|
|
275
|
+
playable.play()
|
|
247
276
|
}
|
|
248
277
|
}
|
|
249
278
|
}
|
|
@@ -260,29 +289,34 @@ class AudioModule : Module() {
|
|
|
260
289
|
}
|
|
261
290
|
|
|
262
291
|
OnDestroy {
|
|
263
|
-
|
|
292
|
+
GlobalScope.launch(Dispatchers.Main) {
|
|
264
293
|
releaseAudioFocus()
|
|
265
294
|
players.values.forEach {
|
|
266
295
|
it.ref.stop()
|
|
267
296
|
}
|
|
268
297
|
|
|
298
|
+
playlists.values.forEach {
|
|
299
|
+
it.ref.stop()
|
|
300
|
+
}
|
|
301
|
+
|
|
269
302
|
recorders.values.forEach {
|
|
270
303
|
it.stopRecording()
|
|
271
304
|
}
|
|
272
|
-
|
|
273
|
-
AudioControlsService.clearSession()
|
|
305
|
+
AudioPreloadManager.clearAll()
|
|
274
306
|
}
|
|
275
307
|
}
|
|
276
308
|
|
|
277
309
|
Class(AudioPlayer::class) {
|
|
278
|
-
Constructor { source: AudioSource?, updateInterval: Double, keepAudioSessionActive: Boolean ->
|
|
310
|
+
Constructor { source: AudioSource?, updateInterval: Double, keepAudioSessionActive: Boolean, preferredForwardBufferDuration: Double ->
|
|
279
311
|
val mediaSource = createMediaItem(source)
|
|
312
|
+
val bufferDurationMs = (preferredForwardBufferDuration * 1000).toLong()
|
|
280
313
|
runOnMain {
|
|
281
314
|
val player = AudioPlayer(
|
|
282
315
|
context,
|
|
283
316
|
appContext,
|
|
284
317
|
mediaSource,
|
|
285
|
-
updateInterval
|
|
318
|
+
updateInterval,
|
|
319
|
+
bufferDurationMs
|
|
286
320
|
)
|
|
287
321
|
player.onPlaybackStateChange = { isPlaying ->
|
|
288
322
|
if (!isPlaying && shouldReleaseFocus()) {
|
|
@@ -453,9 +487,7 @@ class AudioModule : Module() {
|
|
|
453
487
|
|
|
454
488
|
Function("setPlaybackRate") { player: AudioPlayer, rate: Float ->
|
|
455
489
|
appContext.mainQueue.launch {
|
|
456
|
-
|
|
457
|
-
val pitch = if (player.preservesPitch) 1f else playbackRate
|
|
458
|
-
player.ref.playbackParameters = PlaybackParameters(playbackRate, pitch)
|
|
490
|
+
player.setPlaybackRate(rate)
|
|
459
491
|
}
|
|
460
492
|
}
|
|
461
493
|
|
|
@@ -494,7 +526,7 @@ class AudioModule : Module() {
|
|
|
494
526
|
recorder.getCurrentTimeSeconds()
|
|
495
527
|
}
|
|
496
528
|
|
|
497
|
-
AsyncFunction("prepareToRecordAsync") { recorder: AudioRecorder, options: RecordingOptions? ->
|
|
529
|
+
AsyncFunction("prepareToRecordAsync") Coroutine { recorder: AudioRecorder, options: RecordingOptions? ->
|
|
498
530
|
checkRecordingPermission()
|
|
499
531
|
recorder.prepareRecording(options)
|
|
500
532
|
}
|
|
@@ -550,6 +582,200 @@ class AudioModule : Module() {
|
|
|
550
582
|
}
|
|
551
583
|
}
|
|
552
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
|
+
}
|
|
553
779
|
}
|
|
554
780
|
|
|
555
781
|
private fun createMediaItem(source: AudioSource?): MediaSource? = source?.uri?.let { uriString ->
|
|
@@ -562,9 +788,14 @@ class AudioModule : Module() {
|
|
|
562
788
|
else -> MediaItem.fromUri(uri)
|
|
563
789
|
}
|
|
564
790
|
|
|
565
|
-
val
|
|
566
|
-
|
|
567
|
-
|
|
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
|
+
}
|
|
568
799
|
}
|
|
569
800
|
return buildMediaSourceFactory(factory, mediaItem)
|
|
570
801
|
}
|