-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
freeboub
wants to merge
5
commits into
TheWidlarzGroup:master
Choose a base branch
from
freeboub:fix/audioFocusRefactor
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
b7e6d6f
chore(android): refactor audio focus
freeboub 5d5c5ce
chore(android): fix linter
freeboub 0b99c44
fix(android): deprecate disableFocus and implement mixWithOthers
freeboub fd74689
chore: use lambda
freeboub eb6566e
Merge branch 'master' into fix/audioFocusRefactor
freeboub File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
26 changes: 26 additions & 0 deletions
26
android/src/main/java/com/brentvatne/common/api/RNVPlayerInterface.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
106 changes: 106 additions & 0 deletions
106
android/src/main/java/com/brentvatne/common/toolbox/AudioManagerDelegate.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | ||
|
||
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) | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.