转:Android】MediaPlayer之音频播放
转:Android】MediaPlayer生命周期分析
MediaPlayer可以播放音频和视频 ,它用于控制Android下播放文件或流的类。Android的多媒体框架支持各种常见的多媒体类型,这样在程序中可以很容易地集成音频、视频或者图片。Android下对于音频或者视频的支持均需要使用到MediaPlayer类
椭圆代表MediaPlayer对象可能驻留的状态。弧线表示驱动MediaPlayer在各个状态之间迁移的播放控制操作。这里有两种类型的弧线。由一个箭头开始的弧代表同步的方法调用,而以双箭头开头的代表的弧线代表异步方法调用。
MediaPlayer是基于状态的,只有在特定状态才能执行特定的方法。所以认清MediaPlayer生命周期十分重要的。
1、当MediaPlayer通过new方式进行初始化或MediaPlayer调用了reset()方法后,它就处于Idle状态。当调用了release()方法后,它就处于End状态。这两种状态之间是MediaPlayer对象的生命周期。
1.1、在一个新构建的MediaPlayer对象和一个调用了reset()方法的MediaPlayer对象之间有一个微小的但是十分重要的差别。
在处于Idle状态时,调用getCurrentPosition(), getDuration(), getVideoHeight(), getVideoWidth(), setAudioStreamType(int), setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(int), prepare() 或者 prepareAsync() 方法都是编程错误。
当一个MediaPlayer对象刚被构建的时候,内部的播放引擎和对象的状态都没有改变,在这个时候调用以上的那些方法,框架将无法回调客户端程序注册的OnErrorListener.onError()方法;但若这个MediaPlayer对象调用了reset()方法之后,再调用以上的那些方法,内部的播放引擎就会回调客户端程序注册的OnErrorListener.onError()方法了,并将错误的状态传入。
1.2、一旦一个MediaPlayer对象不再被使用,应立即调用release()方法来释放在内部的播放引擎中与这个MediaPlayer对象关联的资源。资源可能包括如硬件加速组件的单态组件,若没有调用release()方法可能会导致之后的MediaPlayer对象实例无法使用这种单态硬件资源,从而退回到软件实现或运行失败。一旦MediaPlayer对象进入了End状态,它不能再被使用,也没有办法再迁移到其它状态。(不可逆,需要重新构建)
1.3、使用new操作符创建的MediaPlayer对象处于Idle状态,而那些通过重载的create()便利方法创建的MediaPlayer对象却不是处于Idle状态。事实上,如果成功调用了重载的create()方法,那么这些对象已经是Prepare状态了。
2、在 一般情况下,由于种种原因一些播放控制操作可能会失败,如不支持的音频/视频格式,缺少隔行扫描的音频/视频,分辨率太高,流超时等等。因此,错误报告和恢复在这种情况下是非常重要的。有时,由于编程错误,在处于无效状态的情况下调用了一个播放控制操作可能发生。在所有这些错误条件下,内部的播放引擎会调用一个由客户端程序员提供的OnErrorListener.onError()方法。客户端程序员可以通过调用 MediaPlayer.setOnErrorListener(android.media.MediaPlayer.OnErrorListener)方法来注册OnErrorListener。
2.1、一旦发生错误,MediaPlayer对象会进入到Error状态。
2.2、为了重用一个处于Error状态的MediaPlayer对象,可以调用reset()方法来把这个对象恢复成Idle状态。
2.3、注册一个OnErrorListener来获知内部播放引擎发生的错误是好的编程习惯。
2.4、在不合法的状态下调用一些方法,如prepare(),prepareAsync()和setDataSource()方法会抛出IllegalStateException异常。
3、调 用setDataSource(FileDescriptor)方法,或setDataSource(String)方法,或 setDataSource(Context,Uri)方法,或setDataSource(FileDescriptor,long,long)方法会使处于Idle状态的对象迁移到Initialized状态。
3.1、若当此MediaPlayer处于其它的状态下,调用setDataSource()方法,会抛出IllegalStateException异常。
3.2、好的编程习惯是不要疏忽了调用setDataSource()方法的时候可能会抛出的IllegalArgumentException异常和IOException异常。
4、在开始播放之前,MediaPlayer对象必须要进入Prepared状态。
4.1、有两种方法(同步和异步)可以使MediaPlayer对象进入Prepared状态:要么调用prepare()方法(同步),此方法返回就表示该MediaPlayer对象已经进入了Prepared状态;要么调用prepareAsync()方法(异步),此方法会使此MediaPlayer对象进入Preparing状态并返回,而内部的播放引擎会继续未完成的准备工作。当同步版本返回时或异步版本的准备工作完全完成时就会调用客户端程序员提供的OnPreparedListener.onPrepared()监听方法。可以调用MediaPlayer.setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener)方法来注册OnPreparedListener。
4.2、Preparing是一个中间状态,在此状态下调用任何具备影响的方法的结果都是未知的!
4.3、在不合适的状态下调用prepare()和prepareAsync()方法会抛出IllegalStateException异常。当MediaPlayer对象处于Prepared状态的时候,可以调整音频/视频的属性,如音量,播放时是否一直亮屏,循环播放等。
5、要开始播放,必须调用start()方法。当此方法成功返回时,MediaPlayer的对象处于Started状态。isPlaying()方法可以被调用来测试某个MediaPlayer对象是否在Started状态。
5.1、当处于Started状态时,内部播放引擎会调用客户端程序员提供的OnBufferingUpdateListener.onBufferingUpdate()回调方法,此回调方法允许应用程序追踪流播放的缓冲的状态。
5.2、对一个已经处于Started 状态的MediaPlayer对象调用start()方法没有影响。
6、播放可以被暂停,停止,以及调整当前播放位置。当调用pause()方法并返回时,会使MediaPlayer对象进入Paused状态。注意 Started与Paused状态的相互转换在内部的播放引擎中是异步的。所以可能需要一点时间在isPlaying()方法中更新状态,若在播放流内 容,这段时间可能会有几秒钟。
6.1、调用start()方法会让一个处于Paused状态的MediaPlayer对象从之前暂停的地方恢复播放。当调用start()方法返回的时候,MediaPlayer对象的状态会又变成Started状态。
6.2、对一个已经处于Paused状态的MediaPlayer对象pause()方法没有影响。
7、调用stop()方法会停止播放,并且还会让一个处于Started,Paused,Prepared或PlaybackCompleted状态的MediaPlayer进入Stopped状态。
7.1、对一个已经处于Stopped状态的MediaPlayer对象stop()方法没有影响。
8、调用seekTo()方法可以调整播放的位置。
8.1、seekTo(int)方法是异步执行的,所以它可以马上返回,但是实际的定位播放操作可能需要一段时间才能完成,尤其是播放流形式的音频/视频。当实际的定位播放操作完成之后,内部的播放引擎会调用客户端程序员提供的OnSeekComplete.onSeekComplete()回调方法。可以通过setOnSeekCompleteListener(OnSeekCompleteListener)方法注册。
8.2、注意,seekTo(int)方法也可以在其它状态下调用,比如Prepared,Paused和PlaybackCompleted状态。此外,目前的播放位置,实际可以调用getCurrentPosition()方法得到,它可以帮助如音乐播放器的应用程序不断更新播放进度
9、当播放到流的末尾,播放就完成了。
9.1、如果调用了setLooping(boolean)方法开启了循环模式,那么这个MediaPlayer对象会重新进入Started状态。
9.2、若没有开启循环模式,那么内部的播放引擎会调用客户端程序员提供的OnCompletion.onCompletion()回调方法。可以通过调用MediaPlayer.setOnCompletionListener(OnCompletionListener)方法来设置。内部的播放引擎一旦调用了OnCompletion.onCompletion()回调方法,说明这个MediaPlayer对象进入了PlaybackCompleted状态。
9.3、当处于PlaybackCompleted状态的时候,可以再调用start()方法来让这个MediaPlayer对象再进入Started状态。
要想利用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");
简单案例:实现音乐后台播放 使用Service 因为Service不随Activity的影响
MySerivce.java
public class MyService extends Service {
public MyService() {
}
MediaPlayer player;
@Override
public void onCreate() {
super.onCreate();
player = new MediaPlayer();
//重置,使MediaPlayer重回idel状态
//player.reset();
Log.e("TAG","调用了OnCreate方法");
try {
//2.设置播放源,Initialized状态
//设置SDCard下面、网络中的音乐
player.setDataSource(Environment.getExternalStorageDirectory() + "/Victoria.mp3");
// player.setDataSource("http://192.168.10.148:8080/music/Victoria.mp3");
//3.进入准备状态
player.prepare();
//4.播放音乐
player.start();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY; //服务的启动必须有明确的调用startService才能生效
//如果不返回这个模式,那么在Service执行ondestroy方法之后还有执行oncreate方法导致
// 音乐还会播放
//使用这个之后,只有在activity startService之后才能播放
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onDestroy() {
super.onDestroy();
player.stop();
player.release();
}
}
public class MainActivity extends AppCompatActivity {
//MediaPlayer player;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//启动服务
startService(new Intent(this,MyService.class));
}
AudioManager(音频管理器),提供了音量控制与铃声模式相关操作。
常用方法:
这里介绍第一种模式
在MainActivty中添加音量的控制方法 增大减少、静音和非静音
public void voiceControll(View v){
//1.获取声音管理器
AudioManager manager = (AudioManager) getSystemService(Service.AUDIO_SERVICE);
//2.操作
switch (v.getId()){
case R.id.up://增大音量
//参数1:声音类型
// AudioManager.STREAM_ALARM 警报
// AudioManager.STREAM_MUSIC 媒体
// AudioManager.STREAM_NOTIFICATION 通知
// AudioManager.STREAM_RING 铃声
// AudioManager.STREAM_VOICE_CALL 通话
// AudioManager.STREAM_SYSTEM 系统
//参数2:调整方向,增加/减少
// AudioManager.ADJUST_RAISE
// AudioManager.ADJUST_LOWER
// AudioManager.ADJUST_SAME
//参数3:FLAG_PLAY_SOUND 播放声音 FLAG_SHOW_UI 出现音量条 0什么也没有
manager.adjustStreamVolume(AudioManager.STREAM_MUSIC ,AudioManager.ADJUST_RAISE,AudioManager.FLAG_PLAY_SOUND);
break;
case R.id.down://减小音量
manager.adjustStreamVolume(AudioManager.STREAM_MUSIC ,AudioManager.ADJUST_LOWER,AudioManager.FLAG_SHOW_UI);
break;
case R.id.mute://静音
//API:>=23 手机版本>6.0
//IlleagalArguementException:Bad deriction -100
//manager.adjustStreamVolume(AudioManager.STREAM_MUSIC ,AudioManager.ADJUST_MUTE,AudioManager.FLAG_SHOW_UI);
//API<23
manager.setStreamMute(AudioManager.STREAM_MUSIC,true);
break;
case R.id.unmute://还原
//manager.adjustStreamVolume(AudioManager.STREAM_MUSIC ,AudioManager.ADJUST_UNMUTE,AudioManager.FLAG_SHOW_UI);
manager.setStreamMute(AudioManager.STREAM_MUSIC,false);
break;
}
}
实现简单的音乐播放器
实现:暂停、停止、下一首、上一首歌
未解决:关闭app后音乐也关闭了 奇怪!
第一部分:布局文件 activity_music.xml
Android读取SD卡中的媒体文件(MP3)的方法?
记得加读取内存的权限
AndroidMainfest.xml Application 标签中 添加Service
MusicService.java
public class MusicService extends Service {
MediaPlayer player;
private static int index;
public MusicService() {
}
@Override
public void onCreate() {
super.onCreate();
player = new MediaPlayer();
//进度条变化
new Thread() {
@Override
public void run() {
super.run();
while (true) {
try {
sleep(200);
//每隔200毫秒更新进度条
if (player != null) {
MusicActivity.musicPro.setProgress(player.getCurrentPosition());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
/**
* 多次点击“播放音乐”按钮,
* “onCreate()”方法只会在初始时调用一次
* “onStartCommand(Intent intent, int flags, int startId)”方法会在每次点击时都被调用
* 点击“停止音乐”按钮,“onDestroy()”方法会被调用
* 当中,每次回调onStartCommand()方法时,参数“startId”的值都是递增的startId用于唯一标识每次对Service发起的处理请求
* 如果服务同时处理多个 onStartCommand() 请求,则不应在处理完一个启动请求之后立即销毁服务
* 因为此时可能已经收到了新的启动请求,在第一个请求结束时停止服务会导致第二个请求被终止。
* 为了避免这一问题,可以使用 stopSelf(int) 确保服务停止请求始终基于最新一次的启动请求。
* 也就是说,如果调用 stopSelf(int) 方法的参数值与onStartCommand()接受到的最新的startId值不相符的话
* stopSelf()方法就会失效,从而避免终止尚未处理的请求
*
* @param intent
* @param flags
* @param startId
* @return
*/
int isStarting = -1;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
/**
* 接收activity传来的值
*/
index = intent.getIntExtra("index", 0);
String tag = intent.getStringExtra("tag");
if (tag.equals("play")) {
play(index);
isStarting = 1;
} else if (tag.equals("pause")) {
player.pause();
isStarting = 1;
} else if (tag.equals("start")) {
//如果是第一次打开,点击播放 默认播放第一首歌
if (isStarting == -1) {
play(0);
} else {
player.start();
}
}
return START_NOT_STICKY; //服务的启动必须有明确的调用startService才能生效
}
/**
* 根据传进来的index 播放响应的歌曲
*
* @param index
*/
private void play(final int index) {
String path = MusicActivity.pathList.get(index);
try {
if (player.isPlaying()) {
//如果音乐正在播放,将音乐停掉
player.stop();
}
//重置 是Medie进入idel状态
player.reset();
player.setDataSource(path);
player.prepare();
player.start();
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
//一首歌曲播放完 自动播放下一首
int i = index + 1;
play(i);
}
});
//设置进度条最大值
MusicActivity.musicPro.setMax(player.getDuration());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
//释放资源
@Override
public void onDestroy() {
super.onDestroy();
player.stop();
player.release();
isStarting = -1;
player = null;
}
}
MusicActivity.java
public class MusicActivity extends AppCompatActivity {
private ListView musicView;
//存放所有音乐曲目的名字
private ArrayList nameList = new ArrayList<>();
//存放歌曲的路径 让Service 访问
public static ArrayList pathList = new ArrayList<>();
private ArrayAdapter adapter;
//当前播放的曲目
private int index = 0;
private Button playpause;
//进度条
public static ProgressBar musicPro;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_music);
musicView = findViewById(R.id.music_view);
playpause = findViewById(R.id.playpause);
musicPro = findViewById(R.id.music_pro);
getMusic(this);
//创建adapter
//上下文,item布局文件,数据源
adapter = new ArrayAdapter(this, android.R.layout.simple_expandable_list_item_1, nameList);
musicView.setAdapter(adapter);
musicView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
/**
* 点击列表将索引传到service中 activity——>service传值
*/
Intent intent = new Intent(MusicActivity.this, MusicService.class);
intent.putExtra("index", position);
//操作标识
intent.putExtra("tag", "play");
startService(intent);
//保存当前播放歌曲的索引
index = position;
playpause.setText("||");
}
});
}
/**
* 获取本地文件的音乐文件
*/
public void getMusic(Context context) {
//查询媒体数据库
ContentResolver resolver = context.getContentResolver();
/**
* Uri:这个Uri代表要查询的数据库名称加上表的名称。
这个Uri一般都直接从MediaStore里取得,例如我要取所有歌的信息,
就必须利用MediaStore.Audio.Media. EXTERNAL _CONTENT_URI这个Uri。
*
*/
Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
//遍历媒体数据库
if (cursor.moveToFirst()) {
int i = 0;
while (!cursor.isAfterLast()) {
//歌曲编号
int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID));
//歌曲标题
String title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
//歌曲文件的路径MediaStore.Audio.Media.DATA
String url = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));
//歌曲的歌手名MediaStore.Audio.Media.ARTIST
String artist = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
//歌曲文件的大小MediaStore.Audio.Media.SIZE
long size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE));
if (size > 1024 * 800) { //是否大于800K
if (title.equals("") || title.equals("")) {
title = "未知";
}
if ("".equals(artist) || "".equals(artist)) {
artist = "未知";
}
nameList.add(++i + "-" + title);
pathList.add(url);
}
cursor.moveToNext();
}
}
}
/**
* 音乐播放控制逻辑界面
*
* @param v
*/
public void controll(View v) {
switch (v.getId()) {
case R.id.playpause:
Intent it1 = new Intent(MusicActivity.this, MusicService.class);
if (playpause.getText().equals("▶")) {
//播放
it1.putExtra("tag", "start");
playpause.setText("||");
} else {
//暂停
it1.putExtra("tag", "pause");
playpause.setText("▶");
}
startService(it1);
break;
case R.id.stop:
Intent it2 = new Intent(MusicActivity.this, MusicService.class);
//停止service 执行 Service 的onDestroy方法
stopService(it2);
playpause.setText("▶");
break;
case R.id.last:
if (index == 0) {//如果已经到了第一首,那么再次点击上一首到末尾
index = pathList.size() - 1;
} else {
index--;
}
Intent it3 = new Intent(MusicActivity.this, MusicService.class);
it3.putExtra("index", index);
it3.putExtra("tag", "play");
startService(it3);
playpause.setText("||");
break;
case R.id.next:
if (index == pathList.size() - 1) {
index = 0;
} else {
index++;
}
Intent it4 = new Intent(MusicActivity.this, MusicService.class);
it4.putExtra("index", index);
it4.putExtra("tag", "play");
startService(it4);
playpause.setText("||");
break;
}
}
}