MediaSession 没有从 MediaStyle 通知中获得任何回调

Posted

技术标签:

【中文标题】MediaSession 没有从 MediaStyle 通知中获得任何回调【英文标题】:MediaSession not getting any callbacks from MediaStyle Notification 【发布时间】:2019-03-08 02:39:39 【问题描述】:

我创建了一个扩展 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>

【问题讨论】:

【参考方案1】:

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

【讨论】:

以上是关于MediaSession 没有从 MediaStyle 通知中获得任何回调的主要内容,如果未能解决你的问题,请参考以下文章

MediaSession和MediaSessionService的交互流程梳理

MediaSession和MediaSessionService的交互流程梳理

MediaSession和MediaSessionService的交互流程梳理

MediaSession 和 MediaSessionManager 是不是具有向后兼容性?

Android车载多媒体与MediaSession框架

Android车载应用开发与分析- 车载多媒体- 多媒体应用架构与MediaSession框架