/app/src/main/java/com/alecgdouglas/gbenthusiast/ui/PlaybackActivity.java
Java | 465 lines | 401 code | 48 blank | 16 comment | 84 complexity | eee054be7b650a14a2cd2d317322be04 MD5 | raw file
Possible License(s): GPL-3.0
- package com.alecgdouglas.gbenthusiast.ui;
- import android.app.Fragment;
- import android.content.Context;
- import android.content.Intent;
- import android.media.AudioManager;
- import android.net.Uri;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.PowerManager;
- import android.os.SystemClock;
- import android.util.Log;
- import android.view.KeyEvent;
- import android.widget.ImageButton;
- import com.alecgdouglas.gbenthusiast.DefaultQualityPrefUtils;
- import com.alecgdouglas.gbenthusiast.EmptyVideoRendererEventListener;
- import com.alecgdouglas.gbenthusiast.R;
- import com.alecgdouglas.gbenthusiast.ResettingBandwidthMeter;
- import com.alecgdouglas.gbenthusiast.Utils;
- import com.alecgdouglas.gbenthusiast.data.VideoPositionDbUtils;
- import com.alecgdouglas.gbenthusiast.model.Video;
- import com.alecgdouglas.gbenthusiast.model.VideoQuality;
- import com.google.android.exoplayer2.C;
- import com.google.android.exoplayer2.DefaultLoadControl;
- import com.google.android.exoplayer2.ExoPlaybackException;
- import com.google.android.exoplayer2.ExoPlayer;
- import com.google.android.exoplayer2.ExoPlayerFactory;
- import com.google.android.exoplayer2.Format;
- import com.google.android.exoplayer2.SimpleExoPlayer;
- import com.google.android.exoplayer2.Timeline;
- import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
- import com.google.android.exoplayer2.source.ExtractorMediaSource;
- import com.google.android.exoplayer2.source.MediaSource;
- import com.google.android.exoplayer2.source.TrackGroupArray;
- import com.google.android.exoplayer2.source.hls.HlsMediaSource;
- import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
- import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
- import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
- import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
- import com.google.android.exoplayer2.trackselection.TrackSelection;
- import com.google.android.exoplayer2.trackselection.TrackSelections;
- import com.google.android.exoplayer2.ui.PlaybackControlView;
- import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
- import com.google.android.exoplayer2.upstream.DataSource;
- import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
- import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
- import java.util.concurrent.TimeUnit;
- public class PlaybackActivity extends CommonActivity
- implements ExoPlayer.EventListener, AudioManager.OnAudioFocusChangeListener {
- private static final String TAG = "PlaybackOverlayAct";
- private static final int DEFAULT_MAX_INITIAL_BITRATE = 70000;
- private static final int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000;
- private static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 15000;
- private static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 15000;
- private static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f;
- private static final int REWIND_MILLIS = 10000;
- private static final int FAST_FORWARD_MILLIS = 15000;
- private static final long SUPER_SEEK_MILLIS = TimeUnit.SECONDS.toMillis(30);
- private static final String BUFFERING_SPINNER_TAG = "buffering_spinner";
- // 10 second interval between saving position while the video is playing.
- private static final int SAVE_POSITION_INTERVAL_MILLIS = 10 * 1000;
- private final ResettingBandwidthMeter mBandwidthMeter = new ResettingBandwidthMeter();
- private final SelectStreamTrackDialogFragment mSelectStreamTrackDialog =
- new SelectStreamTrackDialogFragment();
- private SimpleExoPlayerView mSimpleExoPlayerView;
- private PlaybackControlView mController;
- private SimpleExoPlayer mPlayer;
- private MappingTrackSelector mTrackSelector;
- private boolean mPlayerNeedsSource = true;
- private Video mVideo;
- private VideoQuality mVideoQuality;
- private Handler mMainHandler;
- private Runnable mSavePositionRunnable;
- private PowerManager.WakeLock mWakeLock;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_playback);
- final PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, getLocalClassName());
- mMainHandler = new Handler();
- mSimpleExoPlayerView = (SimpleExoPlayerView) findViewById(R.id.player_view);
- mSimpleExoPlayerView.requestFocus();
- mController =
- (PlaybackControlView) findViewById(com.google.android.exoplayer2.R.id.control);
- // Hide the next/previous buttons.
- final ImageButton prevButton = (ImageButton) mSimpleExoPlayerView
- .findViewById(com.google.android.exoplayer2.R.id.prev);
- final ImageButton nextButton = (ImageButton) mSimpleExoPlayerView
- .findViewById(com.google.android.exoplayer2.R.id.next);
- prevButton.setImageDrawable(null);
- nextButton.setImageDrawable(null);
- mSimpleExoPlayerView.setRewindIncrementMs(REWIND_MILLIS);
- mSimpleExoPlayerView.setFastForwardIncrementMs(FAST_FORWARD_MILLIS);
- }
- @Override
- public void onStart() {
- super.onStart();
- final Intent intent = getIntent();
- mVideo = (Video) intent.getSerializableExtra(VideoDetailsActivity.VIDEO);
- mVideoQuality = (VideoQuality) intent.getSerializableExtra(VideoDetailsActivity.QUALITY);
- if (mVideoQuality == null) {
- mVideoQuality = DefaultQualityPrefUtils.getDefaultQuality(this);
- }
- initializePlayer();
- }
- @Override
- public void onResume() {
- super.onResume();
- if (mPlayer == null) {
- initializePlayer();
- }
- }
- @Override
- public void onPause() {
- super.onPause();
- releasePlayer(true);
- }
- @Override
- public void onStop() {
- super.onStop();
- // Finish the activity now to ensure the app resumes at either the main activity or the
- // details activity and never the playback activity.
- finish();
- }
- @Override
- public void onDestroy() {
- releasePlayer(true);
- mMainHandler.removeCallbacks(mSavePositionRunnable);
- super.onDestroy();
- }
- private void initializePlayer() {
- if (mPlayer == null) {
- final TrackSelection.Factory videoTrackSelectionFactory = mVideo.isLive() ?
- new AdaptiveVideoTrackSelection.Factory(mBandwidthMeter,
- DEFAULT_MAX_INITIAL_BITRATE,
- DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
- DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
- DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
- DEFAULT_BANDWIDTH_FRACTION) : new FixedTrackSelection.Factory();
- mTrackSelector = new DefaultTrackSelector(mMainHandler, videoTrackSelectionFactory);
- mPlayer = ExoPlayerFactory
- .newSimpleInstance(this, mTrackSelector, new DefaultLoadControl(), null, false);
- mPlayer.addListener(this);
- mSelectStreamTrackDialog.init(mTrackSelector, videoTrackSelectionFactory);
- mPlayer.setVideoDebugListener(new EmptyVideoRendererEventListener() {
- @Override
- public void onVideoInputFormatChanged(Format format) {
- if (!mVideo.isLive()) {
- if (!Utils.supportsFormat(format)) {
- Log.i(TAG, "This device doesn't support this format, downgrading to " +
- "the next quality down");
- final VideoQuality downQuality = VideoQuality.stepDown(mVideoQuality);
- Log.i(TAG, "Downgrading from " + mVideoQuality.toString() + " to " +
- downQuality.toString());
- mVideoQuality = downQuality;
- releasePlayer(false);
- initializePlayer();
- }
- } else if (!Utils.supports1080p60()) {
- // Live video on low-end device
- updateTrackBlacklist();
- }
- }
- });
- mSimpleExoPlayerView.setPlayer(mPlayer);
- mPlayerNeedsSource = true;
- }
- if (mPlayerNeedsSource) {
- final MediaSource mediaSource = getMediaSource();
- if (!mVideo.isLive()) {
- final Long savedPosition =
- VideoPositionDbUtils.getCurrentPositionFromDbSync(this, mVideo.getId());
- if (savedPosition != null) {
- Log.i(TAG, "Saved position retrieved: " + savedPosition + " ms");
- Log.i(TAG, "Video length: " + mVideo.getDurationMillis() + " ms");
- if (savedPosition < mVideo.getDurationMillis()) {
- Log.i(TAG, "Restoring saved position at " + savedPosition + " ms");
- mPlayer.seekTo(savedPosition);
- } else {
- Log.i(TAG, "Saved position was at the end of the video, starting video " +
- "over " + "from the beginning");
- }
- }
- }
- mPlayer.prepare(mediaSource);
- mPlayerNeedsSource = false;
- mPlayer.setPlayWhenReady(true);
- }
- }
- private void updateTrackBlacklist() {
- // Blacklist tracks that are too much for some devices to handle.
- final TrackSelections<MappingTrackSelector.MappedTrackInfo> selections =
- mTrackSelector.getCurrentSelections();
- for (int i = 0; i < selections.length; i++) {
- final TrackSelection selection = selections.get(i);
- if (selection == null) continue;
- for (int j = 0; j < selection.length(); j++) {
- final Format trackFormat = selection.getFormat(j);
- if (trackFormat == null) continue;
- if (!Utils.supportsFormat(trackFormat)) {
- selection.blacklist(j,
- SystemClock.elapsedRealtime() + TimeUnit.HOURS.toMillis(24L));
- }
- }
- }
- }
- private MediaSource getMediaSource() {
- final Uri videoUri = Uri.parse(mVideo.getVideoUrlForQuality(mVideoQuality));
- final String userAgent =
- Utils.getUserAgent(this, getResources().getString(R.string.app_name));
- final DataSource.Factory dataSourceFactory =
- mVideo.isLive() ? new DefaultHttpDataSourceFactory(userAgent, mBandwidthMeter) :
- new DefaultDataSourceFactory(this, userAgent);
- if (mVideo.isLive()) {
- return new HlsMediaSource(videoUri, dataSourceFactory, mMainHandler, null);
- } else {
- return new ExtractorMediaSource(videoUri, dataSourceFactory,
- new DefaultExtractorsFactory(), mMainHandler, null);
- }
- }
- private void releasePlayer(boolean savePosition) {
- if (mWakeLock.isHeld()) {
- Log.i(TAG, "Releasing wake lock prior to releasing player");
- mWakeLock.release();
- abandonAudioFocus();
- }
- if (mPlayer == null) return;
- if (savePosition) {
- saveCurrentPosition();
- }
- mPlayer.release();
- mPlayer = null;
- mTrackSelector = null;
- mSelectStreamTrackDialog.release();
- }
- @Override
- public void onAudioFocusChange(int focusChange) {
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS && mPlayer != null) {
- Log.i(TAG, "Audio focus lost, stopping playback");
- mPlayer.setPlayWhenReady(false);
- }
- }
- @Override
- public void onLoadingChanged(boolean isLoading) {
- // Do nothing... yet.
- }
- @Override
- public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
- if (playWhenReady) {
- if (!mWakeLock.isHeld()) {
- Log.i(TAG, "Acquiring wake lock on player state changed to playing");
- mWakeLock.acquire();
- requestAudioFocus();
- /* TODO: Test this with a real live stream.
- if (mVideo.isLive() && mPlayer.getDuration() == C.TIME_UNSET) {
- mPlayer.seekTo(C.TIME_UNSET);
- }
- */
- }
- if (mSavePositionRunnable == null) {
- mSavePositionRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- saveCurrentPosition();
- } finally {
- mMainHandler.postDelayed(this, SAVE_POSITION_INTERVAL_MILLIS);
- }
- }
- };
- mMainHandler.postDelayed(mSavePositionRunnable, SAVE_POSITION_INTERVAL_MILLIS);
- }
- } else {
- if (mWakeLock.isHeld()) {
- Log.i(TAG, "Releasing wake lock on player state changed to paused");
- mWakeLock.release();
- abandonAudioFocus();
- }
- mMainHandler.removeCallbacks(mSavePositionRunnable);
- mSavePositionRunnable = null;
- saveCurrentPosition();
- }
- switch (playbackState) {
- case ExoPlayer.STATE_IDLE:
- break;
- case ExoPlayer.STATE_BUFFERING:
- showSpinner();
- break;
- case ExoPlayer.STATE_READY:
- hideSpinner();
- break;
- case ExoPlayer.STATE_ENDED:
- savePosition(mVideo.getDurationMillis());
- finish();
- break;
- }
- }
- private void showSpinner() {
- if (getFragmentManager().findFragmentByTag(BUFFERING_SPINNER_TAG) != null) return;
- final SpinnerFragment spinnerFragment = new SpinnerFragment();
- getFragmentManager().beginTransaction()
- .add(android.R.id.content, spinnerFragment, BUFFERING_SPINNER_TAG).commit();
- }
- private void hideSpinner() {
- final Fragment removeSpinnerFragment =
- getFragmentManager().findFragmentByTag(BUFFERING_SPINNER_TAG);
- if (removeSpinnerFragment != null) {
- getFragmentManager().beginTransaction().remove(removeSpinnerFragment).commit();
- }
- }
- private void requestAudioFocus() {
- final int requestResult = ((AudioManager) getSystemService(Context.AUDIO_SERVICE))
- .requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
- final boolean success = requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
- Log.i(TAG, "Audio focus was " + (success ? "" : "not ") + "granted");
- }
- private void abandonAudioFocus() {
- final int requestResult =
- ((AudioManager) getSystemService(Context.AUDIO_SERVICE)).abandonAudioFocus(this);
- final boolean success = requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
- Log.i(TAG, "Audio focus was " + (success ? "" : "not ") + "abandoned successfully");
- }
- @Override
- public void onTimelineChanged(Timeline timeline, Object manifest) {
- // Just you wait, we'll do something here eventually.
- }
- @Override
- public void onPlayerError(ExoPlaybackException error) {
- Log.w(TAG, "Player error encountered", error);
- final PlayerErrorDialogFragment playerErrorDialog = new PlayerErrorDialogFragment();
- playerErrorDialog.setError(error);
- playerErrorDialog.show(getFragmentManager(), "playerError");
- }
- @Override
- public void onPositionDiscontinuity() {
- // Skipping around... must be something we can do here at some point.
- }
- private void savePosition(final long positionMillis) {
- if (mVideo == null) {
- return;
- }
- VideoPositionDbUtils.updateCurrentPosition(this, mVideo, positionMillis);
- }
- private void saveCurrentPosition() {
- if (mPlayer == null || mPlayer.getPlaybackState() == ExoPlayer.STATE_ENDED) {
- return;
- }
- savePosition(mPlayer.getCurrentPosition());
- }
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (mPlayer != null && event.getAction() == KeyEvent.ACTION_DOWN) {
- // We consume all seek keys if the video doesn't yet have a duration to avoid resetting
- // saved progress while the video is loading.
- final boolean playerHasDuration = mPlayer.getDuration() > 0;
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
- final KeyEvent redispatchKey =
- new KeyEvent(event.getAction(), KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
- return super.dispatchKeyEvent(redispatchKey);
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (playerHasDuration) superFastForward();
- return true;
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (playerHasDuration) superRewind();
- return true;
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (mController != null) mController.show();
- break;
- case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
- case KeyEvent.KEYCODE_MEDIA_REWIND:
- if (!playerHasDuration) return true;
- break;
- case KeyEvent.KEYCODE_MENU:
- if (showTrackSelectionDialog()) return true;
- break;
- }
- }
- return super.dispatchKeyEvent(event);
- }
- private boolean showTrackSelectionDialog() {
- if (mTrackSelector != null && mVideo != null && mVideo.isLive()) {
- final TrackSelections<MappingTrackSelector.MappedTrackInfo> trackSelections =
- mTrackSelector.getCurrentSelections();
- if (trackSelections == null) {
- return false;
- }
- int videoIndex = -1;
- for (int i = 0; i < trackSelections.length; i++) {
- final TrackGroupArray trackGroups = trackSelections.info.getTrackGroups(i);
- if (trackGroups.length != 0 && mPlayer.getRendererCount() > i &&
- mPlayer.getRendererType(i) == C.TRACK_TYPE_VIDEO) {
- videoIndex = i;
- break;
- }
- }
- if (videoIndex >= 0) {
- mSelectStreamTrackDialog.show(getFragmentManager(), "selectLiveStreamTrack",
- mTrackSelector.getCurrentSelections().info, videoIndex, getResources());
- return true;
- }
- }
- return false;
- }
- private void superFastForward() {
- if (mPlayer == null) return;
- if (mController != null) mController.show();
- mPlayer.seekTo(
- Math.min(mPlayer.getCurrentPosition() + SUPER_SEEK_MILLIS, mPlayer.getDuration()));
- }
- private void superRewind() {
- if (mPlayer == null) return;
- if (mController != null) mController.show();
- mPlayer.seekTo(Math.max(mPlayer.getCurrentPosition() - SUPER_SEEK_MILLIS, 0L));
- }
- }