package za.co.riggaroo.android.arch.lifecycles; import android.arch.lifecycle.Lifecycle; import android.arch.lifecycle.LifecycleObserver; import android.arch.lifecycle.OnLifecycleEvent; import android.content.Context; import android.net.Uri; import android.util.Log; import android.widget.Toast; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.SimpleExoPlayerView; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Util; /** * A version of https://github.com/google/ExoPlayer/blob/release-v2/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java * That uses LifecycleObserver annotations instead of having the code all inside the activity. * NOTE: This is not a full implementation for ExoPlayer to play all different kinds of content (DRM session management etc). * <p> * This is purely to demonstrate the power of a LifecycleObserver and how it can remove a lot of code from your activities. * Please refer to the above URL for a more complete example that takes care of more erroneous situations. * * @author rebeccafranks * @since 2017/07/11. */ public class VideoPlayerComponent implements LifecycleObserver, ExoPlayer.EventListener { private static final String TAG = "VideoPlayerComponent"; private final Context context; private final SimpleExoPlayerView simpleExoPlayerView; private final String videoUrl; private int resumeWindow; private long resumePosition; private SimpleExoPlayer player; private DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); private AdaptiveTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter); private DefaultTrackSelector trackSelector; VideoPlayerComponent(Context context, SimpleExoPlayerView simpleExoPlayerView, String videoUrl) { this.context = context; this.simpleExoPlayerView = simpleExoPlayerView; this.videoUrl = videoUrl; } @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) void onCreate() { clearResumePosition(); simpleExoPlayerView.requestFocus(); } @OnLifecycleEvent(Lifecycle.Event.ON_START) void onStart() { if (Util.SDK_INT > 23) { initializePlayer(); } } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) void onStop() { if (Util.SDK_INT > 23) { releasePlayer(); } } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) void onPause() { if (Util.SDK_INT <= 23) { releasePlayer(); } } private void releasePlayer() { if (player != null) { updateResumePosition(); player.release(); player = null; trackSelector = null; } } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) void onResume() { if ((Util.SDK_INT <= 23)) { initializePlayer(); } } private void initializePlayer() { if (player == null) { trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); player = ExoPlayerFactory.newSimpleInstance(context, trackSelector); player.addListener(this); DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "testApp"), bandwidthMeter); ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); ExtractorMediaSource videoSource = new ExtractorMediaSource(Uri.parse(videoUrl), dataSourceFactory, extractorsFactory, null, null); simpleExoPlayerView.setPlayer(player); player.setPlayWhenReady(true); boolean haveResumePosition = resumeWindow != C.INDEX_UNSET; if (haveResumePosition) { Log.d(TAG, "Have Resume position true!" + resumePosition); player.seekTo(resumeWindow, resumePosition); } player.prepare(videoSource, !haveResumePosition, false); } } private void updateResumePosition() { resumeWindow = player.getCurrentWindowIndex(); resumePosition = player.isCurrentWindowSeekable() ? Math.max(0, player.getCurrentPosition()) : C.TIME_UNSET; } private void clearResumePosition() { resumeWindow = C.INDEX_UNSET; resumePosition = C.TIME_UNSET; } @Override public void onTimelineChanged(Timeline timeline, Object manifest) { } @Override public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { } @Override public void onLoadingChanged(boolean isLoading) { } @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { } @Override public void onPlayerError(ExoPlaybackException e) { String errorString = null; if (e.type == ExoPlaybackException.TYPE_RENDERER) { Exception cause = e.getRendererException(); if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { // Special case for decoder initialization failures. MediaCodecRenderer.DecoderInitializationException decoderInitializationException = (MediaCodecRenderer.DecoderInitializationException) cause; if (decoderInitializationException.decoderName == null) { if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) { errorString = context.getString(R.string.error_querying_decoders); } else if (decoderInitializationException.secureDecoderRequired) { errorString = context.getString(R.string.error_no_secure_decoder, decoderInitializationException.mimeType); } else { errorString = context.getString(R.string.error_no_decoder, decoderInitializationException.mimeType); } } else { errorString = context.getString(R.string.error_instantiating_decoder, decoderInitializationException.decoderName); } } } if (errorString != null) { showToast(errorString); } if (isBehindLiveWindow(e)) { clearResumePosition(); initializePlayer(); } else { updateResumePosition(); } } @Override public void onPositionDiscontinuity() { } @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { } private void showToast(String message) { Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_LONG).show(); } private static boolean isBehindLiveWindow(ExoPlaybackException e) { if (e.type != ExoPlaybackException.TYPE_SOURCE) { return false; } Throwable cause = e.getSourceException(); while (cause != null) { if (cause instanceof BehindLiveWindowException) { return true; } cause = cause.getCause(); } return false; } }