Android歌词秀设计思路(4)通用的音乐播放服务(下)

这篇文章中我们将要说明在MediaPlayerService中用到的几个辅助功能。

1.AudioFocus相关处理

2.监视来电状态

3.监视耳机插头拔出

4.监视线控器按钮

5.Notification表示

AudioFocus相关处理

AudioFocus相关的处理已经被封装在AudioFocusHelper类中。这个类的直接目的虽然是为MediaPlayerService服务的,但是同时又独立与MediaPlayerService,可以独立使用。

功能

1.根据从AudioManager接收到的AudioFocus变化通知,管理内部的Focus状态。

2.结合内部状态和将通知转发给真正需要管理AudioFocus的类(在这里是MediaPlayerService类)

3.提供请求和释放AudioFocus的方法。

4.处理版本问题(AudioFocus只在Android2.2及以后的版本中可用)。

类图

Android歌词秀设计思路(4)通用的音乐播放服务(下)_第1张图片

我们在类图中

用蓝线标出了AudioFocus变化时通知的渠道(不是很严格)。当AudioManager发生AudioFocus的变化时,就会通知OnAudioFocusChangeListener,而这时的OnAudioFocusChangeListener实际上是由AudioFocusHelper提供的具象类的实例,在这个具象类中将通知处理后,又通知给作为MusicFocusable的具象类的MediaPlayerService。

用红线标出的AudioFocus请求和放弃的渠道:MediaPlayerServcie->AudioFocusHelper->AudioManager

以下AudioFocusHelper的源代码。

  
  
  
  
  1. package LyricPlayer.xwg;  
  2.  
  3. import android.content.Context;  
  4. import android.media.AudioManager;  
  5.  
  6. public class AudioFocusHelper {  
  7.     AudioManager mAM;  
  8.     MusicFocusable mFocusable;  
  9.       
  10.     // do we have audio focus?  
  11.     public static final int NoFocusNoDuck = 0;    // we don't have audio focus, and can't duck  
  12.     public static final int NoFocusCanDuck = 1;   // we don't have focus, but can play at a low volume ("ducking")  
  13.     public static final int Focused = 2;           // we have full audio focus  
  14.       
  15.     private int mAudioFocus = NoFocusNoDuck;  
  16.     private AudioManager.OnAudioFocusChangeListener mListener = null;  
  17.           
  18.     public AudioFocusHelper(Context ctx, MusicFocusable focusable) {  
  19.         if (android.os.Build.VERSION.SDK_INT >= 8){  
  20.             mAM = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);  
  21.             mListener = new AudioManager.OnAudioFocusChangeListener(){  
  22.                 /**   
  23.                  * Called by AudioManager on audio focus changes. We implement this by calling our  
  24.                  * MusicFocusable appropriately to relay the message.  
  25.                  */ 
  26.                 @Override 
  27.                 public void onAudioFocusChange(int focusChange) {  
  28.                     if (mFocusable == nullreturn;  
  29.                     switch (focusChange) {  
  30.                         case AudioManager.AUDIOFOCUS_GAIN:  
  31.                             mAudioFocus = Focused;  
  32.                             mFocusable.onGainedAudioFocus();  
  33.                             break;  
  34.                         case AudioManager.AUDIOFOCUS_LOSS:  
  35.                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:  
  36.                              mAudioFocus = NoFocusNoDuck;  
  37.                             mFocusable.onLostAudioFocus();  
  38.                             break;  
  39.                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:  
  40.                              mAudioFocus = NoFocusCanDuck;  
  41.                             mFocusable.onLostAudioFocus();  
  42.                             break;  
  43.                          default:  
  44.                     }  
  45.                 }  
  46.                   
  47.             };  
  48.             mFocusable = focusable;  
  49.         }else{  
  50.              mAudioFocus = Focused; // no focus feature, so we always "have" audio focus  
  51.         }  
  52.     }  
  53.  
  54.     /** Requests audio focus. Returns whether request was successful or not. */ 
  55.     public boolean requestFocus() {  
  56.         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==  
  57.             mAM.requestAudioFocus(mListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);  
  58.     }  
  59.  
  60.     /** Abandons audio focus. Returns whether request was successful or not. */ 
  61.     public boolean abandonFocus() {  
  62.         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAM.abandonAudioFocus(mListener);  
  63.     }  
  64.  
  65.     public void giveUpAudioFocus() {  
  66.         if (mAudioFocus == Focused   
  67.                 && android.os.Build.VERSION.SDK_INT >= 8 
  68.                 && abandonFocus())  
  69.                 mAudioFocus = NoFocusNoDuck;  
  70.     }  
  71.       
  72.     public void tryToGetAudioFocus() {  
  73.         if (mAudioFocus != Focused   
  74.                 && android.os.Build.VERSION.SDK_INT >= 8 
  75.                 && requestFocus())  
  76.             mAudioFocus = Focused;  
  77.     }  
  78.       
  79.     int getAudioFocus(){  
  80.         return mAudioFocus;  
  81.     }  
  82. }  

 监视来电状态

AudioFocus是Android2.2以后才有的功能,对于比2.2低得版本,用的是另一种方法,就是监听电话的状态。最起码在电话打进来是能够暂停音乐的播放。

实现这一功能的第一步是在AndroidManifest.xml中声明用于接收PHONE_STATE通知的receiver

  
  
  
  
  1. <receiver android:name=".PhoneStateReceiver">   
  2.     <intent-filter> 
  3.         <action android:name="android.intent.action.PHONE_STATE"/> 
  4.     </intent-filter>   
  5. </receiver>  

第二步是定义一个对应的PhoneStateReceiver,代码如下

  
  
  
  
  1. package LyricPlayer.xwg;  
  2.  
  3. import android.content.BroadcastReceiver;  
  4. import android.content.Context;  
  5. import android.content.Intent;  
  6. import android.telephony.TelephonyManager;  
  7.  
  8. public class PhoneStateReceiver extends BroadcastReceiver {  
  9.     @Override 
  10.     public void onReceive(Context context, Intent intent) {  
  11.         //if android.os.Build.VERSION.SDK_INT >= 8 we use audio focus.  
  12.         if (android.os.Build.VERSION.SDK_INT < 8){  
  13.             TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);  
  14.             if(tm.getCallState() != TelephonyManager.CALL_STATE_IDLE){  
  15.                 context.startService(new Intent(MediaPlayerService.ACTION_PAUSE));  
  16.             }  
  17.         }  
  18.     }  

这就够了。

监视耳机插头拔出

如果在音乐播放过程中拔出耳机,音乐就会通过扬声器播放出来。为了避免这种尴尬局面,我们会监视耳机拔出状态,并在耳机拔出时暂停播放。

首先是在AndroidManifest.xml中声明用于接收AUDIO_BECOMING_NOISY通知的receiver

  
  
  
  
  1. <receiver android:name=".MusicIntentReceiver">  
  2.     <intent-filter>  
  3.         <action android:name="android.media.AUDIO_BECOMING_NOISY" />  
  4.     </intent-filter>  
  5. </receiver> 

然后就是定义用于处理通知的receiver,类名要和AndroidManifest.xml中声明的一样。

  
  
  
  
  1. package LyricPlayer.xwg;  
  2.  
  3. import android.content.BroadcastReceiver;  
  4. import android.content.Context;  
  5. import android.content.Intent;  
  6.  
  7. public class MusicIntentReceiver extends BroadcastReceiver {  
  8.     @Override 
  9.     public void onReceive(Context ctx, Intent intent) {  
  10.         if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {  
  11.             ctx.startService(new Intent(LyricPlayerService.ACTION_PAUSE));  
  12.         }  
  13.     }  

MEDIA_BUTTON处理

在讨论处理方法之前,必须先明确:那些键属于MEDIA_BUTTON?根据我的试验,MEDIA_BUTTON好像就是线控上面的上个按钮。网上也有用同样的方法取得音量键动作的内容,但是我没有试出来。

继续我们的话题,为了检测MEDIA_BUTTON需要一些准备工作。

首先是在AndroidManifest.xml中声明用于接收MEDIA_BUTTON通知的receiver

  
  
  
  
  1. <receiver android:name="MediaButtonReceiver"> 
  2.     <intent-filter>          
  3.         <action android:name="android.intent.action.MEDIA_BUTTON" /> 
  4.     </intent-filter> 
  5. </receiver> 

当然需要定义真正的receiver,名字要和AndroidManifest.xml中的一样。

  
  
  
  
  1. package LyricPlayer.xwg;  
  2.  
  3. import android.content.BroadcastReceiver;  
  4. import android.content.Context;  
  5. import android.content.Intent;  
  6. import android.telephony.TelephonyManager;  
  7. import android.util.Log;  
  8. import android.view.KeyEvent;  
  9.  
  10. public class MediaButtonReceiver extends BroadcastReceiver {  
  11.     private static final String TAG = new String("LyricVolumeKeyReceiver");  
  12.     @Override 
  13.     public void onReceive(Context context, Intent intent) {  
  14.         if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {  
  15.              KeyEvent key = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);  
  16.              if(key.getAction() == KeyEvent.ACTION_DOWN){  
  17.                  TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);  
  18.                  if(tm.getCallState() == TelephonyManager.CALL_STATE_IDLE){  
  19.                      Log.i(TAG, "OnReceive, getKeyCode = " + key.getKeyCode());  
  20.                      switch(key.getKeyCode()){  
  21.                      case KeyEvent.KEYCODE_HEADSETHOOK :  
  22.                          context.startService(new Intent(MediaPlayerService.ACTION_PLAY_PAUSE));  
  23.                          break;  
  24.                      case KeyEvent.KEYCODE_MEDIA_PREVIOUS:  
  25.                          context.startService(new Intent(MediaPlayerService.ACTION_PREVIOUS));  
  26.                          break;  
  27.                      case KeyEvent.KEYCODE_MEDIA_NEXT:  
  28.                          context.startService(new Intent(MediaPlayerService.ACTION_NEXT));  
  29.                          break;  
  30.                     }  
  31.                 }  
  32.             }  
  33.         }  
  34.     }  

比较特别的是中间的键的键值不是KEYCODE_PLAY_PAUSE而是KEYCODE_HEADSETHOOK。想想也是,接电话也用这个键。

准备工作的最后一步就是要把通过MediaButtonReceiver来接受MEDIA_BUTTON这件事报告给AudioMenager,由于这也是Android2.2及以后版本才有的功能,也需要做版本判断。

  
  
  
  
  1. if (android.os.Build.VERSION.SDK_INT >= 8){  
  2.             mReceiverName = new ComponentName(getPackageName(),MediaButtonReceiver.class.getName());  
  3.             mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);  
  4.             mAudioManager.registerMediaButtonEventReceiver(mReceiverName);  
  5.         } 

当然在结束的时候我们也会保持取消登录的良好习惯。

  
  
  
  
  1. if(mAudioManager != null && mReceiverName != null){  
  2.             mAudioManager.unregisterMediaButtonEventReceiver(mReceiverName);  
  3.         } 

Notification表示

Notification表示首先取得NotificationManager

  
  
  
  
  1. mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

在需要表示的时候调用showNotification()方法。和showNotification()方法有关的代码:

  
  
  
  
  1. public interface NotificationProvider{
  2. public Notification createNotification(Context context);
  3. }
  4. NotificationProvider mNotificationProvider = null;
  5. public void setNotificationProvider(NotificationProvider provider){
  6. mNotificationProvider = provider;
  7. }
  8. /** * Show a notification while this service is running. */
  9. private void showNotification() {
  10. if(mNotificationProvider != null){
  11. // Send the notification.
  12. mNotificationManager.notify(NOTIFICATION, mNotificationProvider.createNotification(this));
  13. }
  14. }

已经用了N次的办法了。不用再解释了吧。当然,看看实现侧的做法还有必要的。

  
  
  
  
  1. mProxy.setNotificationProvider(new MediaPlayerService.NotificationProvider(){
  2. @Override
  3. public Notification createNotification(Context context) {
  4. Notification notification = new Notification(R.drawable.button_blue_play, mProxy.getTitle(), System.currentTimeMillis());
  5. // The PendingIntent to launch our activity if the user selects this notification
  6. PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, LyricMain.class), 0);
  7. // Set the info for the views that show in the notification panel.
  8. notification.setLatestEventInfo(context, getText(R.string.media_player_label), mProxy.getTitle(), contentIntent);
  9. return notification;
  10. }
  11. });

代码本身没有什么,都是程式化的东西。

最后就是在不再需要表示Notification的时候,执行以下代码

  
  
  
  
  1. mNotificationManager.cancel(NOTIFICATION);

完整的代码请参照以下博文的附件。

软件功能说明:原创:Android应用开发-Andorid歌词秀,含源码

工程,源码下载:Android歌词秀源码,工程文件2011/9/11版

你可能感兴趣的:(Android歌词秀设计思路(4)通用的音乐播放服务(下))