《Android优化专题》——音频播放

《Android优化专题》——音频播放

一、控制app的音量与播放

使用硬件音量键来控制音量

需要在Activity或者Fragment创建的时候就设置音量控制,这样确保不管App是否可见,音频控制功能都正常工作。

1
setVolumeControlStream(AudioManager.STREAM_MUSIC);

使用硬件的播放控制按键来控制App的音频播放

无论用户通过手机或者线控耳机等按下哪些控制按钮,比如播放、暂停,系统都会广播一个带有ACTION_MEDIA_BUTTON的Intent。

1
2
3
4
5
<receiver android:name=".RemoteControlReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>

Receiver需要判断广播来自哪个按钮

1
2
3
4
5
6
7
8
9
10
11
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.
}
}
}
}

如何注册监听和取消监听

1
2
3
4
5
6
7
8
9
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...

// Start listening for button presses
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
...

// Stop listening for button presses
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);

二、管理音频焦点

请求获取音频焦点

requestAudioFocus() 来获取到音频流焦点。

  • 短暂的焦点锁定:当期待播放一个短暂的音频时候(比如推送声音)
  • 永久的焦点锁定:当计划播放可预期到的较长的音频时候(比如播放音乐)

我们必须在开始播放前请求音频焦点,比如用户此时点击了播放按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
// Start playback.
}

一旦结束播放,需要调用abandonAudioFocus()方法,通知系统说不再需要获取焦点并且取消注册AudioManager.OnAudioFocusChangeListener的焦点。

1
2
// Abandon audio focus when playback complete
am.abandonAudioFocus(afChangeListener);

当请求短暂音频焦点,我们可以选择是否开启”ducking”,Ducking机制可以允许音频间歇性短暂播放。可以让其他App继续播放,仅在短暂的时间内降低自己的音量。

1
2
3
4
5
6
7
8
9
10
// 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.
}

处理失去音频焦点

  • 失去短暂焦点:在这种情况下,暂停当前音频的播放或者降低音量,需要准备恢复播放在重新获取到焦点之后。
  • 失去永久焦点:假设另一个程序开始播放音乐,此时我们的程序就应该彻底结束。停止播放,放弃自己的音频焦点。
  • Ducking:降低音量,让其余短暂声音突出,之后恢复原音量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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==AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
// Lower the volume
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
am.abandonAudioFocus(afChangeListener);
// Stop playback
// 失去永久焦点
}
}
};

三、音频设备的相关问题

检测目前正在使用的硬件设备

可以使用AudioManager来查询某个音频是否输出到扬声器,有线耳机还是蓝牙上。

1
2
3
4
5
6
7
8
9
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?
}

处理音频输出设备的改变

当耳机线被拔出,或者蓝牙耳机连接断开时,如果在播放音乐/视频,为了用户体验,避免突如其来的扬声器播放,我们通常做法是暂停此时正在播放的音乐/视频。

在这种情况下,系统会广播带有ACTION_AUDIO_BECOMING_NOISY的intent。我们只需要接受这种广播,对其进行处理即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}