[译]MediaSession & MediaContr

作者: wenju_song | 来源:发表于2017-05-27 16:29 被阅读459次

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

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


    VideoControl

    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

    NowPlayingCard

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

    songwenju

    相关文章

      网友评论

      本文标题:[译]MediaSession & MediaContr

      本文链接:https://www.haomeiwen.com/subject/vhfsxxtx.html