Android Media Player

2015/11/27 posted in  Android

Audio Stream

Android为不同的应用场合定义了不同的Audio Stream: Voice Call, Ring, Music,Alarm, Notification, DTMF。 这些AudioStream是相互独立的,所以也有各自的音量

使用

最重要的类是 MediaPlayer,获取、解码、播放
播放 res/raw 中的文件 举个🌰

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you

异步准备

可能需要比较长的时间,media 需要获取资源和解码,最好不要放在 UI 线程。
此时可以使用prepareAsync()方法,这个方法在后台运行,当准备工资就绪之后在MediaPlayer.OnPreparedListener返回,可以在回调函数setOnPreparedListener()中设置

mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
    @Override
    public void onPrepared(MediaPlayer mp) {
        // TODO: 15/10/27  
    }
});

管理状态

player 有特有的状态,当操作 player 时只能当 player 处理特定的状态中,否则会抛出异常或者其他一些想不到的情况。
举个🌰,当 creat一个新MediaPlayer时,他处于idle状态,在这个状态中,可以初始化,调用setDataSource(),这样就进入了Initialized 状态,接着我们调用prepare()或者 prepareAsync(),当MediaPlayer准备完全之后,会进入Prepared状态,当start()播放之后,可以调用 paused stopped 等;

状态流程如图
状态流程

释放MediaPlayer

MediaPlayer会消耗系统资源,当不在需要的时候需要用release()释放,

mediaPlayer.release();
mediaPlayer = null;

使用 Service MediaPlayer

…………

audio focus

按照AudioFocus的机制,在使用Audio之前,需要申请AudioFocus,在获得AudioFocus之后才可以使用Audio;如果有别的程序竞争你正在使用的Audio,你的程序需要在收到通知之后做停止播放或者降低声音的处理。值得指出的是,这种机制是需要合作完成的,需要所有使用Audio资源的程序都按照这种机制来做,而如果有程序在它失去AudioFocus的时候仍然在使用Audio,AudioFocus拿它也没办法。而这一点对于开放系统的Android来说很致命的:用户可能安装没遵守这种机制的程序,或者版本太老还没引入这种机制的程序,这最终会导致很差的用户体验。

当应用需要播放声音或者 notification 时,需要请求audio focus,一旦获取焦点之后,便可以播放声音,如果有很多别的audio focus,要么停止播放此时的音乐,或者lower it to a quiet level,再之后获取audio focus继续播放

获取/放弃AudioFocus的方法都在android.media.AudioManager中,获取AudioFocus用requestAudioFocus();用完之后,放弃AudioFocus,用abandonAudioFocus()

举🌰

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
    AudioManager.AUDIOFOCUS_GAIN);

if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // could not get audio focus.
}

requestAudioFocus()的第一个参数 是AudioManager.OnAudioFocusChangeListener其中的onAudioFocusChange()方法
🌰

class MyService extends Service
                implements AudioManager.OnAudioFocusChangeListener {
    // ....
    public void onAudioFocusChange(int focusChange) {
        // Do something based on focus change...
    }
}

其onAudioFocusChange()方法是Audio Focus被抢占与再次获得通知的地方。所以,每个要使用AudioFocus的程序都要小心实现这个函数,保证AudioFocus实现的一致性。申请成功之后监听AudioFocus使用情况的Listener,参数 foucsChange

  • AUDIOFOCUS_GAIN:获取到了焦点
  • AUDIOFOCUS_LOSS:失去焦点很长一段时间,必须停止所有的 audio playback,此时可以释放资源了,比如释放 MediaPlayer
  • AUDIOFOCUS_LOSS_TRANSIENT:暂时失去焦点,但是会之后获取到焦点。此时必须停止播放,但是资源不必释放,因为之后会获取焦点
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 暂时失去焦点,但是允许继续小声的播放音乐,而不必关闭播放 举个🌰
public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            // resume playback
            if (mMediaPlayer == null) initMediaPlayer();
            else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
            mMediaPlayer.setVolume(1.0f, 1.0f);
            break;

        case AudioManager.AUDIOFOCUS_LOSS:
            // Lost focus for an unbounded amount of time: stop playback and release media player
            if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
            break;

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            // Lost focus for a short time, but we have to stop
            // playback. We don't release the media player because playback
            // is likely to resume
            if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
            break;

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // Lost focus for a short time, but it's ok to keep playing
            // at an attenuated level
            if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
            break;
    }
}

下面的时序图描述了AudioFocus被抢占与再次获取的典型场景

  • AudioFocus Client通过requestAudioFocus()获取AudioFocus,在获得AudioFocus之后,开始播放Audio[Step#1 ~ #2];
  • 其它程序(Other App)也通过requestAudioFocus()获取AudioFocus [Step#3]
  • AudioFocus Client失去了Audio Focus,在onAudioFocusChanged()中,根据focusChange【focusChange的值与Other App申请时的durationHint相反,即focusChange = -1*durationHint】的值,做第二节中所描述的处理[Step#4];
  • 其它程序(Other App)获取Audio Focus之后,开始播放Audio[Step#5];
  • 其它程序(Other App)使用Audio之后,通过abandonAudioFocus()归还AudioFocus [Step#6];
  • AudioFocus Client重新获得了Audio Focus,可做进一步的处理 [Step#7]

AudioFocus中虽然把AudioStream作为参数,但是AudioFocus的内部裁决机制并未针对AudioStream做什么特别的处理。AudioFocus的处理针对所有的申请者来说的,除了它自身内部作为Alert的申请者有点特殊外,其它一律平等。