Управление транспортом уведомлений, похоже, ничего не делает - PullRequest
0 голосов
/ 27 октября 2018

Я создал приложение, которое может воспроизводить аудио, используя MediaBrowserServiceCompat и MediaSessionCompat. В соответствии с инструкциями на веб-сайте разработчиков Android, я создал уведомление в методе MediaSessionCompat.Callback (). OnPlay (), который использует MediaStyle для предоставления элементов управления транспортом, которые должны подключаться к моему сеансу мультимедиа при наличии соответствующего токена. Встроенное приложение управляет воспроизведением и приостановкой работы, как и ожидалось, даже когда приложение закрывается и снова открывается. Служба работает должным образом.

Проблема, однако, заключается в том, что, хотя уведомление выглядит так, как ожидалось, встроенная кнопка паузы, похоже, не может ничего сделать. И несмотря на то, что пример разработчиков Android указывает, что кнопка отмены должна присутствовать, это не так. Кроме того, в примере также указывалось, что служба должна быть остановлена ​​путем считывания уведомления, но это не так.

Достаточно сказать, что в следующем фрагменте кода ничего не работает правильно. За исключением того, что уведомление действительно появляется.

private NotificationCompat.Builder getMediaNotificationBuilder() {

        Intent contentIntent = new Intent(mContext, MainActivity.class);
        contentIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        PendingIntent pendingContentIntent = PendingIntent.getActivity(mContext, 0, contentIntent, 0);

        MediaControllerCompat controller = mMediaSession.getController();

        NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, "PODCAST");

        builder
                .setContentTitle("PODCAST")
                .setContentText("THIS IS A PLACE HOLDER.")
                .setSubText("Still a place holder.")

                // Enable launching the player by clicking the notification
                .setContentIntent(pendingContentIntent)

                // Stop the service when the notification is swiped away
                .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(mContext, PlaybackStateCompat.ACTION_STOP))

                // Make the transport controls visible on the lockscreen
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

                // Add an app icon and set its accent color
                // Be careful about the color
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setColor(ContextCompat.getColor(mContext, R.color.colorPrimaryDark))

                // Add a pause button
                .addAction(new NotificationCompat.Action(
                        R.drawable.ic_pause, "Pause",
                        MediaButtonReceiver.buildMediaButtonPendingIntent(mContext,
                                PlaybackStateCompat.ACTION_PAUSE)))

                // Take advantage of MediaStyle features
                .setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
                        .setMediaSession(mMediaSession.getSessionToken())
                        .setShowActionsInCompactView(0)

                        // Add a cancel button
                        .setShowCancelButton(true)
                        .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(mContext,
                                PlaybackStateCompat.ACTION_STOP)));

        return builder;
    }

Затем я передаю это уведомление

startForground(1, getMediaNotificationBuilder().build())

и затем запустите службу.

Я буду рад поделиться всем исходным кодом приложения, если это необходимо. Я уверен, что здесь я упустил что-то очень простое.

1 Ответ

0 голосов
/ 28 октября 2018

Как я и подозревал, мне не хватало чего-то очень простого.Чтобы мой подкласс MediaBrowserServiceCompat реагировал на мои элементы управления уведомлениями, мне нужно было переопределить onStartCommand из базового класса Service и передать Intent в мой объект MediaSessionCompat.После этого MediaSessionCompat.Callback должен обработать команду, предполагая, что она запрограммирована на это.Вот как выглядит код для этого внутри моего класса MediaBrowserService.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.e(LOG_TAG, "onStartCommand(): received intent " + intent.getAction() + " with flags " + flags + " and startId " + startId);
    MediaButtonReceiver.handleIntent(mMediaSession, intent);
    return super.onStartCommand(intent, flags, startId);
}

После добавления этого кода вы также должны увидеть метод в logcat.На всякий случай, если кто-то все еще что-то пропустил, вы, по крайней мере, будете знать, что код реагирует на нажатия вашей кнопки.

РЕДАКТИРОВАТЬ:

Что касается остановки Сервиса путем проведения уведомления,Я неправильно понял взаимодействие между уведомлением и пользователем.Уведомление МОЖЕТ быть удалено пользователем, но только если носитель сначала приостановлен.Эта парадигма дополнительно поддерживается элементами управления уведомлениями стандартного медиаплеера.Это имеет смысл, поскольку пользователь может случайно смахнуть элементы управления, когда слушает что-то иное.

Кроме того, я решил включить весь исходный код для моего класса MediaBrowserServiceCompat в надежде, что эта дополнительная информация будетпредоставить некоторый контекст для обсуждения

public class MediaPlaybackService extends MediaBrowserServiceCompat {
private static final String LOG_TAG = "MediaPlaybackService";
private static final String MY_MEDIA_ROOT_ID = "media_root_id";
private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id";

// Volume levels: Normal and Duck
// VOLUME_DUCK is the volume we set the media player to when we lose audio focus, but are allowed to reduce the volume instead of stopping playback.
public static final float VOLUME_DUCK = 0.2f;
public static final float VOLUME_NORMAL = 1.0f;

private MediaSessionCompat mMediaSession;
private MediaPlayer        mMediaPlayer;

// Current local media player state
private PlaybackStateCompat.Builder mStateBuilder;
private int                         mState = PlaybackStateCompat.STATE_NONE;

private final class MediaSessionCallback extends MediaSessionCompat.Callback implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, AudioManager.OnAudioFocusChangeListener{

    private Context mContext;

    private AudioManager mAudioManager;

    // Declare the "SHIT THAT'S LOUD" intent, any broadcast receiver
    // that is connected to it will trigger when the headphones come unplugged
    private IntentFilter shitThatsLoudIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
    private BroadcastReceiver shitThatsLoudBroadcastReceiver = new BroadcastReceiver() {
        // TODO: Put me in a separate class
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(LOG_TAG, "SHIT THATS LOUD! The headphones have come unplugged!");
        }
    };

    private MediaSessionCallback(Context context) {
        super();

        mContext = context;
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

        initMediaPlayer();
    }

    private void initMediaPlayer() {
        try {
            mMediaPlayer = new MediaPlayer();

            mMediaPlayer.setDataSource("https://www.blogtalkradio.com/kylekulinski/2018/10/15/the-kyle-kulinski-show.mp3");
            mMediaPlayer.setOnPreparedListener  (this);
            mMediaPlayer.setOnCompletionListener(this);
            mMediaPlayer.setOnErrorListener     (this);

            mMediaPlayer.prepare();
        } catch (IOException e) {
            Log.e(LOG_TAG, ".initMediaPlayer(): IOException: "+e.toString());
        }
    }

    private void mediaPlay() {
        registerReceiver(shitThatsLoudBroadcastReceiver, shitThatsLoudIntentFilter);
        if (mAudioManager.requestAudioFocus(getAudioFocusRequest()) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            Log.d(LOG_TAG, "Audio focus request granted.");

            mState = PlaybackStateCompat.STATE_PLAYING;

            mStateBuilder.setActions(PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP);
            mStateBuilder.setState(mState, mMediaPlayer.getCurrentPosition(), 1.0f, SystemClock.elapsedRealtime());

            mMediaSession.setPlaybackState(mStateBuilder.build());
            mMediaSession.setActive(true);

            mMediaPlayer.start();

            startService(new Intent(mContext, MediaPlaybackService.class));
            startForeground(1, getMediaNotificationBuilder().build());
        }
    }

    private void mediaPause() {

        unregisterReceiver(shitThatsLoudBroadcastReceiver);

        mState = PlaybackStateCompat.STATE_PAUSED;

        mStateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP);
        mStateBuilder.setState(mState, mMediaPlayer.getCurrentPosition(), 1.0f, SystemClock.elapsedRealtime());

        mMediaSession.setPlaybackState(mStateBuilder.build());

        mMediaPlayer.pause();

        stopForeground(false);

    }

    private void releaseResources() {

        mMediaSession.setActive(false);

        mAudioManager.abandonAudioFocusRequest(getAudioFocusRequest());

        unregisterReceiver(shitThatsLoudBroadcastReceiver);

        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.reset();
            mMediaPlayer.release();
            mMediaPlayer = null;
        }

        stopSelf();
        stopForeground(true);
    }

    private NotificationCompat.Builder getMediaNotificationBuilder() {

        Intent contentIntent = new Intent(mContext, MainActivity.class);
        contentIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        PendingIntent pendingContentIntent = PendingIntent.getActivity(mContext, 0, contentIntent, 0);

        MediaControllerCompat controller = mMediaSession.getController();

        NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, "PODCAST");

        builder
                .setContentTitle("PODCAST")
                .setContentText("THIS IS A PLACE HOLDER.")
                .setSubText("Still a place holder.")

                // Enable launching the player by clicking the notification
                .setContentIntent(pendingContentIntent)

                // Stop the service when the notification is swiped away
                .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(mContext, PlaybackStateCompat.ACTION_STOP))

                // Make the transport controls visible on the lockscreen
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

                // Add an app icon and set its accent color
                // Be careful about the color
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setColor(ContextCompat.getColor(mContext, R.color.colorPrimaryDark))

                // Add a pause button
                .addAction(new NotificationCompat.Action(
                        R.drawable.ic_pause, "Pause",
                        MediaButtonReceiver.buildMediaButtonPendingIntent(mContext,
                                PlaybackStateCompat.ACTION_PLAY_PAUSE)))

                // Take advantage of MediaStyle features
                .setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
                        .setMediaSession(mMediaSession.getSessionToken())
                        .setShowActionsInCompactView(0)

                        // Add a cancel button
                        .setShowCancelButton(true)
                        .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(mContext,
                                PlaybackStateCompat.ACTION_STOP)));

        return builder;
    }

    @Override
    public void onPlay() {
        super.onPlay();
        Log.d(LOG_TAG, "I tried to play music");

        mediaPlay();
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(LOG_TAG, "I Tried to pause");

        mediaPause();
    }

    @Override
    public void onStop() {
        super.onStop();
        releaseResources();
    }

    private AudioFocusRequest getAudioFocusRequest() {
        // Request audio focus for playback, this registers the afChangeListener
        AudioAttributes attrs = new AudioAttributes.Builder()
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();
        AudioFocusRequest audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                .setOnAudioFocusChangeListener(this)
                .setAudioAttributes(attrs)
                .build();

        return audioFocusRequest;
    }

    @Override
    public void onAudioFocusChange(int focusChange) {

        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                Log.d(LOG_TAG, "Audio focus has been restored after it was transiently arrested by and intrusive app.  We can now start playing audio normally again.");
                mMediaPlayer.setVolume(VOLUME_NORMAL, VOLUME_NORMAL);
                mediaPlay();
                break;

            case AudioManager.AUDIOFOCUS_LOSS:
                Log.d(LOG_TAG, "Audio focus was lost flat out. Save what we were doing so we don't forget about it later.");
                mediaPause();
                break;

            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                Log.d(LOG_TAG, "Audio focus was lost (Transient) but we might get it back later, still stop and save though.");
                mediaPause();
                break;

            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                Log.d(LOG_TAG, "Audio focus was lost but was just need to keep it down instead of stopping.");
                mMediaPlayer.setVolume(VOLUME_DUCK, VOLUME_DUCK);
                break;

            default:
                Log.d(LOG_TAG, "Ignoring unsupported audio focus change: "+focusChange);
                break;

        }

    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        Log.d(LOG_TAG, "MediaSessionCallback.onPrepared(): MediaPlayer is prepared!");
        // The media player is done preparing. That means we can start playing if we
        // have audio focus.
    }

    @Override
    public void onCompletion(MediaPlayer mp) {

    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        Log.e(LOG_TAG, "Media player error: what=" + what + ", extra=" + extra);
        return false; // true indicates we handled the error
    }
}

@Override
public void onCreate() {
    super.onCreate();

    // Create a MediaSessionCompat
    mMediaSession = new MediaSessionCompat(this, LOG_TAG);
    // Set the session's token so that client activities can communicate with it.
    setSessionToken(mMediaSession.getSessionToken());
    // MediaSessionCallback() has methods that handle callbacks from a media controller
    mMediaSession.setCallback(new MediaSessionCallback(this));
    // Enable callbacks from media buttons and transport controls
    mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
            MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
    );

    // Set initial PlaybackState with ACTION_PLAY, so that media buttons start the player
    mStateBuilder = new PlaybackStateCompat.Builder()
            .setActions(
                    PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE
            );
    mMediaSession.setPlaybackState(mStateBuilder.build());

}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.e(LOG_TAG, "onStartCommand(): received intent " + intent.getAction() + " with flags " + flags + " and startId " + startId);
    MediaButtonReceiver.handleIntent(mMediaSession, intent);
    return super.onStartCommand(intent, flags, startId);
}

@Nullable
@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
    return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null);
}

@Override
public void onLoadChildren(@NonNull String parentMediaId, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
    //  Browsing not allowed
    if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) {
        result.sendResult(null);
        return;
    }

    // TODO: If in the future we decide that we do want this class to handle the podcast metadata
    // Then we must adapt what ever data podcastFactory produces into a List of MediaBrowserCompat.MediaItem objects
    // The constructor of MediaItem requires that a MediaDescription object be passed to it.
    // MediaDescription has a builder class which contains methods for setting Title, Artist, Uri, etc...

    // MediaDescription.Builder mMediaDescriptionBuilder = new MediaDescription.Builder();

    // mMediaDescriptionBuilder.setTitle(String);
    // mMediaDescriptionBuilder.setMediaUri(String);

    // MediaDescription mMediaDescription = mMediaDescriptionBuilder.build()

    // MediaBrowserCompat.MediaItem mMediaItem =
    //     new MediaBrowserCompat.MediaItem(
    //          mMediaDescription,
    //          int flags -> FLAG_BROWSABLE and/or FLAG_PLAYABLE
    // );

    // add MediaItem to SomeList

    // result.sendResult(SomeList);
}
...