MediaSession 框架是 Google 推出專門解決媒體播放時界面和服務通訊問題。這個框架可以讓我們不再使用廣播來控制播放器,而且也能適配耳機,藍牙等一些其它設備,實現線控的功能
要理解MediaSession框架,分別看看Media和Session:首先Media是媒體的意思,也就是說這個框架用於音視頻媒體;而Session呢,翻譯成中文就是會話的意思。一個會話,肯定是涉及兩方或以上;在MediaSession框架中,有受控端(一個)和控制端(可以有多個)。接下來爲了保證受控端和控制端不串號(想象一個遙控器可以遙控同一型號的多臺電視),就有了SessionToken的概念,相當於我們在連接藍牙設備時的配對碼,這樣就保證了不串號。在MediaSession框架中,最重要的三個類的概念就這麼多,
核心類
MediaSessionCompat
PlaybackStateCompat
MediaMetadataCompat
MediaSessionCompat.Callback
初始化配置
/**
* 初始化並激活 MediaSession
*/
private void setupMediaSession() {
// 第二個參數 tag: 這個是用於調試用的,隨便填寫即可
mMediaSession = new MediaSessionCompat(context, TAG);
//指明支持的按鍵信息類型
mMediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
);
mMediaSession.setCallback(callback);
mMediaSession.setActive(true);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
指定可以接收的來自鎖屏頁面的按鍵信息
private static final long MEDIA_SESSION_ACTIONS =
PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
| PlaybackStateCompat.ACTION_STOP
| PlaybackStateCompat.ACTION_SEEK_TO;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
5.0之後耳機物理按鍵的回調監聽
/**
* API 21 以上 耳機多媒體按鈕監聽 MediaSessionCompat.Callback
*/
private MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() {
// 接收到監聽事件,可以有選擇的進行重寫相關方法
@Override
public void onPlay() {
super.onPlay();
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onSkipToNext() {
super.onSkipToNext();
}
@Override
public void onSkipToPrevious() {
super.onSkipToPrevious();
}
@Override
public void onStop() {
super.onStop();
}
@Override
public void onSeekTo(long pos) {
super.onSeekTo(pos);
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
配置完成後,播放狀態,及音樂信息的更新
/**
* 更新播放狀態,播放/暫停/拖動進度條時調用
*/
public void updatePlaybackState() {
int state = isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED;
mMediaSession.setPlaybackState(
new PlaybackStateCompat.Builder()
.setActions(MEDIA_SESSION_ACTIONS)
.setState(state, getCurrentPosition(), 1)
.build());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
/**
* 更新正在播放的音樂信息,切換歌曲時調用
*/
public void updateMetaData(String path) {
if (!StringUtils.isReal(path)) {
mMediaSession.setMetadata(null);
return;
}
SongInfo info = mediaManager.getSongInfo(context, path);
MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, info.getTitle())
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, info.getArtist())
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, info.getAlbum())
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, info.getArtist())
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, info.getDuration())
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, getCoverBitmap(info));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getCount());
}
mMediaSession.setMetadata(metaData.build());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
完整代碼
package com.zdd.musicplayer.service;
import android.content.Context;
import android.drm.DrmStore;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.RemoteException;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import com.zdd.musicplayer.R;
import com.zdd.musicplayer.aidl.IPlayControl;
import com.zdd.musicplayer.manager.MediaManager;
import com.zdd.musicplayer.modle.SongInfo;
import com.zdd.musicplayer.util.StringUtils;
import java.util.zip.Inflater;
/**
* Project: MusicPlayer
* Created by Zdd on 2018/2/2.
* <p>
* 耳機線控
* <p>
* Android 5.0中新增了MediaSession類專門解決媒體播放時界面和服務通訊問題,官方說明是 允許與媒體控制器、音量鍵、媒體按鈕和傳輸控件交互。
* 包含了媒體控制和線控等功能
* 這個框架可以讓我們不再使用廣播來控制播放器,而且也能適配耳機,藍牙等一些其它設備,實現線控的功能。
* <p>
* 但是對於低版本上的仍需要自己控制
* <p>
* 參考文章 http://www.jianshu.com/p/bc2f779a5400;
*/
public class MediaSessionManager {
private static final String TAG = "MediaSessionManager";
//指定可以接收的來自鎖屏頁面的按鍵信息
private static final long MEDIA_SESSION_ACTIONS =
PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
| PlaybackStateCompat.ACTION_STOP
| PlaybackStateCompat.ACTION_SEEK_TO;
private final IPlayControl control;
private final Context context;
private MediaSessionCompat mMediaSession;
private final MediaManager mediaManager;
public MediaSessionManager(IPlayControl control, Context context) {
this.control = control;
this.context = context;
this.mediaManager = MediaManager.getInstance();
setupMediaSession();
}
/**
* 初始化並激活 MediaSession
*/
private void setupMediaSession() {
// 第二個參數 tag: 這個是用於調試用的,隨便填寫即可
mMediaSession = new MediaSessionCompat(context, TAG);
//指明支持的按鍵信息類型
mMediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
);
mMediaSession.setCallback(callback);
mMediaSession.setActive(true);
}
/**
* 更新播放狀態, 播放/暫停/拖動進度條時調用
*/
public void updatePlaybackState() {
int state = isPlaying() ? PlaybackStateCompat.STATE_PLAYING :
PlaybackStateCompat.STATE_PAUSED;
mMediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setActions(MEDIA_SESSION_ACTIONS)
.setState(state, getCurrentPosition(), 1)
.build());
}
private long getCurrentPosition() {
try {
return control.getProgress();
} catch (RemoteException e) {
e.printStackTrace();
return 0;
}
}
/**
* 是否在播放
*
* @return
*/
protected boolean isPlaying() {
try {
return control.status() == PlayController.STATUS_PLAYING;
} catch (RemoteException e) {
e.printStackTrace();
return false;
}
}
/**
* 更新正在播放的音樂信息,切換歌曲時調用
*/
public void updateMetaData(String path) {
if (!StringUtils.isReal(path)) {
mMediaSession.setMetadata(null);
return;
}
SongInfo songInfo = mediaManager.getSongInfo(context, path);
MediaMetadataCompat.Builder metaDta = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, songInfo.getTitle())
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, songInfo.getArtist())
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, songInfo.getAlbum())
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, songInfo.getArtist())
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, songInfo.getDuration())
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, getCoverBitmap(songInfo));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
metaDta.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getCount());
}
mMediaSession.setMetadata(metaDta.build());
}
private long getCount() {
try {
return control.getPlayList().size();
} catch (RemoteException e) {
e.printStackTrace();
return 0;
}
}
private Bitmap getCoverBitmap(SongInfo info) {
if (StringUtils.isReal(info.getAlbum_path())) {
return BitmapFactory.decodeFile(info.getAlbum_path());
} else {
return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_song);
}
}
/**
* 釋放MediaSession,退出播放器時調用
*/
public void release() {
mMediaSession.setCallback(null);
mMediaSession.setActive(false);
mMediaSession.release();
}
/**
* API 21 以上 耳機多媒體按鈕監聽 MediaSessionCompat.Callback
*/
private MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() {
// 接收到監聽事件,可以有選擇的進行重寫相關方法
@Override
public void onPlay() {
try {
control.resume();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onPause() {
try {
control.pause();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onSkipToNext() {
try {
control.next();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onSkipToPrevious() {
try {
control.pre();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onStop() {
try {
control.pause();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onSeekTo(long pos) {
try {
control.seekTo((int) pos);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
}