Android工作总结之如何做一个优秀的MediaPlayer音频播放器

参考:
http://developer.android.com/guide/topics/media/mediaplayer.html

最近接触了项目播放的模块,就来总结下吧.

1.基础工作

声明权限

"android.permission.INTERNET" />

唤醒权限(后面会用到)

"android.permission.WAKE_LOCK" />

顺便说下AudioManager.setAudioStream(STREAM_RING,STREAM_RING,STREAM_VOICE_CALL等等)就是设置MediaPlayer对应的流.我们平时按音量键弹出的音量条有时候是铃声,有时候是媒体,说明不同的流对应独立的音量设置.系统会根据当前播放的流弹出对应的音量条.
关于prepare().谷歌推荐做法是调用prepareAsync()然后设置OnPrepareListener在onPrepare()里进行播放.因为解码一些比较大的文件的时候会花一些时间从而导致ANR.其实这个也不一定,灵活运用就好.

2.状态处理

一句话,在错误的状态做错误的处理会出错.呵呵…..听起来像废话
先上一张图Android工作总结之如何做一个优秀的MediaPlayer音频播放器_第1张图片
setDateSource()–>initialState
prepare() or prepareAsync() –>PrepareState
之后你就可以解锁很多姿势比如start() pause() seekTo()等
调用 stop()之后你就需要重新初始化了,这时候调用start pause等就会报错.
推荐的释放方式:

mediaPlayer.release();mediaPlayer = null;

尽量在Service中控制Mediaplayer,可以避免很多问题,比如横竖屏的切换.
setOnErrorListener()一般用来写reset Mediaplayer的代码.

3.唤醒MediaPlayer

当我们手机在后台运行app时,一旦进入sleep状态,就会做一些省电的措施比如关闭wifi,降低CPU运行效率.这样MediaPlayer就不能正常播放.安卓提供了一些方法能保证MediaPlayer正常播放.
唤醒CPU:
MediaPlayer.setWakeMode().调用这个方法,会在播放的时候hold住这个锁,在release或pause时候释放这个锁.

mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

唤醒wifi:
网络播放需要保持wifi开启.

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

相比上一个锁,这个锁是需要手动释放的

wifiLock.release();

4.在前台运行:

也就是做成一个Notification.

PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,                new Intent(getApplicationContext(), MainActivity.class),                PendingIntent.FLAG_UPDATE_CURRENT);Notification notification = new Notification();notification.tickerText = text;notification.icon = R.drawable.play0;notification.flags |= Notification.FLAG_ONGOING_EVENT;notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",                "Playing: " + songName, pi);startForeground(NOTIFICATION_ID, notification);//不用了的话就调用stopForeground(true)

这里的知识点都跟Service,Notification有关了,不打算在这篇里详讲.就好比面向对象编程的其中一个原则——单一职责原则:一个类设计的时候最好只做一件事(Ps:这特么跟写文章有毛的关系,明明就是懒好吗!).

5.焦点处理AudioFocus

这个功能点是用起来最高大上的.
首先说一下,手机里有很多MediaPlayer播放器,各个app之间是没有代码的交集的.我们在播放音乐的过程中收到短信提示音,播放器会调低音量,收到来电会暂停播放,这些就是通过焦点机制完成的.
请求焦点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) {    // could not get audio focus. } 

this指的是实现OnAudioFocusChangeListene的类

class MyService extends Service implements AudioManager.OnAudioFocusChangeListener {    public void onAudioFocusChange(int focusChange) {        // Do something based on focus change...     } } 
focusChange类型:
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT 推荐做法会让其他播放器暂停pause playback(对应AUDIOFOCUS_LOSS_TRANSIENT)
AudioManager.AUDIOFOCUS_GAIN 这个好像是长期获得焦点推荐做法会让其他播放器停止stop playback(对应AUDIOFOCUS_LOSS)
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 推荐做法会让其他播放器降低音量 setVolume playback(对应AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 和上面的AUDIOFOCUS_GAIN_TRANSIENT_MAY_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;     } } 

6.处理AUDIO_BECOMING_NOISY

就是耳机的插口从洞里面拔出时,如果不做处理那就会让声音从手机扩音器放出来.顾名思义说的就是这种noisy的情况.
这时安卓会发送一个intent,我们需要定义一个广播去接受他.
manifest注册:

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

代码处理:

public class MusicIntentReceiver extends android.content.BroadcastReceiver {   @Override    public void onReceive(Context ctx, Intent intent) {      if (intent.getAction().equals(  android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {          // 暂停就可以了      }    } } 

7.播放本地音频

安卓系统会把音频信息加入数据库(找不到就重启下)方便我们通过Content Provider查找.

ContentResolver contentResolver = getContentResolver();Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;Cursor cursor = contentResolver.query(uri, null, null, null, null);if (cursor == null) {    // query failed, handle error. } else if (!cursor.moveToFirst()) {    // no media on the device } else {     int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);    int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);    do {        long thisId = cursor.getLong(idColumn);       String thisTitle = cursor.getString(titleColumn);       // ...process entry...     } while (cursor.moveToNext());} 

找到数据后进行调用:

long id = /* retrieve it from somewhere */;Uri contentUri = ContentUris.withAppendedId(        android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);mMediaPlayer = new MediaPlayer();mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setDataSource(getApplicationContext(), contentUri);

8.总结

以上是工作中经常用到的东西,安卓已经在APIGuide里帮我们总结了.我只是负责翻译,多看看官网的东西还是比较实用的,有事比去搜索一些博客要全面很多.

更多相关文章

  1. Android NDK c调用java代码
  2. Android 代码风格指南
  3. Android px和dip及sp的区别及转换代码
  4. Android 源代码编译前后的目录结构
  5. Android有用代码片段(三)
  6. Android 通过代码实现控制数据网络的开关(仅适用于5.0以上)
  7. 安卓手机恶意代码——Samsapo

随机推荐

  1. 自定义Button形状(圆形、椭圆)
  2. [RxJava学习]observeOn源码分析
  3. Android欢迎页面自动跳转和触摸进入首页
  4. 比achartengine更加强大的Android图表控
  5. Android查看应用的最大可用内存及其使用
  6. 安卓漫漫路之碎片Fragment不同包下的区别
  7. android使用存储在assets文件夹中的Linux
  8. Android 实现多页界面左右滑动切换效果之
  9. 回调函数在Android监听机制中的体现
  10. 意图过滤器捕获所有共享意图