Android 注册媒体按键监听(MediaSession 兼容 Android 5.0+)

雷子游 7年前
   <p><strong>使用场景描述:</strong></p>    <p>应用中需要播放音乐的时候,通常有个令人捉急的问题就是媒体焦点;假如同时用 QQ音乐 与自己的应用同时播放音乐的时候,媒体焦点到底花落谁家,谁才能响应这次媒体按键;这就要看谁最后申请了这个焦点</p>    <p>系统中可能有多个应用程序在播放音频,而媒体按键只有一个,只会响应在最后一个获取了媒体焦点的应用;</p>    <p>先来看看怎么请求焦点,注册媒体按键监听(在你的播放器播放的时候调用以下代码注册就可以将媒体焦点抢过来);</p>    <pre>  <code class="language-java">audioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);  mComponentName = new ComponentName(mContext.getPackageName(),MediaButtonIntentReceiver.class.getName());    int result = audioManager              .requestAudioFocus(new MyOnAudioFocusChangeListener(),                       AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);  if (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == result) {//到这一步,焦点已经请求成功了          if (android.os.Build.VERSION.SDK_INT >= 21) {              //注册媒体按键 API 21+(Android 5.0)              setMediaButtonEvent();          } else {              //注册媒体按键 API 21 以下, 通常的做法               audioManager.registerMediaButtonEventReceiver(mComponentName);          }  }</code></pre>    <p>媒体焦点变化监听(看上面代码先请求焦点,再根据 API 等级分别注册监听,所以这里焦点变化监听是通用的)</p>    <pre>  <code class="language-java">class MyOnAudioFocusChangeListener implements AudioManager.OnAudioFocusChangeListener {          @Override          public void onAudioFocusChange(int focusChange) {              switch(focusChange) {                    case AudioManager.AUDIOFOCUS_GAIN:                      // 重新获得焦点,  可做恢复播放,恢复后台音量的操作                      break;                  case AudioManager.AUDIOFOCUS_LOSS:                      // 永久丢失焦点除非重新主动获取,这种情况是被其他播放器抢去了焦点,  为避免与其他播放器混音,可将音乐暂停                      break;                  case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:                      // 暂时丢失焦点,这种情况是被其他应用申请了短暂的焦点,可压低后台音量                      break;                  case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:                      // 短暂丢失焦点,这种情况是被其他应用申请了短暂的焦点希望其他声音能压低音量(或者关闭声音)凸显这个声音(比如短信提示音),                      break;              }          }      }</code></pre>    <p>API 21 之前是通过广播注册的方式来获得按键的通知:</p>    <pre>  <code class="language-java">public class MediaButtonIntentReceiver extends BroadcastReceiver {      @Override      public void onReceive(Context context, Intent intent) {            String intentAction = intent.getAction();          if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {              KeyEvent event = (KeyEvent) intent                  .getParcelableExtra(Intent.EXTRA_KEY_EVENT);          if (event == null)return;          int keycode = event.getKeyCode();          switch (keycode) {          case KeyEvent.KEYCODE_MEDIA_STOP:              //CMD STOP               break;          case KeyEvent.KEYCODE_HEADSETHOOK:          case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:              //CMD TOGGLE PAUSE              break;          case KeyEvent.KEYCODE_MEDIA_NEXT:              //CMD NEXT 这里处理播放器逻辑 下一曲              break;          case KeyEvent.KEYCODE_MEDIA_PREVIOUS:              //CMD PREVIOUS 这里处理播放器逻辑 上一曲              break;          case KeyEvent.KEYCODE_MEDIA_PAUSE:              //CMD PAUSE 这里处理播放器逻辑 暂停              break;          case KeyEvent.KEYCODE_MEDIA_PLAY:              //CMD PLAY 这里处理播放器逻辑 播放              break;          }      }  }</code></pre>    <p>接下来看看 API 21 注册媒体按键的监听</p>    <p>如果 API 21+ 用以上广播注册的方式也是可以的监听的,区别在于如果有别的播放器抢占了焦点你可就抢不回来了</p>    <pre>  <code class="language-java">@TargetApi(Build.VERSION_CODES.LOLLIPOP)//注意申明 API 21  private void setMediaButtonEvent() {      session = new MediaSession(mContext, "随便写一串 tag 就行");      session.setCallback(new MediaSession.Callback() {          @Override          public void onPlay() {              super.onPlay();              //这里处理播放器逻辑 播放              updatePlaybackState(true);//播放暂停更新控制中心播放状态          }            @Override          public void onPause() {              super.onPause();              //这里处理播放器逻辑 暂停              updatePlaybackState(false);//播放暂停更新控制中心播放状态          }            @Override          public void onSkipToNext() {              super.onSkipToNext();              //CMD NEXT 这里处理播放器逻辑 下一曲          }            @Override          public void onSkipToPrevious() {              super.onSkipToPrevious();              //这里处理播放器逻辑 上一曲          }      });  }</code></pre>    <p>播放暂停更新控制中心的播放状态逻辑(会在手机锁屏后显示歌曲的播放状态):</p>    <pre>  <code class="language-java">/*      * update mediaCenter state      */  @TargetApi(Build.VERSION_CODES.LOLLIPOP)  private void updatePlaybackState(boolean isPlaying) {      PlaybackState.Builder stateBuilder = new PlaybackState.Builder()              .setActions(PlaybackState.ACTION_PLAY                      | PlaybackState.ACTION_PLAY_PAUSE                      | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID                      | PlaybackState.ACTION_PAUSE                      | PlaybackState.ACTION_SKIP_TO_NEXT                      | PlaybackState.ACTION_SKIP_TO_PREVIOUS                      | PlaybackState.CONTENTS_FILE_DESCRIPTOR);      if (isPlaying) {          stateBuilder.setState(PlaybackState.STATE_PLAYING,                  PlaybackState.PLAYBACK_POSITION_UNKNOWN,                  SystemClock.elapsedRealtime());      } else {          stateBuilder.setState(PlaybackState.STATE_PAUSED,                  PlaybackState.PLAYBACK_POSITION_UNKNOWN,                  SystemClock.elapsedRealtime());      }      session.setPlaybackState(stateBuilder.build());  }</code></pre>    <p>切换歌曲更新控制中心的播放歌曲的信息(会在手机锁屏后显示歌名,歌手等信息)</p>    <pre>  <code class="language-java">//点击播放 注册监听后调用此方法才能将媒体焦点抢过来   @TargetApi(Build.VERSION_CODES.LOLLIPOP)      public void updateMediaCenterInfo(String title, String artist) {          if (session == null) return;          MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();          metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, title);//歌曲名          metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, artist);//歌手          session.setMetadata(metadataBuilder.build());          updatePlaybackState(true);      }</code></pre>    <p>最后不需要使用媒体焦点记得注销:</p>    <pre>  <code class="language-java">@TargetApi(Build.VERSION_CODES.LOLLIPOP)  private void unRegisterMediaButton(){      if(audioManager==null|mComponentName==null)return;      audioManager.abandonAudioFocus(onAudioFocusChangeListener);      audioManager.unregisterMediaButtonEventReceiver(mComponentName);      if (session != null) {          session.setCallback(null);          session.setActive(false);          session.release();      }  }</code></pre>    <p>MediaSession:</p>    <p>用 MediaSession 的方式注册监听,不做处理在一些手机锁屏后就会显示播放界面,并能够控制上下曲,暂停/播放</p>    <p>官方文档的说明:</p>    <p>允许与媒体控制器,音量键,媒体按钮和传输控件进行交互。</p>    <p>当应用程序想要发布媒体播放信息或处理媒体密钥时,应创建MediaSession。 一般来说,应用程序只需要一个会话进行所有播放,虽然可以创建多个会话以提供更精细的媒体控制。</p>    <p>一旦会话被创建,会话的所有者可以将其会话令牌传递给其他进程,以允许他们创建一个MediaController来与会话交互。</p>    <p>要接收命令,媒体键和其他事件,必须使用setCallback(Callback)和setActive(true)设置MediaSession.Callback。</p>    <p>当应用程序完成播放时,必须调用release()清除会话并通知任何控制器。</p>    <p>MediaSession对象是线程安全的。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/b52754c50f89</p>    <p> </p>