MediaSession 没有从 MediaStyle Notification 获得任何回调

MediaSession not getting any callbacks from MediaStyle Notification

我创建了一个扩展 MediaBrowserServiceCompat 的服务。该服务持有对我的播放器的引用,并创建一个带有回调的新 MediaSession。每次播放器更改状态时,我都会更新 MediaSession 的播放状态并创建 MediaStyle 通知。当我开始在播放器中播放某些内容时会显示通知,但通知中的按钮不会触发 MediaSession 回调,它们不会执行任何操作。我在 MediaSession 中设置了正确的标志,我将会话设置为活动状态,我在播放状态下设置了正确的操作,我将会话令牌传递给通知,但仍然没有从中获得任何回调.我真的不知道我做错了什么。所有这些代码都在我的应用程序导入的模块中。

我的 NotificationHelper class:

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;
}

我的服务class:

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>

事实证明,整个问题是由另一个 BroadcastReceiver 处理 MEDIA_BUTTON 在我的应用程序的清单中声明引起的。通过删除该接收器,现在一切正常。