控制音量与音频播放

Android为播放音乐,闹铃,通知铃,来电声音,系统声音,打电话声音与拨号声音分别维护了一个独立的音频流.默认情况下,按下音量控制键会调节当前被激活的音频流,如果我们的应用当前没有播放任何声音,那么按下音量键会调节响铃的音量。


1.设置需要控制的音频类型
AudioTrack Java API 音频流类型:

Stream Type Description
STREAM_VOICE_CALL 电话语音
STREAM_SYSTEM 系统声音
STREAM_RING 铃声声音,如来电铃声、闹钟铃声等
STREAM_MUSIC 音乐声音
STREAM_ALARM 警告音
STREAM_NOTIFICATION 通知音
STREAM_DTMF DTMF 音(拨号盘按键音)

设置音频类型的方法:

setVolumeControlStream(AudioManager.STREAM_MUSIC);

自此之后,不管目标Activity或Fragment是否可见,按下设备的音量键都能够影响我们指定的音频流(在这个例子中,音频流是”music”)。

2. 监听控制音频的物理按钮事件
无论用户按下设备上任意一个控制按钮,系统都会广播一个带有ACTION_MEDIA_BUTTON的Intent。为了正确地响应这些操作,需要在Manifest文件中注册一个针对于该Action的BroadcastReceiver,如下所示:

<receiver android:name=".RemoteControlReceiver">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</receiver>

在Receiver的实现中,需要判断这个广播来自于哪一个按钮,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来为我们的应用注册监听与取消监听媒体按钮事件,当Receiver被注册上时,它将是唯一一个能够响应媒体按钮广播的Receiver。

AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...
// Start listening for button presses
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
...
// Stop listening for button presses
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);

3.管理音频焦点
通常,应用需要在他们失去焦点或者不可见的时候(比如在onStop()方法里面)取消注册监听。但是对于媒体播放应用来说并没有那么简单,实际上,在应用不可见(不能通过可见的UI控件进行控制)的时候,仍然能够响应媒体播放按钮事件是极其重要的。为了实现这一点,有一个更好的方法,我们可以在程序获取与失去音频焦点的时候注册与取消对音频按钮事件的监听。

在我们的应用开始播放音频之前,它需要先请求音频焦点,然后再获取到音频焦点。另外,它还需要知道如何监听失去音频焦点的事件并对此做出合适的响应。

  • 请求获取音频焦点

在我们的应用开始播放音频之前,它需要获取将要使用的音频流的音频焦点。通过使用requestAudioFocus() 方法可以获取我们希望得到的音频流焦点。如果请求成功,该方法会返回AUDIOFOCUS_REQUEST_GRANTED。

另外我们必须指定正在使用的音频流,而且需要确定所请求的音频焦点是短暂的(Transient)还是永久的(Permanent)。

短暂的焦点锁定:当计划播放一个短暂的音频时使用(比如播放导航指示)。
永久的焦点锁定:当计划播放一个较长但时长可预期的音频时使用(比如播放音乐)。

下面的代码片段是一个在播放音乐时请求永久音频焦点的例子,我们必须在开始播放之前立即请求音频焦点,比如在用户点击播放或者游戏中下一关的背景音乐开始前。

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监听器。对于另一种释放短暂音频焦点的情况,这会允许任何被我们打断的应用可以继续播放。

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

当请求短暂音频焦点的时候,我们可以选择是否开启“Ducking”。通常情况下,一个应用在失去音频焦点时会立即关闭它的播放声音。如果我们选择在请求短暂音频焦点的时候开启了Ducking,那意味着其它应用可以继续播放,仅仅是在这一刻降低自己的音量,直到重新获取到音频焦点后恢复正常音量(译注:也就是说,不用理会这个短暂焦点的请求,这并不会打断目前正在播放的音频。比如在播放音乐的时候突然出现一个短暂的短信提示声音,此时仅仅是把歌曲的音量暂时调低,使得用户能够听到短信提示声,在此之后便立马恢复正常播放)。

// 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.
}
  • 处理失去音频焦点

在音频焦点的监听器里面,当接受到描述焦点改变的事件时会触发onAudioFocusChange()回调方法。如之前提到的,获取焦点有三种类型,我们同样会有三种失去焦点的类型:永久失去,短暂失去,允许Ducking的短暂失去。

  1. 失去短暂焦点:通常在失去短暂焦点的情况下,我们会暂停当前音频的播放或者降低音量,同时需要准备在重新获取到焦点之后恢复播放。
  2. 失去永久焦点:假设另外一个应用开始播放音乐,那么我们的应用就应该有效地将自己停止。在实际场景当中,这意味着停止播放,移除媒体按钮监听,允许新的音频播放器可以唯一地监听那些按钮事件,并且放弃自己的音频焦点。此时,如果想要恢复自己的音频播放,我们需要等待某种特定用户行为发生(例如按下了我们应用当中的播放按钮)。

在下面的代码片段当中,如果焦点的失去是短暂型的,我们将音频播放对象暂停,并在重新获取到焦点后进行恢复。如果是永久型的焦点失去事件,那么我们的媒体按钮监听器会被注销,并且不再监听音频焦点的改变。

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
        }else if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {//可以调低音量
            // Lower the volume
        }
    }
};

4.检测目前正在使用的硬件设备
使用不同的硬件播放声音会影响到应用的行为。可以使用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?
}

5.处理音频输出设备的改变
系统会在这种情况下广播带有ACTION_AUDIO_BECOMING_NOISY的Intent。无论何时播放音频,我们都应该注册一个BroadcastReceiver来监听这个Intent。在切换不同的播放音频设备的时候进行不同的处理:

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);
}

猜你喜欢

转载自blog.csdn.net/ysq_chris/article/details/80944777