[译]MediaSession & MediaController – Android TV 应用开发教程九

版权声明:本文为博主原创翻译文章,转载请注明出处。

推荐: 欢迎关注我创建的Android TV 简书专题,会定期给大家分享一些AndroidTv相关的内容: www.jianshu.com/c/37efc6e97…


###Video Controls implementation with MediaSession 示例实现是在Google最新的 Android TV应用程序中完成的。 AOSP示例应用程序实现尚未实现MediaSession(在API 21,22中)。

在上一章中,我解释说,以下视频控件需要被压制。 1.Action的UI更新部分(在上一章完成) 2.视频控制部分(在上一章完成)MediaSession实现,通过MediaController的TransportControls进行视频控制(本章)

3.- 当用户按下电视遥控器的视频控制按钮时,MediaSession可以处理该动作。 它允许其他活动继承视频控制。特别是LeanbackLauncher,家庭显示器,可以在后台播放视频。

4.将MediaMetadata设置为MediaSession(本章) ( “ Now playing card ”将出现在推荐行的顶部。)

在本章中,我们继续使用MediaSession实现视频控件。我们可以通过使用MediaSession将VideoView控件传递给LeanbackLauncher,从而在LeanbackLauncher中实现播放视频背景。

对于3,我们在PlaybackOverlayActivity中创建MediaSession,并从PlaybackOverlayFragment中的MediaController进行控制。

对于4,MediaSession的元数据使用MediaMetadata&PlaybackState类进行更新,以更新“Now playing card”。

我建议您阅读 Displaying a Now Playing Card,以获得官方解释。

本章的实现与上一章的实现几乎是独立的。在实施MediaSession之前,我将执行requestVisibleBehind方法,以便我们可以在LeanbackLauncher应用程序的背景下播放视频。 ###Implement requestVisibleBehind 这个方法是添加在API 21(Lolipop)中,AOSP的解释解释了

If this call is successful then the activity will remain visible after onPause() is called, and is allowed to continue playing media in the background.

示例实现如下。

@Override
    public void onPause() {
        super.onPause();
        if (!requestVisibleBehind(true)) {
            // Try to play behind launcher, but if it fails, stop playback.
            mPlaybackController.playPause(false);
        }
    }
复制代码

实施后,当您在应用程序中播放视频内容并按“Home”键返回LeanbackLauncher时,该视频将在后台继续播放。。

###本章课堂结构

我们在本章中处理3个课程。

  • PlaybackOverlayActivity
    • 管理生命周期,将意图信息传递给PlaybackController
    • MediaSession的生活时间与活动有关
  • PlaybackOverlayFragment
    • 处理PlaybackControlsRow的UI
    • MediaController回调功能用于根据当前播放状态更新UI
  • PlaybackController
    • 管理视频播放
    • 视频控制功能
    • MediaSessionCallback用于从电视遥控器接收视频控制键

###Create & release MediaSession 到目前为止,我们无法使用遥控器中的视频控制键控制此视频。 让我们实现MediaSession来定义遥控器中每个视频控制键的动作。 首先,我们在PlaybackController的Constractor中创建MediaSession,由PlaybackOverlayActivity调用。

    public PlaybackController(Activity activity) {
        mActivity = activity;
        // mVideoView = (VideoView) activity.findViewById(VIDEO_VIEW_RESOURCE_ID);
        createMediaSession(mActivity);
    }

    private void createMediaSession(Activity activity) {
        if (mSession == null) {
            mSession = new MediaSession(activity, MEDIA_SESSION_TAG);
            mMediaSessionCallback = new MediaSessionCallback();
            mSession.setCallback(mMediaSessionCallback);
            mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
                    MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);

            mSession.setActive(true);
            activity.setMediaController(new MediaController(activity, mSession.getSessionToken()));
        }
    }
复制代码

我们可以将回调MediaSessionCallback设置为MediaSession。 它定义了将在后面解释的每个视频控制按钮的精确行为。

需要使用参数FLAG_HANDLES_MEDIA_BUTTONS&FLAG_HANDLES_TRANSPORT_CONTROLS的setFlags方法才能使遥控器键控制视频。

创建后,我们必须在完成后释放MediaSession。

    public void releaseMediaSession() {
        if(mSession != null) {
            mSession.release();
        }
    }
复制代码

###视频控制功能 视频控制功能在MediaSessionCallback类中实现。 顾名思义,每个视频控制动作都在相应的回调函数中实现。 这种回调是从2种方式调用的,即“遥控器媒体键”或“PlaybackControlsRow”中的UI视频控制按钮。

public void playPause(boolean doPlay) {

        if (mCurrentPlaybackState == PlaybackState.STATE_NONE) {
            /* Callbacks for mVideoView */
            setupCallbacks();
        }

        //if (doPlay && mCurrentPlaybackState != PlaybackState.STATE_PLAYING) {
        if (doPlay) { // Play
            Log.d(TAG, "playPause: play");
            if(mCurrentPlaybackState == PlaybackState.STATE_PLAYING) {
                /* if current state is already playing, do nothing */
                return;
            } else {
                mCurrentPlaybackState = PlaybackState.STATE_PLAYING;
                mVideoView.start();
                mStartTimeMillis = System.currentTimeMillis();
            }
        } else { // Pause
            Log.d(TAG, "playPause: pause");
            if(mCurrentPlaybackState == PlaybackState.STATE_PAUSED) {
                /* if current state is already paused, do nothing */
                return;
            } else {
                mCurrentPlaybackState = PlaybackState.STATE_PAUSED;
            }
            setPosition(mVideoView.getCurrentPosition());
            mVideoView.pause();

        }

        updatePlaybackState();
    }

    public void fastForward() {
        if (mDuration != -1) {
            // Fast forward 10 seconds.
            setPosition(getCurrentPosition() + (10 * 1000));
            mVideoView.seekTo(mPosition);
        }

    }

    public void rewind() {
        // rewind 10 seconds
        setPosition(getCurrentPosition() - (10 * 1000));
        mVideoView.seekTo(mPosition);
    }


    private class MediaSessionCallback extends MediaSession.Callback {
        @Override
        public void onPlay() {
            playPause(true);
        }

        @Override
        public void onPause() {
            playPause(false);
        }

        @Override
        public void onSkipToNext() {
            if (++mCurrentItem >= mItems.size()) { // Current Item is set to next here
                mCurrentItem = 0;
            }

            Movie movie = mItems.get(mCurrentItem);
            //Movie movie = VideoProvider.getMovieById(mediaId);
            if (movie != null) {
                setVideoPath(movie.getVideoUrl());
                //mCurrentPlaybackState = PlaybackState.STATE_PAUSED;
                //updateMetadata(movie);
                updateMetadata();
                playPause(mCurrentPlaybackState == PlaybackState.STATE_PLAYING);
            } else {
                Log.e(TAG, "onSkipToNext movie is null!");
            }

        }


        @Override
        public void onSkipToPrevious() {
            if (--mCurrentItem < 0) { // Current Item is set to previous here
                mCurrentItem = mItems.size()-1;
            }

            Movie movie = mItems.get(mCurrentItem);
            //Movie movie = VideoProvider.getMovieById(mediaId);
            if (movie != null) {
                setVideoPath(movie.getVideoUrl());
                //mCurrentPlaybackState = PlaybackState.STATE_PAUSED;
                updateMetadata();
                playPause(mCurrentPlaybackState == PlaybackState.STATE_PLAYING);
            } else {
                Log.e(TAG, "onSkipToPrevious movie is null!");
            }
        }

        @Override
        public void onPlayFromMediaId(String mediaId, Bundle extras) {
            mCurrentItem = Integer.parseInt(mediaId);
            Movie movie = mItems.get(mCurrentItem);
            //Movie movie = VideoProvider.getMovieById(mediaId);
            if (movie != null) {
                setVideoPath(movie.getVideoUrl());
                // mCurrentPlaybackState = PlaybackState.STATE_PAUSED;
                // updateMetadata(movie);
                updateMetadata();
                playPause(mCurrentPlaybackState == PlaybackState.STATE_PLAYING);
            }
        }

        @Override
        public void onSeekTo(long pos) {
            setPosition((int) pos);
            mVideoView.seekTo(mPosition);
            updatePlaybackState();
        }

        @Override
        public void onFastForward() {
            fastForward();
        }

        @Override
        public void onRewind() {
            rewind();
        }
    }
复制代码

###视频控制按遥控器键

    private void updatePlaybackState() {
        PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
                .setActions(getAvailableActions());
        int state = PlaybackState.STATE_PLAYING;
        if (mCurrentPlaybackState == PlaybackState.STATE_PAUSED || mCurrentPlaybackState == PlaybackState.STATE_NONE) {
            state = PlaybackState.STATE_PAUSED;
        }
        stateBuilder.setState(state, getCurrentPosition(), 1.0f);
        mSession.setPlaybackState(stateBuilder.build());
    }

    private long getAvailableActions() {
        long actions = PlaybackState.ACTION_PLAY |
                PlaybackState.ACTION_PAUSE |
                PlaybackState.ACTION_PLAY_PAUSE |
                PlaybackState.ACTION_REWIND |
                PlaybackState.ACTION_FAST_FORWARD |
                PlaybackState.ACTION_SKIP_TO_PREVIOUS |
                PlaybackState.ACTION_SKIP_TO_NEXT |
                PlaybackState.ACTION_PLAY_FROM_MEDIA_ID |
                PlaybackState.ACTION_PLAY_FROM_SEARCH;
        return actions;
    }
复制代码

在这个例子中,可以通过getAvailableActions方法来确定可用的操作,通过使用逻辑分离来添加操作。 ###从UI控制视频- MediaController.getTransportControls

要通过VideoDetailsFragment中的PlaybackControlsRow控制MediaSession,我们使用MediaController。 MediaController是在PlaybackController的构造函数中创建的,它拥有MediaSession的标记。

当用户点击视频控制按钮时,它将使用MediaController.getTransportControls()方法调用MediaSessionCallback方法。

/* onClick */
      playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() {
            public void onActionClicked(Action action) {
                if (action.getId() == mPlayPauseAction.getId()) {
                    /* PlayPause action */
                    if (mPlayPauseAction.getIndex() == PlaybackControlsRow.PlayPauseAction.PLAY) {
                        mMediaController.getTransportControls().play();
                    } else if (mPlayPauseAction.getIndex() == PlaybackControlsRow.PlayPauseAction.PAUSE) {
                        mMediaController.getTransportControls().pause();
                    }
                } else if (action.getId() == mSkipNextAction.getId()) {
                    /* SkipNext action */
                    mMediaController.getTransportControls().skipToNext();
                } else if (action.getId() == mSkipPreviousAction.getId()) {
                    /* SkipPrevious action */
                    mMediaController.getTransportControls().skipToPrevious();
                } else if (action.getId() == mFastForwardAction.getId()) {
                    /* FastForward action  */
                    mMediaController.getTransportControls().fastForward();
                } else if (action.getId() == mRewindAction.getId()) {
                    /* Rewind action */
                    mMediaController.getTransportControls().rewind();
                }
}
复制代码

视频控制部分完成。 但是,我们需要根据视频控制 更新PlayControlsRow的UI。 ###更新VideoDetailsFragment的UI 当执行视频控制动作时,需要更改UI,视频播放状态已更改。 我们可以使用MediaController的回调函数获取此事件。 以下介绍2种回调方法。

  • onPlaybackStateChanged
  • onMetadataChanged

要使用这些回调方法,您可以使扩展MediaController.Callback类的子类,并覆盖这些方法。 要使用这个类,我们可以调用MediaController的registerCallback / unregisterCallback方法来获取MediaController的事件。

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mMediaController = getActivity().getMediaController();
        Log.d(TAG, "register callback of mediaController");
        if(mMediaController == null){
            Log.e(TAG, "mMediaController is null");
        }
        mMediaController.registerCallback(mMediaControllerCallback);

    }

    @Override
    public void onDetach() {
        if (mMediaController != null) {
            Log.d(TAG, "unregister callback of mediaController");
            mMediaController.unregisterCallback(mMediaControllerCallback);
        }
        super.onDetach();
    }

    private class MediaControllerCallback extends MediaController.Callback {
        @Override
        public void onPlaybackStateChanged(final PlaybackState state) {
            Log.d(TAG, "playback state changed: " + state.toString());
        }

        @Override
        public void onMetadataChanged(final MediaMetadata metadata) {
            Log.d(TAG, "received update of media metadata");
        }
    }
复制代码

###更新Playback StateChanged中的视频控制图标和更新PlaybackState PlaybackState在播放控制器中更新。

private void updatePlaybackState() {
        PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
                .setActions(getAvailableActions());
        int state = PlaybackState.STATE_PLAYING;
        if (mCurrentPlaybackState == PlaybackState.STATE_PAUSED || mCurrentPlaybackState == PlaybackState.STATE_NONE) {
            state = PlaybackState.STATE_PAUSED;
        }
        // stateBuilder.setState(state, mPosition, 1.0f);
        stateBuilder.setState(state, getCurrentPosition(), 1.0f);
        mSession.setPlaybackState(stateBuilder.build());
    }
复制代码

例如,它将在playPause方法中调用。 当用户开始播放视频状态将从STATE_PLAYING变为STATE_PAUSED,反之亦然。 PlaybackState更新被设置(通知)到MediaSession。 ###Callback

当PlaybackState由上面的setPlaybackState更改时,可以使用onPlaybackStateChanged回调接收此事件。 我们可以在PlaybackControlsRow中更新播放/暂停图标。

    private class MediaControllerCallback extends MediaController.Callback {
        @Override
        public void onPlaybackStateChanged(final PlaybackState state) {
            Log.d(TAG, "playback state changed: " + state.toString());
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (state.getState() == PlaybackState.STATE_PLAYING) {
                        mPlaybackController.setCurrentPlaybackState(PlaybackState.STATE_PLAYING);
                        startProgressAutomation();
                        // setFadingEnabled(false);
                        mPlayPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PAUSE);
                        mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlaybackControlsRow.PlayPauseAction.PAUSE));
                        notifyChanged(mPlayPauseAction);
                    } else if (state.getState() == PlaybackState.STATE_PAUSED) {
                        mPlaybackController.setCurrentPlaybackState(PlaybackState.STATE_PAUSED);
                        // setFadingEnabled(false);
                        mPlayPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PLAY);
                        mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlaybackControlsRow.PlayPauseAction.PLAY));
                        notifyChanged(mPlayPauseAction);
                    }

                    int currentTime = (int) state.getPosition();
                    mPlaybackControlsRow.setCurrentTime(currentTime);
                    // mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
                    mPlaybackControlsRow.setBufferedProgress(mPlaybackController.calcBufferedTime(currentTime));

                }
            });
        }

        ...
    }
复制代码

###在onMetadataChanged上更新媒体信息 更新MediaMetadata

MediaMetadata类用于设置视频的元数据信息。 我们可以通过MediaMetadata.Builder中的put方法设置元数据的每个属性。 MediaMetadata更新被设置(通知)到MediaSession。

    public void updateMetadata(Movie movie) {
        final MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();

        String title = movie.getTitle().replace("_", " -");

        metadataBuilder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, Long.toString(movie.getId()));
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title);
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, movie.getStudio());
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION, movie.getDescription());
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, movie.getCardImageUrl());
        metadataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION, mDuration);

        // And at minimum the title and artist for legacy support
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, movie.getStudio());

        Glide.with(mActivity)
                .load(Uri.parse(movie.getCardImageUrl()))
                .asBitmap()
                .into(new SimpleTarget<Bitmap>(500, 500) {
                    @Override
                    public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {

                        metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, bitmap);
                        mSession.setMetadata(metadataBuilder.build());
                    }
                });
    }
复制代码

通过将MediaMetadata设置为MediaSession,Android TV将在LeanbackLauncher上显示Now Playing Card ,稍后再说明。 ###CallBack

当MediaMetadata已经通过setMetadata以上改变时,该事件可以与onMetadataChanged回调被接收。我们可以更新PlaybackControlsRow的项目值。

    private class MediaControllerCallback extends MediaController.Callback {

        ...

        @Override
        public void onMetadataChanged(final MediaMetadata metadata) {
            Log.d(TAG, "received update of media metadata");
                    updateMovieView(
                            metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE),
                            metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE),
                            metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI),
                            metadata.getLong(MediaMetadata.METADATA_KEY_DURATION)
                    );
        }
    }

    private void updateMovieView(String title, String studio, String cardImageUrl, long duration) {
        Log.d(TAG, "updateMovieView");

        if (mPlaybackControlsRow.getItem() != null) {
            Movie item = (Movie) mPlaybackControlsRow.getItem();
            item.setTitle(title);
            item.setStudio(studio);
        } else {
            Log.e(TAG, "mPlaybackControlsRow.getItem is null!");
        }
        mPlaybackControlsRow.setTotalTime((int) duration);
        mPlaybackControlsRow.setCurrentTime(0);
        mPlaybackControlsRow.setBufferedProgress(0);
        mRowsAdapter.notifyArrayItemRangeChanged(0, mRowsAdapter.size());

        // Show the video card image if there is enough room in the UI for it.
        // If you have many primary actions, you may not have enough room.
        if (SHOW_IMAGE) {
            mPlaybackControlsRowTarget = new PicassoPlaybackControlsRowTarget(mPlaybackControlsRow);
            updateVideoImage(cardImageUrl);
        }
    }
复制代码

###Now Playing Card

如果MediaMetadata正确设置为MediaSession,则现在播放卡将显示在LeanbackLauncher(主屏幕)中。 它向用户通知当前播放媒体的信息。 此外,现在播放卡使用户可以回到您的应用程序来控制视频(暂停/转到下一个视频等)。 源码在github上. 关注微信公众号,定期为你推荐移动开发相关文章。

转载于:https://juejin.im/post/5b714cb5e51d45661f6f6205

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值