aboutsummaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/activities/PostVideoActivity.java10
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewVideoActivity.java119
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/adapters/HistoryPostRecyclerViewAdapter.java16
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java11
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostRecyclerViewAdapter.java16
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/customviews/LoopAvailableExoCreator.java6
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurVideoFragment.java42
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java55
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryVideoFragment.java42
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/Config.java27
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/DefaultExoCreator.java281
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoCreator.java4
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoPlayable.java253
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoPlayerDispatcher.java61
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/MediaSourceBuilder.java106
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/Playable.java602
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/PlayableImpl.java453
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroExo.java332
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroExoPlayer.java106
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroUtil.java135
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ui/ToroControlView.java278
-rw-r--r--app/src/main/res/layout/toro_exo_control_view.xml181
-rw-r--r--app/src/main/res/layout/toro_exo_player_view.xml89
-rw-r--r--app/src/main/res/values/strings.xml7
24 files changed, 1302 insertions, 1930 deletions
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/PostVideoActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/PostVideoActivity.java
index 8477942e..d9e36ee5 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/PostVideoActivity.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/PostVideoActivity.java
@@ -27,9 +27,9 @@ import androidx.core.content.ContextCompat;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.RequestOptions;
-import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
@@ -189,7 +189,7 @@ public class PostVideoActivity extends BaseActivity implements FlairBottomSheetF
private FlairBottomSheetFragment mFlairSelectionBottomSheetFragment;
private Snackbar mPostingSnackbar;
private DataSource.Factory dataSourceFactory;
- private SimpleExoPlayer player;
+ private ExoPlayer player;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -216,7 +216,7 @@ public class PostVideoActivity extends BaseActivity implements FlairBottomSheetF
mGlide = Glide.with(this);
- player = ExoPlayerFactory.newSimpleInstance(this);
+ player = new ExoPlayer.Builder(this).build();
videoPlayerView.setPlayer(player);
dataSourceFactory = new DefaultDataSourceFactory(this,
Util.getUserAgent(this, "Infinity"));
@@ -491,7 +491,7 @@ public class PostVideoActivity extends BaseActivity implements FlairBottomSheetF
constraintLayout.setVisibility(View.GONE);
selectAgainTextView.setVisibility(View.VISIBLE);
videoPlayerView.setVisibility(View.VISIBLE);
- player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(videoUri));
+ player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(videoUri)));
player.setPlayWhenReady(true);
wasPlaying = true;
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewVideoActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewVideoActivity.java
index 2fab98e7..39398315 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewVideoActivity.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewVideoActivity.java
@@ -7,6 +7,7 @@ import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
import android.Manifest;
import android.app.AlertDialog;
+import android.app.Dialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
@@ -24,6 +25,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.text.Html;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.OrientationEventListener;
@@ -45,29 +47,26 @@ import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
-import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
-import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.trackselection.TrackSelection;
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder;
import com.google.android.exoplayer2.upstream.DataSource;
-import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
-import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
+import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.MimeTypes;
-import com.google.android.exoplayer2.video.VideoListener;
+import com.google.android.exoplayer2.video.VideoSize;
import com.google.android.material.bottomappbar.BottomAppBar;
import com.google.android.material.snackbar.Snackbar;
+import com.google.common.collect.ImmutableList;
import com.otaliastudios.zoom.ZoomEngine;
import com.otaliastudios.zoom.ZoomSurfaceView;
@@ -176,7 +175,7 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
public Typeface typeface;
private Uri mVideoUri;
- private SimpleExoPlayer player;
+ private ExoPlayer player;
private DefaultTrackSelector trackSelector;
private DataSource.Factory dataSourceFactory;
@@ -403,7 +402,7 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
String postTitle = intent.getStringExtra(EXTRA_POST_TITLE);
setSmallTitle(postTitle);
- playerControlView.setVisibilityListener(visibility -> {
+ playerControlView.addVisibilityListener(visibility -> {
switch (visibility) {
case View.GONE:
getWindow().getDecorView().setSystemUiVisibility(
@@ -422,21 +421,20 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
}
});
- TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
- trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
+ trackSelector = new DefaultTrackSelector(this);
if (videoType == VIDEO_TYPE_NORMAL && isDataSavingMode && dataSavingModeDefaultResolution > 0) {
trackSelector.setParameters(
trackSelector.buildUponParameters()
.setMaxVideoSize(dataSavingModeDefaultResolution, dataSavingModeDefaultResolution));
}
- player = ExoPlayerFactory.newSimpleInstance(this, trackSelector);
+ player = new ExoPlayer.Builder(this).setTrackSelector(trackSelector).build();
playerControlView.setPlayer(player);
- player.addVideoListener(new VideoListener() {
+ player.addListener(new Player.Listener() {
@Override
- public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
- zoomSurfaceView.setContentSize(width, height);
+ public void onVideoSizeChanged(VideoSize videoSize) {
+ zoomSurfaceView.setContentSize(videoSize.width, videoSize.height);
}
});
zoomSurfaceView.addCallback(new ZoomSurfaceView.Callback() {
@@ -506,9 +504,10 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
if (mVideoUri == null) {
loadStreamableVideo(shortCode, savedInstanceState);
} else {
- dataSourceFactory = new CacheDataSourceFactory(mSimpleCache,
- new DefaultDataSourceFactory(ViewVideoActivity.this, APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
- player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
+ player.prepare();
+ player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
preparePlayer(savedInstanceState);
}
} else if (videoType == VIDEO_TYPE_V_REDD_IT) {
@@ -537,9 +536,10 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
loadGfycatOrRedgifsVideo(redgifsRetrofit, gfycatId, false, savedInstanceState, false);
}
} else {
- dataSourceFactory = new CacheDataSourceFactory(mSimpleCache,
- new DefaultDataSourceFactory(ViewVideoActivity.this, APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
- player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
+ player.prepare();
+ player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
preparePlayer(savedInstanceState);
}
} else if (videoType == VIDEO_TYPE_DIRECT || videoType == VIDEO_TYPE_IMGUR) {
@@ -550,10 +550,11 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
videoFileName = "imgur-" + FilenameUtils.getName(videoDownloadUrl);
}
// Produces DataSource instances through which media data is loaded.
- dataSourceFactory = new CacheDataSourceFactory(mSimpleCache,
- new DefaultHttpDataSourceFactory(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
// Prepare the player with the source.
- player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri));
+ player.prepare();
+ player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
preparePlayer(savedInstanceState);
} else {
videoDownloadUrl = intent.getStringExtra(EXTRA_VIDEO_DOWNLOAD_URL);
@@ -561,10 +562,11 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
id = intent.getStringExtra(EXTRA_ID);
videoFileName = subredditName + "-" + id + ".mp4";
// Produces DataSource instances through which media data is loaded.
- dataSourceFactory = new CacheDataSourceFactory(mSimpleCache,
- new DefaultHttpDataSourceFactory(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
// Prepare the player with the source.
- player.prepare(new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri));
+ player.prepare();
+ player.setMediaSource(new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
preparePlayer(savedInstanceState);
}
}
@@ -615,25 +617,29 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
muteButton.setImageResource(R.drawable.ic_unmute_24dp);
}
- player.addListener(new Player.EventListener() {
+ player.addListener(new Player.Listener() {
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) {
if (videoType == VIDEO_TYPE_NORMAL) {
hdButton.setVisibility(View.VISIBLE);
hdButton.setOnClickListener(view -> {
- TrackSelectionDialogBuilder builder = new TrackSelectionDialogBuilder(ViewVideoActivity.this, getString(R.string.select_video_quality), trackSelector, 0);
+ TrackSelectionDialogBuilder builder = new TrackSelectionDialogBuilder(ViewVideoActivity.this, getString(R.string.select_video_quality), player, 0);
builder.setShowDisableOption(true);
builder.setAllowAdaptiveSelections(false);
- AlertDialog alertDialog = builder.build();
- alertDialog.show();
- alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor());
- alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor());
+ Dialog dialog = builder.build();
+ dialog.show();
+ if (dialog instanceof AlertDialog) {
+ Log.i("asfadsf", "asdfasdf");
+ ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor());
+ ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor());
+ }
});
}
- for (int i = 0; i < trackGroups.length; i++) {
- String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType;
+ for (int i = 0; i < trackGroups.size(); i++) {
+ String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) {
muteButton.setVisibility(View.VISIBLE);
muteButton.setOnClickListener(view -> {
@@ -688,10 +694,11 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
progressBar.setVisibility(View.GONE);
mVideoUri = Uri.parse(webm);
videoDownloadUrl = mp4;
- dataSourceFactory = new CacheDataSourceFactory(mSimpleCache,
- new DefaultDataSourceFactory(ViewVideoActivity.this, APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
preparePlayer(savedInstanceState);
- player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri));
+ player.prepare();
+ player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
}
@Override
@@ -717,10 +724,11 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
progressBar.setVisibility(View.GONE);
mVideoUri = Uri.parse(webm);
videoDownloadUrl = mp4;
- dataSourceFactory = new CacheDataSourceFactory(mSimpleCache,
- new DefaultDataSourceFactory(ViewVideoActivity.this, APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
preparePlayer(savedInstanceState);
- player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri));
+ player.prepare();
+ player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
}
@Override
@@ -782,10 +790,11 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
videoType = VIDEO_TYPE_IMGUR;
videoFileName = "imgur-" + FilenameUtils.getName(videoDownloadUrl);
// Produces DataSource instances through which media data is loaded.
- dataSourceFactory = new CacheDataSourceFactory(mSimpleCache,
- new DefaultHttpDataSourceFactory(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
// Prepare the player with the source.
- player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri));
+ player.prepare();
+ player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
preparePlayer(savedInstanceState);
} else {
progressBar.setVisibility(View.GONE);
@@ -797,11 +806,12 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
videoFileName = subredditName + "-" + id + ".mp4";
// Produces DataSource instances through which media data is loaded.
- dataSourceFactory = new CacheDataSourceFactory(mSimpleCache,
- new DefaultHttpDataSourceFactory(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
// Prepare the player with the source.
preparePlayer(savedInstanceState);
- player.prepare(new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri));
+ player.prepare();
+ player.setMediaSource(new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
} else {
Toast.makeText(ViewVideoActivity.this, R.string.error_fetching_v_redd_it_video_cannot_get_video_url, Toast.LENGTH_LONG).show();
}
@@ -839,10 +849,11 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
progressBar.setVisibility(View.GONE);
videoDownloadUrl = streamableVideo.mp4 == null ? streamableVideo.mp4Mobile.url : streamableVideo.mp4.url;
mVideoUri = Uri.parse(videoDownloadUrl);
- dataSourceFactory = new CacheDataSourceFactory(mSimpleCache,
- new DefaultDataSourceFactory(ViewVideoActivity.this, APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
preparePlayer(savedInstanceState);
- player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri));
+ player.prepare();
+ player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
}
@Override
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/HistoryPostRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/HistoryPostRecyclerViewAdapter.java
index 2eba5f0c..2f3f9923 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/HistoryPostRecyclerViewAdapter.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/HistoryPostRecyclerViewAdapter.java
@@ -42,10 +42,12 @@ import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
+import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView;
+import com.google.common.collect.ImmutableList;
import com.libRG.CustomTextView;
import org.greenrobot.eventbus.EventBus;
@@ -2755,10 +2757,11 @@ public class HistoryPostRecyclerViewAdapter extends PagingDataAdapter<Post, Recy
helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator);
helper.addEventListener(new Playable.DefaultEventListener() {
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) {
- for (int i = 0; i < trackGroups.length; i++) {
- String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType;
+ for (int i = 0; i < trackGroups.size(); i++) {
+ String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) {
if (mFragment.getMasterMutingOption() != null) {
volume = mFragment.getMasterMutingOption() ? 0f : 1f;
@@ -4022,10 +4025,11 @@ public class HistoryPostRecyclerViewAdapter extends PagingDataAdapter<Post, Recy
helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator);
helper.addEventListener(new Playable.DefaultEventListener() {
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) {
- for (int i = 0; i < trackGroups.length; i++) {
- String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType;
+ for (int i = 0; i < trackGroups.size(); i++) {
+ String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) {
if (mFragment.getMasterMutingOption() != null) {
volume = mFragment.getMasterMutingOption() ? 0f : 1f;
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java
index 4db8166f..b2687f93 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java
@@ -40,10 +40,10 @@ import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
-import com.google.android.exoplayer2.source.TrackGroupArray;
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
+import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView;
+import com.google.common.collect.ImmutableList;
import com.libRG.CustomTextView;
import org.commonmark.ext.gfm.tables.TableBlock;
@@ -1761,10 +1761,11 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler
helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator);
helper.addEventListener(new Playable.DefaultEventListener() {
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) {
- for (int i = 0; i < trackGroups.length; i++) {
- String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType;
+ for (int i = 0; i < trackGroups.size(); i++) {
+ String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) {
helper.setVolume(volume);
muteButton.setVisibility(View.VISIBLE);
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostRecyclerViewAdapter.java
index 4bf33ddf..1a25e34e 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostRecyclerViewAdapter.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostRecyclerViewAdapter.java
@@ -42,10 +42,12 @@ import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
+import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView;
+import com.google.common.collect.ImmutableList;
import com.libRG.CustomTextView;
import org.greenrobot.eventbus.EventBus;
@@ -2860,10 +2862,11 @@ public class PostRecyclerViewAdapter extends PagingDataAdapter<Post, RecyclerVie
helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator);
helper.addEventListener(new Playable.DefaultEventListener() {
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) {
- for (int i = 0; i < trackGroups.length; i++) {
- String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType;
+ for (int i = 0; i < trackGroups.size(); i++) {
+ String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) {
if (mFragment.getMasterMutingOption() != null) {
volume = mFragment.getMasterMutingOption() ? 0f : 1f;
@@ -4175,10 +4178,11 @@ public class PostRecyclerViewAdapter extends PagingDataAdapter<Post, RecyclerVie
helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator);
helper.addEventListener(new Playable.DefaultEventListener() {
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) {
- for (int i = 0; i < trackGroups.length; i++) {
- String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType;
+ for (int i = 0; i < trackGroups.size(); i++) {
+ String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) {
if (mFragment.getMasterMutingOption() != null) {
volume = mFragment.getMasterMutingOption() ? 0f : 1f;
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/customviews/LoopAvailableExoCreator.java b/app/src/main/java/ml/docilealligator/infinityforreddit/customviews/LoopAvailableExoCreator.java
index 01fc9a32..560f7b12 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/customviews/LoopAvailableExoCreator.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/customviews/LoopAvailableExoCreator.java
@@ -4,8 +4,8 @@ import android.content.SharedPreferences;
import androidx.annotation.NonNull;
+import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.videoautoplay.Config;
@@ -22,8 +22,8 @@ public class LoopAvailableExoCreator extends DefaultExoCreator {
@NonNull
@Override
- public SimpleExoPlayer createPlayer() {
- SimpleExoPlayer player = super.createPlayer();
+ public ExoPlayer createPlayer() {
+ ExoPlayer player = super.createPlayer();
if (sharedPreferences.getBoolean(SharedPreferencesUtils.LOOP_VIDEO, true)) {
player.setRepeatMode(Player.REPEAT_MODE_ALL);
} else {
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurVideoFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurVideoFragment.java
index a2fcfb9e..b53b3ddb 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurVideoFragment.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurVideoFragment.java
@@ -7,7 +7,6 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.AudioManager;
-import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -26,22 +25,21 @@ import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
-import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
-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.TrackSelection;
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
-import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
-import com.google.android.exoplayer2.util.Util;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
+import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
+import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.material.bottomappbar.BottomAppBar;
+import com.google.common.collect.ImmutableList;
import javax.inject.Inject;
import javax.inject.Named;
@@ -54,6 +52,7 @@ import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.activities.ViewImgurMediaActivity;
import ml.docilealligator.infinityforreddit.bottomsheetfragments.PlaybackSpeedBottomSheetFragment;
import ml.docilealligator.infinityforreddit.services.DownloadMediaService;
+import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.Utils;
@@ -78,7 +77,7 @@ public class ViewImgurVideoFragment extends Fragment {
ImageView downloadImageView;
private ViewImgurMediaActivity activity;
private ImgurMedia imgurMedia;
- private SimpleExoPlayer player;
+ private ExoPlayer player;
private DataSource.Factory dataSourceFactory;
private boolean wasPlaying = false;
private boolean isMute = false;
@@ -87,6 +86,8 @@ public class ViewImgurVideoFragment extends Fragment {
@Inject
@Named("default")
SharedPreferences mSharedPreferences;
+ @Inject
+ SimpleCache mSimpleCache;
public ViewImgurVideoFragment() {
// Required empty public constructor
@@ -143,13 +144,13 @@ public class ViewImgurVideoFragment extends Fragment {
}
});
- TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
- TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
- player = ExoPlayerFactory.newSimpleInstance(activity, trackSelector);
+ TrackSelector trackSelector = new DefaultTrackSelector(activity);
+ player = new ExoPlayer.Builder(activity).setTrackSelector(trackSelector).build();
videoPlayerView.setPlayer(player);
- dataSourceFactory = new DefaultDataSourceFactory(activity,
- Util.getUserAgent(activity, "Infinity"));
- player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(imgurMedia.getLink())));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(activity)));
+ player.prepare();
+ player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(imgurMedia.getLink())));
if (savedInstanceState != null) {
playbackSpeed = savedInstanceState.getInt(PLAYBACK_SPEED_STATE);
@@ -278,12 +279,13 @@ public class ViewImgurVideoFragment extends Fragment {
muteButton.setImageResource(R.drawable.ic_unmute_24dp);
}
- player.addListener(new Player.EventListener() {
+ player.addListener(new Player.Listener() {
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) {
- for (int i = 0; i < trackGroups.length; i++) {
- String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType;
+ for (int i = 0; i < trackGroups.size(); i++) {
+ String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) {
muteButton.setVisibility(View.VISIBLE);
muteButton.setOnClickListener(view -> {
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java
index a053b68a..02884e88 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java
@@ -1,10 +1,10 @@
package ml.docilealligator.infinityforreddit.fragments;
import android.app.AlertDialog;
+import android.app.Dialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
@@ -23,21 +23,20 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
-import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
-import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.trackselection.TrackSelection;
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder;
import com.google.android.exoplayer2.upstream.DataSource;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
-import com.google.android.exoplayer2.util.Util;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
+import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
+import com.google.android.exoplayer2.upstream.cache.SimpleCache;
+import com.google.common.collect.ImmutableList;
import org.json.JSONException;
import org.json.JSONObject;
@@ -58,6 +57,7 @@ import ml.docilealligator.infinityforreddit.activities.RPANActivity;
import ml.docilealligator.infinityforreddit.adapters.RPANCommentStreamRecyclerViewAdapter;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
+import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.JSONUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import okhttp3.OkHttpClient;
@@ -105,9 +105,11 @@ public class ViewRPANBroadcastFragment extends Fragment {
@Inject
@Named("rpan")
OkHttpClient okHttpClient;
+ @Inject
+ SimpleCache mSimpleCache;
private RPANActivity mActivity;
private RPANBroadcast rpanBroadcast;
- private SimpleExoPlayer player;
+ private ExoPlayer player;
private DefaultTrackSelector trackSelector;
private DataSource.Factory dataSourceFactory;
private Handler handler;
@@ -175,9 +177,8 @@ public class ViewRPANBroadcastFragment extends Fragment {
}
});
- TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
- trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
- player = ExoPlayerFactory.newSimpleInstance(mActivity, trackSelector);
+ trackSelector = new DefaultTrackSelector(mActivity);
+ player = new ExoPlayer.Builder(mActivity).setTrackSelector(trackSelector).build();
playerView.setPlayer(player);
wasPlaying = true;
@@ -202,9 +203,10 @@ public class ViewRPANBroadcastFragment extends Fragment {
muteButton.setImageResource(R.drawable.ic_unmute_24dp);
}
- player.addListener(new Player.EventListener() {
+ player.addListener(new Player.Listener() {
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) {
if (isDataSavingMode) {
trackSelector.setParameters(
@@ -214,18 +216,19 @@ public class ViewRPANBroadcastFragment extends Fragment {
hdButton.setVisibility(View.VISIBLE);
hdButton.setOnClickListener(view -> {
- TrackSelectionDialogBuilder builder = new TrackSelectionDialogBuilder(mActivity,
- getString(R.string.select_video_quality), trackSelector, 0);
+ TrackSelectionDialogBuilder builder = new TrackSelectionDialogBuilder(mActivity, getString(R.string.select_video_quality), player, 0);
builder.setShowDisableOption(true);
builder.setAllowAdaptiveSelections(false);
- AlertDialog alertDialog = builder.build();
- alertDialog.show();
- alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor());
- alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor());
+ Dialog dialog = builder.build();
+ dialog.show();
+ if (dialog instanceof AlertDialog) {
+ ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor());
+ ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor());
+ }
});
- for (int i = 0; i < trackGroups.length; i++) {
- String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType;
+ for (int i = 0; i < trackGroups.size(); i++) {
+ String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) {
muteButton.setVisibility(View.VISIBLE);
muteButton.setOnClickListener(view -> {
@@ -350,9 +353,11 @@ public class ViewRPANBroadcastFragment extends Fragment {
public void onResume() {
super.onResume();
if (dataSourceFactory == null) {
- dataSourceFactory = new DefaultHttpDataSourceFactory(Util.getUserAgent(mActivity, "Infinity"));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(mActivity)));
// Prepare the player with the source.
- player.prepare(new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(rpanBroadcast.rpanStream.hlsUrl)));
+ player.prepare();
+ player.setMediaSource(new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(rpanBroadcast.rpanStream.hlsUrl)));
if (mSharedPreferences.getBoolean(SharedPreferencesUtils.LOOP_VIDEO, true)) {
player.setRepeatMode(Player.REPEAT_MODE_ALL);
} else {
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryVideoFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryVideoFragment.java
index fd27d4db..93e028c1 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryVideoFragment.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryVideoFragment.java
@@ -7,7 +7,6 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.AudioManager;
-import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -26,22 +25,21 @@ import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
-import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
-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.TrackSelection;
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
-import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
-import com.google.android.exoplayer2.util.Util;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
+import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
+import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.material.bottomappbar.BottomAppBar;
+import com.google.common.collect.ImmutableList;
import javax.inject.Inject;
import javax.inject.Named;
@@ -54,6 +52,7 @@ import ml.docilealligator.infinityforreddit.activities.ViewRedditGalleryActivity
import ml.docilealligator.infinityforreddit.bottomsheetfragments.PlaybackSpeedBottomSheetFragment;
import ml.docilealligator.infinityforreddit.post.Post;
import ml.docilealligator.infinityforreddit.services.DownloadMediaService;
+import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.Utils;
@@ -82,7 +81,7 @@ public class ViewRedditGalleryVideoFragment extends Fragment {
private Post.Gallery galleryVideo;
private String subredditName;
private boolean isNsfw;
- private SimpleExoPlayer player;
+ private ExoPlayer player;
private DataSource.Factory dataSourceFactory;
private boolean wasPlaying = false;
private boolean isMute = false;
@@ -91,6 +90,8 @@ public class ViewRedditGalleryVideoFragment extends Fragment {
@Inject
@Named("default")
SharedPreferences mSharedPreferences;
+ @Inject
+ SimpleCache mSimpleCache;
public ViewRedditGalleryVideoFragment() {
// Required empty public constructor
@@ -153,13 +154,13 @@ public class ViewRedditGalleryVideoFragment extends Fragment {
}
});
- TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
- TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
- player = ExoPlayerFactory.newSimpleInstance(activity, trackSelector);
+ TrackSelector trackSelector = new DefaultTrackSelector(activity);
+ player = new ExoPlayer.Builder(activity).setTrackSelector(trackSelector).build();
videoPlayerView.setPlayer(player);
- dataSourceFactory = new DefaultDataSourceFactory(activity,
- Util.getUserAgent(activity, "Infinity"));
- player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(galleryVideo.url)));
+ dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
+ .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(activity)));
+ player.prepare();
+ player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(galleryVideo.url)));
if (savedInstanceState != null) {
playbackSpeed = savedInstanceState.getInt(PLAYBACK_SPEED_STATE);
@@ -290,12 +291,13 @@ public class ViewRedditGalleryVideoFragment extends Fragment {
muteButton.setImageResource(R.drawable.ic_unmute_24dp);
}
- player.addListener(new Player.EventListener() {
+ player.addListener(new Player.Listener() {
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) {
- for (int i = 0; i < trackGroups.length; i++) {
- String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType;
+ for (int i = 0; i < trackGroups.size(); i++) {
+ String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) {
muteButton.setVisibility(View.VISIBLE);
muteButton.setOnClickListener(view -> {
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/Config.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/Config.java
index 7fe095d4..8513c4bf 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/Config.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/Config.java
@@ -29,15 +29,11 @@ import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory.ExtensionRendererMode;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.cache.Cache;
-import ml.docilealligator.infinityforreddit.videoautoplay.annotations.Beta;
-
/**
* Necessary configuration for {@link ExoCreator} to produces {@link SimpleExoPlayer} and
* {@link MediaSource}. Instance of this class must be construct using {@link Builder}.
@@ -61,7 +57,6 @@ public final class Config {
@NonNull final MediaSourceBuilder mediaSourceBuilder;
// Nullable options
- @Nullable final DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
@Nullable final Cache cache; // null by default
// If null, ExoCreator must come up with a default one.
// This is to help customizing the Data source, for example using OkHttp extension.
@@ -69,17 +64,16 @@ public final class Config {
@SuppressWarnings("WeakerAccess") //
Config(@Nullable Context context, int extensionMode, @NonNull BaseMeter meter,
- @NonNull LoadControl loadControl,
- @Nullable DataSource.Factory dataSourceFactory,
- @NonNull MediaSourceBuilder mediaSourceBuilder,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable Cache cache) {
+ @NonNull LoadControl loadControl,
+ @Nullable DataSource.Factory dataSourceFactory,
+ @NonNull MediaSourceBuilder mediaSourceBuilder,
+ @Nullable Cache cache) {
this.context = context != null ? context.getApplicationContext() : null;
this.extensionMode = extensionMode;
this.meter = meter;
this.loadControl = loadControl;
this.dataSourceFactory = dataSourceFactory;
this.mediaSourceBuilder = mediaSourceBuilder;
- this.drmSessionManager = drmSessionManager;
this.cache = cache;
}
@@ -93,7 +87,6 @@ public final class Config {
if (!meter.equals(config.meter)) return false;
if (!loadControl.equals(config.loadControl)) return false;
if (!mediaSourceBuilder.equals(config.mediaSourceBuilder)) return false;
- if (!ObjectsCompat.equals(drmSessionManager, config.drmSessionManager)) return false;
if (!ObjectsCompat.equals(cache, config.cache)) return false;
return ObjectsCompat.equals(dataSourceFactory, config.dataSourceFactory);
}
@@ -103,7 +96,6 @@ public final class Config {
result = 31 * result + meter.hashCode();
result = 31 * result + loadControl.hashCode();
result = 31 * result + mediaSourceBuilder.hashCode();
- result = 31 * result + (drmSessionManager != null ? drmSessionManager.hashCode() : 0);
result = 31 * result + (cache != null ? cache.hashCode() : 0);
result = 31 * result + (dataSourceFactory != null ? dataSourceFactory.hashCode() : 0);
return result;
@@ -111,7 +103,6 @@ public final class Config {
@SuppressWarnings("unused") public Builder newBuilder() {
return new Builder(context).setCache(this.cache)
- .setDrmSessionManager(this.drmSessionManager)
.setExtensionMode(this.extensionMode)
.setLoadControl(this.loadControl)
.setMediaSourceBuilder(this.mediaSourceBuilder)
@@ -145,7 +136,6 @@ public final class Config {
private LoadControl loadControl = new DefaultLoadControl();
private DataSource.Factory dataSourceFactory = null;
private MediaSourceBuilder mediaSourceBuilder = MediaSourceBuilder.DEFAULT;
- private DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
private Cache cache = null;
public Builder setExtensionMode(@ExtensionRendererMode int extensionMode) {
@@ -175,13 +165,6 @@ public final class Config {
return this;
}
- @Beta //
- public Builder setDrmSessionManager(
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
- this.drmSessionManager = drmSessionManager;
- return this;
- }
-
public Builder setCache(@Nullable Cache cache) {
this.cache = cache;
return this;
@@ -189,7 +172,7 @@ public final class Config {
public Config build() {
return new Config(context, extensionMode, meter, loadControl, dataSourceFactory,
- mediaSourceBuilder, drmSessionManager, cache);
+ mediaSourceBuilder, cache);
}
}
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/DefaultExoCreator.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/DefaultExoCreator.java
index af3bbd84..4c82c34b 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/DefaultExoCreator.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/DefaultExoCreator.java
@@ -27,22 +27,26 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.DefaultRenderersFactory;
+import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.RenderersFactory;
-import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.source.LoadEventInfo;
+import com.google.android.exoplayer2.source.MediaLoadData;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
-import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
-import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
+import com.google.android.exoplayer2.upstream.DefaultDataSource;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
+import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
+import ml.docilealligator.infinityforreddit.utils.APIUtils;
+
/**
* Usage: use this as-it or inheritance.
*
@@ -50,144 +54,139 @@ import java.io.IOException;
* @since 3.4.0
*/
-@SuppressWarnings({ "unused", "WeakerAccess" }) //
+@SuppressWarnings({"unused", "WeakerAccess"}) //
public class DefaultExoCreator implements ExoCreator, MediaSourceEventListener {
- final ToroExo toro; // per application
- final Config config;
- private final TrackSelector trackSelector; // 'maybe' stateless
- private final LoadControl loadControl; // stateless
- private final MediaSourceBuilder mediaSourceBuilder; // stateless
- private final RenderersFactory renderersFactory; // stateless
- private final DataSource.Factory mediaDataSourceFactory; // stateless
- private final DataSource.Factory manifestDataSourceFactory; // stateless
-
- public DefaultExoCreator(@NonNull ToroExo toro, @NonNull Config config) {
- this.toro = checkNotNull(toro);
- this.config = checkNotNull(config);
- trackSelector = new DefaultTrackSelector();
- loadControl = config.loadControl;
- mediaSourceBuilder = config.mediaSourceBuilder;
-
- DefaultRenderersFactory tempFactory = new DefaultRenderersFactory(this.toro.context);
- tempFactory.setExtensionRendererMode(config.extensionMode);
- renderersFactory = tempFactory;
-
- DataSource.Factory baseFactory = config.dataSourceFactory;
- if (baseFactory == null) {
- baseFactory = new DefaultHttpDataSourceFactory(toro.appName, config.meter);
+ final ToroExo toro; // per application
+ final Config config;
+ private final TrackSelector trackSelector; // 'maybe' stateless
+ private final LoadControl loadControl; // stateless
+ private final MediaSourceBuilder mediaSourceBuilder; // stateless
+ private final RenderersFactory renderersFactory; // stateless
+ private final DataSource.Factory mediaDataSourceFactory; // stateless
+ private final DataSource.Factory manifestDataSourceFactory; // stateless
+
+ public DefaultExoCreator(@NonNull ToroExo toro, @NonNull Config config) {
+ this.toro = checkNotNull(toro);
+ this.config = checkNotNull(config);
+ trackSelector = new DefaultTrackSelector();
+ loadControl = config.loadControl;
+ mediaSourceBuilder = config.mediaSourceBuilder;
+
+ DefaultRenderersFactory tempFactory = new DefaultRenderersFactory(this.toro.context);
+ tempFactory.setExtensionRendererMode(config.extensionMode);
+ renderersFactory = tempFactory;
+
+ DataSource.Factory baseFactory = config.dataSourceFactory;
+ if (baseFactory == null) {
+ baseFactory = new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(getContext()));
+ }
+ DataSource.Factory factory = new DefaultDataSource.Factory(this.toro.context, baseFactory);
+ if (config.cache != null)
+ factory = new CacheDataSource.Factory().setCache(config.cache).setUpstreamDataSourceFactory(baseFactory);
+ mediaDataSourceFactory = factory;
+ manifestDataSourceFactory = new DefaultDataSource.Factory(this.toro.context);
+ }
+
+ public DefaultExoCreator(Context context, Config config) {
+ this(with(context), config);
+ }
+
+ @SuppressWarnings("SimplifiableIfStatement")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ DefaultExoCreator that = (DefaultExoCreator) o;
+
+ if (!toro.equals(that.toro)) return false;
+ if (!trackSelector.equals(that.trackSelector)) return false;
+ if (!loadControl.equals(that.loadControl)) return false;
+ if (!mediaSourceBuilder.equals(that.mediaSourceBuilder)) return false;
+ if (!renderersFactory.equals(that.renderersFactory)) return false;
+ if (!mediaDataSourceFactory.equals(that.mediaDataSourceFactory)) return false;
+ return manifestDataSourceFactory.equals(that.manifestDataSourceFactory);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = toro.hashCode();
+ result = 31 * result + trackSelector.hashCode();
+ result = 31 * result + loadControl.hashCode();
+ result = 31 * result + mediaSourceBuilder.hashCode();
+ result = 31 * result + renderersFactory.hashCode();
+ result = 31 * result + mediaDataSourceFactory.hashCode();
+ result = 31 * result + manifestDataSourceFactory.hashCode();
+ return result;
+ }
+
+ final TrackSelector getTrackSelector() {
+ return trackSelector;
+ }
+
+ @Nullable
+ @Override
+ public Context getContext() {
+ return toro.context;
+ }
+
+ @NonNull
+ @Override
+ public ExoPlayer createPlayer() {
+ return new ToroExoPlayer(toro.context, renderersFactory, trackSelector, loadControl,
+ new DefaultBandwidthMeter.Builder(toro.context).build(), Util.getCurrentOrMainLooper()).getPlayer();
+ }
+
+ @NonNull
+ @Override
+ public MediaSource createMediaSource(@NonNull Uri uri, String fileExt) {
+ return mediaSourceBuilder.buildMediaSource(this.toro.context, uri, fileExt, new Handler(),
+ manifestDataSourceFactory, mediaDataSourceFactory, this);
+ }
+
+ @NonNull
+ @Override //
+ public Playable createPlayable(@NonNull Uri uri, String fileExt) {
+ return new PlayableImpl(this, uri, fileExt);
+ }
+
+ /// MediaSourceEventListener
+
+ @Override
+ public void onLoadStarted(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
+ LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
+ // no-ops
+ }
+
+ @Override
+ public void onLoadCompleted(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
+ LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
+ // no-ops
+ }
+
+ @Override
+ public void onLoadCanceled(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
+ LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
+ // no-ops
+ }
+
+ @Override
+ public void onLoadError(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
+ LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData, IOException error,
+ boolean wasCanceled) {
+ // no-ops
+ }
+
+ @Override
+ public void onUpstreamDiscarded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId,
+ MediaLoadData mediaLoadData) {
+ // no-ops
+ }
+
+ @Override
+ public void onDownstreamFormatChanged(int windowIndex,
+ @Nullable MediaSource.MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
+ // no-ops
}
- DataSource.Factory factory = new DefaultDataSourceFactory(this.toro.context, //
- config.meter, baseFactory);
- if (config.cache != null) factory = new CacheDataSourceFactory(config.cache, factory);
- mediaDataSourceFactory = factory;
- manifestDataSourceFactory = new DefaultDataSourceFactory(this.toro.context, this.toro.appName);
- }
-
- public DefaultExoCreator(Context context, Config config) {
- this(with(context), config);
- }
-
- @SuppressWarnings("SimplifiableIfStatement")
- @Override public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- DefaultExoCreator that = (DefaultExoCreator) o;
-
- if (!toro.equals(that.toro)) return false;
- if (!trackSelector.equals(that.trackSelector)) return false;
- if (!loadControl.equals(that.loadControl)) return false;
- if (!mediaSourceBuilder.equals(that.mediaSourceBuilder)) return false;
- if (!renderersFactory.equals(that.renderersFactory)) return false;
- if (!mediaDataSourceFactory.equals(that.mediaDataSourceFactory)) return false;
- return manifestDataSourceFactory.equals(that.manifestDataSourceFactory);
- }
-
- @Override public int hashCode() {
- int result = toro.hashCode();
- result = 31 * result + trackSelector.hashCode();
- result = 31 * result + loadControl.hashCode();
- result = 31 * result + mediaSourceBuilder.hashCode();
- result = 31 * result + renderersFactory.hashCode();
- result = 31 * result + mediaDataSourceFactory.hashCode();
- result = 31 * result + manifestDataSourceFactory.hashCode();
- return result;
- }
-
- final TrackSelector getTrackSelector() {
- return trackSelector;
- }
-
- @Nullable @Override public Context getContext() {
- return toro.context;
- }
-
- @NonNull @Override public SimpleExoPlayer createPlayer() {
- return new ToroExoPlayer(toro.context, renderersFactory, trackSelector, loadControl,
- new DefaultBandwidthMeter.Builder(toro.context).build(), config.drmSessionManager,
- Util.getLooper());
- }
-
- @NonNull @Override public MediaSource createMediaSource(@NonNull Uri uri, String fileExt) {
- return mediaSourceBuilder.buildMediaSource(this.toro.context, uri, fileExt, new Handler(),
- manifestDataSourceFactory, mediaDataSourceFactory, this);
- }
-
- @NonNull @Override //
- public Playable createPlayable(@NonNull Uri uri, String fileExt) {
- return new PlayableImpl(this, uri, fileExt);
- }
-
- /// MediaSourceEventListener
-
- @Override
- public void onLoadStarted(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
- LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
- // no-ops
- }
-
- @Override
- public void onLoadCompleted(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
- LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
- // no-ops
- }
-
- @Override
- public void onLoadCanceled(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
- LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
- // no-ops
- }
-
- @Override
- public void onLoadError(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
- LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData, IOException error,
- boolean wasCanceled) {
- // no-ops
- }
-
- @Override public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
- // no-ops
- }
-
- @Override
- public void onUpstreamDiscarded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId,
- MediaLoadData mediaLoadData) {
- // no-ops
- }
-
- @Override public void onDownstreamFormatChanged(int windowIndex,
- @Nullable MediaSource.MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
- // no-ops
- }
-
- @Override
- public void onMediaPeriodCreated(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
- // no-ops
- }
-
- @Override
- public void onMediaPeriodReleased(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
- // no-ops
- }
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoCreator.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoCreator.java
index c4319493..d639c9d3 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoCreator.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoCreator.java
@@ -22,6 +22,7 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
@@ -52,7 +53,8 @@ public interface ExoCreator {
*
* @return a new {@link SimpleExoPlayer} instance.
*/
- @NonNull SimpleExoPlayer createPlayer();
+ @NonNull
+ ExoPlayer createPlayer();
/**
* Create a {@link MediaSource} from media {@link Uri}.
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoPlayable.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoPlayable.java
index a159ea17..9690d014 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoPlayable.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoPlayable.java
@@ -17,7 +17,6 @@
package ml.docilealligator.infinityforreddit.videoautoplay;
import static com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS;
-
import static ml.docilealligator.infinityforreddit.videoautoplay.ToroExo.toro;
import android.net.Uri;
@@ -28,16 +27,16 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.PlaybackException;
+import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
-import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
+import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
-import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.PlayerView;
+import com.google.common.collect.ImmutableList;
import ml.docilealligator.infinityforreddit.R;
@@ -52,154 +51,132 @@ import ml.docilealligator.infinityforreddit.R;
@SuppressWarnings("WeakerAccess")
public class ExoPlayable extends PlayableImpl {
- @SuppressWarnings("unused") private static final String TAG = "ToroExo:Playable";
-
- private EventListener listener;
-
- // Adapt from ExoPlayer demo.
- protected boolean inErrorState = false;
- protected TrackGroupArray lastSeenTrackGroupArray;
-
- /**
- * Construct an instance of {@link ExoPlayable} from an {@link ExoCreator} and {@link Uri}. The
- * {@link ExoCreator} is used to request {@link SimpleExoPlayer} instance, while {@link Uri}
- * defines the media to play.
- *
- * @param creator the {@link ExoCreator} instance.
- * @param uri the {@link Uri} of the media.
- * @param fileExt the custom extension of the media Uri.
- */
- public ExoPlayable(ExoCreator creator, Uri uri, String fileExt) {
- super(creator, uri, fileExt);
- }
-
- @Override public void prepare(boolean prepareSource) {
- if (listener == null) {
- listener = new Listener();
- super.addEventListener(listener);
- }
- super.prepare(prepareSource);
- this.lastSeenTrackGroupArray = null;
- this.inErrorState = false;
- }
-
- @Override public void setPlayerView(@Nullable PlayerView playerView) {
- // This will also clear these flags
- if (playerView != this.playerView) {
- this.lastSeenTrackGroupArray = null;
- this.inErrorState = false;
+ @SuppressWarnings("unused")
+ private static final String TAG = "ToroExo:Playable";
+
+ private EventListener listener;
+
+ // Adapt from ExoPlayer demo.
+ protected boolean inErrorState = false;
+ protected ImmutableList<Tracks.Group> lastSeenTrackGroupArray;
+
+ /**
+ * Construct an instance of {@link ExoPlayable} from an {@link ExoCreator} and {@link Uri}. The
+ * {@link ExoCreator} is used to request {@link SimpleExoPlayer} instance, while {@link Uri}
+ * defines the media to play.
+ *
+ * @param creator the {@link ExoCreator} instance.
+ * @param uri the {@link Uri} of the media.
+ * @param fileExt the custom extension of the media Uri.
+ */
+ public ExoPlayable(ExoCreator creator, Uri uri, String fileExt) {
+ super(creator, uri, fileExt);
}
- super.setPlayerView(playerView);
- }
-
- @Override public void reset() {
- super.reset();
- this.lastSeenTrackGroupArray = null;
- this.inErrorState = false;
- }
-
- @Override public void release() {
- if (listener != null) {
- super.removeEventListener(listener);
- listener = null;
+
+ @Override
+ public void prepare(boolean prepareSource) {
+ if (listener == null) {
+ listener = new Listener();
+ super.addEventListener(listener);
+ }
+ super.prepare(prepareSource);
+ this.lastSeenTrackGroupArray = null;
+ this.inErrorState = false;
}
- super.release();
- this.lastSeenTrackGroupArray = null;
- this.inErrorState = false;
- }
-
- @SuppressWarnings({ "unused" }) //
- protected void onErrorMessage(@NonNull String message) {
- // Sub class can have custom reaction about the error here, including not to show this toast
- // (by not calling super.onErrorMessage(message)).
- if (this.errorListeners.size() > 0) {
- this.errorListeners.onError(new RuntimeException(message));
- } else if (playerView != null) {
- Toast.makeText(playerView.getContext(), message, Toast.LENGTH_SHORT).show();
+
+ @Override
+ public void setPlayerView(@Nullable PlayerView playerView) {
+ // This will also clear these flags
+ if (playerView != this.playerView) {
+ this.lastSeenTrackGroupArray = null;
+ this.inErrorState = false;
+ }
+ super.setPlayerView(playerView);
}
- }
- class Listener extends DefaultEventListener {
+ @Override
+ public void reset() {
+ super.reset();
+ this.lastSeenTrackGroupArray = null;
+ this.inErrorState = false;
+ }
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
- super.onTracksChanged(trackGroups, trackSelections);
- if (trackGroups == lastSeenTrackGroupArray) return;
- lastSeenTrackGroupArray = trackGroups;
- if (!(creator instanceof DefaultExoCreator)) return;
- TrackSelector selector = ((DefaultExoCreator) creator).getTrackSelector();
- if (selector instanceof DefaultTrackSelector) {
- MappedTrackInfo trackInfo = ((DefaultTrackSelector) selector).getCurrentMappedTrackInfo();
- if (trackInfo != null) {
- if (trackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO) == RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
- onErrorMessage(toro.getString(R.string.error_unsupported_video));
- }
-
- if (trackInfo.getTypeSupport(C.TRACK_TYPE_AUDIO) == RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
- onErrorMessage(toro.getString(R.string.error_unsupported_audio));
- }
+ public void release() {
+ if (listener != null) {
+ super.removeEventListener(listener);
+ listener = null;
}
- }
+ super.release();
+ this.lastSeenTrackGroupArray = null;
+ this.inErrorState = false;
}
- @Override public void onPlayerError(ExoPlaybackException error) {
- /// Adapt from ExoPlayer Demo
- String errorString = null;
- if (error.type == ExoPlaybackException.TYPE_RENDERER) {
- Exception cause = error.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 = toro.getString(R.string.error_querying_decoders);
- } else if (decoderInitializationException.secureDecoderRequired) {
- errorString = toro.getString(R.string.error_no_secure_decoder,
- decoderInitializationException.mimeType);
- } else {
- errorString = toro.getString(R.string.error_no_decoder,
- decoderInitializationException.mimeType);
+ @SuppressWarnings({"unused"}) //
+ protected void onErrorMessage(@NonNull String message) {
+ // Sub class can have custom reaction about the error here, including not to show this toast
+ // (by not calling super.onErrorMessage(message)).
+ if (this.errorListeners.size() > 0) {
+ this.errorListeners.onError(new RuntimeException(message));
+ } else if (playerView != null) {
+ Toast.makeText(playerView.getContext(), message, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ class Listener extends DefaultEventListener {
+ @Override
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
+ if (trackGroups == lastSeenTrackGroupArray) return;
+ lastSeenTrackGroupArray = trackGroups;
+ if (!(creator instanceof DefaultExoCreator)) return;
+ TrackSelector selector = ((DefaultExoCreator) creator).getTrackSelector();
+ if (selector instanceof DefaultTrackSelector) {
+ MappedTrackInfo trackInfo = ((DefaultTrackSelector) selector).getCurrentMappedTrackInfo();
+ if (trackInfo != null) {
+ if (trackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO) == RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
+ onErrorMessage(toro.getString(R.string.error_unsupported_video));
+ }
+
+ if (trackInfo.getTypeSupport(C.TRACK_TYPE_AUDIO) == RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
+ onErrorMessage(toro.getString(R.string.error_unsupported_audio));
+ }
+ }
}
- } else {
- errorString = toro.getString(R.string.error_instantiating_decoder,
- decoderInitializationException.decoderName);
- }
}
- }
- if (errorString != null) onErrorMessage(errorString);
+ @Override
+ public void onPlayerError(@NonNull PlaybackException error) {
+ inErrorState = true;
+ if (isBehindLiveWindow(error)) {
+ ExoPlayable.super.reset();
+ } else {
+ ExoPlayable.super.updatePlaybackInfo();
+ }
- inErrorState = true;
- if (isBehindLiveWindow(error)) {
- ExoPlayable.super.reset();
- } else {
- ExoPlayable.super.updatePlaybackInfo();
- }
+ super.onPlayerError(error);
+ }
- super.onPlayerError(error);
+ @Override
+ public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
+ if (inErrorState) {
+ // Adapt from ExoPlayer demo.
+ // "This will only occur if the user has performed a seek whilst in the error state. Update
+ // the resume position so that if the user then retries, playback will resume from the
+ // position to which they seek." - ExoPlayer
+ ExoPlayable.super.updatePlaybackInfo();
+ }
+ super.onPositionDiscontinuity(oldPosition, newPosition, reason);
+ }
}
- @Override public void onPositionDiscontinuity(int reason) {
- if (inErrorState) {
- // Adapt from ExoPlayer demo.
- // "This will only occur if the user has performed a seek whilst in the error state. Update
- // the resume position so that if the user then retries, playback will resume from the
- // position to which they seek." - ExoPlayer
- ExoPlayable.super.updatePlaybackInfo();
- }
-
- super.onPositionDiscontinuity(reason);
- }
- }
-
- static boolean isBehindLiveWindow(ExoPlaybackException error) {
- if (error.type != ExoPlaybackException.TYPE_SOURCE) return false;
- Throwable cause = error.getSourceException();
- while (cause != null) {
- if (cause instanceof BehindLiveWindowException) return true;
- cause = cause.getCause();
+ static boolean isBehindLiveWindow(PlaybackException error) {
+ if (error instanceof ExoPlaybackException && ((ExoPlaybackException) error).type != ExoPlaybackException.TYPE_SOURCE) return false;
+ Throwable cause = error.getCause();
+ while (cause != null) {
+ if (cause instanceof BehindLiveWindowException) return true;
+ cause = cause.getCause();
+ }
+ return false;
}
- return false;
- }
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoPlayerDispatcher.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoPlayerDispatcher.java
deleted file mode 100644
index 031c1f3a..00000000
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ExoPlayerDispatcher.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (c) 2018 Nam Nguyen, nam@ene.im
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ml.docilealligator.infinityforreddit.videoautoplay;
-
-import com.google.android.exoplayer2.DefaultControlDispatcher;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.ui.PlayerView;
-
-import ml.docilealligator.infinityforreddit.videoautoplay.annotations.Beta;
-import ml.docilealligator.infinityforreddit.videoautoplay.widget.PressablePlayerSelector;
-
-/**
- * @author eneim (2018/08/18).
- * @since 3.6.0.2802
- *
- * Work with {@link PressablePlayerSelector} and {@link PlayerView} to handle user's custom playback
- * interaction. A common use-case is when user clicks the Play button to manually start a playback.
- * We should respect this by putting the {@link ToroPlayer}'s priority to highest, and request a
- * refresh for all {@link ToroPlayer}.
- *
- * The same behaviour should be handled for the case user clicks the Pause button.
- *
- * All behaviour should be cleared once user scroll the selection out of playable region. This is
- * already handled by {@link PressablePlayerSelector}.
- */
-@Beta //
-public class ExoPlayerDispatcher extends DefaultControlDispatcher {
-
- private final PressablePlayerSelector playerSelector;
- private final ToroPlayer toroPlayer;
-
- public ExoPlayerDispatcher(PressablePlayerSelector playerSelector, ToroPlayer toroPlayer) {
- this.playerSelector = playerSelector;
- this.toroPlayer = toroPlayer;
- }
-
- @Override public boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady) {
- if (playWhenReady) {
- // Container will handle the call to play.
- return playerSelector.toPlay(toroPlayer.getPlayerOrder());
- } else {
- player.setPlayWhenReady(false);
- playerSelector.toPause(toroPlayer.getPlayerOrder());
- return true;
- }
- }
-}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/MediaSourceBuilder.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/MediaSourceBuilder.java
index 885319d0..d54798cf 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/MediaSourceBuilder.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/MediaSourceBuilder.java
@@ -28,6 +28,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.C.ContentType;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.LoopingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
@@ -46,59 +47,62 @@ import com.google.android.exoplayer2.upstream.DataSource;
public interface MediaSourceBuilder {
- @NonNull MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri,
- @Nullable String fileExt, @Nullable Handler handler,
- @NonNull DataSource.Factory manifestDataSourceFactory,
- @NonNull DataSource.Factory mediaDataSourceFactory,
- @Nullable MediaSourceEventListener listener);
+ @NonNull
+ MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri,
+ @Nullable String fileExt, @Nullable Handler handler,
+ @NonNull DataSource.Factory manifestDataSourceFactory,
+ @NonNull DataSource.Factory mediaDataSourceFactory,
+ @Nullable MediaSourceEventListener listener);
- MediaSourceBuilder DEFAULT = new MediaSourceBuilder() {
- @NonNull @Override
- public MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri,
- @Nullable String ext, @Nullable Handler handler,
- @NonNull DataSource.Factory manifestDataSourceFactory,
- @NonNull DataSource.Factory mediaDataSourceFactory, MediaSourceEventListener listener) {
- @ContentType int type = isEmpty(ext) ? inferContentType(uri) : inferContentType("." + ext);
- MediaSource result;
- switch (type) {
- case C.TYPE_SS:
- result = new SsMediaSource.Factory(
- new DefaultSsChunkSource.Factory(mediaDataSourceFactory), manifestDataSourceFactory)
- .createMediaSource(uri);
- break;
- case C.TYPE_DASH:
- result = new DashMediaSource.Factory(
- new DefaultDashChunkSource.Factory(mediaDataSourceFactory), manifestDataSourceFactory)
- .createMediaSource(uri);
- break;
- case C.TYPE_HLS:
- result = new HlsMediaSource.Factory(mediaDataSourceFactory) //
- .createMediaSource(uri);
- break;
- case C.TYPE_OTHER:
- result = new ProgressiveMediaSource.Factory(mediaDataSourceFactory) //
- .createMediaSource(uri);
- break;
- default:
- throw new IllegalStateException("Unsupported type: " + type);
- }
+ MediaSourceBuilder DEFAULT = new MediaSourceBuilder() {
+ @NonNull
+ @Override
+ public MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri,
+ @Nullable String ext, @Nullable Handler handler,
+ @NonNull DataSource.Factory manifestDataSourceFactory,
+ @NonNull DataSource.Factory mediaDataSourceFactory, MediaSourceEventListener listener) {
+ @ContentType int type = isEmpty(ext) ? inferContentType(uri) : inferContentType("." + ext);
+ MediaSource result;
+ switch (type) {
+ case C.CONTENT_TYPE_SS:
+ result = new SsMediaSource.Factory(
+ new DefaultSsChunkSource.Factory(mediaDataSourceFactory), manifestDataSourceFactory)
+ .createMediaSource(MediaItem.fromUri(uri));
+ break;
+ case C.CONTENT_TYPE_DASH:
+ result = new DashMediaSource.Factory(
+ new DefaultDashChunkSource.Factory(mediaDataSourceFactory), manifestDataSourceFactory)
+ .createMediaSource(MediaItem.fromUri(uri));
+ break;
+ case C.CONTENT_TYPE_HLS:
+ result = new HlsMediaSource.Factory(mediaDataSourceFactory) //
+ .createMediaSource(MediaItem.fromUri(uri));
+ break;
+ case C.CONTENT_TYPE_OTHER:
+ result = new ProgressiveMediaSource.Factory(mediaDataSourceFactory) //
+ .createMediaSource(MediaItem.fromUri(uri));
+ break;
+ default:
+ throw new IllegalStateException("Unsupported type: " + type);
+ }
- result.addEventListener(handler, listener);
- return result;
- }
- };
+ result.addEventListener(handler, listener);
+ return result;
+ }
+ };
- MediaSourceBuilder LOOPING = new MediaSourceBuilder() {
+ MediaSourceBuilder LOOPING = new MediaSourceBuilder() {
- @NonNull @Override
- public MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri,
- @Nullable String fileExt, @Nullable Handler handler,
- @NonNull DataSource.Factory manifestDataSourceFactory,
- @NonNull DataSource.Factory mediaDataSourceFactory,
- @Nullable MediaSourceEventListener listener) {
- return new LoopingMediaSource(
- DEFAULT.buildMediaSource(context, uri, fileExt, handler, manifestDataSourceFactory,
- mediaDataSourceFactory, listener));
- }
- };
+ @NonNull
+ @Override
+ public MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri,
+ @Nullable String fileExt, @Nullable Handler handler,
+ @NonNull DataSource.Factory manifestDataSourceFactory,
+ @NonNull DataSource.Factory mediaDataSourceFactory,
+ @Nullable MediaSourceEventListener listener) {
+ return new LoopingMediaSource(
+ DEFAULT.buildMediaSource(context, uri, fileExt, handler, manifestDataSourceFactory,
+ mediaDataSourceFactory, listener));
+ }
+ };
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/Playable.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/Playable.java
index a713b5a7..fb9e177e 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/Playable.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/Playable.java
@@ -20,20 +20,20 @@ import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue;
+import com.google.android.exoplayer2.text.CueGroup;
import com.google.android.exoplayer2.text.TextOutput;
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.PlayerView;
-import com.google.android.exoplayer2.video.VideoListener;
+import com.google.android.exoplayer2.video.VideoSize;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
@@ -44,7 +44,7 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
/**
* Define an interface to control a playback, specific for {@link SimpleExoPlayer} and {@link PlayerView}.
- *
+ * <p>
* This interface is designed to be reused across Config change. Implementation must not hold any
* strong reference to Activity, and if it supports any kind of that, make sure to implicitly clean
* it up.
@@ -56,310 +56,350 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
@SuppressWarnings("unused") //
public interface Playable {
- /**
- * Prepare the resource for a {@link SimpleExoPlayer}. This method should:
- * - Request for new {@link SimpleExoPlayer} instance if there is not a usable one.
- * - Configure {@link EventListener} for it.
- * - If there is non-trivial PlaybackInfo, update it to the SimpleExoPlayer.
- * - If client request to prepare MediaSource, then prepare it.
- *
- * This method must be called before {@link #setPlayerView(PlayerView)}.
- *
- * @param prepareSource if {@code true}, also prepare the MediaSource when preparing the Player,
- * if {@code false} just do nothing for the MediaSource.
- */
- void prepare(boolean prepareSource);
-
- /**
- * Set the {@link PlayerView} for this Playable. It is expected that a playback doesn't require a
- * UI, so this setup is optional. But it must be called after the SimpleExoPlayer is prepared,
- * that is after {@link #prepare(boolean)} and before {@link #release()}.
- *
- * Changing the PlayerView during playback is expected, though not always recommended, especially
- * on old Devices with low Android API.
- *
- * @param playerView the PlayerView to set to the SimpleExoPlayer.
- */
- void setPlayerView(@Nullable PlayerView playerView);
-
- /**
- * Get current {@link PlayerView} of this Playable.
- *
- * @return current PlayerView instance of this Playable.
- */
- @Nullable PlayerView getPlayerView();
-
- /**
- * Start the playback. If the {@link MediaSource} is not prepared, then also prepare it.
- */
- void play();
-
- /**
- * Pause the playback.
- */
- void pause();
-
- /**
- * Reset all resource, so that the playback can start all over again. This is to cleanup the
- * playback for reuse. The SimpleExoPlayer instance must be still usable without calling
- * {@link #prepare(boolean)}.
- */
- void reset();
-
- /**
- * Release all resource. After this, the SimpleExoPlayer is released to the Player pool and the
- * Playable must call {@link #prepare(boolean)} again to use it again.
- */
- void release();
-
- /**
- * Get current {@link PlaybackInfo} of the playback.
- *
- * @return current PlaybackInfo of the playback.
- */
- @NonNull
- PlaybackInfo getPlaybackInfo();
-
- /**
- * Set the custom {@link PlaybackInfo} for this playback. This could suggest a seek.
- *
- * @param playbackInfo the PlaybackInfo to set for this playback.
- */
- void setPlaybackInfo(@NonNull PlaybackInfo playbackInfo);
-
- /**
- * Add a new {@link EventListener} to this Playable. As calling {@link #prepare(boolean)} also
- * triggers some internal events, this method should be called before {@link #prepare(boolean)} so
- * that Client could received them all.
- *
- * @param listener the EventListener to add, must be not {@code null}.
- */
- void addEventListener(@NonNull EventListener listener);
-
- /**
- * Remove an {@link EventListener} from this Playable.
- *
- * @param listener the EventListener to be removed. If null, nothing happens.
- */
- void removeEventListener(EventListener listener);
-
- /**
- * !This must only work if the Player in use is a {@link ToroExoPlayer}.
- */
- void addOnVolumeChangeListener(@NonNull ToroPlayer.OnVolumeChangeListener listener);
-
- void removeOnVolumeChangeListener(@Nullable ToroPlayer.OnVolumeChangeListener listener);
-
- /**
- * Check if current Playable is playing or not.
- *
- * @return {@code true} if this Playable is playing, {@code false} otherwise.
- */
- boolean isPlaying();
-
- /**
- * Change the volume of current playback.
- *
- * @param volume the volume value to be set. Must be a {@code float} of range from 0 to 1.
- * @deprecated use {@link #setVolumeInfo(VolumeInfo)} instead.
- */
- @RemoveIn(version = "3.6.0") @Deprecated //
- void setVolume(@FloatRange(from = 0.0, to = 1.0) float volume);
-
- /**
- * Obtain current volume value. The returned value is a {@code float} of range from 0 to 1.
- *
- * @return current volume value.
- * @deprecated use {@link #getVolumeInfo()} instead.
- */
- @RemoveIn(version = "3.6.0") @Deprecated //
- @FloatRange(from = 0.0, to = 1.0) float getVolume();
-
- /**
- * Update playback's volume.
- *
- * @param volumeInfo the {@link VolumeInfo} to update to.
- * @return {@code true} if current Volume info is updated, {@code false} otherwise.
- */
- boolean setVolumeInfo(@NonNull VolumeInfo volumeInfo);
-
- /**
- * Get current {@link VolumeInfo}.
- */
- @NonNull VolumeInfo getVolumeInfo();
-
- /**
- * Same as {@link Player#setPlaybackParameters(PlaybackParameters)}
- */
- void setParameters(@Nullable PlaybackParameters parameters);
-
- /**
- * Same as {@link Player#getPlaybackParameters()}
- */
- @Nullable PlaybackParameters getParameters();
-
- void addErrorListener(@NonNull ToroPlayer.OnErrorListener listener);
-
- void removeErrorListener(@Nullable ToroPlayer.OnErrorListener listener);
-
- // Combine necessary interfaces.
- interface EventListener extends Player.EventListener, VideoListener, TextOutput, MetadataOutput {
-
- }
-
- /** Default empty implementation */
- class DefaultEventListener implements EventListener {
-
- @Override public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
-
- }
-
- @Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
-
+ /**
+ * Prepare the resource for a {@link SimpleExoPlayer}. This method should:
+ * - Request for new {@link SimpleExoPlayer} instance if there is not a usable one.
+ * - Configure {@link EventListener} for it.
+ * - If there is non-trivial PlaybackInfo, update it to the SimpleExoPlayer.
+ * - If client request to prepare MediaSource, then prepare it.
+ * <p>
+ * This method must be called before {@link #setPlayerView(PlayerView)}.
+ *
+ * @param prepareSource if {@code true}, also prepare the MediaSource when preparing the Player,
+ * if {@code false} just do nothing for the MediaSource.
+ */
+ void prepare(boolean prepareSource);
+
+ /**
+ * Set the {@link PlayerView} for this Playable. It is expected that a playback doesn't require a
+ * UI, so this setup is optional. But it must be called after the SimpleExoPlayer is prepared,
+ * that is after {@link #prepare(boolean)} and before {@link #release()}.
+ * <p>
+ * Changing the PlayerView during playback is expected, though not always recommended, especially
+ * on old Devices with low Android API.
+ *
+ * @param playerView the PlayerView to set to the SimpleExoPlayer.
+ */
+ void setPlayerView(@Nullable PlayerView playerView);
+
+ /**
+ * Get current {@link PlayerView} of this Playable.
+ *
+ * @return current PlayerView instance of this Playable.
+ */
+ @Nullable
+ PlayerView getPlayerView();
+
+ /**
+ * Start the playback. If the {@link MediaSource} is not prepared, then also prepare it.
+ */
+ void play();
+
+ /**
+ * Pause the playback.
+ */
+ void pause();
+
+ /**
+ * Reset all resource, so that the playback can start all over again. This is to cleanup the
+ * playback for reuse. The SimpleExoPlayer instance must be still usable without calling
+ * {@link #prepare(boolean)}.
+ */
+ void reset();
+
+ /**
+ * Release all resource. After this, the SimpleExoPlayer is released to the Player pool and the
+ * Playable must call {@link #prepare(boolean)} again to use it again.
+ */
+ void release();
+
+ /**
+ * Get current {@link PlaybackInfo} of the playback.
+ *
+ * @return current PlaybackInfo of the playback.
+ */
+ @NonNull
+ PlaybackInfo getPlaybackInfo();
+
+ /**
+ * Set the custom {@link PlaybackInfo} for this playback. This could suggest a seek.
+ *
+ * @param playbackInfo the PlaybackInfo to set for this playback.
+ */
+ void setPlaybackInfo(@NonNull PlaybackInfo playbackInfo);
+
+ /**
+ * Add a new {@link EventListener} to this Playable. As calling {@link #prepare(boolean)} also
+ * triggers some internal events, this method should be called before {@link #prepare(boolean)} so
+ * that Client could received them all.
+ *
+ * @param listener the EventListener to add, must be not {@code null}.
+ */
+ void addEventListener(@NonNull EventListener listener);
+
+ /**
+ * Remove an {@link EventListener} from this Playable.
+ *
+ * @param listener the EventListener to be removed. If null, nothing happens.
+ */
+ void removeEventListener(EventListener listener);
+
+ /**
+ * !This must only work if the Player in use is a {@link ToroExoPlayer}.
+ */
+ void addOnVolumeChangeListener(@NonNull ToroPlayer.OnVolumeChangeListener listener);
+
+ void removeOnVolumeChangeListener(@Nullable ToroPlayer.OnVolumeChangeListener listener);
+
+ /**
+ * Check if current Playable is playing or not.
+ *
+ * @return {@code true} if this Playable is playing, {@code false} otherwise.
+ */
+ boolean isPlaying();
+
+ /**
+ * Change the volume of current playback.
+ *
+ * @param volume the volume value to be set. Must be a {@code float} of range from 0 to 1.
+ * @deprecated use {@link #setVolumeInfo(VolumeInfo)} instead.
+ */
+ @RemoveIn(version = "3.6.0")
+ @Deprecated
+ //
+ void setVolume(@FloatRange(from = 0.0, to = 1.0) float volume);
+
+ /**
+ * Obtain current volume value. The returned value is a {@code float} of range from 0 to 1.
+ *
+ * @return current volume value.
+ * @deprecated use {@link #getVolumeInfo()} instead.
+ */
+ @RemoveIn(version = "3.6.0")
+ @Deprecated //
+ @FloatRange(from = 0.0, to = 1.0)
+ float getVolume();
+
+ /**
+ * Update playback's volume.
+ *
+ * @param volumeInfo the {@link VolumeInfo} to update to.
+ * @return {@code true} if current Volume info is updated, {@code false} otherwise.
+ */
+ boolean setVolumeInfo(@NonNull VolumeInfo volumeInfo);
+
+ /**
+ * Get current {@link VolumeInfo}.
+ */
+ @NonNull
+ VolumeInfo getVolumeInfo();
+
+ /**
+ * Same as {@link Player#setPlaybackParameters(PlaybackParameters)}
+ */
+ void setParameters(@Nullable PlaybackParameters parameters);
+
+ /**
+ * Same as {@link Player#getPlaybackParameters()}
+ */
+ @Nullable
+ PlaybackParameters getParameters();
+
+ void addErrorListener(@NonNull ToroPlayer.OnErrorListener listener);
+
+ void removeErrorListener(@Nullable ToroPlayer.OnErrorListener listener);
+
+ // Combine necessary interfaces.
+ interface EventListener extends Player.Listener, TextOutput, MetadataOutput {
+
+ @Override
+ default void onCues(@NonNull List<Cue> cues) {
+
+ }
+
+ @Override
+ default void onCues(@NonNull CueGroup cueGroup) {
+
+ }
+
+ @Override
+ default void onMetadata(@NonNull Metadata metadata) {
+
+ }
}
- @Override public void onLoadingChanged(boolean isLoading) {
+ /**
+ * Default empty implementation
+ */
+ class DefaultEventListener implements EventListener {
+ @Override
+ public void onTimelineChanged(@NonNull Timeline timeline, int reason) {
- }
+ }
- @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
-
- }
-
- @Override public void onRepeatModeChanged(int repeatMode) {
-
- }
+ @Override
+ public void onTracksChanged(@NonNull Tracks tracks) {
- @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
+ }
- }
+ @Override
+ public void onIsLoadingChanged(boolean isLoading) {
- @Override public void onPlayerError(ExoPlaybackException error) {
+ }
- }
+ @Override
+ public void onPlaybackStateChanged(int playbackState) {
- @Override public void onPositionDiscontinuity(int reason) {
+ }
- }
+ @Override
+ public void onRepeatModeChanged(int repeatMode) {
- @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
+ }
- }
+ @Override
+ public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
- @Override public void onSeekProcessed() {
+ }
- }
+ @Override
+ public void onPlayerError(@NonNull PlaybackException error) {
- @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
- float pixelWidthHeightRatio) {
+ }
- }
+ @Override
+ public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
- @Override public void onRenderedFirstFrame() {
+ }
- }
+ @Override
+ public void onPlaybackParametersChanged(@NonNull PlaybackParameters playbackParameters) {
- @Override public void onCues(List<Cue> cues) {
+ }
- }
+ @Override
+ public void onSeekProcessed() {
- @Override public void onMetadata(Metadata metadata) {
+ }
- }
- }
+ @Override
+ public void onVideoSizeChanged(@NonNull VideoSize videoSize) {
+ EventListener.super.onVideoSizeChanged(videoSize);
+ }
- /** List of EventListener */
- class EventListeners extends CopyOnWriteArraySet<EventListener> implements EventListener {
+ @Override
+ public void onRenderedFirstFrame() {
- EventListeners() {
- }
+ }
- @Override public void onVideoSizeChanged(int width, int height, int unAppliedRotationDegrees,
- float pixelWidthHeightRatio) {
- for (EventListener eventListener : this) {
- eventListener.onVideoSizeChanged(width, height, unAppliedRotationDegrees,
- pixelWidthHeightRatio);
- }
- }
+ @Override
+ public void onCues(@NonNull CueGroup cueGroup) {
+ EventListener.super.onCues(cueGroup);
+ }
- @Override public void onRenderedFirstFrame() {
- for (EventListener eventListener : this) {
- eventListener.onRenderedFirstFrame();
- }
- }
-
- @Override public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
- for (EventListener eventListener : this) {
- eventListener.onTimelineChanged(timeline, manifest, reason);
- }
- }
-
- @Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
- for (EventListener eventListener : this) {
- eventListener.onTracksChanged(trackGroups, trackSelections);
- }
- }
-
- @Override public void onLoadingChanged(boolean isLoading) {
- for (EventListener eventListener : this) {
- eventListener.onLoadingChanged(isLoading);
- }
- }
-
- @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
- for (EventListener eventListener : this) {
- eventListener.onPlayerStateChanged(playWhenReady, playbackState);
- }
- }
-
- @Override public void onRepeatModeChanged(int repeatMode) {
- for (EventListener eventListener : this) {
- eventListener.onRepeatModeChanged(repeatMode);
- }
- }
-
- @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
- for (EventListener eventListener : this) {
- eventListener.onShuffleModeEnabledChanged(shuffleModeEnabled);
- }
- }
-
- @Override public void onPlayerError(ExoPlaybackException error) {
- for (EventListener eventListener : this) {
- eventListener.onPlayerError(error);
- }
- }
-
- @Override public void onPositionDiscontinuity(int reason) {
- for (EventListener eventListener : this) {
- eventListener.onPositionDiscontinuity(reason);
- }
- }
-
- @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
- for (EventListener eventListener : this) {
- eventListener.onPlaybackParametersChanged(playbackParameters);
- }
- }
-
- @Override public void onSeekProcessed() {
- for (EventListener eventListener : this) {
- eventListener.onSeekProcessed();
- }
- }
+ @Override
+ public void onMetadata(@NonNull Metadata metadata) {
- @Override public void onCues(List<Cue> cues) {
- for (EventListener eventListener : this) {
- eventListener.onCues(cues);
- }
+ }
}
- @Override public void onMetadata(Metadata metadata) {
- for (EventListener eventListener : this) {
- eventListener.onMetadata(metadata);
- }
+ /**
+ * List of EventListener
+ */
+ class EventListeners extends CopyOnWriteArraySet<EventListener> implements EventListener {
+
+ EventListeners() {
+ }
+
+ @Override
+ public void onVideoSizeChanged(@NonNull VideoSize videoSize) {
+ for (EventListener eventListener : this) {
+ eventListener.onVideoSizeChanged(videoSize);
+ }
+ }
+
+ @Override
+ public void onRenderedFirstFrame() {
+ for (EventListener eventListener : this) {
+ eventListener.onRenderedFirstFrame();
+ }
+ }
+
+ @Override
+ public void onTimelineChanged(@NonNull Timeline timeline, int reason) {
+ for (EventListener eventListener : this) {
+ eventListener.onTimelineChanged(timeline, reason);
+ }
+ }
+
+ @Override
+ public void onTracksChanged(@NonNull Tracks tracks) {
+ for (EventListener eventListener : this) {
+ eventListener.onTracksChanged(tracks);
+ }
+ }
+
+ @Override
+ public void onIsLoadingChanged(boolean isLoading) {
+ for (EventListener eventListener : this) {
+ eventListener.onIsLoadingChanged(isLoading);
+ }
+ }
+
+ @Override
+ public void onPlaybackStateChanged(int playbackState) {
+ for (EventListener eventListener : this) {
+ eventListener.onPlaybackStateChanged(playbackState);
+ }
+ }
+
+ @Override
+ public void onRepeatModeChanged(int repeatMode) {
+ for (EventListener eventListener : this) {
+ eventListener.onRepeatModeChanged(repeatMode);
+ }
+ }
+
+ @Override
+ public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
+ for (EventListener eventListener : this) {
+ eventListener.onShuffleModeEnabledChanged(shuffleModeEnabled);
+ }
+ }
+
+ @Override
+ public void onPlayerError(@NonNull PlaybackException error) {
+ for (EventListener eventListener : this) {
+ eventListener.onPlayerError(error);
+ }
+ }
+
+ @Override
+ public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
+ for (EventListener eventListener : this) {
+ eventListener.onPositionDiscontinuity(oldPosition, newPosition, reason);
+ }
+ }
+
+ @Override
+ public void onPlaybackParametersChanged(@NonNull PlaybackParameters playbackParameters) {
+ for (EventListener eventListener : this) {
+ eventListener.onPlaybackParametersChanged(playbackParameters);
+ }
+ }
+
+ @Override
+ public void onCues(@NonNull CueGroup cueGroup) {
+ for (EventListener eventListener : this) {
+ eventListener.onCues(cueGroup);
+ }
+ }
+
+ @Override
+ public void onMetadata(@NonNull Metadata metadata) {
+ for (EventListener eventListener : this) {
+ eventListener.onMetadata(metadata);
+ }
+ }
}
- }
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/PlayableImpl.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/PlayableImpl.java
index d1d47c07..a3f762e9 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/PlayableImpl.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/PlayableImpl.java
@@ -30,7 +30,6 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.ui.PlayerView;
@@ -39,9 +38,9 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
/**
* [20180225]
- *
+ * <p>
* Default implementation of {@link Playable}.
- *
+ * <p>
* Instance of {@link Playable} should be reusable. Retaining instance of Playable across config
* change must guarantee that all {@link EventListener} are cleaned up on config change.
*
@@ -50,238 +49,264 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
@SuppressWarnings("WeakerAccess") //
class PlayableImpl implements Playable {
- private final PlaybackInfo playbackInfo = new PlaybackInfo(); // never expose to outside.
+ private final PlaybackInfo playbackInfo = new PlaybackInfo(); // never expose to outside.
+
+ protected final EventListeners listeners = new EventListeners(); // original listener.
+ protected final ToroPlayer.VolumeChangeListeners volumeChangeListeners = new ToroPlayer.VolumeChangeListeners();
+ protected final ToroPlayer.ErrorListeners errorListeners = new ToroPlayer.ErrorListeners();
+
+ protected final Uri mediaUri; // immutable, parcelable
+ protected final String fileExt;
+ protected final ExoCreator creator; // required, cached
+
+ protected ToroExoPlayer player; // on-demand, cached
+ protected MediaSource mediaSource; // on-demand, since we do not reuse MediaSource now.
+ protected PlayerView playerView; // on-demand, not always required.
+
+ private boolean sourcePrepared = false;
+ private boolean listenerApplied = false;
+
+ PlayableImpl(ExoCreator creator, Uri uri, String fileExt) {
+ this.creator = creator;
+ this.mediaUri = uri;
+ this.fileExt = fileExt;
+ }
- protected final EventListeners listeners = new EventListeners(); // original listener.
- protected final ToroPlayer.VolumeChangeListeners volumeChangeListeners = new ToroPlayer.VolumeChangeListeners();
- protected final ToroPlayer.ErrorListeners errorListeners = new ToroPlayer.ErrorListeners();
+ @CallSuper
+ @Override
+ public void prepare(boolean prepareSource) {
+ if (prepareSource) {
+ ensureMediaSource();
+ ensurePlayerView();
+ }
+ }
- protected final Uri mediaUri; // immutable, parcelable
- protected final String fileExt;
- protected final ExoCreator creator; // required, cached
+ @CallSuper
+ @Override
+ public void setPlayerView(@Nullable PlayerView playerView) {
+ if (this.playerView == playerView) return;
+ if (playerView == null) {
+ this.playerView.setPlayer(null);
+ } else {
+ if (this.player != null) {
+ PlayerView.switchTargetView(this.player.getPlayer(), this.playerView, playerView);
+ }
+ }
- protected SimpleExoPlayer player; // on-demand, cached
- protected MediaSource mediaSource; // on-demand, since we do not reuse MediaSource now.
- protected PlayerView playerView; // on-demand, not always required.
+ this.playerView = playerView;
+ }
+
+ @Override
+ public final PlayerView getPlayerView() {
+ return this.playerView;
+ }
- private boolean sourcePrepared = false;
- private boolean listenerApplied = false;
+ @CallSuper
+ @Override
+ public void play() {
+ ensureMediaSource();
+ ensurePlayerView();
+ checkNotNull(player, "Playable#play(): Player is null!");
+ player.getPlayer().setPlayWhenReady(true);
+ }
+
+ @CallSuper
+ @Override
+ public void pause() {
+ // Player is not required to be non-null here.
+ if (player != null) player.getPlayer().setPlayWhenReady(false);
+ }
+
+ @CallSuper
+ @Override
+ public void reset() {
+ this.playbackInfo.reset();
+ if (player != null) {
+ // reset volume to default
+ ToroExo.setVolumeInfo(this.player, new VolumeInfo(false, 1.f));
+ player.getPlayer().stop();
+ player.getPlayer().clearMediaItems();
+ }
+ this.mediaSource = null; // so it will be re-prepared when play() is called.
+ this.sourcePrepared = false;
+ }
+
+ @CallSuper
+ @Override
+ public void release() {
+ this.setPlayerView(null);
+ if (this.player != null) {
+ // reset volume to default
+ ToroExo.setVolumeInfo(this.player, new VolumeInfo(false, 1.f));
+ player.getPlayer().stop();
+ player.getPlayer().clearMediaItems();
+ if (listenerApplied) {
+ player.getPlayer().removeListener(listeners);
+ if (this.player != null) {
+ this.player.removeOnVolumeChangeListener(this.volumeChangeListeners);
+ }
+ listenerApplied = false;
+ }
+ with(checkNotNull(creator.getContext(), "ExoCreator has no Context")) //
+ .releasePlayer(this.creator, this.player.getPlayer());
+ }
+ this.player = null;
+ this.mediaSource = null;
+ this.sourcePrepared = false;
+ }
+
+ @CallSuper
+ @NonNull
+ @Override
+ public PlaybackInfo getPlaybackInfo() {
+ updatePlaybackInfo();
+ return new PlaybackInfo(playbackInfo.getResumeWindow(), playbackInfo.getResumePosition(),
+ playbackInfo.getVolumeInfo());
+ }
+
+ @CallSuper
+ @Override
+ public void setPlaybackInfo(@NonNull PlaybackInfo playbackInfo) {
+ this.playbackInfo.setResumeWindow(playbackInfo.getResumeWindow());
+ this.playbackInfo.setResumePosition(playbackInfo.getResumePosition());
+ this.setVolumeInfo(playbackInfo.getVolumeInfo());
+
+ if (player != null) {
+ ToroExo.setVolumeInfo(player, this.playbackInfo.getVolumeInfo());
+ boolean haveResumePosition = this.playbackInfo.getResumeWindow() != INDEX_UNSET;
+ if (haveResumePosition) {
+ player.getPlayer().seekTo(this.playbackInfo.getResumeWindow(), this.playbackInfo.getResumePosition());
+ }
+ }
+ }
- PlayableImpl(ExoCreator creator, Uri uri, String fileExt) {
- this.creator = creator;
- this.mediaUri = uri;
- this.fileExt = fileExt;
- }
+ @Override
+ public final void addEventListener(@NonNull EventListener listener) {
+ //noinspection ConstantConditions
+ if (listener != null) this.listeners.add(listener);
+ }
- @CallSuper @Override public void prepare(boolean prepareSource) {
- if (prepareSource) {
- ensureMediaSource();
- ensurePlayerView();
+ @Override
+ public final void removeEventListener(EventListener listener) {
+ this.listeners.remove(listener);
}
- }
-
- @CallSuper @Override public void setPlayerView(@Nullable PlayerView playerView) {
- if (this.playerView == playerView) return;
- if (playerView == null) {
- this.playerView.setPlayer(null);
- } else {
- if (this.player != null) {
- PlayerView.switchTargetView(this.player, this.playerView, playerView);
- }
+
+ @CallSuper
+ @Override
+ public void setVolume(float volume) {
+ checkNotNull(player, "Playable#setVolume(): Player is null!");
+ playbackInfo.getVolumeInfo().setTo(volume == 0, volume);
+ ToroExo.setVolumeInfo(player, this.playbackInfo.getVolumeInfo());
}
- this.playerView = playerView;
- }
-
- @Override public final PlayerView getPlayerView() {
- return this.playerView;
- }
-
- @CallSuper @Override public void play() {
- ensureMediaSource();
- ensurePlayerView();
- checkNotNull(player, "Playable#play(): Player is null!");
- player.setPlayWhenReady(true);
- }
-
- @CallSuper @Override public void pause() {
- // Player is not required to be non-null here.
- if (player != null) player.setPlayWhenReady(false);
- }
-
- @CallSuper @Override public void reset() {
- this.playbackInfo.reset();
- if (player != null) {
- // reset volume to default
- ToroExo.setVolumeInfo(this.player, new VolumeInfo(false, 1.f));
- player.stop(true);
+ @CallSuper
+ @Override
+ public float getVolume() {
+ return checkNotNull(player.getPlayer(), "Playable#getVolume(): Player is null!").getVolume();
}
- this.mediaSource = null; // so it will be re-prepared when play() is called.
- this.sourcePrepared = false;
- }
-
- @CallSuper @Override public void release() {
- this.setPlayerView(null);
- if (this.player != null) {
- // reset volume to default
- ToroExo.setVolumeInfo(this.player, new VolumeInfo(false, 1.f));
- this.player.stop(true);
- if (listenerApplied) {
- player.removeListener(listeners);
- player.removeVideoListener(listeners);
- player.removeTextOutput(listeners);
- player.removeMetadataOutput(listeners);
- if (this.player instanceof ToroExoPlayer) {
- ((ToroExoPlayer) this.player).removeOnVolumeChangeListener(this.volumeChangeListeners);
+
+ @Override
+ public boolean setVolumeInfo(@NonNull VolumeInfo volumeInfo) {
+ boolean changed = !this.playbackInfo.getVolumeInfo().equals(checkNotNull(volumeInfo));
+ if (changed) {
+ this.playbackInfo.getVolumeInfo().setTo(volumeInfo.isMute(), volumeInfo.getVolume());
+ if (player != null) ToroExo.setVolumeInfo(player, this.playbackInfo.getVolumeInfo());
}
- listenerApplied = false;
- }
- with(checkNotNull(creator.getContext(), "ExoCreator has no Context")) //
- .releasePlayer(this.creator, this.player);
+ return changed;
+ }
+
+ @NonNull
+ @Override
+ public VolumeInfo getVolumeInfo() {
+ return this.playbackInfo.getVolumeInfo();
+ }
+
+ @Override
+ public void setParameters(@Nullable PlaybackParameters parameters) {
+ checkNotNull(player.getPlayer(), "Playable#setParameters(PlaybackParameters): Player is null") //
+ .setPlaybackParameters(parameters);
}
- this.player = null;
- this.mediaSource = null;
- this.sourcePrepared = false;
- }
-
- @CallSuper @NonNull @Override public PlaybackInfo getPlaybackInfo() {
- updatePlaybackInfo();
- return new PlaybackInfo(playbackInfo.getResumeWindow(), playbackInfo.getResumePosition(),
- playbackInfo.getVolumeInfo());
- }
-
- @CallSuper @Override public void setPlaybackInfo(@NonNull PlaybackInfo playbackInfo) {
- this.playbackInfo.setResumeWindow(playbackInfo.getResumeWindow());
- this.playbackInfo.setResumePosition(playbackInfo.getResumePosition());
- this.setVolumeInfo(playbackInfo.getVolumeInfo());
-
- if (player != null) {
- ToroExo.setVolumeInfo(player, this.playbackInfo.getVolumeInfo());
- boolean haveResumePosition = this.playbackInfo.getResumeWindow() != INDEX_UNSET;
- if (haveResumePosition) {
- player.seekTo(this.playbackInfo.getResumeWindow(), this.playbackInfo.getResumePosition());
- }
+
+ @Override
+ public PlaybackParameters getParameters() {
+ return checkNotNull(player.getPlayer(), "Playable#getParameters(): Player is null").getPlaybackParameters();
}
- }
-
- @Override public final void addEventListener(@NonNull EventListener listener) {
- //noinspection ConstantConditions
- if (listener != null) this.listeners.add(listener);
- }
-
- @Override public final void removeEventListener(EventListener listener) {
- this.listeners.remove(listener);
- }
-
- @CallSuper @Override public void setVolume(float volume) {
- checkNotNull(player, "Playable#setVolume(): Player is null!");
- playbackInfo.getVolumeInfo().setTo(volume == 0, volume);
- ToroExo.setVolumeInfo(player, this.playbackInfo.getVolumeInfo());
- }
-
- @CallSuper @Override public float getVolume() {
- return checkNotNull(player, "Playable#getVolume(): Player is null!").getVolume();
- }
-
- @Override public boolean setVolumeInfo(@NonNull VolumeInfo volumeInfo) {
- boolean changed = !this.playbackInfo.getVolumeInfo().equals(checkNotNull(volumeInfo));
- if (changed) {
- this.playbackInfo.getVolumeInfo().setTo(volumeInfo.isMute(), volumeInfo.getVolume());
- if (player != null) ToroExo.setVolumeInfo(player, this.playbackInfo.getVolumeInfo());
+
+ @Override
+ public void addOnVolumeChangeListener(@NonNull ToroPlayer.OnVolumeChangeListener listener) {
+ volumeChangeListeners.add(checkNotNull(listener));
}
- return changed;
- }
-
- @NonNull @Override public VolumeInfo getVolumeInfo() {
- return this.playbackInfo.getVolumeInfo();
- }
-
- @Override public void setParameters(@Nullable PlaybackParameters parameters) {
- checkNotNull(player, "Playable#setParameters(PlaybackParameters): Player is null") //
- .setPlaybackParameters(parameters);
- }
-
- @Override public PlaybackParameters getParameters() {
- return checkNotNull(player, "Playable#getParameters(): Player is null").getPlaybackParameters();
- }
-
- @Override
- public void addOnVolumeChangeListener(@NonNull ToroPlayer.OnVolumeChangeListener listener) {
- volumeChangeListeners.add(checkNotNull(listener));
- }
-
- @Override
- public void removeOnVolumeChangeListener(@Nullable ToroPlayer.OnVolumeChangeListener listener) {
- volumeChangeListeners.remove(listener);
- }
-
- @Override public boolean isPlaying() {
- return player != null && player.getPlayWhenReady();
- }
-
- @Override public void addErrorListener(@NonNull ToroPlayer.OnErrorListener listener) {
- this.errorListeners.add(checkNotNull(listener));
- }
-
- @Override public void removeErrorListener(@Nullable ToroPlayer.OnErrorListener listener) {
- this.errorListeners.remove(listener);
- }
-
- final void updatePlaybackInfo() {
- if (player == null || player.getPlaybackState() == Player.STATE_IDLE) return;
- playbackInfo.setResumeWindow(player.getCurrentWindowIndex());
- playbackInfo.setResumePosition(player.isCurrentWindowSeekable() ? //
- Math.max(0, player.getCurrentPosition()) : TIME_UNSET);
- playbackInfo.setVolumeInfo(ToroExo.getVolumeInfo(player));
- }
-
- private void ensurePlayerView() {
- if (playerView != null && playerView.getPlayer() != player) playerView.setPlayer(player);
- }
-
- // TODO [20180822] Double check this.
- private void ensureMediaSource() {
- if (mediaSource == null) { // Only actually prepare the source when play() is called.
- sourcePrepared = false;
- mediaSource = creator.createMediaSource(mediaUri, fileExt);
+
+ @Override
+ public void removeOnVolumeChangeListener(@Nullable ToroPlayer.OnVolumeChangeListener listener) {
+ volumeChangeListeners.remove(listener);
}
- if (!sourcePrepared) {
- ensurePlayer(); // sourcePrepared is set to false only when player is null.
- beforePrepareMediaSource();
- player.prepare(mediaSource, playbackInfo.getResumeWindow() == C.INDEX_UNSET, false);
- sourcePrepared = true;
+ @Override
+ public boolean isPlaying() {
+ return player != null && player.getPlayer().getPlayWhenReady();
}
- }
-
- private void ensurePlayer() {
- if (player == null) {
- sourcePrepared = false;
- player = with(checkNotNull(creator.getContext(), "ExoCreator has no Context")) //
- .requestPlayer(creator);
- listenerApplied = false;
+
+ @Override
+ public void addErrorListener(@NonNull ToroPlayer.OnErrorListener listener) {
+ this.errorListeners.add(checkNotNull(listener));
+ }
+
+ @Override
+ public void removeErrorListener(@Nullable ToroPlayer.OnErrorListener listener) {
+ this.errorListeners.remove(listener);
}
- if (!listenerApplied) {
- if (player instanceof ToroExoPlayer) {
- ((ToroExoPlayer) player).addOnVolumeChangeListener(volumeChangeListeners);
- }
- player.addListener(listeners);
- player.addVideoListener(listeners);
- player.addTextOutput(listeners);
- player.addMetadataOutput(listeners);
- listenerApplied = true;
+ final void updatePlaybackInfo() {
+ if (player == null || player.getPlayer().getPlaybackState() == Player.STATE_IDLE) return;
+ playbackInfo.setResumeWindow(player.getPlayer().getCurrentWindowIndex());
+ playbackInfo.setResumePosition(player.getPlayer().isCurrentWindowSeekable() ? //
+ Math.max(0, player.getPlayer().getCurrentPosition()) : TIME_UNSET);
+ playbackInfo.setVolumeInfo(ToroExo.getVolumeInfo(player));
}
- ToroExo.setVolumeInfo(player, this.playbackInfo.getVolumeInfo());
- boolean haveResumePosition = playbackInfo.getResumeWindow() != C.INDEX_UNSET;
- if (haveResumePosition) {
- player.seekTo(playbackInfo.getResumeWindow(), playbackInfo.getResumePosition());
+ private void ensurePlayerView() {
+ if (playerView != null && playerView.getPlayer() != player.getPlayer()) playerView.setPlayer(player.getPlayer());
}
- }
- // Trick to inject to the Player creation event.
- // Required for AdsLoader to set Player.
- protected void beforePrepareMediaSource() {
- }
+ // TODO [20180822] Double check this.
+ private void ensureMediaSource() {
+ if (mediaSource == null) { // Only actually prepare the source when play() is called.
+ sourcePrepared = false;
+ mediaSource = creator.createMediaSource(mediaUri, fileExt);
+ }
+
+ if (!sourcePrepared) {
+ ensurePlayer(); // sourcePrepared is set to false only when player is null.
+ beforePrepareMediaSource();
+ player.getPlayer().prepare(mediaSource, playbackInfo.getResumeWindow() == C.INDEX_UNSET, false);
+ sourcePrepared = true;
+ }
+ }
+
+ private void ensurePlayer() {
+ if (player == null) {
+ sourcePrepared = false;
+ player = with(checkNotNull(creator.getContext(), "ExoCreator has no Context")) //
+ .requestPlayer(creator);
+ listenerApplied = false;
+ }
+
+ if (!listenerApplied) {
+ player.addOnVolumeChangeListener(volumeChangeListeners);
+ player.getPlayer().addListener(listeners);
+ listenerApplied = true;
+ }
+
+ ToroExo.setVolumeInfo(player, this.playbackInfo.getVolumeInfo());
+ boolean haveResumePosition = playbackInfo.getResumeWindow() != C.INDEX_UNSET;
+ if (haveResumePosition) {
+ player.getPlayer().seekTo(playbackInfo.getResumeWindow(), playbackInfo.getResumePosition());
+ }
+ }
+
+ // Trick to inject to the Player creation event.
+ // Required for AdsLoader to set Player.
+ protected void beforePrepareMediaSource() {
+ }
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroExo.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroExo.java
index 147157b6..5d49f0b8 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroExo.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroExo.java
@@ -16,34 +16,21 @@
package ml.docilealligator.infinityforreddit.videoautoplay;
-import static android.widget.Toast.LENGTH_SHORT;
-import static com.google.android.exoplayer2.drm.UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME;
-import static com.google.android.exoplayer2.util.Util.getDrmUuid;
import static java.lang.Runtime.getRuntime;
import static ml.docilealligator.infinityforreddit.videoautoplay.ToroUtil.checkNotNull;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
-import android.text.TextUtils;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes;
import androidx.core.util.Pools;
+import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
-import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
-import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
-import com.google.android.exoplayer2.drm.UnsupportedDrmException;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
-import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util;
import java.net.CookieHandler;
@@ -52,18 +39,15 @@ import java.net.CookiePolicy;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
-import java.util.UUID;
-import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
-import ml.docilealligator.infinityforreddit.videoautoplay.media.DrmMedia;
import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
/**
* Global helper class to manage {@link ExoCreator} and {@link SimpleExoPlayer} instances.
* In this setup, {@link ExoCreator} and SimpleExoPlayer pools are cached. A {@link Config}
* is a key for each {@link ExoCreator}.
- *
+ * <p>
* A suggested usage is as below:
* <pre><code>
* ExoCreator creator = ToroExo.with(this).getDefaultCreator();
@@ -78,220 +62,152 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
public final class ToroExo {
- private static final String TAG = "ToroExo";
+ private static final String TAG = "ToroExo";
- // Magic number: Build.VERSION.SDK_INT / 6 --> API 16 ~ 18 will set pool size to 2, etc.
- @SuppressWarnings("WeakerAccess") //
- static final int MAX_POOL_SIZE = Math.max(Util.SDK_INT / 6, getRuntime().availableProcessors());
- @SuppressLint("StaticFieldLeak") //
- static volatile ToroExo toro;
+ // Magic number: Build.VERSION.SDK_INT / 6 --> API 16 ~ 18 will set pool size to 2, etc.
+ @SuppressWarnings("WeakerAccess") //
+ static final int MAX_POOL_SIZE = Math.max(Util.SDK_INT / 6, getRuntime().availableProcessors());
+ @SuppressLint("StaticFieldLeak") //
+ static volatile ToroExo toro;
- public static ToroExo with(Context context) {
- if (toro == null) {
- synchronized (ToroExo.class) {
- if (toro == null) toro = new ToroExo(context.getApplicationContext());
- }
+ public static ToroExo with(Context context) {
+ if (toro == null) {
+ synchronized (ToroExo.class) {
+ if (toro == null) toro = new ToroExo(context.getApplicationContext());
+ }
+ }
+ return toro;
}
- return toro;
- }
-
- @NonNull final String appName;
- @NonNull final Context context; // Application context
- @NonNull private final Map<Config, ExoCreator> creators;
- @NonNull private final Map<ExoCreator, Pools.Pool<SimpleExoPlayer>> playerPools;
- private Config defaultConfig; // will be created on the first time it is used.
+ @NonNull
+ final String appName;
+ @NonNull
+ final Context context; // Application context
+ @NonNull
+ private final Map<Config, ExoCreator> creators;
+ @NonNull
+ private final Map<ExoCreator, Pools.Pool<ExoPlayer>> playerPools;
+
+ private Config defaultConfig; // will be created on the first time it is used.
+
+ private ToroExo(@NonNull Context context /* Application context */) {
+ this.context = context;
+ this.appName = getUserAgent();
+ this.playerPools = new HashMap<>();
+ this.creators = new HashMap<>();
+
+ // Adapt from ExoPlayer demo app. Start this on demand.
+ CookieManager cookieManager = new CookieManager();
+ cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
+ if (CookieHandler.getDefault() != cookieManager) {
+ CookieHandler.setDefault(cookieManager);
+ }
+ }
- private ToroExo(@NonNull Context context /* Application context */) {
- this.context = context;
- this.appName = getUserAgent();
- this.playerPools = new HashMap<>();
- this.creators = new HashMap<>();
+ /**
+ * Utility method to produce {@link ExoCreator} instance from a {@link Config}.
+ */
+ public final ExoCreator getCreator(Config config) {
+ ExoCreator creator = this.creators.get(config);
+ if (creator == null) {
+ creator = new DefaultExoCreator(this, config);
+ this.creators.put(config, creator);
+ }
- // Adapt from ExoPlayer demo app. Start this on demand.
- CookieManager cookieManager = new CookieManager();
- cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
- if (CookieHandler.getDefault() != cookieManager) {
- CookieHandler.setDefault(cookieManager);
+ return creator;
}
- }
- /**
- * Utility method to produce {@link ExoCreator} instance from a {@link Config}.
- */
- public final ExoCreator getCreator(Config config) {
- ExoCreator creator = this.creators.get(config);
- if (creator == null) {
- creator = new DefaultExoCreator(this, config);
- this.creators.put(config, creator);
+ @SuppressWarnings("WeakerAccess")
+ public final Config getDefaultConfig() {
+ if (defaultConfig == null) defaultConfig = new Config.Builder(context).build();
+ return defaultConfig;
}
- return creator;
- }
-
- @SuppressWarnings("WeakerAccess") public final Config getDefaultConfig() {
- if (defaultConfig == null) defaultConfig = new Config.Builder(context).build();
- return defaultConfig;
- }
-
- /**
- * Get the default {@link ExoCreator}. This ExoCreator is configured by {@link #defaultConfig}.
- */
- public final ExoCreator getDefaultCreator() {
- return getCreator(getDefaultConfig());
- }
-
- /**
- * Request an instance of {@link SimpleExoPlayer}. It can be an existing instance cached by Pool
- * or new one.
- *
- * The creator may or may not be the one created by either {@link #getCreator(Config)} or
- * {@link #getDefaultCreator()}.
- *
- * @param creator the {@link ExoCreator} that is scoped to the {@link SimpleExoPlayer} config.
- * @return an usable {@link SimpleExoPlayer} instance.
- */
- @NonNull //
- public final SimpleExoPlayer requestPlayer(@NonNull ExoCreator creator) {
- SimpleExoPlayer player = getPool(checkNotNull(creator)).acquire();
- if (player == null) player = creator.createPlayer();
- return player;
- }
-
- /**
- * Release player to Pool attached to the creator.
- *
- * @param creator the {@link ExoCreator} that created the player.
- * @param player the {@link SimpleExoPlayer} to be released back to the Pool
- * @return true if player is released to relevant Pool, false otherwise.
- */
- @SuppressWarnings({ "WeakerAccess", "UnusedReturnValue" }) //
- public final boolean releasePlayer(@NonNull ExoCreator creator, @NonNull SimpleExoPlayer player) {
- return getPool(checkNotNull(creator)).release(player);
- }
-
- /**
- * Release and clear all current cached ExoPlayer instances. This should be called when
- * client Application runs out of memory ({@link Application#onTrimMemory(int)} for example).
- */
- public final void cleanUp() {
- // TODO [2018/03/07] Test this. Ref: https://stackoverflow.com/a/1884916/1553254
- for (Iterator<Map.Entry<ExoCreator, Pools.Pool<SimpleExoPlayer>>> it =
- playerPools.entrySet().iterator(); it.hasNext(); ) {
- Pools.Pool<SimpleExoPlayer> pool = it.next().getValue();
- SimpleExoPlayer item;
- while ((item = pool.acquire()) != null) item.release();
- it.remove();
+ /**
+ * Get the default {@link ExoCreator}. This ExoCreator is configured by {@link #defaultConfig}.
+ */
+ public final ExoCreator getDefaultCreator() {
+ return getCreator(getDefaultConfig());
}
- }
- /// internal APIs
- private Pools.Pool<SimpleExoPlayer> getPool(ExoCreator creator) {
- Pools.Pool<SimpleExoPlayer> pool = playerPools.get(creator);
- if (pool == null) {
- pool = new Pools.SimplePool<>(MAX_POOL_SIZE);
- playerPools.put(creator, pool);
+ /**
+ * Request an instance of {@link SimpleExoPlayer}. It can be an existing instance cached by Pool
+ * or new one.
+ * <p>
+ * The creator may or may not be the one created by either {@link #getCreator(Config)} or
+ * {@link #getDefaultCreator()}.
+ *
+ * @param creator the {@link ExoCreator} that is scoped to the {@link SimpleExoPlayer} config.
+ * @return an usable {@link SimpleExoPlayer} instance.
+ */
+ @NonNull //
+ public final ToroExoPlayer requestPlayer(@NonNull ExoCreator creator) {
+ ExoPlayer player = getPool(checkNotNull(creator)).acquire();
+ if (player == null) player = creator.createPlayer();
+ return new ToroExoPlayer(player);
}
- return pool;
- }
-
- /**
- * Get a possibly-non-localized String from existing resourceId.
- */
- /* pkg */ String getString(@StringRes int resId, @Nullable Object... params) {
- return params == null || params.length < 1 ? //
- this.context.getString(resId) : this.context.getString(resId, params);
- }
+ /**
+ * Release player to Pool attached to the creator.
+ *
+ * @param creator the {@link ExoCreator} that created the player.
+ * @param player the {@link SimpleExoPlayer} to be released back to the Pool
+ * @return true if player is released to relevant Pool, false otherwise.
+ */
+ @SuppressWarnings({"WeakerAccess", "UnusedReturnValue"}) //
+ public final boolean releasePlayer(@NonNull ExoCreator creator, @NonNull ExoPlayer player) {
+ return getPool(checkNotNull(creator)).release(player);
+ }
- /**
- * Utility method to build a {@link DrmSessionManager} that can be used in {@link Config}
- *
- * Usage:
- * <pre><code>
- * DrmSessionManager manager = ToroExo.with(context).createDrmSessionManager(mediaDrm);
- * Config config = new Config.Builder().setDrmSessionManager(manager);
- * ExoCreator creator = ToroExo.with(context).getCreator(config);
- * </code></pre>
- */
- @SuppressWarnings("unused") @RequiresApi(18) @Nullable //
- public DrmSessionManager<FrameworkMediaCrypto> createDrmSessionManager(@NonNull DrmMedia drm) {
- DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
- int errorStringId = R.string.error_drm_unknown;
- String subString = null;
- if (Util.SDK_INT < 18) {
- errorStringId = R.string.error_drm_not_supported;
- } else {
- UUID drmSchemeUuid = getDrmUuid(checkNotNull(drm).getType());
- if (drmSchemeUuid == null) {
- errorStringId = R.string.error_drm_unsupported_scheme;
- } else {
- HttpDataSource.Factory factory = new DefaultHttpDataSourceFactory(appName);
- try {
- drmSessionManager = buildDrmSessionManagerV18(drmSchemeUuid, drm.getLicenseUrl(),
- drm.getKeyRequestPropertiesArray(), drm.multiSession(), factory);
- } catch (UnsupportedDrmException e) {
- e.printStackTrace();
- errorStringId = e.reason == REASON_UNSUPPORTED_SCHEME ? //
- R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown;
- if (e.reason == REASON_UNSUPPORTED_SCHEME) {
- subString = drm.getType();
- }
+ /**
+ * Release and clear all current cached ExoPlayer instances. This should be called when
+ * client Application runs out of memory ({@link Application#onTrimMemory(int)} for example).
+ */
+ public final void cleanUp() {
+ // TODO [2018/03/07] Test this. Ref: https://stackoverflow.com/a/1884916/1553254
+ for (Iterator<Map.Entry<ExoCreator, Pools.Pool<ExoPlayer>>> it =
+ playerPools.entrySet().iterator(); it.hasNext(); ) {
+ Pools.Pool<ExoPlayer> pool = it.next().getValue();
+ ExoPlayer item;
+ while ((item = pool.acquire()) != null) item.release();
+ it.remove();
}
- }
}
- if (drmSessionManager == null) {
- String error = TextUtils.isEmpty(subString) ? context.getString(errorStringId)
- : context.getString(errorStringId) + ": " + subString;
- Toast.makeText(context, error, LENGTH_SHORT).show();
- }
+ /// internal APIs
+ private Pools.Pool<ExoPlayer> getPool(ExoCreator creator) {
+ Pools.Pool<ExoPlayer> pool = playerPools.get(creator);
+ if (pool == null) {
+ pool = new Pools.SimplePool<>(MAX_POOL_SIZE);
+ playerPools.put(creator, pool);
+ }
- return drmSessionManager;
- }
+ return pool;
+ }
- @RequiresApi(18) private static DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManagerV18(
- @NonNull UUID uuid, @Nullable String licenseUrl, @Nullable String[] keyRequestPropertiesArray,
- boolean multiSession, @NonNull HttpDataSource.Factory httpDataSourceFactory)
- throws UnsupportedDrmException {
- HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory);
- if (keyRequestPropertiesArray != null) {
- for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
- drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
- keyRequestPropertiesArray[i + 1]);
- }
+ /**
+ * Get a possibly-non-localized String from existing resourceId.
+ */
+ /* pkg */ String getString(@StringRes int resId, @Nullable Object... params) {
+ return params == null || params.length < 1 ? //
+ this.context.getString(resId) : this.context.getString(resId, params);
}
- return new DefaultDrmSessionManager<>(uuid, FrameworkMediaDrm.newInstance(uuid), drmCallback,
- null, multiSession);
- }
- // Share the code of setting Volume. For use inside library only.
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) //
- public static void setVolumeInfo(@NonNull SimpleExoPlayer player,
- @NonNull VolumeInfo volumeInfo) {
- if (player instanceof ToroExoPlayer) {
- ((ToroExoPlayer) player).setVolumeInfo(volumeInfo);
- } else {
- if (volumeInfo.isMute()) {
- player.setVolume(0f);
- } else {
- player.setVolume(volumeInfo.getVolume());
- }
+ // Share the code of setting Volume. For use inside library only.
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) //
+ public static void setVolumeInfo(@NonNull ToroExoPlayer player,
+ @NonNull VolumeInfo volumeInfo) {
+ player.setVolumeInfo(volumeInfo);
}
- }
- @SuppressWarnings("WeakerAccess") @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) //
- public static VolumeInfo getVolumeInfo(SimpleExoPlayer player) {
- if (player instanceof ToroExoPlayer) {
- return new VolumeInfo(((ToroExoPlayer) player).getVolumeInfo());
- } else {
- float volume = player.getVolume();
- return new VolumeInfo(volume == 0, volume);
+ @SuppressWarnings("WeakerAccess")
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) //
+ public static VolumeInfo getVolumeInfo(ToroExoPlayer player) {
+ return new VolumeInfo(player.getVolumeInfo());
}
- }
- @SuppressWarnings("SameParameterValue")
- private static String getUserAgent() {
- return APIUtils.USER_AGENT;
- }
+ @SuppressWarnings("SameParameterValue")
+ private static String getUserAgent() {
+ return APIUtils.USER_AGENT;
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroExoPlayer.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroExoPlayer.java
index 2fe92569..ea8bf638 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroExoPlayer.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroExoPlayer.java
@@ -23,13 +23,11 @@ import android.os.Looper;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
@@ -41,53 +39,65 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
* @author eneim (2018/03/27).
*/
@SuppressWarnings("WeakerAccess") //
-public class ToroExoPlayer extends SimpleExoPlayer {
-
- protected ToroExoPlayer(Context context, RenderersFactory renderersFactory,
- TrackSelector trackSelector, LoadControl loadControl, BandwidthMeter bandwidthMeter,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, Looper looper) {
- super(context, renderersFactory, trackSelector, loadControl, bandwidthMeter, drmSessionManager,
- looper);
- }
-
- private ToroPlayer.VolumeChangeListeners listeners;
-
- public final void addOnVolumeChangeListener(@NonNull ToroPlayer.OnVolumeChangeListener listener) {
- if (this.listeners == null) this.listeners = new ToroPlayer.VolumeChangeListeners();
- this.listeners.add(checkNotNull(listener));
- }
-
- public final void removeOnVolumeChangeListener(ToroPlayer.OnVolumeChangeListener listener) {
- if (this.listeners != null) this.listeners.remove(listener);
- }
-
- public final void clearOnVolumeChangeListener() {
- if (this.listeners != null) this.listeners.clear();
- }
-
- @CallSuper @Override public void setVolume(float audioVolume) {
- this.setVolumeInfo(new VolumeInfo(audioVolume == 0, audioVolume));
- }
-
- private final VolumeInfo volumeInfo = new VolumeInfo(false, 1f);
-
- @SuppressWarnings("UnusedReturnValue")
- public final boolean setVolumeInfo(@NonNull VolumeInfo volumeInfo) {
- boolean changed = !this.volumeInfo.equals(volumeInfo);
- if (changed) {
- this.volumeInfo.setTo(volumeInfo.isMute(), volumeInfo.getVolume());
- super.setVolume(volumeInfo.isMute() ? 0 : volumeInfo.getVolume());
- if (listeners != null) {
- for (ToroPlayer.OnVolumeChangeListener listener : this.listeners) {
- listener.onVolumeChanged(volumeInfo);
+public class ToroExoPlayer {
+
+ private ExoPlayer player;
+
+ public ToroExoPlayer(Context context, RenderersFactory renderersFactory,
+ TrackSelector trackSelector, LoadControl loadControl, BandwidthMeter bandwidthMeter,
+ Looper looper) {
+ player = new ExoPlayer.Builder(context).setRenderersFactory(renderersFactory).setTrackSelector(trackSelector).setLoadControl(loadControl).setBandwidthMeter(bandwidthMeter).setLooper(looper).build();
+ }
+
+ public ToroExoPlayer(ExoPlayer exoPlayer) {
+ this.player = exoPlayer;
+ }
+
+ private ToroPlayer.VolumeChangeListeners listeners;
+
+ public final void addOnVolumeChangeListener(@NonNull ToroPlayer.OnVolumeChangeListener listener) {
+ if (this.listeners == null) this.listeners = new ToroPlayer.VolumeChangeListeners();
+ this.listeners.add(checkNotNull(listener));
+ }
+
+ public final void removeOnVolumeChangeListener(ToroPlayer.OnVolumeChangeListener listener) {
+ if (this.listeners != null) this.listeners.remove(listener);
+ }
+
+ public final void clearOnVolumeChangeListener() {
+ if (this.listeners != null) this.listeners.clear();
+ }
+
+ @CallSuper
+ public void setVolume(float audioVolume) {
+ this.setVolumeInfo(new VolumeInfo(audioVolume == 0, audioVolume));
+ }
+
+ private final VolumeInfo volumeInfo = new VolumeInfo(false, 1f);
+
+ @SuppressWarnings("UnusedReturnValue")
+ public final boolean setVolumeInfo(@NonNull VolumeInfo volumeInfo) {
+ boolean changed = !this.volumeInfo.equals(volumeInfo);
+ if (changed) {
+ this.volumeInfo.setTo(volumeInfo.isMute(), volumeInfo.getVolume());
+ player.setVolume(volumeInfo.isMute() ? 0 : volumeInfo.getVolume());
+ if (listeners != null) {
+ for (ToroPlayer.OnVolumeChangeListener listener : this.listeners) {
+ listener.onVolumeChanged(volumeInfo);
+ }
+ }
}
- }
+
+ return changed;
}
- return changed;
- }
+ @SuppressWarnings("unused")
+ @NonNull
+ public final VolumeInfo getVolumeInfo() {
+ return volumeInfo;
+ }
- @SuppressWarnings("unused") @NonNull public final VolumeInfo getVolumeInfo() {
- return volumeInfo;
- }
+ public ExoPlayer getPlayer() {
+ return player;
+ }
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroUtil.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroUtil.java
index ecfc7d1d..563d5094 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroUtil.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ToroUtil.java
@@ -34,82 +34,85 @@ import ml.docilealligator.infinityforreddit.videoautoplay.widget.Container;
public final class ToroUtil {
- @SuppressWarnings("unused") private static final String TAG = "ToroLib:Util";
+ @SuppressWarnings("unused")
+ private static final String TAG = "ToroLib:Util";
- private ToroUtil() {
- throw new RuntimeException("Meh!");
- }
+ private ToroUtil() {
+ throw new RuntimeException("Meh!");
+ }
- /**
- * Get the ratio in range of 0.0 ~ 1.0 the visible area of a {@link ToroPlayer}'s playerView.
- *
- * @param player the {@link ToroPlayer} need to investigate.
- * @param container the {@link ViewParent} that holds the {@link ToroPlayer}. If {@code null}
- * then this method must returns 0.0f;
- * @return the value in range of 0.0 ~ 1.0 of the visible area.
- */
- @FloatRange(from = 0.0, to = 1.0) //
- public static float visibleAreaOffset(@NonNull ToroPlayer player, ViewParent container) {
- if (container == null) return 0.0f;
+ /**
+ * Get the ratio in range of 0.0 ~ 1.0 the visible area of a {@link ToroPlayer}'s playerView.
+ *
+ * @param player the {@link ToroPlayer} need to investigate.
+ * @param container the {@link ViewParent} that holds the {@link ToroPlayer}. If {@code null}
+ * then this method must returns 0.0f;
+ * @return the value in range of 0.0 ~ 1.0 of the visible area.
+ */
+ @FloatRange(from = 0.0, to = 1.0) //
+ public static float visibleAreaOffset(@NonNull ToroPlayer player, ViewParent container) {
+ if (container == null) return 0.0f;
- View playerView = player.getPlayerView();
- Rect drawRect = new Rect();
- playerView.getDrawingRect(drawRect);
- int drawArea = drawRect.width() * drawRect.height();
+ View playerView = player.getPlayerView();
+ Rect drawRect = new Rect();
+ playerView.getDrawingRect(drawRect);
+ int drawArea = drawRect.width() * drawRect.height();
- Rect playerRect = new Rect();
- boolean visible = playerView.getGlobalVisibleRect(playerRect, new Point());
+ Rect playerRect = new Rect();
+ boolean visible = playerView.getGlobalVisibleRect(playerRect, new Point());
- float offset = 0.f;
- if (visible && drawArea > 0) {
- int visibleArea = playerRect.height() * playerRect.width();
- offset = visibleArea / (float) drawArea;
+ float offset = 0.f;
+ if (visible && drawArea > 0) {
+ int visibleArea = playerRect.height() * playerRect.width();
+ offset = visibleArea / (float) drawArea;
+ }
+ return offset;
}
- return offset;
- }
- /**
- * Ensures that an object reference passed as a parameter to the calling
- * method is not null.
- *
- * @param reference an object reference
- * @return the non-null reference that was validated
- * @throws NullPointerException if {@code reference} is null
- */
- public static @NonNull <T> T checkNotNull(final T reference) {
- if (reference == null) {
- throw new NullPointerException();
+ /**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an object reference
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static @NonNull
+ <T> T checkNotNull(final T reference) {
+ if (reference == null) {
+ throw new NullPointerException();
+ }
+ return reference;
}
- return reference;
- }
- /**
- * Ensures that an object reference passed as a parameter to the calling
- * method is not null.
- *
- * @param reference an object reference
- * @param errorMessage the exception message to use if the check fails; will
- * be converted to a string using {@link String#valueOf(Object)}
- * @return the non-null reference that was validated
- * @throws NullPointerException if {@code reference} is null
- */
- public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) {
- if (reference == null) {
- throw new NullPointerException(String.valueOf(errorMessage));
+ /**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an object reference
+ * @param errorMessage the exception message to use if the check fails; will
+ * be converted to a string using {@link String#valueOf(Object)}
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static @NonNull
+ <T> T checkNotNull(final T reference, final Object errorMessage) {
+ if (reference == null) {
+ throw new NullPointerException(String.valueOf(errorMessage));
+ }
+ return reference;
}
- return reference;
- }
- @SuppressWarnings("unchecked") //
- public static void wrapParamBehavior(@NonNull final Container container,
- final Container.BehaviorCallback callback) {
- container.setBehaviorCallback(callback);
- ViewGroup.LayoutParams params = container.getLayoutParams();
- if (params instanceof CoordinatorLayout.LayoutParams) {
- CoordinatorLayout.Behavior temp = ((CoordinatorLayout.LayoutParams) params).getBehavior();
- if (temp != null) {
- ((CoordinatorLayout.LayoutParams) params).setBehavior(new Container.Behavior(temp));
- }
+ @SuppressWarnings("unchecked") //
+ public static void wrapParamBehavior(@NonNull final Container container,
+ final Container.BehaviorCallback callback) {
+ container.setBehaviorCallback(callback);
+ ViewGroup.LayoutParams params = container.getLayoutParams();
+ if (params instanceof CoordinatorLayout.LayoutParams) {
+ CoordinatorLayout.Behavior temp = ((CoordinatorLayout.LayoutParams) params).getBehavior();
+ if (temp != null) {
+ ((CoordinatorLayout.LayoutParams) params).setBehavior(new Container.Behavior(temp));
+ }
+ }
}
- }
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ui/ToroControlView.java b/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ui/ToroControlView.java
deleted file mode 100644
index 0ab474db..00000000
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/videoautoplay/ui/ToroControlView.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (c) 2018 Nam Nguyen, nam@ene.im
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ml.docilealligator.infinityforreddit.videoautoplay.ui;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.core.view.ViewCompat;
-
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.ui.PlayerControlView;
-import com.google.android.exoplayer2.ui.PlayerView;
-import com.google.android.exoplayer2.ui.TimeBar;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import ml.docilealligator.infinityforreddit.R;
-import ml.docilealligator.infinityforreddit.videoautoplay.ToroExo;
-import ml.docilealligator.infinityforreddit.videoautoplay.ToroExoPlayer;
-import ml.docilealligator.infinityforreddit.videoautoplay.ToroPlayer;
-import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
-
-/**
- * An extension of {@link PlayerControlView} that adds Volume control buttons. It works on-par
- * with {@link PlayerView}. Will be automatically inflated when client uses {@link R.layout.toro_exo_player_view}
- * for {@link PlayerView} layout.
- *
- * @author eneim (2018/08/20).
- * @since 3.6.0.2802
- */
-public class ToroControlView extends PlayerControlView {
-
- @SuppressWarnings("unused") static final String TAG = "ToroExo:Control";
-
- // Statically obtain from super class.
- protected static Method hideAfterTimeoutMethod; // from parent ...
- protected static boolean hideMethodFetched;
- protected static Field hideActionField;
- protected static boolean hideActionFetched;
-
- final ComponentListener componentListener;
- final View volumeUpButton;
- final View volumeOffButton;
- final TimeBar volumeBar;
- final VolumeInfo volumeInfo = new VolumeInfo(false, 1);
-
- public ToroControlView(Context context) {
- this(context, null);
- }
-
- public ToroControlView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ToroControlView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- volumeOffButton = findViewById(R.id.exo_volume_off);
- volumeUpButton = findViewById(R.id.exo_volume_up);
- volumeBar = findViewById(R.id.volume_bar);
- componentListener = new ComponentListener();
- }
-
- @Override public void onAttachedToWindow() {
- super.onAttachedToWindow();
- if (volumeUpButton != null) volumeUpButton.setOnClickListener(componentListener);
- if (volumeOffButton != null) volumeOffButton.setOnClickListener(componentListener);
- if (volumeBar != null) volumeBar.addListener(componentListener);
-
- updateVolumeButtons();
- }
-
- @Override public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (volumeUpButton != null) volumeUpButton.setOnClickListener(null);
- if (volumeOffButton != null) volumeOffButton.setOnClickListener(null);
- if (volumeBar != null) volumeBar.removeListener(componentListener);
- this.setPlayer(null);
- }
-
- @SuppressLint("ClickableViewAccessibility") @Override
- public boolean onTouchEvent(MotionEvent event) {
- // After processing all children' touch event, this View will just stop it here.
- // User can click to PlayerView to show/hide this view, but since this View's height is not
- // significantly large, clicking to show/hide may disturb other actions like clicking to button,
- // seeking the bars, etc. This extension will stop the touch event here so that PlayerView has
- // nothing to do when User touch this View.
- return true;
- }
-
- @Override public void setPlayer(Player player) {
- Player current = super.getPlayer();
- if (current == player) return;
-
- if (current instanceof ToroExoPlayer) {
- ((ToroExoPlayer) current).removeOnVolumeChangeListener(componentListener);
- }
-
- super.setPlayer(player);
- current = super.getPlayer();
- @NonNull final VolumeInfo tempVol;
- if (current instanceof ToroExoPlayer) {
- tempVol = ((ToroExoPlayer) current).getVolumeInfo();
- ((ToroExoPlayer) current).addOnVolumeChangeListener(componentListener);
- } else if (current instanceof SimpleExoPlayer) {
- float volume = ((SimpleExoPlayer) current).getVolume();
- tempVol = new VolumeInfo(volume == 0, volume);
- } else {
- tempVol = new VolumeInfo(false, 1f);
- }
-
- this.volumeInfo.setTo(tempVol.isMute(), tempVol.getVolume());
- updateVolumeButtons();
- }
-
- @Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if (changedView == this) updateVolumeButtons();
- }
-
- @SuppressWarnings("ConstantConditions") //
- void updateVolumeButtons() {
- if (!isVisible() || !ViewCompat.isAttachedToWindow(this)) {
- return;
- }
- boolean requestButtonFocus = false;
- // if muted then show volumeOffButton, or else show volumeUpButton
- boolean muted = volumeInfo.isMute();
- if (volumeOffButton != null) {
- requestButtonFocus |= muted && volumeOffButton.isFocused();
- volumeOffButton.setVisibility(muted ? View.VISIBLE : View.GONE);
- }
- if (volumeUpButton != null) {
- requestButtonFocus |= !muted && volumeUpButton.isFocused();
- volumeUpButton.setVisibility(!muted ? View.VISIBLE : View.GONE);
- }
-
- if (volumeBar != null) {
- volumeBar.setDuration(100);
- volumeBar.setPosition(muted ? 0 : (long) (volumeInfo.getVolume() * 100));
- }
-
- if (requestButtonFocus) {
- requestButtonFocus();
- }
-
- // A hack to access PlayerControlView's hideAfterTimeout. Don't want to re-implement it.
- // Reflection happens once for all instances, so it should not affect the performance.
- if (!hideMethodFetched) {
- try {
- hideAfterTimeoutMethod = PlayerControlView.class.getDeclaredMethod("hideAfterTimeout");
- hideAfterTimeoutMethod.setAccessible(true);
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- }
- hideMethodFetched = true;
- }
-
- if (hideAfterTimeoutMethod != null) {
- try {
- hideAfterTimeoutMethod.invoke(this);
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- }
- }
- }
-
- private void requestButtonFocus() {
- boolean muted = volumeInfo.isMute();
- if (!muted && volumeUpButton != null) {
- volumeUpButton.requestFocus();
- } else if (muted && volumeOffButton != null) {
- volumeOffButton.requestFocus();
- }
- }
-
- void dispatchOnScrubStart() {
- // Fetch the 'hideAction' Runnable from super class. We need this to synchronize the show/hide
- // behaviour when user does something.
- if (!hideActionFetched) {
- try {
- hideActionField = PlayerControlView.class.getDeclaredField("hideAction");
- hideActionField.setAccessible(true);
- } catch (NoSuchFieldException e) {
- e.printStackTrace();
- }
- hideActionFetched = true;
- }
-
- if (hideActionField != null) {
- try {
- removeCallbacks((Runnable) hideActionField.get(this));
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- }
-
- // Scrub Move will always modify actual Volume, there is no 'mute-with-non-zero-volume' state.
- void dispatchOnScrubMove(long position) {
- if (position > 100) position = 100;
- if (position < 0) position = 0;
-
- float actualVolume = position / (float) 100;
- this.volumeInfo.setTo(actualVolume == 0, actualVolume);
- if (getPlayer() instanceof SimpleExoPlayer) {
- ToroExo.setVolumeInfo((SimpleExoPlayer) getPlayer(), this.volumeInfo);
- }
-
- updateVolumeButtons();
- }
-
- void dispatchOnScrubStop(long position) {
- this.dispatchOnScrubMove(position);
- }
-
- private class ComponentListener
- implements OnClickListener, TimeBar.OnScrubListener, ToroPlayer.OnVolumeChangeListener {
-
- ComponentListener() {
- }
-
- @Override public void onClick(View v) {
- Player player = ToroControlView.super.getPlayer();
- if (!(player instanceof SimpleExoPlayer)) return;
- if (v == volumeOffButton) { // click to vol Off --> unmute
- volumeInfo.setTo(false, volumeInfo.getVolume());
- } else if (v == volumeUpButton) { // click to vol Up --> mute
- volumeInfo.setTo(true, volumeInfo.getVolume());
- }
- ToroExo.setVolumeInfo((SimpleExoPlayer) player, volumeInfo);
- updateVolumeButtons();
- }
-
- /// TimeBar.OnScrubListener
-
- @Override public void onScrubStart(TimeBar timeBar, long position) {
- dispatchOnScrubStart();
- }
-
- @Override public void onScrubMove(TimeBar timeBar, long position) {
- dispatchOnScrubMove(position);
- }
-
- @Override public void onScrubStop(TimeBar timeBar, long position, boolean canceled) {
- dispatchOnScrubStop(position);
- }
-
- /// ToroPlayer.OnVolumeChangeListener
-
- @Override public void onVolumeChanged(@NonNull VolumeInfo volumeInfo) {
- ToroControlView.this.volumeInfo.setTo(volumeInfo.isMute(), volumeInfo.getVolume());
- updateVolumeButtons();
- }
- }
-}
diff --git a/app/src/main/res/layout/toro_exo_control_view.xml b/app/src/main/res/layout/toro_exo_control_view.xml
deleted file mode 100644
index 481230bf..00000000
--- a/app/src/main/res/layout/toro_exo_control_view.xml
+++ /dev/null
@@ -1,181 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (c) 2018 Nam Nguyen, nam@ene.im
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<androidx.appcompat.widget.LinearLayoutCompat
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_marginTop="4dp"
- android:background="#CC000000"
- android:gravity="center_vertical"
- android:layoutDirection="ltr"
- android:orientation="horizontal"
- android:theme="@style/Theme.AppCompat"
- tools:ignore="UnusedAttribute"
- >
-
- <!--<androidx.appcompat.widget.AppCompatImageButton
- android:id="@id/exo_play"
- tools:ignore="ContentDescription"
- style="@style/ToroMediaButton.Play"
- />
-
- <androidx.appcompat.widget.AppCompatImageButton
- android:id="@id/exo_pause"
- tools:ignore="ContentDescription"
- tools:visibility="gone"
- style="@style/ToroMediaButton.Pause"
- />
-
- <androidx.appcompat.widget.AppCompatTextView
- android:id="@id/exo_position"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:includeFontPadding="false"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- android:textColor="#FFBEBEBE"
- android:textSize="14sp"
- android:textStyle="bold"
- tools:text="1:25"
- />
-
- <androidx.appcompat.widget.AppCompatTextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:includeFontPadding="false"
- android:text="/"
- android:textColor="#FFBEBEBE"
- android:textSize="14sp"
- android:textStyle="bold"
- tools:ignore="HardcodedText"
- />
-
- <androidx.appcompat.widget.AppCompatTextView
- android:id="@id/exo_duration"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:includeFontPadding="false"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- android:textColor="#FFBEBEBE"
- android:textSize="14sp"
- android:textStyle="bold"
- tools:text="5:25"
- />
-
- <com.google.android.exoplayer2.ui.DefaultTimeBar
- android:id="@id/exo_progress"
- android:layout_width="0dp"
- android:layout_height="24dp"
- android:layout_weight="1"
- />
-
- <androidx.appcompat.widget.AppCompatImageButton
- android:id="@+id/exo_volume_up"
- tools:ignore="ContentDescription"
- style="@style/ToroMediaButton.VolumeUp"
- />
-
- <androidx.appcompat.widget.AppCompatImageButton
- android:id="@+id/exo_volume_off"
- tools:ignore="ContentDescription"
- tools:visibility="gone"
- style="@style/ToroMediaButton.VolumeOff"
- />-->
-
- <androidx.appcompat.widget.AppCompatImageButton
- android:id="@id/exo_play"
- tools:ignore="ContentDescription"
- style="@style/ToroMediaButton.Play"
- />
-
- <androidx.appcompat.widget.AppCompatImageButton
- android:id="@id/exo_pause"
- tools:ignore="ContentDescription"
- tools:visibility="gone"
- style="@style/ToroMediaButton.Pause"
- />
-
- <androidx.appcompat.widget.AppCompatTextView
- android:id="@id/exo_position"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:includeFontPadding="false"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- android:textColor="#FFBEBEBE"
- android:textSize="14sp"
- android:textStyle="bold"
- tools:text="1:25"
- />
-
- <androidx.appcompat.widget.AppCompatTextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:includeFontPadding="false"
- android:text="/"
- android:textColor="#FFBEBEBE"
- android:textSize="14sp"
- android:textStyle="bold"
- tools:ignore="HardcodedText"
- />
-
- <androidx.appcompat.widget.AppCompatTextView
- android:id="@id/exo_duration"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:includeFontPadding="false"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- android:textColor="#FFBEBEBE"
- android:textSize="14sp"
- android:textStyle="bold"
- tools:text="5:25"
- />
-
- <com.google.android.exoplayer2.ui.DefaultTimeBar
- android:id="@id/exo_progress"
- android:layout_width="0dp"
- android:layout_height="24dp"
- android:layout_weight="1"
- />
-
- <androidx.appcompat.widget.AppCompatImageButton
- android:id="@+id/exo_volume_up"
- tools:ignore="ContentDescription"
- style="@style/ToroMediaButton.VolumeUp"
- />
-
- <androidx.appcompat.widget.AppCompatImageButton
- android:id="@+id/exo_volume_off"
- tools:ignore="ContentDescription"
- tools:visibility="gone"
- style="@style/ToroMediaButton.VolumeOff"
- />
-
- <com.google.android.exoplayer2.ui.DefaultTimeBar
- android:id="@+id/volume_bar"
- android:layout_width="80dp"
- android:layout_height="24dp"
- android:layout_marginEnd="8dp"
- android:layout_marginRight="8dp"
- />
-
-</androidx.appcompat.widget.LinearLayoutCompat> \ No newline at end of file
diff --git a/app/src/main/res/layout/toro_exo_player_view.xml b/app/src/main/res/layout/toro_exo_player_view.xml
deleted file mode 100644
index b318b954..00000000
--- a/app/src/main/res/layout/toro_exo_player_view.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (c) 2018 Nam Nguyen, nam@ene.im
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- >
-
- <com.google.android.exoplayer2.ui.AspectRatioFrameLayout
- android:id="@id/exo_content_frame"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- >
-
- <!-- Video surface will be inserted as the first child of the content frame. -->
-
- <View
- android:id="@id/exo_shutter"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/black"
- />
-
- <androidx.appcompat.widget.AppCompatImageView
- android:id="@id/exo_artwork"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="fitXY"
- />
-
- <com.google.android.exoplayer2.ui.SubtitleView
- android:id="@id/exo_subtitles"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
- <androidx.appcompat.widget.AppCompatTextView
- android:id="@id/exo_error_message"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:background="@color/exo_error_message_background_color"
- android:gravity="center"
- android:padding="16dp"
- />
-
- </com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
-
- <FrameLayout
- android:id="@id/exo_ad_overlay"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
- <FrameLayout
- android:id="@id/exo_overlay"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
- <ProgressBar
- android:id="@id/exo_buffering"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:indeterminate="true"
- />
-
- <ml.docilealligator.infinityforreddit.videoautoplay.ui.ToroControlView
- android:id="@id/exo_controller"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- app:controller_layout_id="@layout/toro_exo_control_view" />
-
-</merge> \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 29108bee..39cc538a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1297,13 +1297,6 @@
<string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string>
<string name="enable_random_adaptation">Enable random adaptation</string>
- <string name="error_drm_not_supported">Protected content not supported on API levels below 18</string>
- <string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string>
- <string name="error_drm_unknown">An unknown DRM error occurred</string>
- <string name="error_no_decoder">This device does not provide a decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
- <string name="error_no_secure_decoder">This device does not provide a secure decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
- <string name="error_querying_decoders">Unable to query device decoders</string>
- <string name="error_instantiating_decoder">Unable to instantiate decoder <xliff:g id="decoder_name">%1$s</xliff:g></string>
<string name="error_unsupported_video">Media includes video tracks, but none are playable by this device</string>
<string name="error_unsupported_audio">Media includes audio tracks, but none are playable by this device</string>
<string name="storage_permission_denied">Permission to access storage was denied</string>