[译]视频控制最小化实现 – Android TV 应用开发教程八

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

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


[译]视频控制最小化实现 – Android TV 应用开发教程八_第1张图片
action_icon_change

视频控制最小化实现

视频在VideoView中流式传输。

  • 我引用了Google最新的Android TV应用程序 。 AOSP示例应用程序实现是不确定的。

对于视频控件,我们要解释以下的东西。

  1. Action的UI更新部分(本章)
  2. 视频控制部分(本章)
  3. 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();
}
[译]视频控制最小化实现 – Android TV 应用开发教程八_第2张图片
action_icon_change

代码已经上传到github上了。
下一章,我们将要实现MediaSession。
关注微信公众号,定期为你推荐移动开发相关文章。

[译]视频控制最小化实现 – Android TV 应用开发教程八_第3张图片
songwenju

你可能感兴趣的:([译]视频控制最小化实现 – Android TV 应用开发教程八)