原文地址:https://medium.com/google-developers/building-a-video-player-app-in-android-part-4-5-c69f12b49143
前一篇文章介绍了如何将ExoPlayer实例与MediaSession连接的步骤,本文将介绍在应用程序中支持Audio Focus的情况。
Audio Focus
Android允许许多应用程序通过将所有音频流混合在一起来同事播放音频,然而如果多个应用程序同时播放音频,则会对用户造成干扰。这就像许多人同时与用户交谈一样,其结果是他无法真正听到或关注任何一个人,为了避免这个,Android提供了一个允许应用程序共享音频焦点的API,其中只有一个应用程序可以在任何给定的时间保持音频焦点。
你可以从下面文章中了解更多关于音频焦点的细节以及如何实现它:
- Understanding Audio Focus articles on medium.这篇文章深入探讨了什么是音频焦点,什么是低音,AudioAttributes是什么,以及你应该为您的应用做哪些考虑。
- Android sample app that uses Audio Focus,这里有一个audioFocusHelper类可以用来处理音频焦点问题(用Java编写的)
使用ExoPlayer的Kotlin实现
在Kotlin中使用委托模式很容易,可以创建一个超类实例提供所有继承行为的子类。这样你就可以专注于你的子类中的有差异的代码,而无需编写样板代码。下面的代码显示了如何扩展SimpleExoPlayer来实现处理音频焦点的能力。你可以像使用SimpleExoPlayer一样使用此类,方法是传递你的应用程序所需的音频焦点类型(使用AudioAttributes)。
下面是用于为播放音乐的应用创建音频属性的代码片段:
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val audioAttributes = AudioAttributesCompat.Builder()
.setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributesCompat.USAGE_MEDIA)
.build()
下面是扩展SimpleExoPlayer的AudioFocusWrapper类的代码片段:
class AudioFocusWrapper(private val audioAttributes: AudioAttributesCompat,
private val audioManager: AudioManager,
private val player: SimpleExoPlayer) : ExoPlayer by player, AnkoLogger{
private var shouldPlayWhenReady = false
private val audioFocusListener =
AudioManager.OnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> {
if (shouldPlayWhenReady || player.playWhenReady) {
player.playWhenReady = true
player.volume = MEDIA_VOLUME_DEFAULT
}
shouldPlayWhenReady = false
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
if (player.playWhenReady) {
player.volume = MEDIA_VOLUME_DUCK
}
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
shouldPlayWhenReady = player.playWhenReady
player.playWhenReady = false
}
AudioManager.AUDIOFOCUS_LOSS -> {
abandonAudioFocus()
}
}
}
@get:RequiresApi(Build.VERSION_CODES.O)
private val audioFocusRequest by lazy { buildFocusRequest() }
override fun setPlayWhenReady(playWhenReady: Boolean) {
if (playWhenReady) requestAudioFocus() else abandonAudioFocus()
}
private fun requestAudioFocus() {
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requestAudioFocusOreo()
} else {
@Suppress("deprecation")
audioManager.requestAudioFocus(audioFocusListener,
audioAttributes.legacyStreamType,
AudioManager.AUDIOFOCUS_GAIN)
}
// Call the listener whenever focus is granted - even the first time!
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
shouldPlayWhenReady = true
audioFocusListener.onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN)
} else {
warn { "Playback not started: Audio focus request denied" }
}
}
private fun abandonAudioFocus() {
player.playWhenReady = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
abandonAudioFocusOreo()
} else {
@Suppress("deprecation")
audioManager.abandonAudioFocus(audioFocusListener)
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun requestAudioFocusOreo(): Int =
audioManager.requestAudioFocus(audioFocusRequest)
@RequiresApi(Build.VERSION_CODES.O)
private fun abandonAudioFocusOreo() =
audioManager.abandonAudioFocusRequest(audioFocusRequest)
@TargetApi(Build.VERSION_CODES.O)
private fun buildFocusRequest(): AudioFocusRequest =
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(
audioAttributes.unwrap() as? AudioAttributes)
.setOnAudioFocusChangeListener(audioFocusListener)
.build()
}
private const val MEDIA_VOLUME_DEFAULT = 1.0f
private const val MEDIA_VOLUME_DUCK = 0.2f
class PlayerHolder(val context: Context,
val playerState: PlayerState,
val playerView: PlayerView) : AnkoLogger {
val audioFocusPlayer: ExoPlayer
// Create the player instance.
init {
val audioManager =
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val audioAttributes = AudioAttributesCompat.Builder()
.setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributesCompat.USAGE_MEDIA)
.build()
audioFocusPlayer = AudioFocusWrapper(
audioAttributes,
audioManager,
ExoPlayerFactory.newSimpleInstance(
context, DefaultTrackSelector())
.apply { playerView.player = this }
)
info { "SimpleExoPlayer created" }
}
}
PIP and Audio Focus
到目前为止,我们的应用程序不能再后台工作,只要启动任何其他应用程序,或用户切换到主屏幕,播放就会停止。所以在这种情况下,Audio Focus没有多少用处,但是,只要你给应用程序添加将自己最小化为小窗口并在其他应用程序运行时播放媒体的功能,音频焦点就变得非常重要。
`
想象一下,在这种情况下,你启动视频播放器并开始播放视频,然后将其最小化为PIP窗口,然后启动YouTube,此时,用户可以看到Youtube和视频播放器应用。
- 当您选择在Youtube应用中播放视频时,应该在示例应用中暂停播放。
- 当你选择在示例应用中播放视频时,应该暂停播放视频应用。
我们将在下一篇文章中探讨这一点。