EasyPlayer声音自己主动停止、恢复,一键静音等功能

我们在开发播放器时,可能会须要静音或者减少音量的功能。比方说某款音乐播放器,当在后台播放时,假设此时有另外的系统通知声音发出。可能播放器会把音量减少,系统声音结束后,再调高;假设有来电了,播放器可能会把音乐暂停,等通话结束后再继续播放。还有,比方说我们在某个场合放个视频,不料音量非常大。会引来非常多目光(非常尴尬),这时候可能我们须要一键静音的功能。那这些功能我们应该怎样实现呢?

Android播放声音的类为AudioTrack,播放器会先把音频流demux出来,再decode。之后,把音频PCM数据通过AudioTrack类write到音频设备中,从而通过话筒或者扬声器发出声音。

为了方便地实现声音控制。我们须要从应用的最上层进行操作(由于底层可能已经被抽象成库了),也就是要从AudioTrack来入手。让我们看看AudioTrack的一些API吧。

int getPlayState ()
Returns the playback state of the AudioTrack instance.
获取当前的播放状态。这个接口会返回PLAYSTATE_STOPPED、PLAYSTATE_PAUSED、PLAYSTATE_PLAYING
三种状态,分别表示未播放、暂停中、正在播放
void pause ()
Pauses the playback of the audio data. Data that has not been played back will not be discarded. Subsequent calls to play() will play this data back. See flush() to discard this data.
暂停播放音频数据。

已经在缓冲区中的未播放数据将不会被丢弃,在下次play的时候继续播放。调用flush则会丢弃缓冲数据。

void play ()
Starts playing an AudioTrack.
開始播放
int setStereoVolume (float leftGain, 
                float rightGain)

Sets the specified left and right output gain values on the AudioTrack.
设置左右声道的音量增益。

有了这几个API。足以满足我们的需求。实现起来就非常easy了。

首先我们做一键静音功能。

我们能够做个切换的button。这个button初始状态是要显示当前的播放状态:正在播放音频或未在播放音频。

播放状态能够调用getPlayState ()来获取到;然后button按下后,再依据播放状态进行播放或暂停。
代码例如以下:

mAudioEnable = mAudioTrack!=null && mAudioTrack.getPlayState()==PLAYSTATE_PLAYING;

public void setAudioEnable(boolean enable) {
     mAudioEnable = enable;
     AudioTrack at = mAudioTrack;
     if (at != null) {
         synchronized (at) {
             if (!enable) {
                 at.pause();
                 at.flush();
             } else {
                 at.flush();
                 at.play();
             }
         }
     }
 }

注意这里在pause之后。play之前都调用了flush接口。这样能够确保在由暂停到播放切换时,不会把暂停时未播放的“旧数据”播放出来。

接下来我们实现音频资源被其他进程占用(失去焦点)时,自己主动减少声音或者停止声音;在音频资源又被释放(又一次获取到焦点)时再恢复播放的功能。

我们须要通过AudioManager来推断当前音频资源的状态。而且在音频焦点更改时得到回调。其关键API接口有:

int requestAudioFocus (AudioManager.OnAudioFocusChangeListener l, 
                int streamType, 
                int durationHint)
Request audio focus. Send a request to obtain the audio focus
请求获取音频焦点。

第一个參数为音频焦点更改时的回调; 第二个參数为音频类型,在我们调节音量时能够看到有若干种音量,就相应的这里的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实例
final AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener l = new AudioManager.OnAudioFocusChangeListener() {
    @Override
    public void onAudioFocusChange(int focusChange) {
        if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {// 焦点获取到了,那继续播放,并恢复音量。
            AudioTrack audioTrack = mAudioTrack;
            if (audioTrack != null) {
                audioTrack.setStereoVolume(1.0f, 1.0f);
                if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
                    audioTrack.flush();
                    audioTrack.play();
                }
            }
        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {// 焦点丢失了,暂停播放。
             AudioTrack audioTrack = mAudioTrack;
            if (audioTrack != null) {
                if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                    audioTrack.pause();
                }
            }
        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { // 焦点丢失了。可是同意在减少音量的前提下继续播放,那么减少声音。
            AudioTrack audioTrack = mAudioTrack;
            if (audioTrack != null) {
                audioTrack.setStereoVolume(0.5f, 0.5f);
            }
        }
    }
};
// 由于这里要获得的焦点无法预知时长,因此用AUDIOFOCUS_GAIN模式。
int requestCode = am.requestAudioFocus(l, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (requestCode == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
   // 成功获取到了焦点。那启动播放
   AudioTrack audioTrack = mAudioTrack;
    if (audioTrack != null) {
        audioTrack.setStereoVolume(1.0f, 1.0f);
        if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
            audioTrack.flush();
            audioTrack.play();
        }
    }
}else{  // 没有获取到音频焦点。那不播放声音
    AudioTrack audioTrack = mAudioTrack;
    if (audioTrack != null) {
        if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
            audioTrack.pause();
        }
    }
}

至此,我们便实现了EasyPlayer的声音自己主动停止、恢复,一键静音的功能的实现。看起来挺麻烦对吗?事实上做一个app非常easy,可是要想做的好,各种情况都兼顾了,却是非常不easy的。我们不防多看些系统APP的实现。或者Google官方的一些DEMO,它们往往都看似功能非常easy,会让我们认为:“假设是我做的话。几行代码就可以搞定。

。”,可是它们的代码量却非常大,由于它们兼顾了各种细节。而往往我们开发出来绝大多数app的都仅仅能算是半成品。都有继续优化的余地。

原文地址:https://www.cnblogs.com/wgwyanfs/p/7294967.html