作者:邹峰立,微博:zrunker,邮箱:zrunker@yahoo.com,微信公众号:书客创作,个人平台:www.ibooker.cc。
书客创作
如果对MediaPlayer不理解推荐这篇文章【Android】MediaPlayer生命周期分析。
要想利用MediaPlayer实现音频的播放,首先要对MediaPlayer进行初始化工作,得到MediaPlayer对象,在通过MediaPlayer进行相应的操作。
一般过程:初始化MediaPlayer - 加载媒体源 - 准备 - 开始播放
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource("...");
mediaPlayer.prepare();
mediaPlayer.start();
MediaPlayer支持多种不同的媒体源: 本地资源、内部的URI,比如一个你可能会从ContentResolver获取的uri、外部URL(流)。
1、raw文件中媒体源:假如res/raw文件中包含一个sound_music.mp3文件。
MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.sound_music);
2、assets文件中媒体源:假如在assets中包含一个sound_music.mp3文件。
try {
AssetFileDescriptor fd = getAssets().openFd("sound_music.mp3");
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
} catch (IOException e) {
e.printStackTrace();
}
3、SD卡中媒体源:假如在SD卡中包含一个sound_music.mp3文件。
try {
MediaPlayer mediaPlayer = new MediaPlayer();
String path = "/sdcard/sound_music.mp3";
mediaPlayer.setDataSource(path);
} catch (IOException e) {
e.printStackTrace();
}
4、网络资源:假如有一个网络资源http://ibooker.cc/ibooker/musics/sound_music.mp3。
MediaPlayer mediaPlayer = new MediaPlayer();
// 方式一
// Uri uri = Uri.parse("http://ibooker.cc/ibooker/musics/sound_music.mp3");
// mediaPlayer.setDataSource(this, uri);
// 方式二
mediaPlayer.setDataSource("http://ibooker.cc/ibooker/musics/sound_music.mp3");
MediaPlayer常用方法
int getCurrentPosition();// 得到当前播放位置(ms)
int getDuration();// 得到文件的时间(ms)
void setLooping(boolean var1);// 设置是否循环播放
boolean isLooping();// 是否循环播放
boolean isPlaying();// 是否正在播放
void pause();// 暂停
void prepare();// 同步准备
void prepareAsync();// 异步准备
void release();// 释放MediaPlayer对象
void reset();// 重置MediaPlayer对象
void seekTo(int msec);// 指定播放位置(以毫秒为单位)
void setDataSource(String path);// 设置播放资源
void setScreenOnWhilePlaying(boolean screenOn);// 设置播放的时候一直让屏幕变亮
void setWakeMode(Context context, int mode);// 设置唤醒模式
void setVolume(float leftVolume, float rightVolume);// 设置音量,参数分别表示左右声道声音大小,取值范围为0~1
void start();// 开始播放
void stop();// 停止播放
MediaPlayer常用事件监听
播放出错监听
MediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
return false;
}
});
播放完成监听
MediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
// todo
}
});
网络流媒体缓冲监听
MediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(MediaPlayer mediaPlayer, int i) {
// i 0~100
Log.d("Progress:", "缓存进度" + i + "%");
}
});
准备Prepared完成监听
MediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
// todo
}
});
进度调整完成SeekComplete监听,主要是配合seekTo(int)方法
MediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(MediaPlayer mediaPlayer) {
// todo
}
});
使用锁
当音频在后台播放时,设备可能进入到休眠状态,此时系统会关闭一些不必要的资源,包括CPU和wifi等等...如果想要在后台播放或者缓冲音乐并且不想被系统干扰必须使用锁,并且在paused或者stopped状态下释放它。
// 设置设备进入锁状态模式-可在后台播放或者缓冲音乐-CPU一直工作
mediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
同时需要在清单文件AndroidManifest.xml中添加权限
<uses-permission android:name="android.permission.WAKE_LOCK" />
上面代码只会确保cpu一直工作,如果你使用wifi播放流媒体,你还需要持有wifi锁
// 如果你使用wifi播放流媒体,你还需要持有wifi锁
WifiManager.WifiLock wifiLock = ((WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, "wifilock");
wifiLock.acquire();
当你停止播放或者不再需要连接网络释放
wifiLock.release();
处理音频焦点
android是一个支持多任务的系统,这给使用audio的程序带来了一定的风险,因为可能几个程序会来竞争音频输出设备,在android2.2之前没有内置的机制来处理这个问题,导致体验很差。比如用户在听音乐时,另一个应用需要向用户提示一个非常重要的通知,但音乐声盖过了通知音,导致用户错失了最佳接收提示的时间,为了协调设备的音频输出,android提出了Audio Focus机机制,获取audio focus必须调用AudioManager的requestAudioFocus()方法。
// 处理音频焦点-处理多个程序会来竞争音频输出设备
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 征对于Android 8.0+
AudioFocusRequest audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
.setOnAudioFocusChangeListener(focusChangeListener).build();
audioFocusRequest.acceptsDelayedFocusGain();
audioManager.requestAudioFocus(audioFocusRequest);
} else {
// 小于Android 8.0
int result = audioManager.requestAudioFocus(focusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// could not get audio focus.
}
}
focusChange参数值如下
- AUDIOFOCUS_GAIN:获取audio focus
- AUDIOFOCUS_LOSS:失去audio focus很长一段时间,必须停止所有的audio播放,清理资源
- AUDIOFOCUS_ LOSS_TRANSIENT:暂时失去audio focus,但是很快就会重新获得,在此状态应该暂停所有音频播放,但是不能清除资源
- AUDIOFOCUS_ LOSS_TRANSIENT _CAN_DUCK:暂时失去 audio focus,但是允许持续播放音频(以很小的声音),不需要完全停止播放。
AudioManager.OnAudioFocusChangeListener focusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// 获取audio focus
if (mediaPlayer == null)
mediaPlayer = new MediaPlayer();
else if (!mediaPlayer.isPlaying())
mediaPlayer.start();
mediaPlayer.setVolume(1.0f, 1.0f);
break;
case AudioManager.AUDIOFOCUS_LOSS:
// 失去audio focus很长一段时间,必须停止所有的audio播放,清理资源
if (mediaPlayer.isPlaying())
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 暂时失去audio focus,但是很快就会重新获得,在此状态应该暂停所有音频播放,但是不能清除资源
if (mediaPlayer.isPlaying())
mediaPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 暂时失去 audio focus,但是允许持续播放音频(以很小的声音),不需要完全停止播放。
if (mediaPlayer.isPlaying())
mediaPlayer.setVolume(0.1f, 0.1f);
break;
}
}
};
处理AUDIO_ BECOMING_NOISY意图
当用户插着耳机听音乐突然拔掉耳机,如果没有对以上意图进行处理,音频将会通过外部扬声器来播放音频,这也许不会是用户想要的,所以我们必须对此处理。
可以使用动态广播broadcastReceiver
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
// 拔掉耳机时候进行相应的操作
}
}
};
registerReceiver(broadcastReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
然后在页面onDestory方法中注销广播即可。
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(broadcastReceiver);
}
当然也可以使用静态广播
1、在manifest文件中注册一个广播
<receiver android:name=".MusicIntentReceiver">
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
2、注册MusicIntentReceiver 广播来处理这个intent
public class MusicIntentReceiver extends 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)
}
}
}
检索媒体文件
// 通过ContentResolver来获取外部媒体文件
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());
}
// 配合MediaPlayer使用
try {
long id = /* retrieve it from somewhere */;
Uri contentUri = ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, thisId);
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), contentUri);
// ...prepare and start...
} catch (IOException e) {
e.printStackTrace();
}
简易播放音乐器案例
常见的音乐播放器都有,上一首、下一首、播放、暂停、停止等功能,那接下来就用一个简易的音乐播放器来说明MediaPlayer的使用。首先看一下布局效果图:
布局效果图<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF">
<LinearLayout
android:id="@+id/layout_bottom"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:background="#F5F5F5"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_pre"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="上一首"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_pause"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="暂停"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_start"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="播放"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_stop"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="停止"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_next"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="下一首"
android:textSize="14sp" />
</LinearLayout>
<TextView
android:id="@+id/tv_music"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/layout_bottom"
android:gravity="center"
android:orientation="vertical"
android:text="@string/app_name"
android:textSize="20sp" />
</RelativeLayout>
在该实例中以加载网络资源为例,这里定义一个字符串数据保存网络URL。
private String[] musics = {"http://ibooker.cc/ibooker/musics/1234.mp3",
"http://ibooker.cc/ibooker/musics/2345.mp3"}; // 设置音频资源(网络)
在程序一来是,需要对MediaPlayer进行初始化工作,所以定义一个全局变量MediaPlayer。
// 初始化MediaPlayer
private void initMediaPlayer() {
if (mediaPlayer == null)
mediaPlayer = new MediaPlayer();
// 设置音量,参数分别表示左右声道声音大小,取值范围为0~1
mediaPlayer.setVolume(0.5f, 0.5f);
// 设置是否循环播放
mediaPlayer.setLooping(false);
}
当点击播放按钮,程序要马上播放相应的音乐文件,因为是加载网络资源,所以在这里采用prepareAsync进行准备,同时设置OnPreparedListener监听是否准备完成。
// 播放
private void play() {
try {
if (mediaPlayer == null)
initMediaPlayer();
if (isPause) {
mediaPlayer.start();
updateDescTv();
} else {
// 重置mediaPlayer
mediaPlayer.reset();
// 重新加载音频资源
// Uri uri = Uri.parse(musics[current_item]);
// mediaPlayer.setDataSource(this, uri);
mediaPlayer.setDataSource(musics[current_item]);
// 准备播放(同步)-预期准备,因为setDataSource()方法之后,MediaPlayer并未真正的去装载那些音频文件,需要调用prepare()这个方法去准备音频
// mediaPlayer.prepare();
// 准备播放(异步)
mediaPlayer.prepareAsync();
}
} catch (IOException e) {
e.printStackTrace();
}
}
注:isPause是用来标记当前播放是否处于暂停状态。
// 异步准备Prepared完成监听
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
// 开始播放
mediaPlayer.start();
updateDescTv();
}
});
updateDescTv();方法是用来显示播放进度。这里是通过开启一个线程来时时监听播放进度。
// 开启线程,修改descTv
private void updateDescTv() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (mediaPlayer != null && mediaPlayer.isPlaying()) {// 判断音频是否正在播放
myHandler.sendEmptyMessage(100);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
if (executorService == null || executorService.isShutdown())
executorService = Executors.newSingleThreadExecutor();
executorService.execute(thread);
}
MyHandler myHandler = new MyHandler(this);
private static class MyHandler extends Handler {
private WeakReference<Activity> mActivity;
MyHandler(Activity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity currentActivity = (MainActivity) mActivity.get();
switch (msg.what) {
case 100:
if (currentActivity.mediaPlayer != null)
currentActivity.descTv.setText("播放进度:" + currentActivity.mediaPlayer.getCurrentPosition() * 100 / currentActivity.mediaPlayer.getDuration() + "%");
break;
}
}
}
要想实现下一首和上一首的功能,还需要定义一个变量current_item标记当前播放哪一首。
// 下一首
private void nextMusic() {
current_item++;
if (current_item >= musics.length)
current_item = 0;
play();
}
// 上一首
private void preMusic() {
current_item--;
if (current_item < 0)
current_item = musics.length - 1;
play();
}
当点击暂停时
if (mediaPlayer.isPlaying()) {
isPause = true;
mediaPlayer.pause();
}
当点击停止时
if (mediaPlayer.isPlaying())
mediaPlayer.reset();
最后在页面onStop()中回收资源即可
@Override
protected void onStop() {
super.onStop();
// 释放mediaPlayer
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
微信公众号:书客创作
网友评论