Skip to content

Commit d5f09df

Browse files
Merge pull request #148 from SimformSolutionsPvtLtd/feature/UNT-T30685-Permissions-for-ducking/background-audio
feat: UNT-T30685: Permissions for ducking/background audio
2 parents 459961e + a78fc34 commit d5f09df

File tree

3 files changed

+104
-9
lines changed

3 files changed

+104
-9
lines changed

android/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,7 @@ dependencies {
9191
// Android Audio Media3 Dependency
9292
implementation 'androidx.media3:media3-exoplayer:1.3.1'
9393
implementation 'androidx.media3:media3-common:1.3.1'
94+
95+
implementation "androidx.media3:media3-session:1.3.1"
96+
implementation 'androidx.media3:media3-ui:1.3.1'
9497
}

android/src/main/java/com/audiowaveform/AudioPlayer.kt

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package com.audiowaveform
22

3+
import android.content.Context
4+
import android.media.AudioAttributes
5+
import android.media.AudioFocusRequest
6+
import android.media.AudioManager
37
import android.net.Uri
8+
import android.os.Build
49
import android.os.CountDownTimer
510
import com.facebook.react.bridge.Arguments
611
import com.facebook.react.bridge.Promise
@@ -18,13 +23,93 @@ class AudioPlayer(
1823
) {
1924
private val appContext = context
2025
private lateinit var player: ExoPlayer
26+
private var audioManager: AudioManager = appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
27+
private var audioFocusRequest: AudioFocusRequest? = null
2128
private var playerListener: Player.Listener? = null
2229
private var isPlayerPrepared: Boolean = false
2330
private var finishMode = FinishMode.Stop
2431
private val key = playerKey
2532
private var updateFrequency = UpdateFrequency.Low
2633
private lateinit var audioPlaybackListener: CountDownTimer
2734
private var isComponentMounted = true // Flag to track mounting status
35+
private var isAudioFocusGranted=false
36+
37+
init {
38+
// Set up the audio focus request
39+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
40+
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
41+
.setAudioAttributes(
42+
AudioAttributes.Builder()
43+
.setUsage(AudioAttributes.USAGE_MEDIA)
44+
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
45+
.build()
46+
).setAcceptsDelayedFocusGain(true)
47+
.setOnAudioFocusChangeListener { focusChange ->
48+
handleAudioFocusChange(focusChange)
49+
}
50+
.build()
51+
}
52+
}
53+
54+
private fun handleAudioFocusChange(focusChange: Int) {
55+
when (focusChange) {
56+
AudioManager.AUDIOFOCUS_GAIN -> {
57+
// Audio focus granted; resume playback if necessary
58+
if (!player.isPlaying) {
59+
player.play()
60+
}
61+
player.volume = 1.0f // Restore full volume
62+
}
63+
AudioManager.AUDIOFOCUS_LOSS -> {
64+
// Permanent loss of audio focus; pause playback
65+
if (player.isPlaying) {
66+
val args: WritableMap = Arguments.createMap()
67+
stopListening()
68+
player.pause()
69+
abandonAudioFocus()
70+
args.putInt(Constants.finishType, 1)
71+
args.putString(Constants.playerKey, key)
72+
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)?.emit("onDidFinishPlayingAudio", args)
73+
}
74+
}
75+
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
76+
// Temporary loss of audio focus; pause playback
77+
if (player.isPlaying) {
78+
player.pause()
79+
}
80+
}
81+
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
82+
// Temporarily loss of audio focus; but can continue playing at a lower volume.
83+
player.volume = 0.2f
84+
}
85+
}
86+
}
87+
88+
private fun requestAudioFocus(): Boolean {
89+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
90+
audioFocusRequest?.let {
91+
val result = audioManager.requestAudioFocus(it)
92+
isAudioFocusGranted = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
93+
return isAudioFocusGranted
94+
} ?: false
95+
} else {
96+
val result = audioManager.requestAudioFocus(
97+
{ focusChange -> handleAudioFocusChange(focusChange) },
98+
AudioManager.STREAM_MUSIC,
99+
AudioManager.AUDIOFOCUS_GAIN
100+
)
101+
isAudioFocusGranted = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
102+
return isAudioFocusGranted
103+
}
104+
}
105+
106+
private fun abandonAudioFocus() {
107+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
108+
audioManager.abandonAudioFocusRequest(audioFocusRequest!!)
109+
}
110+
isAudioFocusGranted = false
111+
}
112+
28113

29114
fun markPlayerAsUnmounted() {
30115
isComponentMounted = false
@@ -134,12 +219,15 @@ class AudioPlayer(
134219
this.finishMode = FinishMode.Stop
135220
}
136221

137-
validateAndSetPlaybackSpeed(player, speed)
138-
139-
player.playWhenReady = true
140-
player.play()
141-
promise.resolve(true)
142-
startListening(promise)
222+
validateAndSetPlaybackSpeed(player, speed)
223+
if (requestAudioFocus()) {
224+
player.playWhenReady = true
225+
player.play()
226+
promise.resolve(true)
227+
startListening(promise)}
228+
else {
229+
promise.reject("AudioFocusError", "Failed to gain audio focus")
230+
}
143231
} catch (e: Exception) {
144232
promise.reject("Can not start the player", e.toString())
145233
}
@@ -153,15 +241,17 @@ class AudioPlayer(
153241
isPlayerPrepared = false
154242
player.stop()
155243
player.release()
244+
abandonAudioFocus()
156245
}
157246

158-
fun pause(promise: Promise) {
247+
fun pause(promise: Promise?) {
159248
try {
160249
stopListening()
161250
player.pause()
162-
promise.resolve(true)
251+
abandonAudioFocus()
252+
promise?.resolve(true)
163253
} catch (e: Exception) {
164-
promise.reject("Failed to pause the player", e.toString())
254+
promise?.reject("Failed to pause the player", e.toString())
165255
}
166256

167257
}

src/components/Waveform/Waveform.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,8 @@ export const Waveform = forwardRef<IWaveformRef, IWaveform>((props, ref) => {
479479
if (data.playerKey === `PlayerFor${path}`) {
480480
if (data.finishType === FinishMode.stop) {
481481
stopPlayerAction();
482+
} else if (data.finishType === FinishMode.pause) {
483+
setPlayerState(PlayerState.paused);
482484
}
483485
}
484486
});

0 commit comments

Comments
 (0)