Android多媒体MediaPlayer【待修改草稿】

Quibbler 2019-10-14 647

Android多媒体MediaPlayer


        Android提供了多媒体播放库MediaPlayer,在播放音视频的时候可以方便的使用。存储在应用程序资源(原始资源)中的媒体文件、文件系统中的独立文件或通过网络连接到达的网络数据流中播放音频或视频。在使用MediaPlayer对应用程序进行开发之前,必须声明一下权限,才能正常使用:

        Internet权限——如果您正在使用MediaPlayer来播放流基于网络的内容,那么您的应用程序必须请求网络访问。

<uses-permission android:name="android.permission.INTERNET" />

        WakeLock权限——如果您的播放器应用程序需要阻止屏幕变暗或处理器休眠,或者使用Mediaplayer.setScreenonWhilePlay()MediaPlayer.setWakeMode()方法,您必须请求此权限。

<uses-permission android:name="android.permission.WAKE_LOCK" />


使用MediaPlayer

        媒体框架最重要的组件之一是MediaPlayer类。这个类的对象可以使用最少的设置获取、解码和播放音频和视频。它支持几种不同的媒体来源,如:

  • 本地资源
  • 内部uri,例如您可能从contentProvider获得的uri
  • 外部url(流) 有关Android支持的媒体格式列表

        下面举个例子来展示如何播放本地(存储在 res/raw/ 目录下)音频文件:

        //一种使用/res/raw资源的播放方式,操作代码比较复杂,但是通用
        MediaPlayer mediaPlayer = new MediaPlayer();
        try {
            AssetFileDescriptor afd = this.getResources().openRawResourceFd(R.raw.shape_of_you);
            mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
            afd.close();
            mediaPlayer.prepare();
            mediaPlayer.start();
        } catch (Exception e) {
            mediaPlayer.release();
        }

        另一种简单简单快速的方式是使用MediaPlayer类中的create()静态方法快速创建一个设置好资源的多媒体对象:

        MediaPlayer mediaPlayer = MediaPlayer.create(this,R.raw.shape_of_you);
        try {
            mediaPlayer.start();
        } catch (Exception e) {
            mediaPlayer.release();
        }

        查看MediaPlayer中的源码,可以发现这其中已经做了大量的封装工作,方便使用。可以从中借鉴一些写自己的通用播放的方法,因为MediaPlayer.setDataSource(),没有直接设置资源ID进行播放的方法,只能采用第一张相对麻烦的方法。

        使用MediaPlayer播放网络资源也是非常的方便,不够需要注意的是通过一个URL来传输流媒体在线文件,该文件必须能够逐步下载。网络方面注意是否开启网络权限以及android:usesCleartextTraffic="true",否则可能调试半天还不能播放。

MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); //目前已经废弃该方法
mediaPlayer.setDataSource("http://m7.music.126.net/123/test.mp3");                            //网络资源url
mediaPlayer.prepare();                                     // might take long! (for buffering, etc)
mediaPlayer.start();

MediaPlayer.prepare()和MediaPlayer.prepareAsync()异步加载

        MediaPlayer 试用起来很简单。然而,当我们将它集成在应用中时,需要特别留意一些注意点。举个例子,prepare() 这个方法可能会执行很长时间,因为它会去获取并解析多媒体文件。所以这种耗时的操作,我们 千万不要在UI线程中去执行. 这样的操作会阻塞UI线程,这是非常差的用户体验,并且有可能造成程序ANR(应用程序无响应)。像前面那种播放网络资源的情况,需要缓存,网络请求会非常的慢,所以最好在子线程中处理完,再回到主线程播放:

      	    Handler mHandler = new Handler(getMainLooper());
            new Thread(new Runnable() {
                @Override
                public void run() {
                    MediaPlayer mediaPlayer = new MediaPlayer();
                    try {
                        mediaPlayer.setDataSource("http://m7.music.126.net/123/test.mp3");
                        mediaPlayer.prepare();//最好放在子线程中,当媒体文件准备好了,再通知主线程播放
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mediaPlayer.start();
                            }
                        });
                    } catch (Exception e) {
                        mediaPlayer.release()
                    }
                }
            }).start();

        为了避免阻塞UI线程,可以开辟一个线程来准备 MediaPlayer,准备完成后通知UI线程。可以自己写线程实现这样的异步操作,就像上面那样,不过MediaPlayer 提供了 prepareAsync() 方法来让我们更加容易实现这样的逻辑。这个方法将在后台去准备多媒体的播放。当完成准备工作后,MediaPlayer.OnPreparedListener 接口中的 onPrepared() 方法将会执行,这个接口是通过 setOnPreparedListener() 方法来设置的:

        MediaPlayer mediaPlayer = new MediaPlayer();
        try {
            mediaPlayer.setDataSource("http://m7.music.126.net/123/test.mp3");
            mediaPlayer.prepareAsync();
            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mp.start();
                }
            });
        } catch (Exception e) {
            mediaPlayer.release();
        }

        在后台Service中播放音乐一定要使用prepareAsync(),因为Service和Activity一样在主线程,不能执行耗时操作。


基于状态的MediaPlayer 

        MediaPlayer 需要记住的另一点是:它是基于状态的。也就是说,我们在写代码时必须要清楚 MediaPlayer 的状态,因为一些特定的操作只可以在特定的状态下才可以执行。如果你在某个状态下执行的错误的操作,系统可能会抛出异常或者造成其他不合理的行为。

        上图展示了完整的状态,它清晰的描述了哪个方法能够改变 MediaPlayer 的状态。比如说,当创建了一个 MediaPlayer, 它处于 Idle 状态。这时候调用 setDataSource(), 将会进入 Initialized 状态。然后调用 prepare() 或者 prepareAsync() 方法进行初始化操作操作。当 MediaPlayer 完成初始化操作后,就进入了 Prepared 状态, 这时候就可以调用 start() 来播放了。这个状态下,可以通过调用 start(), pause(),和 seekTo() 去将状态改变为 Started, Paused and PlaybackCompleted。除非调用了 stop(), 否则不可以对同一个 MediaPlayer 调用多次 start()


释放媒体播放器:MediaPlayer.release() 

        MediaPlayer 会消耗珍贵的系统资源。因此,必须要确保能够及时释放掉 MediaPlayer。当使用完 MediaPlayer 后,应该调用 release() 来确保分配的资源能够正确的得到释放。比如说,当我们在 Activity 中使用 MediaPlayer,当 Activity的 onStop() 方法被调用时, 这时候必须释放掉 MediaPlayer, 因为当 Activity 不再和用户交互时,维持着它的引用没有任何意义.必须在重写的onDestory()方法中释放MediaPlayer对象。

protected void onDestroy() {
        super.onDestroy();
        if (mMediaPlayer != null) {
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }


使用唤醒锁:Wake Lock

        当应用程序在后台播放多媒体时,设备可能会进入休眠状态即使 service 还在运行。因为Android系统通过设备休眠来节省电量,系统将会尽可能关闭不必要的模块,包括 CPU 和 Wifi硬件。如果想要service在后台播放或者缓冲音乐,并且不想系统干扰。想要保证 service 持续运行,必须使用"wake locks." Wake lock 被用来通知应用程序正在使用一些功能,并且这些功能不应该在待机状态下被关闭。

        Notice: 你应该谨慎使用 wake locks,并且使用时间需要尽可能短,因为他们会显著地减少电池使用寿命。

        为了确保 MediaPlayer 播放时CPU持续工作,应该调用 setWakeMode() 方法当初始化 MediaPlayer的时候。一旦这么做了,MediaPlayer 将在播放时持有这个特殊的 lock,并在 paused或 stopped时释放它:

MediaPlayer mMediaPlayer = new MediaPlayer();
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

        上面示例中的代码只能确保CPU会一直工作。如果你使用 Wi-Fi 播放流媒体,你还需要持有 WifiLock,需要手动获取和释放。当通过远程的 URL 来准备 MediaPlayer 时,应该创建并获取 Wi-Fi lock. 示例如下:

WifiManager.WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
                                 .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
                                 
wifiLock.acquire();

         当你暂、者停止了播放,或者不再需要连接网络,应该释放掉 lock:

wifiLock.release();


Android 的 Audio Focus机制

        在 Android2.2 之前,没有内置的机制来处理这个问题,这可能会导致非常差的用户体验。比如当用户在听音乐时,另外一个应用需要提示用户一个非常重要的通知,但是由于音乐声音太大用户无法听见通知的声音。Android2.2之后,平台提供了一种方式来协调设备的音频输出。这就是 Android 的 Audio Focus 机制

        要获得 Audio Focus,必须调用 AudioManager 的 requestAudioFocus() 方法,像下面的代码一样:

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
        if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            //获取Audio Focus失败
        }
    }

        requestAudioFocus() 的第一个参数是 AudioManager.OnAudioFocusChangeListener,这个接口中的 whose onAudioFocusChange() 方法将会在 audio focus 改变时调用。因此,你应该在 Service 或者 Activity 中实现这个接口implements AudioManager.OnAudioFocusChangeListener。举个例子:

@Override
    public void onAudioFocusChange(int focusChange) {
        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                //获得了 Audio Focus.
                break;
            case AudioManager.AUDIOFOCUS_LOSS:
                //失去 audio focus 很长一段时间。必须停止所有的 Audio 播放。因为很长一段时间内将不会再次获取 Audio Focus,此时应该尽可能地清理资源。比如,应该释放掉 MediaPlayer.
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                //暂时失去 Audio Focus,但是很快就会重新获得。应该停止所有的音频播放,但是可以不清里资源,因为可能很快就会再次获取 Audio Focus.
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                //暂时失去 Audio Focus,但是允许持续播放音频(以很小的声音),不需要完全停止播放。
                break;
            default:
                break;
        }
    }
  • AUDIOFOCUS_GAIN: 获得了 audio focus.
  • AUDIOFOCUS_LOSS: 失去 audio focus 很长一段时间。必须停止所有的 audio 播放。因为很长一段时间内将不会再次获取 audio focus,此时应该尽可能地清理资源。比如,应该释放掉 MediaPlayer.
  • AUDIOFOCUS_LOSS_TRANSIENT: 暂时失去 audio focus,但是很快就会重新获得。应该停止所有的音频播放,但是可以不清里资源,因为可能很快就会再次获取 audio focus.
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 暂时失去 audio focus,但是允许持续播放音频(以很小的声音),不需要完全停止播放。

        需要注意的是Audio Focus APIs 只能使用在 API level 8 (Android 2.2)以上,所以想要兼容 Android 后面的版本,必须要采取一个向后兼容的策略来使用 Audio Focus 机制。可以在检测到 Android 版本大于或等于8时,创建一个 AudioFocusHelper 实例。示例如下:

if (android.os.Build.VERSION.SDK_INT >= 8) {
    mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
    mAudioFocusHelper = null;
}


突然拔出耳机AUDIO_BECOMING_NOISY

        许多优秀的音频应用能够在声音变得嘈杂(通过外部扬声器播放)时自动停止播放。比如说,当用户戴着耳机听歌时,突然拔出耳机。当然,这个行为不会自动发生。如果没有实现这个功能,将会通过外部扬声器来播放音频,这可能不是用户期望的行为。

        可以通过处理 ACTION_AUDIO_BECOMING_NOISY 来让应用停止播放音乐,可以在 manifest 中注册一个广播来实现:

<receiver android:name=".MusicIntentReceiver">
   <intent-filter>
      <action android:name="android.media.AUDIO_BECOMING_NOISY" />
   </intent-filter></receiver>

         监听下面这条广播即可:

<action android:name="android.media.AUDIO_BECOMING_NOISY" />

        注册了 MusicIntentReceiver 广播来处理这个 intent.应该实现下面的逻辑:

public class MusicIntentReceiver implements android.content.BroadcastReceiver {
   @Override
   public void onReceive(Context ctx, Intent intent) {
      if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
          // signal your service to stop playback
          // (via an Intent, for instance)
      }
   }}


MediaStore检索媒体

        借助ContentProvider,通过MediaStore.Audio.Media.EXTERNAL_CONTENT_URI这个URI来查询媒体数据:

                ContentResolver localMusicResolver = getContentResolver();
                Cursor localMusicCursor = localMusicResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
                if (localMusicCursor == null) {
                    return;
                }
                while (localMusicCursor.moveToNext()) {
					//歌曲名
                    localMusicCursor.getString(localMusicCursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME));
                    //歌手名
					localMusicCursor.getString(localMusicCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
                    //歌曲大小
					localMusicCursor.getLong(localMusicCursor.getColumnIndex(MediaStore.Audio.Media.SIZE));
                    //歌曲路径
					localMusicCursor.getString(localMusicCursor.getColumnIndex(MediaStore.Audio.Media.DATA));
                    //歌曲本地ID
					localMusicCursor.getLong(localMusicCursor.getColumnIndex(MediaStore.Audio.Media._ID));
                    //歌曲专辑ID
					localMusicCursor.getString(localMusicCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
                }
                localMusicCursor.close();



不忘初心的阿甘
最新回复 (2)
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com