版权声明:本文为博主原创翻译文章,转载请注明出处。
推荐:
欢迎关注我创建的Android TV 专题,会定期给大家分享一些AndroidTv相关的内容:
https://www.jianshu.com/c/3f0ab61a1322
视频控制最小化实现
视频在VideoView中流式传输。
- 我引用了Google最新的Android TV应用程序 。 AOSP示例应用程序实现是不确定的。
对于视频控件,我们要解释以下的东西。
- Action的UI更新部分(本章)
- 视频控制部分(本章)
- MediaSession实现,通过MediaController的TransportControls进行视频控制(下一章)
(当用户按下电视遥控器的视频控制按钮时,MediaSession可以处理该动作。
它允许其他活动继承视频控制。 特别是LeanbackLauncher,家庭显示,可以在后台播放视频。)
4.将MediaMetadata设置为MediaSession(下一章)
( “ Now playing card ”将出现在推荐行的顶部。)
在本章中,介绍了视频控件的实现。 由于Google的示例应用程序实现了所有的1〜4,源代码有点长,难以理解为初学者。 我只做了最少的实施只有1〜2。 我将在本章中为每个部分解释这些实现,因此请先在github上下载并引用源代码 (我使用MovieProvider类重构了准备电影内容)。 本章的技术在Android中是普遍的。
下一章我将介绍MediaSession的实现。 我们可以通过使用MediaSession将VideoView控件传递给LeanbackLauncher,从而在LeanbackLauncher中实现播放视频背景。
VideoView处理
PlaybackOverlayActivity需要具有VideoView字段变量“mVideoView”来控制视频。
public class PlaybackOverlayActivity extends Activity {
private static final String TAG = PlaybackOverlayActivity.class.getSimpleName();
private VideoView mVideoView;
private LeanbackPlaybackState mPlaybackState = LeanbackPlaybackState.IDLE;
private int mPosition = 0;
private long mStartTimeMillis;
private long mDuration = -1;
/*
* List of various states that we can be in
*/
public enum LeanbackPlaybackState {
PLAYING, PAUSED, IDLE
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playback_overlay);
loadViews();
}
@Override
public void onDestroy() {
super.onDestroy();
stopPlayback();
mVideoView.suspend();
mVideoView.setVideoURI(null);
}
private void loadViews() {
mVideoView = (VideoView) findViewById(R.id.videoView);
mVideoView.setFocusable(false);
mVideoView.setFocusableInTouchMode(false);
Movie movie = (Movie) getIntent().getSerializableExtra(DetailsActivity.MOVIE);
setVideoPath(movie.getVideoUrl());
}
public void setVideoPath(String videoUrl) {
setPosition(0);
mVideoView.setVideoPath(videoUrl);
mStartTimeMillis = 0;
mDuration = Utils.getDuration(videoUrl);
}
private void stopPlayback() {
if (mVideoView != null) {
mVideoView.stopPlayback();
}
}
实现setOnActionClickedListener和onActionClicked回调
要分配每个视频控制按钮的动作,我们使用setOnActionClickedListener,它是PlaybackControlsRowPresenter的一种方法。
private void setUpRows() {
...
/* add ListRow to second row of mRowsAdapter */
addOtherRows();
/* onClick */
playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() {
public void onActionClicked(Action action) {
if (action.getId() == mPlayPauseAction.getId()) {
/* PlayPause action */
togglePlayback(mPlayPauseAction.getIndex() == PlaybackControlsRow.PlayPauseAction.PLAY);
} else if (action.getId() == mSkipNextAction.getId()) {
/* SkipNext action */
next(mCurrentPlaybackState == PlaybackState.STATE_PLAYING);
} else if (action.getId() == mSkipPreviousAction.getId()) {
/* SkipPrevious action */
prev(mCurrentPlaybackState == PlaybackState.STATE_PLAYING);
} else if (action.getId() == mFastForwardAction.getId()) {
/* FastForward action */
fastForward();
} else if (action.getId() == mRewindAction.getId()) {
/* Rewind action */
rewind();
}
if (action instanceof PlaybackControlsRow.MultiAction) {
/* Following action is subclass of MultiAction
* - PlayPauseAction
* - FastForwardAction
* - RewindAction
* - ThumbsAction
* - RepeatAction
* - ShuffleAction
* - HighQualityAction
* - ClosedCaptioningAction
*/
notifyChanged(action);
}
}
});
setAdapter(mRowsAdapter);
}
从这里可以解释每个动作的实现。 请注意,区分“UI更新部分”和“视频控制部分”很重要,因为视频控制部分将在下一章中移动到MediaSession。
在源代码中,我在PlaybackOverlayFragment.java中实现了“UI更新部分”,而“Video control part”则在PlaybackOverlayActivity.java中实现。
PlayPauseAction
PlaybackOverlayFragment.java - PlayPauseAction
private void togglePlayback(boolean playPause) {
/* Video control part */
((PlaybackOverlayActivity) getActivity()).playPause(playPause);
/* UI control part */
playbackStateChanged();
}
视频控制部分将在VideoView中处理播放/暂停视频。
PlaybackOverlayActivity.java - PlayPauseAction视频控制部分
public void playPause(boolean doPlay) {
if (mPlaybackState == LeanbackPlaybackState.IDLE) {
/* Callbacks for mVideoView */
setupCallbacks();
}
if (doPlay && mPlaybackState != LeanbackPlaybackState.PLAYING) {
mPlaybackState = LeanbackPlaybackState.PLAYING;
if (mPosition > 0) {
mVideoView.seekTo(mPosition);
}
mVideoView.start();
mStartTimeMillis = System.currentTimeMillis();
} else {
mPlaybackState = LeanbackPlaybackState.PAUSED;
int timeElapsedSinceStart = (int) (System.currentTimeMillis() - mStartTimeMillis);
setPosition(mPosition + timeElapsedSinceStart);
mVideoView.pause();
}
}
private void setupCallbacks() {
mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
mVideoView.stopPlayback();
mPlaybackState = LeanbackPlaybackState.IDLE;
return false;
}
});
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
mVideoView.start();
}
}
});
mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
mPlaybackState = LeanbackPlaybackState.IDLE;
}
});
}
UI控件部分将处理
- 切换播放/暂停的图标
- 更新视频的当前时间
public void playbackStateChanged() {
if (mCurrentPlaybackState != PlaybackState.STATE_PLAYING) {
mCurrentPlaybackState = PlaybackState.STATE_PLAYING;
startProgressAutomation();
setFadingEnabled(true);
mPlayPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PAUSE);
mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlaybackControlsRow.PlayPauseAction.PAUSE));
notifyChanged(mPlayPauseAction);
} else if (mCurrentPlaybackState != PlaybackState.STATE_PAUSED) {
mCurrentPlaybackState = PlaybackState.STATE_PAUSED;
stopProgressAutomation();
//setFadingEnabled(false); // if set to false, PlaybackcontrolsRow will always be on the screen
mPlayPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PLAY);
mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlaybackControlsRow.PlayPauseAction.PLAY));
notifyChanged(mPlayPauseAction);
}
int currentTime = ((PlaybackOverlayActivity) getActivity()).getPosition();
mPlaybackControlsRow.setCurrentTime(currentTime);
mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
}
private void startProgressAutomation() {
if (mRunnable == null) {
mRunnable = new Runnable() {
@Override
public void run() {
int updatePeriod = getUpdatePeriod();
int currentTime = mPlaybackControlsRow.getCurrentTime() + updatePeriod;
int totalTime = mPlaybackControlsRow.getTotalTime();
mPlaybackControlsRow.setCurrentTime(currentTime);
mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
if (totalTime > 0 && totalTime <= currentTime) {
stopProgressAutomation();
//next(true);
} else {
mHandler.postDelayed(this, updatePeriod);
}
}
};
mHandler.postDelayed(mRunnable, getUpdatePeriod());
}
}
private void stopProgressAutomation() {
if (mHandler != null && mRunnable != null) {
mHandler.removeCallbacks(mRunnable);
mRunnable = null;
}
}
倒带&快进
PlaybackOverlayFragment.java - rewind/fastForward
private void fastForward() {
/* Video control part */
((PlaybackOverlayActivity) getActivity()).fastForward();
/* UI part */
int currentTime = ((PlaybackOverlayActivity) getActivity()).getPosition();
mPlaybackControlsRow.setCurrentTime(currentTime);
mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
}
private void rewind() {
/* Video control part */
((PlaybackOverlayActivity) getActivity()).rewind();
/* UI part */
int currentTime = ((PlaybackOverlayActivity) getActivity()).getPosition();
mPlaybackControlsRow.setCurrentTime(currentTime);
mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
}
在这里,倒带和快进视频控制实现方式简单,只需快进/快进当前位置10秒钟。
PlaybackOverlayActivity.java - rewind/fastForward Video control 部分
public void fastForward() {
if (mDuration != -1) {
// Fast forward 10 seconds.
setPosition(mVideoView.getCurrentPosition() + (10 * 1000));
mVideoView.seekTo(mPosition);
}
}
public void rewind() {
// rewind 10 seconds
setPosition(mVideoView.getCurrentPosition() - (10 * 1000));
mVideoView.seekTo(mPosition);
}
UI控件部分正在更新视频的当前时间。
SkipPrevious & SkipNext
PlaybackOverlayFragment.java - skipPrevious/skipNext
private void next(boolean autoPlay) {
/* Video control part */
if (++mCurrentItem >= mItems.size()) { // Current Item is set to next here
mCurrentItem = 0;
}
if (autoPlay) {
mCurrentPlaybackState = PlaybackState.STATE_PAUSED;
}
Movie movie = mItems.get(mCurrentItem);
if (movie != null) {
((PlaybackOverlayActivity) getActivity()).setVideoPath(movie.getVideoUrl());
((PlaybackOverlayActivity) getActivity()).setPlaybackState(PlaybackOverlayActivity.LeanbackPlaybackState.PAUSED);
((PlaybackOverlayActivity) getActivity()).playPause(autoPlay);
}
/* UI part */
playbackStateChanged();
updatePlaybackRow(mCurrentItem);
}
private void prev(boolean autoPlay) {
/* Video control part */
if (--mCurrentItem < 0) { // Current Item is set to previous here
mCurrentItem = mItems.size() - 1;
}
if (autoPlay) {
mCurrentPlaybackState = PlaybackState.STATE_PAUSED;
}
Movie movie = mItems.get(mCurrentItem);
if (movie != null) {
((PlaybackOverlayActivity) getActivity()).setVideoPath(movie.getVideoUrl());
((PlaybackOverlayActivity) getActivity()).setPlaybackState(PlaybackOverlayActivity.LeanbackPlaybackState.PAUSED);
((PlaybackOverlayActivity) getActivity()).playPause(autoPlay);
}
/* UI part */
playbackStateChanged();
updatePlaybackRow(mCurrentItem);
}
对于视频控制部分,除了第一行外,还有2个功能正在做同样的事情。 mCurrentItem设置为上一个/下一个,然后使用setVideoPath方法和播放/暂停根据当前播放/暂停状态使用playPause方法设置正确的视频路径。
UI控制部分,第一行调用playbackStateChanged()方法,但只需要控制startProgressAutomation / stopProgressAutomation来更新视频的当前时间状态。 updateplaybackRow方法是更新视频内容的DetailsDescription信息。
PlaybackOverlayFragment.java - updatePlaybackRow
private void updatePlaybackRow(int index) {
Log.d(TAG, "updatePlaybackRow");
if (mPlaybackControlsRow.getItem() != null) {
Movie item = (Movie) mPlaybackControlsRow.getItem();
item.setTitle(mItems.get(mCurrentItem).getTitle());
item.setStudio(mItems.get(mCurrentItem).getStudio());
mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
/* total time is necessary to show video playing time progress bar */
int duration = (int) Utils.getDuration(mItems.get(mCurrentItem).getVideoUrl());
Log.i(TAG, "videoUrl: " + mItems.get(mCurrentItem).getVideoUrl());
Log.i(TAG, "duration = " + duration);
mPlaybackControlsRow.setTotalTime(duration);
mPlaybackControlsRow.setCurrentTime(0);
mPlaybackControlsRow.setBufferedProgress(0);
}
if (SHOW_IMAGE) {
mPlaybackControlsRowTarget = new PicassoPlaybackControlsRowTarget(mPlaybackControlsRow);
updateVideoImage(mItems.get(mCurrentItem).getCardImageURI());
}
}
ThumbUp & ThumbDown & Repeat & Shuffle & HighQuality & ClosedCaptioning & MoreActions
如何切换图标的颜色? 您可以通过设置操作的索引来更改。 在onActionClicked方法下面实现,以检查每个索引设置的行为。
PlaybackOverlayFragment.java - onActionClicked
/* Change icon */
if (action instanceof PlaybackControlsRow.ThumbsUpAction ||
action instanceof PlaybackControlsRow.ThumbsDownAction ||
action instanceof PlaybackControlsRow.RepeatAction ||
action instanceof PlaybackControlsRow.ShuffleAction ||
action instanceof PlaybackControlsRow.HighQualityAction ||
action instanceof PlaybackControlsRow.ClosedCaptioningAction) {
((PlaybackControlsRow.MultiAction) action).nextIndex();
}
代码已经上传到github上了。
下一章,我们将要实现MediaSession。
关注微信公众号,定期为你推荐移动开发相关文章。