我创建了一个扩展 MediaBrowserServiceCompat 的服务。此服务包含对我的播放器的引用并创建一个带有回调的新 MediaSession。每次播放器更改状态时,我都会更新 MediaSession 的播放状态并创建一个 MediaStyle 通知。当我开始在播放器中播放某些内容时,会显示通知,但通知中的按钮不会触发 MediaSession 回调,它们不会执行任何操作。我在 MediaSession 中设置了正确的标志,我将会话设置为活动,我在播放状态下设置了正确的操作,我将会话令牌传递给通知,但仍然没有从中获得任何回调. 我真的不知道我做错了什么。所有这些代码都在我的应用程序导入的模块中。
我的 NotificationHelper 类:
private final MusicService mService;
private final NotificationCompat.Action mPlayAction;
private final NotificationCompat.Action mPauseAction;
private final NotificationCompat.Action mNextAction;
private final NotificationCompat.Action mPrevAction;
private final NotificationManager mNotificationManager;
public MediaNotificationManager(MusicService service) {
mService = service;
mNotificationManager =
(NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
mPlayAction =
new NotificationCompat.Action(
R.drawable.exo_icon_play,
"Play",
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_PLAY));
mPauseAction =
new NotificationCompat.Action(
R.drawable.exo_icon_pause,
"Pause",
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_PAUSE));
mNextAction =
new NotificationCompat.Action(
R.drawable.exo_icon_next,
"Next",
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_SKIP_TO_NEXT));
mPrevAction =
new NotificationCompat.Action(
R.drawable.exo_icon_previous,
"Previous",
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS));
// Cancel all notifications to handle the case where the Service was killed and
// restarted by the system.
mNotificationManager.cancelAll();
}
public Notification getNotification(MediaMetadataCompat metadata,
@NonNull PlaybackStateCompat state,
MediaSessionCompat.Token token) {
boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_PLAYING;
MediaDescriptionCompat description = metadata.getDescription();
NotificationCompat.Builder builder =
buildNotification(state, token, isPlaying, description);
return builder.build();
}
private NotificationCompat.Builder buildNotification(@NonNull PlaybackStateCompat state,
MediaSessionCompat.Token token,
boolean isPlaying,
MediaDescriptionCompat description) {
// Create the (mandatory) notification channel when running on Android Oreo.
if (isAndroidOOrHigher()) {
createChannel();
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(mService, CHANNEL_ID)
.setSmallIcon(R.drawable.exo_notification_small_icon)
.setContentTitle("Track title")
.setContentText("Artist - Album")
.setLargeIcon(BitmapFactory.decodeResource(mService.getResources(), R.drawable.exo_notification_small_icon))
.setStyle(new MediaStyle().setShowActionsInCompactView(0).setMediaSession(token));
builder.addAction(mPrevAction);
builder.addAction(isPlaying ? mPauseAction : mPlayAction);
builder.addAction(mNextAction);
return builder;
}
// Does nothing on versions of Android earlier than O.
@RequiresApi(Build.VERSION_CODES.O)
private void createChannel() {
if (mNotificationManager.getNotificationChannel(CHANNEL_ID) == null) {
// The user-visible name of the channel.
CharSequence name = "MediaSession";
// The user-visible description of the channel.
String description = "MediaSession and MediaPlayer";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);
// Configure the notification channel.
mChannel.setDescription(description);
mChannel.enableLights(true);
// Sets the notification light color for notifications posted to this
// channel, if the device supports this feature.
mChannel.setLightColor(Color.RED);
mChannel.enableVibration(true);
mChannel.setVibrationPattern(
new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
mNotificationManager.createNotificationChannel(mChannel);
Log.d(TAG, "createChannel: New channel created");
} else {
Log.d(TAG, "createChannel: Existing channel reused");
}
}
private boolean isAndroidOOrHigher() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
我的服务类:
public class MusicService extends MediaBrowserServiceCompat {
private static final String TAG = MusicService.class.getSimpleName();
private MediaSessionCompat mSession;
private PlayerManager playerManager;
private MediaSessionCallback mCallback;
private MediaNotificationManager mediaNotificationManager;
@Override
public void onCreate() {
super.onCreate();
playerManager = PlayerManager.getInstance(this);
playerManager.addListener(new PlayerManagerServiceListener());
mediaNotificationManager = new MediaNotificationManager(this);
// Create a new MediaSession.
mSession = new MediaSessionCompat(this, "MusicService");
mCallback = new MediaSessionCallback();
mSession.setCallback(mCallback);
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
setSessionToken(mSession.getSessionToken());
mSession.setActive(true);
}
@Override
public void onDestroy() {
mSession.release();
Log.d(TAG, "onDestroy: MediaPlayerAdapter stopped, and MediaSession released");
}
@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName,
int clientUid,
Bundle rootHints) {
return new BrowserRoot("root", null);
}
@Override
public void onLoadChildren(
@NonNull final String parentMediaId,
@NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
result.sendResult(null);
}
// MediaSession Callback: Transport Controls -> MediaPlayerAdapter
public class MediaSessionCallback extends MediaSessionCompat.Callback {
@Override
public void onPlay() {
playerManager.play();
}
@Override
public void onPause() {
playerManager.pause();
}
@Override
public void onStop() {
playerManager.stop();
}
@Override
public void onSkipToNext() {
playerManager.next();
}
@Override
public void onSkipToPrevious() {
playerManager.previous();
}
@Override
public void onSeekTo(long pos) {
playerManager.seekTo(pos);
}
}
public class PlayerManagerServiceListener implements PlayerManager.PlayerManagerListener {
@Override
public void onError(@Nullable Exception error) {
}
@Override
public void onProgress(long duration, long position) {
}
@Override
public void onPlayerChange(int change) {
}
@Override
public void onTrackChange(TrackVO track) {
}
@Override
public void onListChange(List tracks) {
}
@Override
public void onPlaybackStateChange(int playbackState) {
PlaybackStateCompat.Builder playbackstateBuilder = new PlaybackStateCompat.Builder();
int playbackStateCompat = -1;
switch(playbackState) {
case PlaybackStateListener.STATE_PLAYING:
playbackStateCompat = PlaybackStateCompat.STATE_PLAYING;
//playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PAUSE);
break;
case PlaybackStateListener.STATE_PAUSED:
playbackStateCompat = PlaybackStateCompat.STATE_PAUSED;
//playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY);
break;
}
if (playbackStateCompat == -1) {
return;
}
mSession.setActive(true);
playbackstateBuilder.setActions(
PlaybackStateCompat.ACTION_PLAY |
PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_SKIP_TO_NEXT |
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS);
playbackstateBuilder.setState(playbackStateCompat, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0);
PlaybackStateCompat state = playbackstateBuilder.build();
MediaMetadataCompat mediaMetadata = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, playerManager.getCurrenTrack().getName())
.build();
mSession.setMetadata(mediaMetadata);
mSession.setPlaybackState(state);
Notification notification = mediaNotificationManager.getNotification(
mediaMetadata,
state,
getSessionToken()
);
Intent intent = new Intent(MusicService.this, MusicService.class);
ContextCompat.startForegroundService(MusicService.this, intent);
startForeground(417, notification);
}
}
}
MediaBrowserHelper 初始化服务:
public class MediaBrowserHelper {
private static final String TAG = MediaBrowserHelper.class.getSimpleName();
private final Context mContext;
private final Class<? extends MediaBrowserServiceCompat> mMediaBrowserServiceClass;
private final List<Callback> mCallbackList = new ArrayList<>();
private final MediaBrowserConnectionCallback mMediaBrowserConnectionCallback;
private final MediaControllerCallback mMediaControllerCallback;
private final MediaBrowserSubscriptionCallback mMediaBrowserSubscriptionCallback;
private MediaBrowserCompat mMediaBrowser;
@Nullable
private MediaControllerCompat mMediaController;
public MediaBrowserHelper(Context context,
Class<? extends MediaBrowserServiceCompat> serviceClass) {
mContext = context;
mMediaBrowserServiceClass = serviceClass;
mMediaBrowserConnectionCallback = new MediaBrowserConnectionCallback();
mMediaControllerCallback = new MediaControllerCallback();
mMediaBrowserSubscriptionCallback = new MediaBrowserSubscriptionCallback();
}
public void onStart() {
if (mMediaBrowser == null) {
mMediaBrowser =
new MediaBrowserCompat(
mContext,
new ComponentName(mContext, mMediaBrowserServiceClass),
mMediaBrowserConnectionCallback,
null);
mMediaBrowser.connect();
}
Log.d(TAG, "onStart: Creating MediaBrowser, and connecting");
}
public void onStop() {
if (mMediaController != null) {
mMediaController.unregisterCallback(mMediaControllerCallback);
mMediaController = null;
}
if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
mMediaBrowser.disconnect();
mMediaBrowser = null;
}
resetState();
Log.d(TAG, "onStop: Releasing MediaController, Disconnecting from MediaBrowser");
}
/**
* Called after connecting with a {@link MediaBrowserServiceCompat}.
* <p>
* Override to perform processing after a connection is established.
*
* @param mediaController {@link MediaControllerCompat} associated with the connected
* MediaSession.
*/
protected void onConnected(@NonNull MediaControllerCompat mediaController) {
}
/**
* Called after loading a browsable {@link MediaBrowserCompat.MediaItem}
*
* @param parentId The media ID of the parent item.
* @param children List (possibly empty) of child items.
*/
protected void onChildrenLoaded(@NonNull String parentId,
@NonNull List<MediaBrowserCompat.MediaItem> children) {
}
/**
* Called when the {@link MediaBrowserServiceCompat} connection is lost.
*/
protected void onDisconnected() {
}
@NonNull
protected final MediaControllerCompat getMediaController() {
if (mMediaController == null) {
throw new IllegalStateException("MediaController is null!");
}
return mMediaController;
}
/**
* The internal state of the app needs to revert to what it looks like when it started before
* any connections to the {@link MusicService} happens via the {@link MediaSessionCompat}.
*/
private void resetState() {
performOnAllCallbacks(new CallbackCommand() {
@Override
public void perform(@NonNull Callback callback) {
callback.onPlaybackStateChanged(null);
}
});
Log.d(TAG, "resetState: ");
}
public MediaControllerCompat.TransportControls getTransportControls() {
if (mMediaController == null) {
Log.d(TAG, "getTransportControls: MediaController is null!");
throw new IllegalStateException("MediaController is null!");
}
return mMediaController.getTransportControls();
}
public void registerCallback(Callback callback) {
if (callback != null) {
mCallbackList.add(callback);
// Update with the latest metadata/playback state.
if (mMediaController != null) {
final MediaMetadataCompat metadata = mMediaController.getMetadata();
if (metadata != null) {
callback.onMetadataChanged(metadata);
}
final PlaybackStateCompat playbackState = mMediaController.getPlaybackState();
if (playbackState != null) {
callback.onPlaybackStateChanged(playbackState);
}
}
}
}
private void performOnAllCallbacks(@NonNull CallbackCommand command) {
for (Callback callback : mCallbackList) {
if (callback != null) {
command.perform(callback);
}
}
}
/**
* Helper for more easily performing operations on all listening clients.
*/
private interface CallbackCommand {
void perform(@NonNull Callback callback);
}
// Receives callbacks from the MediaBrowser when it has successfully connected to the
// MediaBrowserService (MusicService).
private class MediaBrowserConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
// Happens as a result of onStart().
@Override
public void onConnected() {
try {
// Get a MediaController for the MediaSession.
mMediaController =
new MediaControllerCompat(mContext, mMediaBrowser.getSessionToken());
mMediaController.registerCallback(mMediaControllerCallback);
// Sync existing MediaSession state to the UI.
mMediaControllerCallback.onMetadataChanged(mMediaController.getMetadata());
mMediaControllerCallback.onPlaybackStateChanged(
mMediaController.getPlaybackState());
MediaBrowserHelper.this.onConnected(mMediaController);
} catch (RemoteException e) {
Log.d(TAG, String.format("onConnected: Problem: %s", e.toString()));
throw new RuntimeException(e);
}
mMediaBrowser.subscribe(mMediaBrowser.getRoot(), mMediaBrowserSubscriptionCallback);
}
}
// Receives callbacks from the MediaBrowser when the MediaBrowserService has loaded new media
// that is ready for playback.
public class MediaBrowserSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback {
@Override
public void onChildrenLoaded(@NonNull String parentId,
@NonNull List<MediaBrowserCompat.MediaItem> children) {
MediaBrowserHelper.this.onChildrenLoaded(parentId, children);
}
}
// Receives callbacks from the MediaController and updates the UI state,
// i.e.: Which is the current item, whether it's playing or paused, etc.
private class MediaControllerCallback extends MediaControllerCompat.Callback {
@Override
public void onMetadataChanged(final MediaMetadataCompat metadata) {
performOnAllCallbacks(new CallbackCommand() {
@Override
public void perform(@NonNull Callback callback) {
callback.onMetadataChanged(metadata);
}
});
}
@Override
public void onPlaybackStateChanged(@Nullable final PlaybackStateCompat state) {
performOnAllCallbacks(new CallbackCommand() {
@Override
public void perform(@NonNull Callback callback) {
callback.onPlaybackStateChanged(state);
}
});
}
// This might happen if the MusicService is killed while the Activity is in the
// foreground and onStart() has been called (but not onStop()).
@Override
public void onSessionDestroyed() {
resetState();
onPlaybackStateChanged(null);
MediaBrowserHelper.this.onDisconnected();
}
}
}
显现:
<service android:name="com.amco.playermanager.MusicService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</receiver>