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

推荐: 欢迎关注我创建的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.

示例实现如下。

@Overridepublic 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) { // PlayLog.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 { // PauseLog.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 secondssetPosition(getCurrentPosition() - (10 * 1000));mVideoView.seekTo(mPosition);}private class MediaSessionCallback extends MediaSession.Callback {@Overridepublic void onPlay() {playPause(true);}@Overridepublic void onPause() {playPause(false);}@Overridepublic void onSkipToNext() {if (++mCurrentItem >= mItems.size()) { // Current Item is set to next heremCurrentItem = 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!");}}@Overridepublic void onSkipToPrevious() {if (--mCurrentItem < 0) { // Current Item is set to previous heremCurrentItem = 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!");}}@Overridepublic 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);}}@Overridepublic void onSeekTo(long pos) {setPosition((int) pos);mVideoView.seekTo(mPosition);updatePlaybackState();}@Overridepublic void onFastForward() {fastForward();}@Overridepublic 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的事件。

    @Overridepublic 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);}@Overridepublic void onDetach() {if (mMediaController != null) {Log.d(TAG, "unregister callback of mediaController");mMediaController.unregisterCallback(mMediaControllerCallback);}super.onDetach();}private class MediaControllerCallback extends MediaController.Callback {@Overridepublic void onPlaybackStateChanged(final PlaybackState state) {Log.d(TAG, "playback state changed: " + state.toString());}@Overridepublic 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 {@Overridepublic void onPlaybackStateChanged(final PlaybackState state) {Log.d(TAG, "playback state changed: " + state.toString());mHandler.post(new Runnable() {@Overridepublic 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 supportmetadataBuilder.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) {@Overridepublic 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 {...@Overridepublic 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

[译]MediaSession MediaController – Android TV 应用开发教程九相关推荐

  1. Android OpenGL ES 开发教程(20):颜色Color

    OpenGL ES 支持的颜色格式为RGBA模式(红,绿,蓝,透明度).颜色的定义通常使用Hex格式0xFF00FF 或十进制格式(255,0,255), 在OpenGL 中却是使用0-1之间的浮点数 ...

  2. Protobuf实现Android Socket通讯开发教程

    本节为您介绍Protobuf实现Android Socket通讯开发教程,因此,我们需要先了理一下protobuf 是什么? Protocol buffers是一种编码方法构造的一种有效而可扩展的格式 ...

  3. Android APP 快速开发教程(安卓)

    Android APP 快速开发教程(安卓) 前言 本篇博客从开发的角度来介绍如何开发一个Android App,需要说明一点是,这里只是提供一个如何开发一个app的思路,并不会介绍很多技术上的细节, ...

  4. Android移动应用开发教程笔记①

    本文为第一篇,主要为Android的简介和Android studio的安装以及第一个安卓程序"hello world"的创建! 本文是对B站教程 动脑学院 Android教程 学 ...

  5. android TV盒子开发心得(一)

    今天给大家介绍一下本人在开发android TV APP的时候所遇到的一些问题及心得体会,希望能帮助各位解决问题 首先,简单介绍一下TV开发和手机开发的不同之处: 1.我们在开发TV的时候,经常会遇到 ...

  6. Android移动应用开发教程②

    本文为第二篇,对Android开发做一个全面但不深入的了解,让大家对Android开发有一个笼统印象.主要讲述了如何利用真机调试Android应用,关于开发Android应用的语言的选择,以及Andr ...

  7. Android移动应用开发教程④

    本文为第四篇,主要讲述了控件讲解介绍(总体思维导图),如何创建新模块,设置文本的内容,设置文本的大小(px,dp,sp不同字体大小的区别),设置文本的颜色(ARGB),设置背景颜色.最后学习了视图基础 ...

  8. android嵌入式底层开发教程

    android嵌入式底层开发教程 课程针对人群 熟悉.NET,J2EE应用开发,希望往嵌入式底层学习的工程师 熟悉Android应用和框架开发,希望从上到下走通Android系统的工程师 不希望局限在 ...

  9. 树莓派+android+盒子,树莓派3安装Android TV系统图文教程

    树莓派3 Android TV系统怎样安装?树莓派3一个重要用途就是当智能机顶盒,那么安装Android系统肯定是大家的首选,但目前支持树莓派3的Android系统都是第三方移植的,各种问题不断,最重 ...

最新文章

  1. opengl 创建context_OpenGL学习笔记1-创建窗口,绘制三角形
  2. django连接redis 集群(安装redis的相关包的时候亲测)
  3. python字典和集合双向索引_Python-为什么字典和集合中的顺序是任意的?
  4. Debian Gnu/Linux 9关闭 111端口
  5. 10 3 java_10.3 UiPath如何调用Java
  6. 1月至今 微信共对超十万个确认存在欺诈行为的帐号进行了阶梯式处理
  7. 使用Kubeadm(1.13+)快速搭建Kubernetes集群
  8. 模电_热敏PTC电阻_NTC电阻-区别与作用-20190507
  9. 为什么程序猿 996 会猝死,而企业家 007 却不会?
  10. 【杂学笔记-表观遗传学-updating】
  11. 最近羊毛小更新 青龙面板 薅羊毛 22/6/6更新
  12. 别不信,小宝宝爱盯着妈妈看原来跟大脑发育有关
  13. redis的setex key seconds value命令的bug
  14. mysql的四表查询_Mysql经典四表查询
  15. HDU4751 -(广义孪生素数猜想)
  16. Flutter事件分发流程简析
  17. 2021paypal仿牌收款免费赠送
  18. 字符串双引号表示c语言,c语言中单引号和双引号的区别(顺利解决从字符串中提取IP地址的困惑)...
  19. mysql几核几G是什么意思_CPU分几核几核的是什么意思?
  20. iOS 时间比较大小

热门文章

  1. accumulate返回值类型 提醒
  2. 1-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案微信小程序篇(域名备案)
  3. 迅为4412开发板开发笔记--开发板与主机和ubuntu的ping通
  4. zend studio9.0.4正式版破解方法
  5. 管理数据通用权限系统快速开发框架设计
  6. PHP版本选择讲解:VC6与VC9,Thread Safe与None-Thread Safe等的选择
  7. 把这个写成一个类吧TREEVIEW
  8. python包导入详细教程脚本之家_python包导入详细教程脚本之家_Python使用import导入本地脚本及导入模块的技巧总结......
  9. 动态规划(三)——最少硬币和所有硬币问题
  10. 内存泄露部分检测工具