Android VideoView 缓冲

Android VideoView buffering

我正在开发 Android TV 应用程序,它使用 VideoView 播放来自 url 的视频。每次视频暂停然后恢复 - 首先需要一些时间来预下载(几秒钟期间会出现进度条)。目标是在播放期间缓冲视频,以便当我暂停它然后恢复时 - 它立即从预缓冲状态恢复而不会延迟。我也尝试过 videoView.resume() 方法,但它也无济于事。有没有办法做到这一点,还是我做错了什么?

这是 plays/resumes 视频的代码:

public void onFragmentPlayPause(final Video video,
                                    final VideoQuality quality,
                                    final int position,
                                    final Boolean play) {
        stopSeeking();

        videoView.setVideoPath(video.videoUrl(quality));

        if (position == 0 || playbackState == LeanbackPlaybackState.IDLE) {
            setupCallbacks();
            playbackState = LeanbackPlaybackState.IDLE;
        }

        if (play && playbackState != LeanbackPlaybackState.PLAYING) {
            progressBar.setVisibility(View.VISIBLE);
            playbackState = LeanbackPlaybackState.PLAYING;
            if (position > 0) {
                videoView.seekTo(position);
                videoView.start();
            }
            playbackFragment.startProgressObservation(progressFlowable());
        } else {
            playbackState = LeanbackPlaybackState.PAUSED;
            videoView.pause();
            playbackFragment.stopProgressObservation();
        }

        updatePlaybackState(position);
        updateMetadata(video);
    }

不幸的是,我没有找到使用 VideoView 实现此目的的方法,只能使用 ExoPlayer, thanks to @pskink for the right direction. The API was returning mp4 videos, so I've used ExtractorMediaSource. The complete demo example with support of multiple formats can be found on ExoPlayer GitHub page.

这是我使用 ExoPlayer:

得到的最终代码
private void playPause(final Video video, final int position, final Boolean play) {
        if (position == 0 || playbackState == LeanbackPlaybackState.IDLE) {
            setupCallbacks();
            playbackState = LeanbackPlaybackState.IDLE;
        }

        if (play && playbackState != LeanbackPlaybackState.PLAYING) {
            progressBar.setVisibility(View.VISIBLE);
            playbackState = LeanbackPlaybackState.PLAYING;
            player.start();
            playbackFragment.startProgressObservation(progressFlowable());
        } else {
            playbackState = LeanbackPlaybackState.PAUSED;
            player.pause();
            playbackFragment.stopProgressObservation();
        }

        updatePlaybackState(position);
        updateMetadata(video);
    }

Player实施:

public class Player implements MediaController.MediaPlayerControl, ExoPlayer.EventListener {

    private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();

    private final SimpleExoPlayer exoPlayer;
    private final Context context;

    private OnErrorListener onErrorListener;
    private OnPreparedListener onPreparedListener;
    private OnCompletionListener onCompletionListener;

    private String url;

    public interface OnErrorListener {
        void onError(final Exception e);
    }

    public interface OnPreparedListener {
        void onPrepared();
    }

    public interface OnCompletionListener {
        void onComplete();
    }

    public Player(final Context context) {
        this.context = context;

        final @SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode =
                SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF;
        final TrackSelection.Factory videoTrackSelectionFactory =
                new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
        this.exoPlayer = ExoPlayerFactory.newSimpleInstance(
                context,
                new DefaultTrackSelector(videoTrackSelectionFactory),
                new DefaultLoadControl(),
                null,
                extensionRendererMode
        );
        this.exoPlayer.addListener(this);
    }

    @Override
    public boolean canPause() {
        return true;
    }

    @Override
    public boolean canSeekBackward() {
        return true;
    }

    @Override
    public boolean canSeekForward() {
        return true;
    }

    @Override
    public int getAudioSessionId() {
        throw new UnsupportedOperationException();
    }

    @Override
    public int getBufferPercentage() {
        return exoPlayer.getBufferedPercentage();
    }

    @Override
    public int getCurrentPosition() {
        return exoPlayer.getDuration() == com.google.android.exoplayer2.C.TIME_UNSET ? 0
                : (int) exoPlayer.getCurrentPosition();
    }

    @Override
    public int getDuration() {
        return exoPlayer.getDuration() == com.google.android.exoplayer2.C.TIME_UNSET ? 0
                : (int) exoPlayer.getDuration();
    }

    @Override
    public boolean isPlaying() {
        return exoPlayer.getPlayWhenReady();
    }

    @Override
    public void start() {
        exoPlayer.setPlayWhenReady(true);
    }

    @Override
    public void pause() {
        exoPlayer.setPlayWhenReady(false);
    }

    public void stop() {
        exoPlayer.seekTo(0);
        pause();
    }

    public void setOnErrorListener(final OnErrorListener onErrorListener) {
        this.onErrorListener = onErrorListener;
    }

    public void setOnPreparedListener(final OnPreparedListener onPreparedListener) {
        this.onPreparedListener = onPreparedListener;
    }

    public void setOnCompletionListener(final OnCompletionListener onCompletionListener) {
        this.onCompletionListener = onCompletionListener;
    }

    public void setVolume(final float volume) {
        exoPlayer.setVolume(volume);
    }

    public void release() {
        exoPlayer.release();
    }

    public void updateUrl(final String url) {
        this.url = url;
        exoPlayer.prepare(buildMediaSource(Uri.parse(url)));
    }

    public SimpleExoPlayer exoPlayer() {
        return exoPlayer;
    }

    @Override
    public void seekTo(final int timeMillis) {
        exoPlayer.seekTo(timeMillis);
    }

    @Override
    public void onTimelineChanged(final Timeline timeline, final Object manifest) {
    }

    @Override
    public void onTracksChanged(final TrackGroupArray trackGroups, final TrackSelectionArray trackSelections) {
    }

    @Override
    public void onLoadingChanged(final boolean isLoading) {
    }

    @Override
    public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
        if (playbackState == ExoPlayer.STATE_READY) {
            onPreparedListener.onPrepared();
        }
        if (playbackState == ExoPlayer.STATE_ENDED) {
            onCompletionListener.onComplete();
        }
    }

    @Override
    public void onPlayerError(final ExoPlaybackException error) {
        onErrorListener.onError(error);
    }

    @Override
    public void onPositionDiscontinuity() {
    }

    public String url() {
        return url;
    }

    private MediaSource buildMediaSource(final Uri uri) {
        return new ExtractorMediaSource(uri, buildDataSourceFactory(true), new DefaultExtractorsFactory(),
                null, null);
    }

    private DataSource.Factory buildDataSourceFactory(final DefaultBandwidthMeter bandwidthMeter) {
        return new DefaultDataSourceFactory(context, bandwidthMeter,
                buildHttpDataSourceFactory(bandwidthMeter));
    }

    private HttpDataSource.Factory buildHttpDataSourceFactory(final DefaultBandwidthMeter bandwidthMeter) {
        return new DefaultHttpDataSourceFactory(Util.getUserAgent(context, Application.TAG), bandwidthMeter);
    }

    private DataSource.Factory buildDataSourceFactory(final boolean useBandwidthMeter) {
        return buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
    }
}