本文是从Android官网文档上的《Managing Audio Playback》章节直接翻译下来的,因为本人英语实在是太差,故而只能配合电子词典进行翻译。望各位见谅。
《管理音频回放》
如果你的应用要播放音频,那么保证你的用户能用一种可预计的方式控制音频就是非常重要的了。为了确认提供一个好的用户体验,保证你的应用管理音频焦点从而使多个应用不在同时播放也是非常重要的了。
在后边的课程里,你将能构建这样一个应用,能响应硬件音频按键按下、在播放音频时能请求音频焦点、能适当响应由系统或其他应用引起的音频焦点变化。
课程
第一课:《控制你的应用的音量和播放》
简介:学习如何才能保证你的用户能通过如下方式控制音量,当 播放、暂停、停止、跳过、前一个这些按键可用时,通过硬件或软件方法控制。
一个好的用户体验是可预计的。如果你的应用播放多媒体,那么,你的用户能通过设备的硬件或软件音量控制机制来控制你的应用的音量就是非常重要的了。
同样的,在适当的和可用的情况下,播放、暂停、停止、跳过、前一个按键应该执行各自的动作,在你的应用使用的音频流上。
第一节《标识所使用的音频流》
创建一个可预计的音频体验的第一步就是理解你的应用应该使用哪个音频流。
为了播放音乐、警告、通知、来电铃声、系统声音、呼叫音量、音频选择,Android系统维护了一个各自独立的音频流机制。这么做主要是允许用户独立的控制每个每个音频流的音量。
这些流的大部分都受限于系统事件,所以除非你的应用是要替换闹钟,你将几乎必定用SYSTEM_MUSIC音频流来播放你的音频。
第二节《用硬件音量键来控制你的应用的音量》
默认的,按下音量控制装置,将会修改激活的音频流的音量。如果你的应用当前没有播放任何东西,触碰音量控制键将会修改铃声的音量。
如果你已经进入一个游戏或者音乐app,那么很可能当用户触碰音量控制键时,他们是想要控制这个游戏或音乐App的音量,即使他们当前在歌曲之间或者他们没有音乐在当前游戏位置。
你很可能被诱惑尝试监听音量键按下,并且像那样更改你的音频流的音量。Android提供了一个便利的方法:setVolumeControlStream() ,它能直接在你指定的音频流上按下音量键。
在标识了音量流之后,你的应用将会使用它,你应该设置他作为目标音频流。你应该确保在你的应用的生命周期里尽早执行这个设置音频流的调用,因为你只需要调用它一次,在你Activity的生命周期里,你应该典型地在OnCreate函数(这个函数属于控制你的多媒体的Activity或Fragment)里调用它。这可以确保当你的应用是可见时,那个音量控制函数将会如用户希望的那样。
setVolumeControlStream(AudioManager.STREAM_MUSIC);从这个函数调用之后,按下设备上的音量控制键将会影响你指定的音频流(在上述例子里时STREAM_MUSIC),每当那个目标Activity或Fragment可见时。
第三节《用硬件回放键来控制你的应用的音频播放》
一些多媒体回放按键,如播放、暂停、停止、跳过、前一个这些,都能在一些手机、有线或无线的手机上找到。每当用户按下这些按键中的一个,系统就会广播一个带ACTION_MEDIA_BUTTON
action 的Intent。
为了响应多媒体按键按下,你需要在你的AndroidManifest.xml里注册一个广播接收器,用来监听如下这个广播Action:
android:name=".RemoteControlReceiver">
android:name="android.intent.action.MEDIA_BUTTON" />
这个接收器实现本身需要提取时哪个按键被按下才引起的广播。那个Intent通过
EXTRA_KEY_EVENT
key 包含的事哪个按键引起的广播,KeyEvent类包含一个列表,这个列表是由
KEYCODE_MEDIA_* 这样的静态常量组成,代表每个可能的多媒体按钮,比如 KEYCODE_MEDIA_PLAY_PAUSE 和 KEYCODE_MEDIA_NEXT 。
下边的片段将展示如何提取这样一个多媒体按钮,并且影响相应的多媒体播放。
public class RemoteControlReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) { KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); if (KeyEvent.KEYCODE_MEDIA_PLAY == event.getKeyCode()) { // Handle key press. } } } }因为多个应用可能都想要监听多媒体按钮按下,你必须也以编程来控制,在你的应用将要接到多媒体按钮按下事件时。
下面的代码能用在你的应用里,用来注册和反注册你的多媒体按钮事件接收器,通过AudioManager来实现。当注册之后,你的广播接收器就是全局唯一、整个Android系统独有的多媒体按钮广播接收器。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE); ... // Start listening for button presses am.registerMediaButtonEventReceiver(RemoteControlReceiver); ... // Stop listening for button presses am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
典型地,当他们开始变的不活跃或不可见时(比如在onStop函数里),应用应该反注册它的大部分接收器。然而,对于多媒体播放App来说,这不是那么简单的,实际上,在你的应用是不可见时,并且因此不能被屏幕上的UI控制时,响应多媒体按钮事件是最重要的。
一个比较好的注册和反注册多媒体按钮事件广播接收器方法是在你的应用获得音频焦点和失去音频焦点时。这段将在下一课程详细讲解。
第二课:《管理音频焦点》
简介:在多个App可能一起播放音频时,思考他们之间需要如何交互就是非常重要的了。为了避免每个音乐App同时播放,Android用音频焦点机制来节制回放,学习如何请求一个音频焦点,监听一个音频焦点的失去,以及当失去音频焦点时该如何做。
只有获得音频焦点的App能播放音频。在你的App开始播放之前,它应该请求并且获得音频焦点。同样地,它应该知道怎么样监听一个音频焦点的失去,并且当它发生时该如何做。
第一节:《请求音频焦点》
在你的应用开始播放音频之前,它应该拥有音频焦点,为了它将要用的音频流。这个动作时通过一个函数调用:
requestAudioFocus() 完成的,当它返回
AUDIOFOCUS_REQUEST_GRANTED 时,表明获取音频焦点成功。
你必须指定你正在使用的流类型,并且无论你期望请求的是一个瞬时焦点或永久焦点。当你期望只播放一小段音频时,应该请求瞬时焦点(比如播放导航提示)。当你计划
在可预见的未来播放音频时,应该请求永久焦点(比如播放音乐)。
下面的代码片段是请求一个永久焦点,在MUSIC音频流上。在你开始回放之前,你应该立即请求音频焦点,比如当用户按下播放按钮时,或者下一关的游戏背景音乐开始时。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE); ... // Request audio focus for playback int result = am.requestAudioFocus(afChangeListener, // Use the music stream. AudioManager.STREAM_MUSIC, // Request permanent focus. AudioManager.AUDIOFOCUS_GAIN); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { am.registerMediaButtonEventReceiver(RemoteControlReceiver); // Start playback. }
一旦你完成回放,请确认调用abandonAudioFocus()。这将会通知系统你不再需要音频焦点,并且反注册被关联的
AudioManager.OnAudioFocusChangeListener 。在丢弃瞬时焦
点这种Case下,系统将会允许被中断的App继续回放。
// Abandon audio focus when playback complete am.abandonAudioFocus(afChangeListener);
当你请求瞬时焦点时,你需要附加一个选项:你是否想要启用“闪避”选项。通常,一个行为良好的App在失去音频焦点时,会立即沉默它的回放。通过请求一个带“闪避”
选项的瞬时焦点,你能告诉其他App,他们保持播放对你来说是可接受的,只是需要他们压低自己的音量,直到音频焦点返回到他们那里。
// Request audio focus for playback int result = am.requestAudioFocus(afChangeListener, // Use the music stream. AudioManager.STREAM_MUSIC, // Request permanent focus. AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // Start playback. }
闪避选项对一些间歇使用音频流的App来说是特别合适的,比如能听见的驾驶指导命令。
每当另一个App像上面描述那样请求音频焦点,它的选择只能是在永久焦点和瞬时焦点两者之间,它的选择将会被你注册的监听接收到。
第二节:《处理失去音频焦点》
如果你的App能请求音频焦点,也就会允许当其他App请求时转而失去这个焦点。你的App如何响应音频焦点的失去取决于失去的方式。
onAudioFocusChange() 方法,属于当你请求音频焦点时,你注册的那个音频焦点改变的监听的,这个方法能接收一个参数,用来描述音频焦点改变的事件。特别地,可能的焦点遗失事件对应着前一节谈到的焦点获取类型,比如:永久遗失、瞬时遗失和瞬时遗失带闪避选项。
一般而言,一个焦点的瞬时遗失,应该导致你的App沉默它的音频流,否则就会保持原来的状态。你应该继续监视音频焦点的变化,并且准备好一旦你重新获取到音频焦点,在它被暂停的地方重新进行回放。
如果音频焦点遗失是永久的,就可以假设现在另外一个应用正在被用来听音频,你的App应该有效的结束自己。实际上,这意味着停止回放,移除多媒体按键监听,允许其他应用唯一的处理这些按键事件,抛弃你的音频焦点。在那时,你可能会需要一个用户动作(比如按下播放按钮),在你重新开始回放之前。
在下面的代码片段, 如果焦点遗失是瞬时的,我们暂停回放或者我们的多媒体播放对象,在我们重新获得焦点时,重新开始播放。如果焦点遗失是永久的,就反注册多媒体按钮事件监听,停止监视音频焦点变更。
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() { public void onAudioFocusChange(int focusChange) { if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT // Pause playback } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { // Resume playback } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { am.unregisterMediaButtonEventReceiver(RemoteControlReceiver); am.abandonAudioFocus(afChangeListener); // Stop playback } } };
在瞬时遗失一个焦点带闪避的Case下,比暂停回放好,你可以用“闪避”代替。
第三节:《闪避》
闪避是这样一种处理,压低你的音频输出流的音量来完成瞬时的切换到其他应用的音频上,听起来不会完全破坏你自己App的音频。
在下面的代码片段里,当我们临时的失去焦点时,压低我们多媒体对象的音量,当再次获取焦点时,返回到之前的音量级别。
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() { public void onAudioFocusChange(int focusChange) { if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { // Lower the volume } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { // Raise it back to normal } } };一个音频焦点的遗失是最重要的广播,但不是唯一重要,对于做出反应的那些来说。系统广播大量的Intent来警告你用户体验的变更。下一节展示如何监视他们,从而改善整体的用户体验。
第三课:《处理音频输出硬件》
音频能被大量的源播放。学习找到音频在哪里被播放,并且学习怎样处理在回放中,耳机断线的处理。
当用户在他们的Android设备上开始享受音频时,他们有大量的替代选择。大部分设备有一个内建的扬声器,有线耳机的插孔,并且需要还有些特殊的蓝牙连接,支持A2DP音频。
第一节:《检查哪个硬件被使用》
你的App的行为怎样,很可能受硬件的哪个输出被路由到影响。
你能查询 AudioManager 来决定,当前的音频是否被路由到设备的扬声器,有线耳机,或被连接的蓝牙。像下面代码一样:
if (isBluetoothA2dpOn()) { // Adjust output for Bluetooth. } else if (isSpeakerphoneOn()) { // Adjust output for Speakerphone. } else if (isWiredHeadsetOn()) { // Adjust output for headsets } else { // If audio plays and noone can hear it, is it still playing? }
第二节:《处理音频输出硬件的改变》
当一个耳机被拔掉,蓝牙断开连接,那个音频流自动重新路由到内建的扬声器里。如果你听到你的音乐像我做的那样音量高了一下,那可能是一个嘈杂的惊喜。
幸运的是当发生这个时,系统会广播一个Intent:
ACTION_AUDIO_BECOMING_NOISY 。 这是一个好的做法,当你正在播放音频时,注册一个广播来监听这个Intent。在这种Case下的音乐播放者,用户典型地希望它能暂停,而对于游戏,你可以选择一个显著的降低音量动作。
private class NoisyAudioStreamReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) { // Pause the playback } } } private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); private void startPlayback() { registerReceiver(myNoisyAudioStreamReceiver(), intentFilter); } private void stopPlayback() { unregisterReceiver(myNoisyAudioStreamReceiver); }