Skip to content

Commit

Permalink
Merge pull request #148 from SimformSolutionsPvtLtd/feature/UNT-T3068…
Browse files Browse the repository at this point in the history
…5-Permissions-for-ducking/background-audio

feat: UNT-T30685: Permissions for ducking/background audio
  • Loading branch information
mukesh-simform authored Dec 19, 2024
2 parents 459961e + a78fc34 commit d5f09df
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 9 deletions.
3 changes: 3 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,7 @@ dependencies {
// Android Audio Media3 Dependency
implementation 'androidx.media3:media3-exoplayer:1.3.1'
implementation 'androidx.media3:media3-common:1.3.1'

implementation "androidx.media3:media3-session:1.3.1"
implementation 'androidx.media3:media3-ui:1.3.1'
}
108 changes: 99 additions & 9 deletions android/src/main/java/com/audiowaveform/AudioPlayer.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.audiowaveform

import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.net.Uri
import android.os.Build
import android.os.CountDownTimer
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
Expand All @@ -18,13 +23,93 @@ class AudioPlayer(
) {
private val appContext = context
private lateinit var player: ExoPlayer
private var audioManager: AudioManager = appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
private var audioFocusRequest: AudioFocusRequest? = null
private var playerListener: Player.Listener? = null
private var isPlayerPrepared: Boolean = false
private var finishMode = FinishMode.Stop
private val key = playerKey
private var updateFrequency = UpdateFrequency.Low
private lateinit var audioPlaybackListener: CountDownTimer
private var isComponentMounted = true // Flag to track mounting status
private var isAudioFocusGranted=false

init {
// Set up the audio focus request
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
).setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener { focusChange ->
handleAudioFocusChange(focusChange)
}
.build()
}
}

private fun handleAudioFocusChange(focusChange: Int) {
when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> {
// Audio focus granted; resume playback if necessary
if (!player.isPlaying) {
player.play()
}
player.volume = 1.0f // Restore full volume
}
AudioManager.AUDIOFOCUS_LOSS -> {
// Permanent loss of audio focus; pause playback
if (player.isPlaying) {
val args: WritableMap = Arguments.createMap()
stopListening()
player.pause()
abandonAudioFocus()
args.putInt(Constants.finishType, 1)
args.putString(Constants.playerKey, key)
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)?.emit("onDidFinishPlayingAudio", args)
}
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
// Temporary loss of audio focus; pause playback
if (player.isPlaying) {
player.pause()
}
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
// Temporarily loss of audio focus; but can continue playing at a lower volume.
player.volume = 0.2f
}
}
}

private fun requestAudioFocus(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audioFocusRequest?.let {
val result = audioManager.requestAudioFocus(it)
isAudioFocusGranted = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
return isAudioFocusGranted
} ?: false
} else {
val result = audioManager.requestAudioFocus(
{ focusChange -> handleAudioFocusChange(focusChange) },
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
)
isAudioFocusGranted = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
return isAudioFocusGranted
}
}

private fun abandonAudioFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audioManager.abandonAudioFocusRequest(audioFocusRequest!!)
}
isAudioFocusGranted = false
}


fun markPlayerAsUnmounted() {
isComponentMounted = false
Expand Down Expand Up @@ -134,12 +219,15 @@ class AudioPlayer(
this.finishMode = FinishMode.Stop
}

validateAndSetPlaybackSpeed(player, speed)

player.playWhenReady = true
player.play()
promise.resolve(true)
startListening(promise)
validateAndSetPlaybackSpeed(player, speed)
if (requestAudioFocus()) {
player.playWhenReady = true
player.play()
promise.resolve(true)
startListening(promise)}
else {
promise.reject("AudioFocusError", "Failed to gain audio focus")
}
} catch (e: Exception) {
promise.reject("Can not start the player", e.toString())
}
Expand All @@ -153,15 +241,17 @@ class AudioPlayer(
isPlayerPrepared = false
player.stop()
player.release()
abandonAudioFocus()
}

fun pause(promise: Promise) {
fun pause(promise: Promise?) {
try {
stopListening()
player.pause()
promise.resolve(true)
abandonAudioFocus()
promise?.resolve(true)
} catch (e: Exception) {
promise.reject("Failed to pause the player", e.toString())
promise?.reject("Failed to pause the player", e.toString())
}

}
Expand Down
2 changes: 2 additions & 0 deletions src/components/Waveform/Waveform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ export const Waveform = forwardRef<IWaveformRef, IWaveform>((props, ref) => {
if (data.playerKey === `PlayerFor${path}`) {
if (data.finishType === FinishMode.stop) {
stopPlayerAction();
} else if (data.finishType === FinishMode.pause) {
setPlayerState(PlayerState.paused);
}
}
});
Expand Down

0 comments on commit d5f09df

Please sign in to comment.