Подключение экзоплеера с MediaSessionCompat

Я хочу связать свою реализацию exoplayer с объектом медиа-сеанса. Я настроил SimpleExoPlayerView для показа видео. Каждый раз, когда нажимается кнопка, я хочу, чтобы обратные вызовы сеанса мультимедиа запускались. Я могу заставить обратные вызовы срабатывать только тогда, когда используется что-то вроде пары наушников. Код, используемый в приложении, написан ниже

@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void setUp(LifecycleOwner lifecycleOwner){
    // Create a MediaSessionCompat
    Log.i("Hoe8", "lco setup called");
    mMediaSession = new MediaSessionCompat(activity, "this");

    // Enable callbacks from MediaButtons and TransportControls
    mMediaSession.setFlags(
            MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
                    MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);

    // Do not let MediaButtons restart the player when the app is not visible
    mMediaSession.setMediaButtonReceiver(null);

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

    // MySessionCallback has methods that handle callbacks from a media controller
    mMediaSession.setCallback(new MediaSessionCompat.Callback() {
        @Override
        public void onPlay() {
            super.onPlay();
            Log.i("Hoe8", "MediaSession callback play called");
            mMediaSession.setActive(true);
            ((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(true);
            ((JokesAdapter.VideoPostViewHolder) rv).setHasStarted(true);

        }

        @Override
        public void onPause() {
            super.onPause();
            ((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(false);
        }

        @Override
        public void onStop() {
            super.onStop();
            mMediaSession.setActive(false);
            ((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(false);
            ((JokesAdapter.VideoPostViewHolder) rv).setHasStarted(false);
        }
    });

    // Create a MediaControllerCompat
    MediaControllerCompat mediaController =
            new MediaControllerCompat(activity, mMediaSession);

    MediaControllerCompat.setMediaController(activity, mediaController);

    //Handler mainHandler = new Handler();
    BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
    TrackSelection.Factory videoTrackSelectionFactory =
            new AdaptiveTrackSelection.Factory(bandwidthMeter);
    TrackSelector trackSelector =
            new DefaultTrackSelector(videoTrackSelectionFactory);




// 2. Create the player
        player = ExoPlayerFactory.newSimpleInstance(activity, trackSelector);
        playerView.setPlayer(player);

    MediaSessionConnector mediaSessionConnector =
            new MediaSessionConnector(mMediaSession);
    mediaSessionConnector.setPlayer(player, null,null );


}

Внесены некоторые изменения в код

public class VideoLifeCyclerObserver implements LifecycleObserver {

MediaSessionCompat mMediaSession;
PlaybackStateCompat.Builder mStateBuilder;
AppCompatActivity activity;
SimpleExoPlayerView playerView;
SimpleExoPlayer player;
ExoPlayer.ExoPlayerComponent rv;
MediaSessionConnector mediaSessionConnector;

public VideoLifeCyclerObserver(AppCompatActivity activity, SimpleExoPlayerView playerView, ExoPlayer.ExoPlayerComponent rv){
    this.activity = activity;
    this.playerView = playerView;
    this.activity.getLifecycle().addObserver(this);
    this.rv = rv;
    Log.i("Hoe8","video lco created");
}


@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void setUp(LifecycleOwner lifecycleOwner){
    // Create a MediaSessionCompat
    Log.i("Hoe8", "lco setup called");
    mMediaSession = new MediaSessionCompat(activity, "this");

    // Create a MediaControllerCompat
    MediaControllerCompat mediaController =
            new MediaControllerCompat(activity, mMediaSession);

    MediaControllerCompat.setMediaController(activity, mediaController);

    mediaSessionConnector =
            new MediaSessionConnector(mMediaSession, new PlayBackController());
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void startPlayer(LifecycleOwner lifecycleOwner){
    BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
    TrackSelection.Factory videoTrackSelectionFactory =
            new AdaptiveTrackSelection.Factory(bandwidthMeter);
    TrackSelector trackSelector =
            new DefaultTrackSelector(videoTrackSelectionFactory);
    player = ExoPlayerFactory.newSimpleInstance(activity, trackSelector);
    playerView.setPlayer(player);
    mediaSessionConnector.setPlayer(player, null,null );
}

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void tearDown(LifecycleOwner lifecycleOwner){
    player.stop();
    player.release();
    player.sendMessages(new ExoPlayer.ExoPlayerMessage(rv,1,player.getContentPosition()));
}

public class PlayBackController extends DefaultPlaybackController{
    @Override
    public void onPause(Player player) {
        Log.i("Hoe8", "onPause called");
        ((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(false);
        super.onPause(player);
    }

    @Override
    public void onPlay(Player player) {
        Log.i("Hoe8", "MediaSession callback play called 2");
        mMediaSession.setActive(true);
        ((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(true);
        ((JokesAdapter.VideoPostViewHolder) rv).setHasStarted(true);
        super.onPlay(player);
    }

    @Override
    public void onStop(Player player) {
        Log.i("Hoe8", "onStop called");
        mMediaSession.setActive(false);
        ((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(false);
        ((JokesAdapter.VideoPostViewHolder) rv).setHasStarted(false);
        super.onStop(player);
    }


}
}

Как я могу заставить кнопки, которые отображаются в SimpleExoPlayerView, запускать обратные вызовы мультимедийного сеанса?


person Joel Robinson-Johnson    schedule 01.01.2018    source источник
comment
Посмотрите эту статью «Расширение MediaSession для ExoPlayer» medium .com/google-exoplayer/   -  person pantos27    schedule 02.01.2018
comment
@ pantos27 Я видел это, видимо, я все еще делаю что-то не так. Я реализовал интерфейс MediaSessionConnector.PlaybackController в классе, который запускает код.   -  person Joel Robinson-Johnson    schedule 02.01.2018


Ответы (1)


Короче говоря:

удалите весь код в вашем onCreate, начиная с (включительно)

// Включить обратные вызовы от MediaButtons и TransportControls

к (исключительно)

// Создаем MediaControllerCompat

:)

Более длинное:

Я рекомендую запускать обратные вызовы сеанса мультимедиа, прослушивая переходы состояний проигрывателя, а не нажимая кнопки. Это избавляет вас от необходимости делать это для каждого элемента пользовательского интерфейса, взаимодействующего с игроком. Это на самом деле то, что MediaSessionConnector делает для вас.

С MediaSessionConnector вам не нужно самостоятельно манипулировать MediaSession. Соединитель является посредником между экземпляром проигрывателя и мультимедийным сеансом. Это означает, что коннектор прослушивает изменения состояния проигрывателя и сопоставляет состояние проигрывателя с состоянием мультимедийного сеанса. Соединитель также прослушивает действия мультимедиа, отправленные элементами управления транспортом, и делегирует их проигрывателю или вашему приложению. Примечание. Вашему приложению не нужно предоставлять MediaSessionCompat.Callback, соединитель регистрирует свой собственный (и переопределяет ваш, так как в сеансе может быть только один).

В общем: ваше приложение взаимодействует только с экземпляром SimpleExoPlayer, в то время как коннектор сопоставляет состояние проигрывателя с сеансом.

Начнем с базового подхода, который сопоставляет состояние проигрывателя с сеансом, вызывающим соответствующие методы MediaControllerCompat.Callback:

// code running in a activity or service where (this instanceof Context)

mediaSession = new MediaSessionCompat(this, getPackageName());
mediaSessionConnector = new MediaSessionConnector(mediaSession)
mediaSessionConnector.setPlayer(player, null, null);
mediaSession.setActive(true);

Теперь вы можете подготовить и использовать проигрыватель, как и раньше, например, вызвать setPlayWhenReady(true|false), seekTo(t), и коннектор поддерживает PlaybackStateCompat, который транслируется на контроллеры сеанса.

Коннектор получает и реализует пару действий с мультимедиа на этом уровне (нет необходимости в вашем собственном MediaSession.Callback):

PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_PLAY | 
PlaybackStateCompat.ACTION_PAUSE | 
PlaybackStateCompat.ACTION_STOP |
PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE |
PlaybackStateCompat.ACTION_SET_REPEAT_MODE;

Действия PlayFromXYZ

Вы можете поддерживать дополнительные действия с мультимедиа, такие как ACTION_PLAY_FROM_MEDIA_ID. Вы можете сделать это, предоставив свой PlaybackPreparer:

playbackPreparer = new YourPlaybackPreparer(); 
mediaSessionConnector.setPlayer(player, playbackPreparer, null);

Соединитель теперь делегирует такие действия, как ACTION_PLAY_FROM_MEDIA_ID или ACTION_PREPARE_FROM_MEDIA_ID, средству подготовки воспроизведения, которое создает MediaSource для данного идентификатора носителя для подготовки проигрывателя.

Управление метаданными и очередями

Также интересна возможность сопоставления временной шкалы проигрывателя непосредственно с очередью и метаданными медиа-сессии. Для этого вы можете предоставить QueueNavigator. Существует абстрактный TimelineQueueNavigator, предоставленный расширением:

QueueNavigator queueNavigator = new TimelineQueueNavigator(mediaSession) {
  @Override
  public MediaDescriptionCompat getMediaDescription(int windowIndex) {
    // implement this method and read from your backing data:
    getMediaDescriptionAtQueuePosition(windowIndex):
    return mediaDescription;
  }
}
mediaSessionConnector.setQueueNavigator(queueNavigator);

Благодаря этому медиа-контроллеры теперь могут читать метаданные и очередь сеанса. Очередь представляет текущую временную шкалу проигрывателя, а метаданные сеанса описывают окно на временной шкале, которое воспроизводится в данный момент. (подробнее о плейлистах).

При наличии TimelineQueueNavigator соединитель прослушивает ACTION_SKIP_TO_NEXT, ACTION_SKIP_TO_PREVIOUS и ACTION_SKIP_TO_QUEUE_ITEM, отправленные элементами управления транспортом, и соответствующим образом перемещается по временной шкале.

Интеграция жизненного цикла

Обратите внимание, что вы должны создать экземпляр проигрывателя onStart/onResume и выпустить его onPause/onStop. Это гарантирует, что ресурсы кодека, которыми вы делитесь с другими приложениями, освобождаются, когда вы находитесь в фоновом режиме. Ваш образец кода делает это только один раз при создании, что не является хорошим гражданством :). Посмотрите, как это делает демонстрационное приложение ExoPlayer.

Также обратите внимание на блог Medium о MediaSessionConnector.

person marcbaechinger    schedule 02.01.2018
comment
Итак, я несколько раз перечитал ваше сообщение и сообщение в СМИ. В итоге я создал дочерний класс DefaultPlaybackController. Ни один из созданных мной операторов журнала не отображается, и ни одна из дополнительных инструкций, которые я установил, похоже, не выполняется. Я также внес соответствующие изменения, такие как создание экземпляра экзоплеера в onStart. - person Joel Robinson-Johnson; 03.01.2018
comment
Считаете ли вы, что код может работать неправильно из-за того, что он выполняется в объекте, поддерживающем жизненный цикл? - person Joel Robinson-Johnson; 03.01.2018
comment
Ваш второй подход должен работать ИМО. Ваш PlayBackController должен вызываться, когда вы отправляете данную команду мультимедиа через элементы управления транспортом: mediaController.getTransportControls().play() запускает PlaybackController.onPlay(Player). Сеанс мультимедиа должен быть активен, иначе инфраструктура Android не будет выполнять действия с мультимедиа. Вызовите mediaSession.setActive(true) сразу после установки проигрывателя на коннектор. В сторону: вы рассматривали возможность использования Player.EventListener для запуска вашего кода? - person marcbaechinger; 04.01.2018
comment
Используя Player.EventListener, я получил обратные вызовы для запуска, о чем свидетельствуют операторы журнала, появляющиеся, когда они должны, но приложение по-прежнему не демонстрирует правильную функциональность. У меня сейчас проблемы со съемкой. - person Joel Robinson-Johnson; 04.01.2018
comment
Вам, @JoelRobinson-Johnson, удалось решить эту проблему? У меня похожая проблема, и я не могу найти никакого полезного руководства или примера использования MediaSessionConnector. - person thorin86; 27.03.2018
comment
Активировали ли вы медиа-сессию? session.setActive (истина) - person marcbaechinger; 27.03.2018
comment
@thorin86 для того, чтобы обратные вызовы срабатывали без использования наушников, я создал класс, который реализовал интерфейс Player.EventListener, переопределил onPlayerStateChanged и добавил слушателя к объекту SimpleExoPlayer (через addListener()) - person Joel Robinson-Johnson; 29.03.2018
comment
@marcbaechinger, насколько я знаю, да. - person Joel Robinson-Johnson; 29.03.2018
comment
У меня есть вопрос, связанный с этим подходом. Как бы вы предложили работать с getMediaDescriptionAtQueuePosition(windowIndex), где изображение должно поступать из Интернета? - person Androider; 24.03.2019