博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[译]MediaSession & MediaController – Android TV 应用开发教程九
阅读量:7211 次
发布时间:2019-06-29

本文共 17658 字,大约阅读时间需要 58 分钟。

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

推荐: 欢迎关注我创建的Android TV 简书专题,会定期给大家分享一些AndroidTv相关的内容:


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

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

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

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

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

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

对于4,MediaSession的元数据使用MediaMetadata&PlaybackState类进行更新,以更新“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
(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(主屏幕)中。 它向用户通知当前播放媒体的信息。 此外,现在播放卡使用户可以回到您的应用程序来控制视频(暂停/转到下一个视频等)。 源码在上. 关注微信公众号,定期为你推荐移动开发相关文章。

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

你可能感兴趣的文章
Crypto API
查看>>
读书笔记2013第10本:《学得少却考得好Learn More Study Less》
查看>>
【c++】指针参数是如何传递内存的
查看>>
装饰模式(Decorator Pattern)--------结构型模式
查看>>
微信公众平台消息接口PHP版
查看>>
[Cocos2d-x For WP8]矩形碰撞检测
查看>>
Java Bad version
查看>>
android的listview组件
查看>>
网页 内部转发和网址输入不同
查看>>
matlab中find函数的使用说明
查看>>
这是一张很有趣的图片, 通常女性会先看到月亮, 男性会先看到人脸. 如果相反, 表示你体内的异性荷尔蒙偏高哦!...
查看>>
SGU 403 Game with points
查看>>
2014中国软件开发者调查(一):Java最受欢迎 第二语言JS使用比例最高
查看>>
三级管的原理
查看>>
Java基础—ClassLoader的理解
查看>>
Android App监听软键盘按键的三种方式(转)
查看>>
2、Android应用程序基本特性
查看>>
Android开发之Buidler模式初探结合AlertDialog.Builder解说
查看>>
bash shell命令(2)
查看>>
html中#include file的使用方法
查看>>