这一次主要是研究Jamendo的播放流程,相对比较繁复一些。播放显然要启动Service来实现,在这之前,Jamendo是怎么处理的。本文将简单展开。

(一) 基本流程图

 

(二) 实现

1. 启动

在Jamendo中,开发者习惯用被启动Activity的静态方法来完成这个功能。Gallery的Item点击事件之后,代码如下:

 
  
  1. Album album = (Album) adapterView.getItemAtPosition(position); 
  2.             PlayerActivity.launch(HomeActivity.this, album); 
  3.              

2. 数据加载封装

这一部分的处理思路在笔记之七中已经详细的说明。只在此列出代码,如下:

 
  
  1. public class PlayerAlbumLoadingDialog extends LoadingDialog<Album, Track[]>{ 
  2.      
  3.     private Album mAlbum; 
  4.  
  5.     public PlayerAlbumLoadingDialog(Activity activity, int loadingMsg, int failMsg) { 
  6.         super(activity, loadingMsg, failMsg); 
  7.     } 
  8.  
  9.     @Override 
  10.     public Track[] doInBackground(Album... params) { 
  11.         mAlbum = params[0]; 
  12.          
  13.         JamendoGet2Api service = new JamendoGet2ApiImpl(); 
  14.         Track[] tracks = null
  15.          
  16.         try { 
  17.             tracks = service.getAlbumTracks(mAlbum, JamendoApplication.getInstance().getStreamEncoding()); 
  18.             Log.d("play_workflow""doInBackground tracks is:" + tracks.toString()); 
  19.         } catch (JSONException e) { 
  20.             e.printStackTrace(); 
  21.             return null
  22.         } catch (WSError e) { 
  23.             publishProgress(e); 
  24.             cancel(true); 
  25.         } 
  26.         return tracks; 
  27.          
  28.     } 
  29.  
  30.  
  31.     @Override 
  32.     public void doStuffWithResult(Track[] tracks) { 
  33.          
  34.         Intent intent = new Intent(mActivity, PlayerActivity.class); 
  35.         Playlist playlist = new Playlist(); 
  36.         mAlbum.setTracks(tracks); 
  37.         playlist.addTracks(mAlbum); 
  38.          
  39.         intent.putExtra("playlist", playlist); 
  40.         Log.d("play_workflow""doStuffWithResult playlist is:" + playlist.toString()); 
  41.         mActivity.startActivity(intent); 
  42.     } 
  43.  

封装的示意图如下:

 

3. 播放列表加载及准备

数据封装完毕后,开始进行各种播放器的准备。我猜因为是开源软件的原因,部分代码写的不是很完善。比如if的某个条件,打了log之后,始终并未执行。我在代码中已经做了注释。handleIntent()方法里边处理已经封装好的PlayList类。代码如下:

 
  
  1. private void handleIntent(){ 
  2.         Log.i(JamendoApplication.TAG, "PlayerActivity.handleIntent"); 
  3.  
  4.         // This will be result of this intent handling 
  5.         Playlist playlist = null
  6.  
  7.         Log.d("play_workflow""data is:" + getIntent().getData());//本身并未给data赋值,所以执行的都是data==null的情形 
  8.         // We need to handle Uri 
  9.         if(getIntent().getData() != null){//不执行此条判断 
  10.              
  11.             // Check if this intent was already once parsed  
  12.             // we don't need to do that again 
  13.             if(!getIntent().getBooleanExtra("handled"false)){ 
  14.                 Log.d("play_workflow""handled is true" ); 
  15.                 mUriLoadingDialog = (LoadingDialog) new UriLoadingDialog(this, R.string.loading, R.string.loading_fail).execute(); 
  16.             } 
  17.                  
  18.         } else { 
  19.             Log.d("play_workflow""handled is false" ); 
  20.             playlist = (Playlist) getIntent().getSerializableExtra("playlist"); 
  21.             Log.d("play_workflow""handle_intent playlist is:" + playlist); 
  22.             loadPlaylist(playlist); 
  23.         } 
  24.     } 

其中loadPlaylist()方法的代码如下:

 
  
  1. private void loadPlaylist(Playlist playlist){ 
  2.         Log.i(JamendoApplication.TAG, "PlayerActivity.loadPlaylist"); 
  3.         if(playlist == null
  4.             return
  5.          
  6.         mPlaylist = playlist; 
  7.         if(mPlaylist != getPlayerEngine().getPlaylist()){//播放列表不相等,就要重新加载 
  8.             //getPlayerEngine().stop(); 
  9.             getPlayerEngine().openPlaylist(mPlaylist); 
  10.             getPlayerEngine().play(); 
  11.         } 
  12.     } 

其实,jamendo的处理是使用了两步来实现的,示意图如下:

按照先后顺序,以openPlayList()为例,代码如下:

Step1:在handleIntent中执行

       代码如上所示

Step2:在IntentPlayEngine中

 
  
  1. @Override 
  2.         public void openPlaylist(Playlist playlist) { 
  3.             mPlaylist = playlist; 
  4.             if(mServicePlayerEngine != null){ 
  5.                 mServicePlayerEngine.openPlaylist(playlist); 
  6.             } 
  7.         } 

Step3:在PlayEngineImpl中

 
  
  1. @Override 
  2.     public void openPlaylist(Playlist playlist) { 
  3.         if(!playlist.isEmpty()) 
  4.             mPlaylist = playlist; 
  5.         else 
  6.             mPlaylist = null
  7.     } 

 

 

4. 启动服务播放

从3中, play方法最终调用到的也是PlayEngine中的。

这样,整个流程就ok了。但是整个流程下来,给人的感觉是,冗繁。

(三) 播放的控制

对于播放控制的处理是(以play键为例):

1, 监听按键

代码如下:

 
  
  1. /** 
  2.      * on click play/pause and open playlist if necessary 
  3.      */ 
  4.     private OnClickListener mPlayOnClickListener = new OnClickListener(){ 
  5.  
  6.         @Override 
  7.         public void onClick(View v) { 
  8.             if(getPlayerEngine().isPlaying()){ 
  9.                 getPlayerEngine().pause(); 
  10.             } else { 
  11.                 getPlayerEngine().play(); 
  12.             } 
  13.         } 
  14.  
  15.     }; 

2, 调用接口,启动intent

代码如下:

 
  
  1. @Override 
  2.         public void play() { 
  3.             if (mServicePlayerEngine != null) { 
  4.                 Log.d("play_workflow""mServicePlayerEngine is not null:" ); 
  5.                 playlistCheck(); 
  6.                 mServicePlayerEngine.play(); 
  7.             } else { 
  8.                 Log.d("play_workflow""mServicePlayerEngine is null"); 
  9.                 startAction(PlayerService.ACTION_PLAY); 
  10.             } 
  11.         } 

3, 执行Service指定操作

代码如下:

 
  
  1. if(action.equals(ACTION_PLAY)){  
  2.             mPlayerEngine.play(); 
  3.             return
  4.         } 

最终执行的代码如下:

 
  
  1. @Override 
  2.     public void play() { 
  3.          
  4.         if( mPlayerEngineListener.onTrackStart() == false ){ 
  5.             return// apparently sth prevents us from playing tracks 
  6.         } 
  7.  
  8.         // check if there is anything to play 
  9.         if(mPlaylist != null){ 
  10.  
  11.             // check if media player is initialized 
  12.             if(mCurrentMediaPlayer == null){ 
  13.                 mCurrentMediaPlayer = build(mPlaylist.getSelectedTrack()); 
  14.             } 
  15.  
  16.             // check if current media player is set to our song 
  17.             if(mCurrentMediaPlayer != null && mCurrentMediaPlayer.playlistEntry != mPlaylist.getSelectedTrack()){ 
  18.                 cleanUp(); // this will do the cleanup job               
  19.                 mCurrentMediaPlayer = build(mPlaylist.getSelectedTrack()); 
  20.             } 
  21.              
  22.             // check if there is any player instance, if not, abort further execution  
  23.             if(mCurrentMediaPlayer == null
  24.                 return
  25.  
  26.             // check if current media player is not still buffering 
  27.             if(!mCurrentMediaPlayer.preparing){ 
  28.  
  29.                 // prevent double-press 
  30.                 if(!mCurrentMediaPlayer.isPlaying()){ 
  31.                     // i guess this mean we can play the song 
  32.                     Log.i(JamendoApplication.TAG, "Player [playing] "+mCurrentMediaPlayer.playlistEntry.getTrack().getName()); 
  33.                      
  34.                     // starting timer 
  35.                     mHandler.removeCallbacks(mUpdateTimeTask); 
  36.                     mHandler.postDelayed(mUpdateTimeTask, 1000); 
  37.                      
  38.                     mCurrentMediaPlayer.start(); 
  39.                 } 
  40.             } else { 
  41.                 // tell the mediaplayer to play the song as soon as it ends preparing 
  42.                 mCurrentMediaPlayer.playAfterPrepare = true
  43.             } 
  44.         } 
  45.     } 

 

(四) 思考

在播放准备及播放的流程上,有些冗余,代码不够清晰。我之前做过一个音乐播放器,采用的是bind service,回调让控制比较方便。Jamendo对于IntentPlayerEngine和PlayEngineImpl做出了相应的解释。未绑定的原因是适应代码的原始版本,减少重构量。

l IntentPlayerEngine

 
  
  1. /** 
  2.      * Since 0.9.8.7 we embrace "bindless" PlayerService thus this adapter. No 
  3.      * big need of code refactoring, we just wrap sending intents around defined 
  4.      * interface   
  5.      *  
  6.      * 意思是:因为jamendo使用的是无绑定的PlayService,所以有着这个适配器。不需要对代码 
  7.      * 进行太多的重构。我们只需要通过这个指定的接口发送Intent就ok了。 
  8.      * @author Lukasz Wisniewski 
  9.      */ 
  10.     private class IntentPlayerEngine implements PlayerEngine  

l PlayEngineImpl

 
  
  1. /** 
  2.  * Player core engine allowing playback, in other words, a 
  3.  * wrapper around Android's <code>MediaPlayer</code>, supporting 
  4.  * <code>Playlist</code> classes 
  5.  *  
  6.  * @author Lukasz Wisniewski 
  7.  */ 
  8. public class PlayerEngineImpl implements PlayerEngine