Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(android): refactor audio focus #4176

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.brentvatne.common.api

import com.brentvatne.common.react.VideoEventEmitter

// RNVPlayerInterface is an abstraction of a player implementation
// It allows to use player in a generic code
interface RNVPlayerInterface {
// return true if playback is muted

val isMuted: Boolean

// return true if playback is ongoing and false when paused
val isPlaying: Boolean

// return the eventEmitter associated to the player
val eventEmitter: VideoEventEmitter

// pause player
fun pausePlayback()

// decrease audio volume internally to handle audio ducking request
fun audioDuck()

// decrease audio volume internally from ducking request
fun audioRestoreFromDuck()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.brentvatne.common.toolbox

import android.content.Context
import android.media.AudioManager
import android.media.AudioManager.OnAudioFocusChangeListener
import com.brentvatne.common.api.RNVPlayerInterface
import com.facebook.react.uimanager.ThemedReactContext

/**
* Delegate audio management to this class
* This is an helper to group all generic android code which do not depend on player implementation
*/
class AudioManagerDelegate(player: RNVPlayerInterface, themedReactContext: ThemedReactContext) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add private val because they are not expected to change after initialization.

Suggested change
class AudioManagerDelegate(player: RNVPlayerInterface, themedReactContext: ThemedReactContext) {
class AudioManagerDelegate(private val player: RNVPlayerInterface, private val themedReactContext: ThemedReactContext
)```


companion object {
const val TAG = "AudioFocusDelegate"
}

// indicates if audio focus shall be handled
var disableFocus: Boolean = false

// indicates app currently have audio focus
var hasAudioFocus = false

val audioManager: AudioManager = themedReactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager

private val audioFocusChangeListener = OnAudioFocusChangedListener(player, themedReactContext, this)

/** implementation of OnAudioFocusChangedListener
* It reports audio events to the app and request volume change to the player
**/
private class OnAudioFocusChangedListener(
private val player: RNVPlayerInterface,
private val themedReactContext: ThemedReactContext,
private val audioFocusDelegate: AudioManagerDelegate
) : OnAudioFocusChangeListener {
override fun onAudioFocusChange(focusChange: Int) {
when (focusChange) {
AudioManager.AUDIOFOCUS_LOSS -> {
audioFocusDelegate.hasAudioFocus = false
player.eventEmitter.onAudioFocusChanged.invoke(false)
// FIXME this pause can cause issue if content doesn't have pause capability (can happen on live channel)
themedReactContext.currentActivity?.runOnUiThread(player::pausePlayback)
audioFocusDelegate.audioManager.abandonAudioFocus(this)
}

AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> player.eventEmitter.onAudioFocusChanged.invoke(false)

AudioManager.AUDIOFOCUS_GAIN -> {
audioFocusDelegate.hasAudioFocus = true
player.eventEmitter.onAudioFocusChanged.invoke(true)
}

else -> {
DebugLog.e(TAG, "unhandled audioFocusChange $focusChange")
}
}
if (player.isPlaying) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume
if (!player.isMuted) {
player.audioDuck()
}
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal
if (!player.isMuted) {
player.audioRestoreFromDuck()
}
}
}
}
}

/**
* request audio Focus
*/
fun requestAudioFocus(): Boolean {
if (disableFocus || hasAudioFocus) {
return true
}
val result: Int = audioManager.requestAudioFocus(
audioFocusChangeListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
)
hasAudioFocus = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
return hasAudioFocus
}

/**
* Abandon audio Focus
*/
fun abandonAudioFocus() {
audioManager.abandonAudioFocus(audioFocusChangeListener)
}

/**
* change system audio output
*/
fun changeOutput(isSpeakerOutput: Boolean) {
audioManager.setMode(
if (isSpeakerOutput) AudioManager.MODE_NORMAL else AudioManager.MODE_IN_COMMUNICATION
)
audioManager.setSpeakerphoneOn(isSpeakerOutput)
}
}
Loading
Loading