1·在Service中实例化MusicPlayer,实现对整个播放过程的控制

上一次做到了找到音乐数据,并封装成对象装在ArrayList里,把数据的信息显示在UI上。下面一个阶段就要开始真正的音乐播放器的制作了。做音乐播放器之前首先要想到的就是用什么来做这个部分。于是我查阅了Android Developers官网的一些有关内容,果不其然的确有方便的API供开发者使用。这就是MediaPlayer这个类。
用官网的话概括这个类就是这样:
MediaPlayer class can be used to control playback of audio/video files and streams.
这个类可以用来控制音视频的文件或者流的播放。
在用这个类之前一定要熟悉MediaPlayer对象的生命周期。也就是下面这个图。

废话不多说,这个图直接用原文档来解释就很清楚了。
State Diagram

Playback control of audio/video files and streams is managed as a state machine.
The diagram shows the life cycle and the states of a MediaPlayer object driven by the supported playback control operations.
The ovals represent the states a MediaPlayer object may reside in.
The arcs represent the playback control operations that drive the object state transition.
There are two types of arcs. The arcs with a single arrow head represent synchronous method calls,
while those with a double arrow head represent asynchronous method calls.

MediaPlayer State diagram

From this state diagram, one can see that a MediaPlayer object has the following states:

When a MediaPlayer object is just created using new or after reset() is called, it is in the Idle state; and after release() is called, it is in the End state. Between these two states is the life cycle of the MediaPlayer object.
There is a subtle but important difference between a newly constructed MediaPlayer object and the MediaPlayer object after reset() is called. It is a programming error to invoke methods such as getCurrentPosition(), getDuration(), getVideoHeight(), getVideoWidth(), setAudioStreamType(int), setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(int), prepare() or prepareAsync() in the Idle state for both cases. If any of these methods is called right after a MediaPlayer object is constructed, the user supplied callback method OnErrorListener.onError() won't be called by the internal player engine and the object state remains unchanged; but if these methods are called right after reset(), the user supplied callback method OnErrorListener.onError() will be invoked by the internal player engine and the object will be transfered to the Error state.
It is also recommended that once a MediaPlayer object is no longer being used, call release() immediately so that resources used by the internal player engine associated with the MediaPlayer object can be released immediately. Resource may include singleton resources such as hardware acceleration components and failure to call release() may cause subsequent instances of MediaPlayer objects to fallback to software implementations or fail altogether. Once the MediaPlayer object is in the End state, it can no longer be used and there is no way to bring it back to any other state.
Furthermore, the MediaPlayer objects created using new is in the Idle state, while those created with one of the overloaded convenient create methods are NOT in the Idle state. In fact, the objects are in the Prepared state if the creation using create method is successful.
In general, some playback control operation may fail due to various reasons, such as unsupported audio/video format, poorly interleaved audio/video, resolution too high, streaming timeout, and the like. Thus, error reporting and recovery is an important concern under these circumstances. Sometimes, due to programming errors, invoking a playback control operation in an invalid state may also occur. Under all these error conditions, the internal player engine invokes a user supplied OnErrorListener.onError() method if an OnErrorListener has been registered beforehand via setOnErrorListener(android.media.MediaPlayer.OnErrorListener).
It is important to note that once an error occurs, the MediaPlayer object enters the Error state (except as noted above), even if an error listener has not been registered by the application.
In order to reuse a MediaPlayer object that is in the Error state and recover from the error, reset() can be called to restore the object to its Idle state.
It is good programming practice to have your application register a OnErrorListener to look out for error notifications from the internal player engine.
IllegalStateException is thrown to prevent programming errors such as calling prepare(), prepareAsync(), or one of the overloaded setDataSource methods in an invalid state.
Calling setDataSource(FileDescriptor), or setDataSource(String), or setDataSource(Context, Uri), or setDataSource(FileDescriptor, long, long) transfers a MediaPlayer object in the Idle state to the Initialized state.
An IllegalStateException is thrown if setDataSource() is called in any other state.
It is good programming practice to always look out for IllegalArgumentException and IOException that may be thrown from the overloaded setDataSource methods.
A MediaPlayer object must first enter the Prepared state before playback can be started.
There are two ways (synchronous vs. asynchronous) that the Prepared state can be reached: either a call to prepare() (synchronous) which transfers the object to the Prepared state once the method call returns, or a call to prepareAsync() (asynchronous) which first transfers the object to the Preparing state after the call returns (which occurs almost right way) while the internal player engine continues working on the rest of preparation work until the preparation work completes. When the preparation completes or when prepare() call returns, the internal player engine then calls a user supplied callback method, onPrepared() of the OnPreparedListener interface, if an OnPreparedListener is registered beforehand via setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener).
It is important to note that the Preparing state is a transient state, and the behavior of calling any method with side effect while a MediaPlayer object is in the Preparing state is undefined.
An IllegalStateException is thrown if prepare() or prepareAsync() is called in any other state.
While in the Prepared state, properties such as audio/sound volume, screenOnWhilePlaying, looping can be adjusted by invoking the corresponding set methods.
To start the playback, start() must be called. After start() returns successfully, the MediaPlayer object is in the Started state. isPlaying() can be called to test whether the MediaPlayer object is in the Started state.
While in the Started state, the internal player engine calls a user supplied OnBufferingUpdateListener.onBufferingUpdate() callback method if a OnBufferingUpdateListener has been registered beforehand via setOnBufferingUpdateListener(OnBufferingUpdateListener). This callback allows applications to keep track of the buffering status while streaming audio/video.
Calling start() has not effect on a MediaPlayer object that is already in the Started state.
Playback can be paused and stopped, and the current playback position can be adjusted. Playback can be paused via pause(). When the call to pause() returns, the MediaPlayer object enters the Paused state. Note that the transition from the Started state to the Paused state and vice versa happens asynchronously in the player engine. It may take some time before the state is updated in calls to isPlaying(), and it can be a number of seconds in the case of streamed content.
Calling start() to resume playback for a paused MediaPlayer object, and the resumed playback position is the same as where it was paused. When the call to start() returns, the paused MediaPlayer object goes back to the Started state.
Calling pause() has no effect on a MediaPlayer object that is already in the Paused state.
Calling stop() stops playback and causes a MediaPlayer in the Started, Paused, Prepared or PlaybackCompleted state to enter the Stopped state.
Once in the Stopped state, playback cannot be started until prepare() or prepareAsync() are called to set the MediaPlayer object to the Prepared state again.
Calling stop() has no effect on a MediaPlayer object that is already in the Stopped state.
The playback position can be adjusted with a call to seekTo(int).
Although the asynchronuous seekTo(int) call returns right way, the actual seek operation may take a while to finish, especially for audio/video being streamed. When the actual seek operation completes, the internal player engine calls a user supplied OnSeekComplete.onSeekComplete() if an OnSeekCompleteListener has been registered beforehand via setOnSeekCompleteListener(OnSeekCompleteListener).
Please note that seekTo(int) can also be called in the other states, such as Prepared, Paused and PlaybackCompleted state.
Furthermore, the actual current playback position can be retrieved with a call to getCurrentPosition(), which is helpful for applications such as a Music player that need to keep track of the playback progress.
When the playback reaches the end of stream, the playback completes.
If the looping mode was being set to truewith setLooping(boolean), the MediaPlayer object shall remain in the Started state.
If the looping mode was set to false , the player engine calls a user supplied callback method, OnCompletion.onCompletion(), if a OnCompletionListener is registered beforehand via setOnCompletionListener(OnCompletionListener). The invoke of the callback signals that the object is now in the PlaybackCompleted state.
While in the PlaybackCompleted state, calling start() can restart the playback from the beginning of the audio/video source.
可见这个MediaPlayer对象是非常耗时的。我们需要在一个Service里来实例化它。完成它的整个播放流程。
创建一个Service的子类,我把这个子类叫做MusicService.同时,我们必须要重写一些掌控Services生命周期的方法并提供一个结构化的组件来绑定到我们的MusicService上。下面就新建一个MusicService.java并重写方法。
我们这里的MusicService不对外提供绑定,不需要重写onStartCommand()方法,onBind()方法返回null即可。
同其他三大组件一样,需要在application's manifest file里声明。
AndroidManifest.xml

      <manifest ... >       ...    <application ... >      <service        android:name="com.graceplayer.activity.MusicService"        android:exported="true" >        <intent-filter>            <action android:name="VideoService.START_Video_SERVICE" />            <category android:name="android.intent.category.DEFAULT" />        </intent-filter>       </service>     ...    </application>  </manifest>        

这样Service的基本框架就做好了。
在开始整个Service的编写操纵MediaPlayer对象的同时,引入广播机制,当MediaPlayer的对象在媒体播放引擎中的状态发生改变的时候,通过广播机制,将MediaPlayer对象的状态改变信息广播出去。同时设置给好Service绑定广播接收器,随时接受来自Acitvity的广播。通过广播中要求的信息,进行条件判断,做出相应的播放状态的改变。这样整个UI线程与后台耗时的媒体播放就完成了信息的交互。

根据这个需求,先给MusicService绑定一个广播接收器,专门接收来自UI的控制命令。这里用到了BroadCastReceiver这个类
在MusicService.java中封装成一个方法:(BROADCAST_MUSICSERVICE_CONTROL = "MusicService.ACTION_CONTROL"在完整的程序中会有声明)
MusicService.java
private void bindCommandReceiver() {
//new 一个CommandReceiver的对象,以接收到的广播数据为判断条件,完成对MediaPlayer对象的操作。
receiver = new CommandReceiver();
//过滤出我们想要接收的Intent
IntentFilter filter = new IntentFilter(BROADCAST_MUSICSERVICE_CONTROL);
//完成广播接收器注册,让receiver随时接收filter指定的广播
registerReceiver(receiver, filter);
}
//在MusicService中写一个继承自BroadcastReceiver的内部类,在这个类中对接收到的广播进行读取,根据接收到的广播的信息在
//MusicService中完成MediaPlayer对象的相应操作。我们先把MediaPlayer对象的具体操作抽象成方法。对方法先进行调用。
private class CommandReceiver extends BroadcastReceiver {

@Overridepublic void onReceive(Context context, Intent intent) {int command = intent.getIntExtra("command", COMMAND_UNKNOWN);switch (command) {case COMMAND_SEEK_TO:seekTo(intent.getIntExtra("time", 0));break;case COMMAND_PLAY:number = intent.getIntExtra("number", 0);play(number);break;case COMMAND_PREVIOUS:moveNumberToPrevious();break;case COMMAND_NEXT:moveNumberToNext();break;case COMMAND_STOP:stop();break;case COMMAND_RESUME:resume();break;case COMMAND_CHECK_IS_PLAYING:isPlaying();default:break;}}}

MusicService.java

package com.zharma.greatlovemusic;import com.zharma.data.MusicList;import android.app.Service;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.media.MediaPlayer;import android.os.IBinder;public class MusicService extends Service{// 播放控制命令,标识操作public static final int COMMAND_UNKNOWN = -1;public static final int COMMAND_PLAY = 0;public static final int COMMAND_PAUSE = 1;public static final int COMMAND_STOP = 2;public static final int COMMAND_RESUME = 3;public static final int COMMAND_PREVIOUS = 4;public static final int COMMAND_NEXT = 5;public static final int COMMAND_CHECK_IS_PLAYING = 6;public static final int COMMAND_SEEK_TO = 7;// 播放器状态public static final int STATUS_PLAYING = 0;public static final int STATUS_PAUSED = 1;public static final int STATUS_STOPPED = 2;public static final int STATUS_COMPLETED = 3;// 广播标识public static final String BROADCAST_MUSICSERVICE_CONTROL = "MusicService.ACTION_CONTROL";public static final String BROADCAST_MUSICSERVICE_UPDATE_STATUS = "MusicService.ACTION_UPDATE";// 广播接收器private CommandReceiver receiver;private int status;// 媒体播放类private MediaPlayer player = new MediaPlayer();//歌曲序号,从0开始private int number = 0;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();                            //绑定广播接收器,专门负责接收UI传过来的播放命令,判断条件并处理                            bindCommandReceiver();                            status = MusicService.STATUS_STOPPED;}@Overridepublic void onDestroy() {                            //在Destory整个Service之前要释放掉MidiaPlayer对象调用的资源。                            if(player != null) {                                   player.release();                            }super.onDestroy();}private void bindCommandReceiver() {receiver = new CommandReceiver();IntentFilter filter = new IntentFilter(BROADCAST_MUSICSERVICE_CONTROL);registerReceiver(receiver, filter);}private class CommandReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {int command = intent.getIntExtra("command", COMMAND_UNKNOWN);switch (command) {case COMMAND_SEEK_TO:seekTo(intent.getIntExtra("time", 0));break;case COMMAND_PLAY:number = intent.getIntExtra("number", 0);play(number);break;case COMMAND_PREVIOUS:moveNumberToPrevious();break;case COMMAND_NEXT:moveNumberToNext();break;case COMMAND_STOP:stop();break;case COMMAND_RESUME:resume();break;case COMMAND_CHECK_IS_PLAYING:isPlaying();default:break;}}}        //当MediaPlayer对象的状态发生改变的时候,比如从prepare状态变换到pause状态都要通知给UI,让UI的广播接收器接收处理。让UI                    //得到变换 private void sendBroadcastOnStatusChanged(int status) {Intent intent = new Intent(BROADCAST_MUSICSERVICE_UPDATE_STATUS);intent.putExtra("status", status);if (status != STATUS_STOPPED) {intent.putExtra("time", player.getCurrentPosition());intent.putExtra("duration", player.getDuration());intent.putExtra("number", number);intent.putExtra("musicName", MusicList.getMusicList().get(number).getMusicName());intent.putExtra("musicArtist", MusicList.getMusicList().get(number).getMusicArtist());}sendBroadcast(intent);}}这样就做到了整个MusicService的主体编写。下面就把变换的细节实现到相应的方法里。                private void load(int number) {try {player.reset();player.setDataSource(MusicList.getMusicList().get(number).getMusicPath());player.prepare();} catch (IllegalArgumentException e) {e.printStackTrace();} catch (SecurityException e) {e.printStackTrace();} catch (IllegalStateException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}// 注册监听器player.setOnCompletionListener(completionListener);}OnCompletionListener completionListener = new OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {if (player.isLooping()) {replay();} else {sendBroadcastOnStatusChanged(MusicService.STATUS_COMPLETED);}}};private void moveNumberToNext() {if (number == MusicList.getMusicList().size()-1) {Toast.makeText(MusicService.this,"已经到达列表底部",Toast.LENGTH_SHORT).show();} else {number ++;play(number);}}private void moveNumberToPrevious() {if (number == 0) {Toast.makeText(MusicService.this, "已经到达列表顶端", Toast.LENGTH_SHORT).show();} else {number --;play(number);}}private void play(int number) {if (player != null && player.isPlaying()) {player.stop();}load(number);player.start();status = MusicService.STATUS_PLAYING;sendBroadcastOnStatusChanged(MusicService.STATUS_PLAYING); }private void pause() {if (player.isPlaying()) {player.pause();status = MusicService.STATUS_PAUSED;sendBroadcastOnStatusChanged(MusicService.STATUS_PAUSED);}}private void stop() {if (status != MusicService.STATUS_STOPPED) {player.stop();sendBroadcastOnStatusChanged(MusicService.STATUS_STOPPED);}}/** 恢复播放(暂停之后) */private void resume() {player.start();status = MusicService.STATUS_PLAYING;sendBroadcastOnStatusChanged(MusicService.STATUS_PLAYING);}private void isPlaying() {if (player != null && player.isPlaying()) {//status = MusicService.STATUS_PLAYING;sendBroadcastOnStatusChanged(MusicService.STATUS_PLAYING);}}private void replay() {player.start();status = MusicService.STATUS_PLAYING;sendBroadcastOnStatusChanged(MusicService.STATUS_PLAYING);}/** 跳转至播放位置 */private void seekTo(int time) {player.seekTo(time);status = MusicService.STATUS_PLAYING;sendBroadcastOnStatusChanged(MusicService.STATUS_PLAYING);}

整个Service的核心思想就是设置广播接收器,接收来自UI的控制命令的广播,根据接收到的数据做出对MediaPlayer对象的操作。这个过程中,intent对象起着信使的作用。通过它,完成了组件之间的通信。

更多相关文章

  1. 在Android中为啥建议你用Message.obtain()方法获取Message对象,而
  2. android装逼技术之暗码小DOME
  3. Android(安卓)用户界面---操作栏(Action Bar 三)
  4. Android序列化详解及最佳实践(Serialize&Parcel)
  5. Android(安卓)launcher动态Icon的实现方法
  6. Android面试系列之一
  7. 线程对象Android(安卓)开发之多线程处理、Handler 详解
  8. 论Camera和MediaRecorder的友情(安卓学习年度总结篇)
  9. Tomcat Servlet 往外传输数据(对象 or list) 给 Android

随机推荐

  1. Android实现WebView删除缓存的方法
  2. android电话接听过程简单解析
  3. Android输入法弹出时覆盖输入框问题
  4. 浅谈Android下的Wifi&&安卓WIFI 移植
  5. Android(安卓)短信解析
  6. 【移动生活】Google项目副总裁安迪・鲁宾
  7. Android系列之广播
  8. Android模块开发框架 LiveData+ViewModel
  9. Android系统中的输入输出设备
  10. 转android123 预防Android内存泄露