一.问题
最近app中有边听广播边看新闻的功能要求,同时其他新闻页面中又有可能有音视频。问题来了如果本来有广播音频正在播放,此时你想看视频,或者想听当前页中的音频那么就要停止广播的声音。其实比较简单这里用到了音频焦点的知识,但是由于“历史原因”,有的页面的视频播放使用的VideoView实现的,在测试的过程中发现不同系统版本的手机在播放视频的时候有的能够“自己”获取音频焦点,有的不能(除了使用VideovView播放视频外,其他音视频播放的时候都要自己手动添加获取音频焦点的代码)。那就只能从不同版本的sdk中来找原因了。
二.涉及的点
1.android系统中的音频焦点
2.获取焦点的具体代码
3.不同版本的系统(sdk)中VideoView有没有自带获取音频焦点功能
三.android系统中的音频焦点以及简单使用
3.1android系统中的音频焦点
3.1.1概念
android系统中,虽然在任意一个给定时间,只有一个activity能运行,android也是一个多任务环境。这给应用程序使用音频带来了一个特殊的挑战,因为只有一个音频输出,并且可能有几个媒体服务争夺使用这个音频输出。在Android 2.2之前,没有内建的机制来解决这个问题,这个问题在一些case下能导致一个坏的用户体验。例如,在用户正在听音乐时,其他应用需要通知用户非常重要的事情,用户可能不会听到通知铃声,因为声音很大的音乐。从Android 2.2 开始,平台提供一个方法为应用,为了让这些播放音频的应用协调他们如何使用设备的音频输出。这个机制被叫做音频焦点。
3.1.2非强制性
虽然有这个规矩,但是你的app可以不遵守。那带来的用户体验就会下降,甚至用户会卸载。
3.2获取焦点的具体代码
获取的代码比较简单,如下:
AudioManager am = (AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
private AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange){
case AudioManager.AUDIOFOCUS_LOSS:
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
break;
case AudioManager.AUDIOFOCUS_GAIN:
break;
}
}
};
这里只是一个简单的实例,具体每个回调参数的意义自行百度或者查看sdk中的代码
四.不同版本的sdk(系统)中VideoView中对于音频焦点的处理
4.1先给个结论,其实从android系统5.0(api21)开始才开始在VideoView中写入获取焦点的操作。
4.2下面是各个版本中的sdk中的VideoView关于获取音频焦点源码
4.2.1先了解一下获取焦点的入口,对就是设置播放资源地址setVideoPath方法,如下从上到下一步步到达openVideo方法,在这里进行了获取音频焦点操作。(这里的代码是api21的)
/**
* Sets video path.
*
* @param path the path of the video.
*/
public void setVideoPath(String path) {
setVideoURI(Uri.parse(path));
}
/**
* Sets video URI.
*
* @param uri the URI of the video.
*/
public void setVideoURI(Uri uri) {
setVideoURI(uri, null);
}
public void setVideoURI(Uri uri, Map<String, String> headers) {
mUri = uri;
mHeaders = headers;
mSeekWhenPrepared = 0;
openVideo();
requestLayout();
invalidate();
}
private void openVideo() {
...省略代码...
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
...省略代码...
}
4.2.2各个版本sdk中VideoView中对于音频焦点的处理这里(这里介绍openVideo和release方法因为在这俩方法中涉及了开始播放时候音频焦点的获取和释放)
api19和20;
private void openVideo() {
...省略代码...
//这里是发送广播,想暂停后台音乐的播放,但是在android 5.0 以下不起作用
Intent i = new Intent("com.android.music.musicservicecommand");
i.putExtra("command", "pause");
mContext.sendBroadcast(i);
release(false);
...省略代码...
}
//释放资源
private void release(boolean cleartargetstate) {
if (mMediaPlayer != null) {
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
mPendingSubtitleTracks.clear();
mCurrentState = STATE_IDLE;
if (cleartargetstate) {
mTargetState = STATE_IDLE;
}
}
}
api21
private void openVideo() {
...省略代码...
//从api 21开始获取音频的焦点
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
release(false);
...省略代码...
}
/*
* 释放播放的资源,但是还没有释放音频焦点的操作
*/
private void release(boolean cleartargetstate) {
if (mMediaPlayer != null) {
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
mPendingSubtitleTracks.clear();
mCurrentState = STATE_IDLE;
if (cleartargetstate) {
mTargetState = STATE_IDLE;
}
}
}
api 23 24 25
private void openVideo() {
...省略代码...
release(false);
//相对于之前的版本还没有变化
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
...省略代码...
}
/*
* 释放播放的资源,但是这里增加了释放音频焦点的操作
*/
private void release(boolean cleartargetstate) {
if (mMediaPlayer != null) {
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
mPendingSubtitleTracks.clear();
mCurrentState = STATE_IDLE;
if (cleartargetstate) {
mTargetState = STATE_IDLE;
}
//释放音频焦点的操作
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
am.abandonAudioFocus(null);
}
}
api 26
//这里在获取音频焦点的时候做了一个判断先
private void openVideo() {
...省略代码...
release(false);
if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
// TODO this should have a focus listener
mAudioManager.requestAudioFocus(null, mAudioAttributes, mAudioFocusType, 0 /*flags*/);
}
...省略代码...
}
private void release(boolean cleartargetstate) {
if (mMediaPlayer != null) {
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
mPendingSubtitleTracks.clear();
mCurrentState = STATE_IDLE;
if (cleartargetstate) {
mTargetState = STATE_IDLE;
}
//这里同理,在释放音频焦点的时候作了一个判断
if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
mAudioManager.abandonAudioFocus(null);
}
}
}
五.总结
5.1 在app的开发中碰上有后台音频播放的时候一定要做音频焦点得失的处理,以带来更好 的用户体验
5.2 VideoView使用的时候,注意不同的版本的内部有的添加了音频焦点的获取,有的没有这时候自己添加