Android EasyPlayer声音自动停止、恢复,一键静音等功能
AndroidEasyPlayer声音自动停止、恢复,一键静音等功能
我们在开发播放器时,可能会需要静音或者降低音量的功能。比如说某款音乐播放器,当在后台播放时,如果此时有另外的系统通知声音发出,可能播放器会把音量降低,系统声音结束后,再调高;如果有来电了,播放器可能会把音乐暂停,等通话结束后再继续播放。还有,比方说我们在某个场合放个视频,不料音量很大,会引来很多目光(很尴尬),这时候可能我们需要一键静音的功能。那这些功能我们应该如何实现呢?
Android播放声音的类为AudioTrack,播放器会先把音频流demux出来,再decode,之后,把音频PCM数据通过AudioTrack类write到音频设备中,从而通过话筒或者扬声器发出声音。
为了方便地实现声音控制,我们需要从应用的最上层进行操作(因为底层可能已经被抽象成库了),也就是要从AudioTrack来入手。让我们看看AudioTrack的一些API吧。
intgetPlayState() ReturnstheplaybackstateoftheAudioTrackinstance. 获取当前的播放状态。这个接口会返回PLAYSTATE_STOPPED、PLAYSTATE_PAUSED、PLAYSTATE_PLAYING 三种状态,分别表示未播放、暂停中、正在播放
voidpause() Pausestheplaybackoftheaudiodata.Datathathasnotbeenplayedbackwillnotbediscarded.Subsequentcallstoplay()willplaythisdataback.Seeflush()todiscardthisdata. 暂停播放音频数据。已经在缓冲区中的未播放数据将不会被丢弃,在下次play的时候继续播放。调用flush则会丢弃缓冲数据。
voidplay() StartsplayinganAudioTrack. 开始播放
intsetStereoVolume(floatleftGain, floatrightGain) SetsthespecifiedleftandrightoutputgainvaluesontheAudioTrack. 设置左右声道的音量增益。
有了这几个API,足以满足我们的需求。实现起来就非常简单了。
首先我们做一键静音功能。我们可以做个切换的按钮,这个按钮初始状态是要显示当前的播放状态:正在播放音频或未在播放音频。播放状态可以调用getPlayState()来获取到;然后按钮按下后,再根据播放状态进行播放或暂停。
代码如下:
mAudioEnable=mAudioTrack!=null&&mAudioTrack.getPlayState()==PLAYSTATE_PLAYING; publicvoidsetAudioEnable(booleanenable){ mAudioEnable=enable; AudioTrackat=mAudioTrack; if(at!=null){ synchronized(at){ if(!enable){ at.pause(); at.flush(); }else{ at.flush(); at.play(); } } } }
注意这里在pause之后,play之前都调用了flush接口。这样可以确保在由暂停到播放切换时,不会把暂停时未播放的“旧数据”播放出来。
接下来我们实现音频资源被其它进程占用(失去焦点)时,自动降低声音或者停止声音;在音频资源又被释放(重新获取到焦点)时再恢复播放的功能。
我们需要通过AudioManager来判断当前音频资源的状态,并且在音频焦点更改时得到回调。其关键API接口有:
intrequestAudioFocus(AudioManager.OnAudioFocusChangeListenerl, intstreamType, intdurationHint) Requestaudiofocus.Sendarequesttoobtaintheaudiofocus 请求获取音频焦点。 第一个参数为音频焦点更改时的回调; 第二个参数为音频类型,在我们调节音量时可以看到有若干种音量,就对应的这里的streamType,这里我们基本用MUSIC,表示“媒体”。 第三个参数表示获取焦点的“时长”,有如下几种情况: AUDIOFOCUS_GAIN_TRANSIENT 表示仅仅为临时获取焦点。比如播放导航语音、通知声音等,属于时间很短暂的情况; AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 表示为DUCK模式,表示当获取焦点后,允许先前获取过焦点的程序在降低输出音量的前提下继续播放。 AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 痛第一种情况类似,只是不允许系统再播放其他声音。通常应用在语音备忘、语音识别等情况; AUDIOFOCUS_GAIN 表示要获取焦点的时长未知。比如播放音乐等等。 当获取到焦点时,函数放回AUDIOFOCUS_REQUEST_GRANTED,当获取失败时,返回AUDIOFOCUS_REQUEST_FAILED
结合上面的API说明,参考如下代码以及解释:
//获取AudioManager实例 finalAudioManageram=(AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE); AudioManager.OnAudioFocusChangeListenerl=newAudioManager.OnAudioFocusChangeListener(){ @Override publicvoidonAudioFocusChange(intfocusChange){ if(focusChange==AudioManager.AUDIOFOCUS_GAIN){//焦点获取到了,那继续播放,并恢复音量。 AudioTrackaudioTrack=mAudioTrack; if(audioTrack!=null){ audioTrack.setStereoVolume(1.0f,1.0f); if(audioTrack.getPlayState()==AudioTrack.PLAYSTATE_PAUSED){ audioTrack.flush(); audioTrack.play(); } } }elseif(focusChange==AudioManager.AUDIOFOCUS_LOSS){//焦点丢失了,暂停播放。 AudioTrackaudioTrack=mAudioTrack; if(audioTrack!=null){ if(audioTrack.getPlayState()==AudioTrack.PLAYSTATE_PLAYING){ audioTrack.pause(); } } }elseif(focusChange==AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK){//焦点丢失了,但是允许在降低音量的前提下继续播放,那么降低声音。 AudioTrackaudioTrack=mAudioTrack; if(audioTrack!=null){ audioTrack.setStereoVolume(0.5f,0.5f); } } } }; //因为这里要获得的焦点无法预知时长,因此用AUDIOFOCUS_GAIN模式。 intrequestCode=am.requestAudioFocus(l,AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN); if(requestCode==AudioManager.AUDIOFOCUS_REQUEST_GRANTED){ //成功获取到了焦点。那启动播放 AudioTrackaudioTrack=mAudioTrack; if(audioTrack!=null){ audioTrack.setStereoVolume(1.0f,1.0f); if(audioTrack.getPlayState()==AudioTrack.PLAYSTATE_PAUSED){ audioTrack.flush(); audioTrack.play(); } } }else{//没有获取到音频焦点。那不播放声音 AudioTrackaudioTrack=mAudioTrack; if(audioTrack!=null){ if(audioTrack.getPlayState()==AudioTrack.PLAYSTATE_PLAYING){ audioTrack.pause(); } } }
至此,我们便实现了EasyPlayer的声音自动停止、恢复,一键静音的功能的实现。看起来挺麻烦对吗?其实做一个app很容易,但是要想做的好,各种情况都兼顾了,却是很不容易的。我们不防多看些系统APP的实现,或者Google官方的一些DEMO,它们往往都看似功能很简单,会让我们觉得:“如果是我做的话,几行代码即可搞定。。”,但是它们的代码量却很大,因为它们兼顾了各种细节。而往往我们开发出来绝大多数app的都只能算是半成品,都有继续优化的余地。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!