Update ExoPlayer.

This commit is contained in:
Docile-Alligator 2022-09-09 00:41:35 +10:00
parent 84b5b1d95d
commit 7293b9e758
25 changed files with 1303 additions and 1936 deletions

View File

@ -72,17 +72,12 @@ dependencies {
implementation 'com.google.android.material:material:1.5.0-alpha05' implementation 'com.google.android.material:material:1.5.0-alpha05'
/** ExoPlayer **/ /** ExoPlayer **/
def exoplayerVersion = "2.10.8" def exoplayerVersion = "2.18.1"
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayerVersion" implementation "com.google.android.exoplayer:exoplayer-core:$exoplayerVersion"
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayerVersion" implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayerVersion"
implementation "com.google.android.exoplayer:exoplayer-hls:$exoplayerVersion" implementation "com.google.android.exoplayer:exoplayer-hls:$exoplayerVersion"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayerVersion" implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayerVersion"
implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:$exoplayerVersion" implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:$exoplayerVersion"
/*def toroVersion = "3.7.0.2010003"
implementation "im.ene.toro3:toro:$toroVersion"
implementation("im.ene.toro3:toro-ext-exoplayer:$toroVersion") {
exclude module: 'extension-ima'
}*/
/** Third-party **/ /** Third-party **/

View File

@ -27,9 +27,9 @@ import androidx.core.content.ContextCompat;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager; import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.RequestOptions; 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.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
@ -189,7 +189,7 @@ public class PostVideoActivity extends BaseActivity implements FlairBottomSheetF
private FlairBottomSheetFragment mFlairSelectionBottomSheetFragment; private FlairBottomSheetFragment mFlairSelectionBottomSheetFragment;
private Snackbar mPostingSnackbar; private Snackbar mPostingSnackbar;
private DataSource.Factory dataSourceFactory; private DataSource.Factory dataSourceFactory;
private SimpleExoPlayer player; private ExoPlayer player;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -216,7 +216,7 @@ public class PostVideoActivity extends BaseActivity implements FlairBottomSheetF
mGlide = Glide.with(this); mGlide = Glide.with(this);
player = ExoPlayerFactory.newSimpleInstance(this); player = new ExoPlayer.Builder(this).build();
videoPlayerView.setPlayer(player); videoPlayerView.setPlayer(player);
dataSourceFactory = new DefaultDataSourceFactory(this, dataSourceFactory = new DefaultDataSourceFactory(this,
Util.getUserAgent(this, "Infinity")); Util.getUserAgent(this, "Infinity"));
@ -491,7 +491,7 @@ public class PostVideoActivity extends BaseActivity implements FlairBottomSheetF
constraintLayout.setVisibility(View.GONE); constraintLayout.setVisibility(View.GONE);
selectAgainTextView.setVisibility(View.VISIBLE); selectAgainTextView.setVisibility(View.VISIBLE);
videoPlayerView.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); player.setPlayWhenReady(true);
wasPlaying = true; wasPlaying = true;
} }

View File

@ -7,6 +7,7 @@ import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
import android.Manifest; import android.Manifest;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
@ -24,6 +25,7 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.provider.Settings; import android.provider.Settings;
import android.text.Html; import android.text.Html;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.OrientationEventListener; import android.view.OrientationEventListener;
@ -45,29 +47,26 @@ import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.google.android.exoplayer2.C; 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.Format;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player; 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.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.hls.HlsMediaSource; 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.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.PlayerControlView;
import com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder; import com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.MimeTypes; 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.bottomappbar.BottomAppBar;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList;
import com.otaliastudios.zoom.ZoomEngine; import com.otaliastudios.zoom.ZoomEngine;
import com.otaliastudios.zoom.ZoomSurfaceView; import com.otaliastudios.zoom.ZoomSurfaceView;
@ -176,7 +175,7 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
public Typeface typeface; public Typeface typeface;
private Uri mVideoUri; private Uri mVideoUri;
private SimpleExoPlayer player; private ExoPlayer player;
private DefaultTrackSelector trackSelector; private DefaultTrackSelector trackSelector;
private DataSource.Factory dataSourceFactory; private DataSource.Factory dataSourceFactory;
@ -403,7 +402,7 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
String postTitle = intent.getStringExtra(EXTRA_POST_TITLE); String postTitle = intent.getStringExtra(EXTRA_POST_TITLE);
setSmallTitle(postTitle); setSmallTitle(postTitle);
playerControlView.setVisibilityListener(visibility -> { playerControlView.addVisibilityListener(visibility -> {
switch (visibility) { switch (visibility) {
case View.GONE: case View.GONE:
getWindow().getDecorView().setSystemUiVisibility( getWindow().getDecorView().setSystemUiVisibility(
@ -422,21 +421,20 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
} }
}); });
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(); trackSelector = new DefaultTrackSelector(this);
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
if (videoType == VIDEO_TYPE_NORMAL && isDataSavingMode && dataSavingModeDefaultResolution > 0) { if (videoType == VIDEO_TYPE_NORMAL && isDataSavingMode && dataSavingModeDefaultResolution > 0) {
trackSelector.setParameters( trackSelector.setParameters(
trackSelector.buildUponParameters() trackSelector.buildUponParameters()
.setMaxVideoSize(dataSavingModeDefaultResolution, dataSavingModeDefaultResolution)); .setMaxVideoSize(dataSavingModeDefaultResolution, dataSavingModeDefaultResolution));
} }
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector); player = new ExoPlayer.Builder(this).setTrackSelector(trackSelector).build();
playerControlView.setPlayer(player); playerControlView.setPlayer(player);
player.addVideoListener(new VideoListener() { player.addListener(new Player.Listener() {
@Override @Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { public void onVideoSizeChanged(VideoSize videoSize) {
zoomSurfaceView.setContentSize(width, height); zoomSurfaceView.setContentSize(videoSize.width, videoSize.height);
} }
}); });
zoomSurfaceView.addCallback(new ZoomSurfaceView.Callback() { zoomSurfaceView.addCallback(new ZoomSurfaceView.Callback() {
@ -506,9 +504,10 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
if (mVideoUri == null) { if (mVideoUri == null) {
loadStreamableVideo(shortCode, savedInstanceState); loadStreamableVideo(shortCode, savedInstanceState);
} else { } else {
dataSourceFactory = new CacheDataSourceFactory(mSimpleCache, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
new DefaultDataSourceFactory(ViewVideoActivity.this, APIUtils.getRedgifsUserAgent(ViewVideoActivity.this))); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri)); player.prepare();
player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
preparePlayer(savedInstanceState); preparePlayer(savedInstanceState);
} }
} else if (videoType == VIDEO_TYPE_V_REDD_IT) { } 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); loadGfycatOrRedgifsVideo(redgifsRetrofit, gfycatId, false, savedInstanceState, false);
} }
} else { } else {
dataSourceFactory = new CacheDataSourceFactory(mSimpleCache, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
new DefaultDataSourceFactory(ViewVideoActivity.this, APIUtils.getRedgifsUserAgent(ViewVideoActivity.this))); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri)); player.prepare();
player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
preparePlayer(savedInstanceState); preparePlayer(savedInstanceState);
} }
} else if (videoType == VIDEO_TYPE_DIRECT || videoType == VIDEO_TYPE_IMGUR) { } 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); videoFileName = "imgur-" + FilenameUtils.getName(videoDownloadUrl);
} }
// Produces DataSource instances through which media data is loaded. // Produces DataSource instances through which media data is loaded.
dataSourceFactory = new CacheDataSourceFactory(mSimpleCache, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
new DefaultHttpDataSourceFactory(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this))); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
// Prepare the player with the source. // 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); preparePlayer(savedInstanceState);
} else { } else {
videoDownloadUrl = intent.getStringExtra(EXTRA_VIDEO_DOWNLOAD_URL); videoDownloadUrl = intent.getStringExtra(EXTRA_VIDEO_DOWNLOAD_URL);
@ -561,10 +562,11 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
id = intent.getStringExtra(EXTRA_ID); id = intent.getStringExtra(EXTRA_ID);
videoFileName = subredditName + "-" + id + ".mp4"; videoFileName = subredditName + "-" + id + ".mp4";
// Produces DataSource instances through which media data is loaded. // Produces DataSource instances through which media data is loaded.
dataSourceFactory = new CacheDataSourceFactory(mSimpleCache, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
new DefaultHttpDataSourceFactory(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this))); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
// Prepare the player with the source. // 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); preparePlayer(savedInstanceState);
} }
} }
@ -615,25 +617,29 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
muteButton.setImageResource(R.drawable.ic_unmute_24dp); muteButton.setImageResource(R.drawable.ic_unmute_24dp);
} }
player.addListener(new Player.EventListener() { player.addListener(new Player.Listener() {
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) { if (!trackGroups.isEmpty()) {
if (videoType == VIDEO_TYPE_NORMAL) { if (videoType == VIDEO_TYPE_NORMAL) {
hdButton.setVisibility(View.VISIBLE); hdButton.setVisibility(View.VISIBLE);
hdButton.setOnClickListener(view -> { 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.setShowDisableOption(true);
builder.setAllowAdaptiveSelections(false); builder.setAllowAdaptiveSelections(false);
AlertDialog alertDialog = builder.build(); Dialog dialog = builder.build();
alertDialog.show(); dialog.show();
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor()); if (dialog instanceof AlertDialog) {
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor()); 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++) { for (int i = 0; i < trackGroups.size(); i++) {
String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType; String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) { if (mimeType != null && mimeType.contains("audio")) {
muteButton.setVisibility(View.VISIBLE); muteButton.setVisibility(View.VISIBLE);
muteButton.setOnClickListener(view -> { muteButton.setOnClickListener(view -> {
@ -688,10 +694,11 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
mVideoUri = Uri.parse(webm); mVideoUri = Uri.parse(webm);
videoDownloadUrl = mp4; videoDownloadUrl = mp4;
dataSourceFactory = new CacheDataSourceFactory(mSimpleCache, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
new DefaultDataSourceFactory(ViewVideoActivity.this, APIUtils.getRedgifsUserAgent(ViewVideoActivity.this))); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
preparePlayer(savedInstanceState); preparePlayer(savedInstanceState);
player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri)); player.prepare();
player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
} }
@Override @Override
@ -717,10 +724,11 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
mVideoUri = Uri.parse(webm); mVideoUri = Uri.parse(webm);
videoDownloadUrl = mp4; videoDownloadUrl = mp4;
dataSourceFactory = new CacheDataSourceFactory(mSimpleCache, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
new DefaultDataSourceFactory(ViewVideoActivity.this, APIUtils.getRedgifsUserAgent(ViewVideoActivity.this))); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
preparePlayer(savedInstanceState); preparePlayer(savedInstanceState);
player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri)); player.prepare();
player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
} }
@Override @Override
@ -782,10 +790,11 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
videoType = VIDEO_TYPE_IMGUR; videoType = VIDEO_TYPE_IMGUR;
videoFileName = "imgur-" + FilenameUtils.getName(videoDownloadUrl); videoFileName = "imgur-" + FilenameUtils.getName(videoDownloadUrl);
// Produces DataSource instances through which media data is loaded. // Produces DataSource instances through which media data is loaded.
dataSourceFactory = new CacheDataSourceFactory(mSimpleCache, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
new DefaultHttpDataSourceFactory(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this))); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
// Prepare the player with the source. // 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); preparePlayer(savedInstanceState);
} else { } else {
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
@ -797,11 +806,12 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe
videoFileName = subredditName + "-" + id + ".mp4"; videoFileName = subredditName + "-" + id + ".mp4";
// Produces DataSource instances through which media data is loaded. // Produces DataSource instances through which media data is loaded.
dataSourceFactory = new CacheDataSourceFactory(mSimpleCache, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
new DefaultHttpDataSourceFactory(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this))); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
// Prepare the player with the source. // Prepare the player with the source.
preparePlayer(savedInstanceState); preparePlayer(savedInstanceState);
player.prepare(new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri)); player.prepare();
player.setMediaSource(new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
} else { } else {
Toast.makeText(ViewVideoActivity.this, R.string.error_fetching_v_redd_it_video_cannot_get_video_url, Toast.LENGTH_LONG).show(); 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); progressBar.setVisibility(View.GONE);
videoDownloadUrl = streamableVideo.mp4 == null ? streamableVideo.mp4Mobile.url : streamableVideo.mp4.url; videoDownloadUrl = streamableVideo.mp4 == null ? streamableVideo.mp4Mobile.url : streamableVideo.mp4.url;
mVideoUri = Uri.parse(videoDownloadUrl); mVideoUri = Uri.parse(videoDownloadUrl);
dataSourceFactory = new CacheDataSourceFactory(mSimpleCache, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
new DefaultDataSourceFactory(ViewVideoActivity.this, APIUtils.getRedgifsUserAgent(ViewVideoActivity.this))); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(ViewVideoActivity.this)));
preparePlayer(savedInstanceState); preparePlayer(savedInstanceState);
player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri)); player.prepare();
player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(mVideoUri)));
} }
@Override @Override

View File

@ -42,10 +42,12 @@ import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target; 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.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.PlayerView;
import com.google.common.collect.ImmutableList;
import com.libRG.CustomTextView; import com.libRG.CustomTextView;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
@ -2755,10 +2757,11 @@ public class HistoryPostRecyclerViewAdapter extends PagingDataAdapter<Post, Recy
helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator); helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator);
helper.addEventListener(new Playable.DefaultEventListener() { helper.addEventListener(new Playable.DefaultEventListener() {
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) { if (!trackGroups.isEmpty()) {
for (int i = 0; i < trackGroups.length; i++) { for (int i = 0; i < trackGroups.size(); i++) {
String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType; String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) { if (mimeType != null && mimeType.contains("audio")) {
if (mFragment.getMasterMutingOption() != null) { if (mFragment.getMasterMutingOption() != null) {
volume = mFragment.getMasterMutingOption() ? 0f : 1f; volume = mFragment.getMasterMutingOption() ? 0f : 1f;
@ -4022,10 +4025,11 @@ public class HistoryPostRecyclerViewAdapter extends PagingDataAdapter<Post, Recy
helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator); helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator);
helper.addEventListener(new Playable.DefaultEventListener() { helper.addEventListener(new Playable.DefaultEventListener() {
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) { if (!trackGroups.isEmpty()) {
for (int i = 0; i < trackGroups.length; i++) { for (int i = 0; i < trackGroups.size(); i++) {
String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType; String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) { if (mimeType != null && mimeType.contains("audio")) {
if (mFragment.getMasterMutingOption() != null) { if (mFragment.getMasterMutingOption() != null) {
volume = mFragment.getMasterMutingOption() ? 0f : 1f; volume = mFragment.getMasterMutingOption() ? 0f : 1f;

View File

@ -40,10 +40,10 @@ import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.target.Target;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.PlayerView;
import com.google.common.collect.ImmutableList;
import com.libRG.CustomTextView; import com.libRG.CustomTextView;
import org.commonmark.ext.gfm.tables.TableBlock; 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 = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator);
helper.addEventListener(new Playable.DefaultEventListener() { helper.addEventListener(new Playable.DefaultEventListener() {
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) { if (!trackGroups.isEmpty()) {
for (int i = 0; i < trackGroups.length; i++) { for (int i = 0; i < trackGroups.size(); i++) {
String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType; String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) { if (mimeType != null && mimeType.contains("audio")) {
helper.setVolume(volume); helper.setVolume(volume);
muteButton.setVisibility(View.VISIBLE); muteButton.setVisibility(View.VISIBLE);

View File

@ -42,10 +42,12 @@ import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target; 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.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.PlayerView;
import com.google.common.collect.ImmutableList;
import com.libRG.CustomTextView; import com.libRG.CustomTextView;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
@ -2860,10 +2862,11 @@ public class PostRecyclerViewAdapter extends PagingDataAdapter<Post, RecyclerVie
helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator); helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator);
helper.addEventListener(new Playable.DefaultEventListener() { helper.addEventListener(new Playable.DefaultEventListener() {
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) { if (!trackGroups.isEmpty()) {
for (int i = 0; i < trackGroups.length; i++) { for (int i = 0; i < trackGroups.size(); i++) {
String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType; String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) { if (mimeType != null && mimeType.contains("audio")) {
if (mFragment.getMasterMutingOption() != null) { if (mFragment.getMasterMutingOption() != null) {
volume = mFragment.getMasterMutingOption() ? 0f : 1f; volume = mFragment.getMasterMutingOption() ? 0f : 1f;
@ -4175,10 +4178,11 @@ public class PostRecyclerViewAdapter extends PagingDataAdapter<Post, RecyclerVie
helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator); helper = new ExoPlayerViewHelper(this, mediaUri, null, mExoCreator);
helper.addEventListener(new Playable.DefaultEventListener() { helper.addEventListener(new Playable.DefaultEventListener() {
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) { if (!trackGroups.isEmpty()) {
for (int i = 0; i < trackGroups.length; i++) { for (int i = 0; i < trackGroups.size(); i++) {
String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType; String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) { if (mimeType != null && mimeType.contains("audio")) {
if (mFragment.getMasterMutingOption() != null) { if (mFragment.getMasterMutingOption() != null) {
volume = mFragment.getMasterMutingOption() ? 0f : 1f; volume = mFragment.getMasterMutingOption() ? 0f : 1f;

View File

@ -4,8 +4,8 @@ import android.content.SharedPreferences;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.videoautoplay.Config; import ml.docilealligator.infinityforreddit.videoautoplay.Config;
@ -22,8 +22,8 @@ public class LoopAvailableExoCreator extends DefaultExoCreator {
@NonNull @NonNull
@Override @Override
public SimpleExoPlayer createPlayer() { public ExoPlayer createPlayer() {
SimpleExoPlayer player = super.createPlayer(); ExoPlayer player = super.createPlayer();
if (sharedPreferences.getBoolean(SharedPreferencesUtils.LOOP_VIDEO, true)) { if (sharedPreferences.getBoolean(SharedPreferencesUtils.LOOP_VIDEO, true)) {
player.setRepeatMode(Player.REPEAT_MODE_ALL); player.setRepeatMode(Player.REPEAT_MODE_ALL);
} else { } else {

View File

@ -7,7 +7,6 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -26,22 +25,21 @@ import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; 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.PlaybackParameters;
import com.google.android.exoplayer2.Player; 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.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.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.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.util.Util; 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.android.material.bottomappbar.BottomAppBar;
import com.google.common.collect.ImmutableList;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -54,6 +52,7 @@ import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.activities.ViewImgurMediaActivity; import ml.docilealligator.infinityforreddit.activities.ViewImgurMediaActivity;
import ml.docilealligator.infinityforreddit.bottomsheetfragments.PlaybackSpeedBottomSheetFragment; import ml.docilealligator.infinityforreddit.bottomsheetfragments.PlaybackSpeedBottomSheetFragment;
import ml.docilealligator.infinityforreddit.services.DownloadMediaService; import ml.docilealligator.infinityforreddit.services.DownloadMediaService;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
@ -78,7 +77,7 @@ public class ViewImgurVideoFragment extends Fragment {
ImageView downloadImageView; ImageView downloadImageView;
private ViewImgurMediaActivity activity; private ViewImgurMediaActivity activity;
private ImgurMedia imgurMedia; private ImgurMedia imgurMedia;
private SimpleExoPlayer player; private ExoPlayer player;
private DataSource.Factory dataSourceFactory; private DataSource.Factory dataSourceFactory;
private boolean wasPlaying = false; private boolean wasPlaying = false;
private boolean isMute = false; private boolean isMute = false;
@ -87,6 +86,8 @@ public class ViewImgurVideoFragment extends Fragment {
@Inject @Inject
@Named("default") @Named("default")
SharedPreferences mSharedPreferences; SharedPreferences mSharedPreferences;
@Inject
SimpleCache mSimpleCache;
public ViewImgurVideoFragment() { public ViewImgurVideoFragment() {
// Required empty public constructor // Required empty public constructor
@ -143,13 +144,13 @@ public class ViewImgurVideoFragment extends Fragment {
} }
}); });
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(); TrackSelector trackSelector = new DefaultTrackSelector(activity);
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); player = new ExoPlayer.Builder(activity).setTrackSelector(trackSelector).build();
player = ExoPlayerFactory.newSimpleInstance(activity, trackSelector);
videoPlayerView.setPlayer(player); videoPlayerView.setPlayer(player);
dataSourceFactory = new DefaultDataSourceFactory(activity, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
Util.getUserAgent(activity, "Infinity")); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(activity)));
player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(imgurMedia.getLink()))); player.prepare();
player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(imgurMedia.getLink())));
if (savedInstanceState != null) { if (savedInstanceState != null) {
playbackSpeed = savedInstanceState.getInt(PLAYBACK_SPEED_STATE); playbackSpeed = savedInstanceState.getInt(PLAYBACK_SPEED_STATE);
@ -278,12 +279,13 @@ public class ViewImgurVideoFragment extends Fragment {
muteButton.setImageResource(R.drawable.ic_unmute_24dp); muteButton.setImageResource(R.drawable.ic_unmute_24dp);
} }
player.addListener(new Player.EventListener() { player.addListener(new Player.Listener() {
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) { if (!trackGroups.isEmpty()) {
for (int i = 0; i < trackGroups.length; i++) { for (int i = 0; i < trackGroups.size(); i++) {
String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType; String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) { if (mimeType != null && mimeType.contains("audio")) {
muteButton.setVisibility(View.VISIBLE); muteButton.setVisibility(View.VISIBLE);
muteButton.setOnClickListener(view -> { muteButton.setOnClickListener(view -> {

View File

@ -1,10 +1,10 @@
package ml.docilealligator.infinityforreddit.fragments; package ml.docilealligator.infinityforreddit.fragments;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -23,21 +23,20 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import com.google.android.exoplayer2.ExoPlaybackException; 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.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.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.hls.HlsMediaSource; 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.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.PlayerView;
import com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder; import com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.util.Util; 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.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -58,6 +57,7 @@ import ml.docilealligator.infinityforreddit.activities.RPANActivity;
import ml.docilealligator.infinityforreddit.adapters.RPANCommentStreamRecyclerViewAdapter; import ml.docilealligator.infinityforreddit.adapters.RPANCommentStreamRecyclerViewAdapter;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed; import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.JSONUtils; import ml.docilealligator.infinityforreddit.utils.JSONUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -105,9 +105,11 @@ public class ViewRPANBroadcastFragment extends Fragment {
@Inject @Inject
@Named("rpan") @Named("rpan")
OkHttpClient okHttpClient; OkHttpClient okHttpClient;
@Inject
SimpleCache mSimpleCache;
private RPANActivity mActivity; private RPANActivity mActivity;
private RPANBroadcast rpanBroadcast; private RPANBroadcast rpanBroadcast;
private SimpleExoPlayer player; private ExoPlayer player;
private DefaultTrackSelector trackSelector; private DefaultTrackSelector trackSelector;
private DataSource.Factory dataSourceFactory; private DataSource.Factory dataSourceFactory;
private Handler handler; private Handler handler;
@ -175,9 +177,8 @@ public class ViewRPANBroadcastFragment extends Fragment {
} }
}); });
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(); trackSelector = new DefaultTrackSelector(mActivity);
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); player = new ExoPlayer.Builder(mActivity).setTrackSelector(trackSelector).build();
player = ExoPlayerFactory.newSimpleInstance(mActivity, trackSelector);
playerView.setPlayer(player); playerView.setPlayer(player);
wasPlaying = true; wasPlaying = true;
@ -202,9 +203,10 @@ public class ViewRPANBroadcastFragment extends Fragment {
muteButton.setImageResource(R.drawable.ic_unmute_24dp); muteButton.setImageResource(R.drawable.ic_unmute_24dp);
} }
player.addListener(new Player.EventListener() { player.addListener(new Player.Listener() {
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) { if (!trackGroups.isEmpty()) {
if (isDataSavingMode) { if (isDataSavingMode) {
trackSelector.setParameters( trackSelector.setParameters(
@ -214,18 +216,19 @@ public class ViewRPANBroadcastFragment extends Fragment {
hdButton.setVisibility(View.VISIBLE); hdButton.setVisibility(View.VISIBLE);
hdButton.setOnClickListener(view -> { hdButton.setOnClickListener(view -> {
TrackSelectionDialogBuilder builder = new TrackSelectionDialogBuilder(mActivity, TrackSelectionDialogBuilder builder = new TrackSelectionDialogBuilder(mActivity, getString(R.string.select_video_quality), player, 0);
getString(R.string.select_video_quality), trackSelector, 0);
builder.setShowDisableOption(true); builder.setShowDisableOption(true);
builder.setAllowAdaptiveSelections(false); builder.setAllowAdaptiveSelections(false);
AlertDialog alertDialog = builder.build(); Dialog dialog = builder.build();
alertDialog.show(); dialog.show();
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor()); if (dialog instanceof AlertDialog) {
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(mCustomThemeWrapper.getPrimaryTextColor()); ((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++) { for (int i = 0; i < trackGroups.size(); i++) {
String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType; String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) { if (mimeType != null && mimeType.contains("audio")) {
muteButton.setVisibility(View.VISIBLE); muteButton.setVisibility(View.VISIBLE);
muteButton.setOnClickListener(view -> { muteButton.setOnClickListener(view -> {
@ -350,9 +353,11 @@ public class ViewRPANBroadcastFragment extends Fragment {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (dataSourceFactory == null) { 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. // 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)) { if (mSharedPreferences.getBoolean(SharedPreferencesUtils.LOOP_VIDEO, true)) {
player.setRepeatMode(Player.REPEAT_MODE_ALL); player.setRepeatMode(Player.REPEAT_MODE_ALL);
} else { } else {

View File

@ -7,7 +7,6 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -26,22 +25,21 @@ import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; 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.PlaybackParameters;
import com.google.android.exoplayer2.Player; 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.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.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.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.util.Util; 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.android.material.bottomappbar.BottomAppBar;
import com.google.common.collect.ImmutableList;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -54,6 +52,7 @@ import ml.docilealligator.infinityforreddit.activities.ViewRedditGalleryActivity
import ml.docilealligator.infinityforreddit.bottomsheetfragments.PlaybackSpeedBottomSheetFragment; import ml.docilealligator.infinityforreddit.bottomsheetfragments.PlaybackSpeedBottomSheetFragment;
import ml.docilealligator.infinityforreddit.post.Post; import ml.docilealligator.infinityforreddit.post.Post;
import ml.docilealligator.infinityforreddit.services.DownloadMediaService; import ml.docilealligator.infinityforreddit.services.DownloadMediaService;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.Utils; import ml.docilealligator.infinityforreddit.utils.Utils;
@ -82,7 +81,7 @@ public class ViewRedditGalleryVideoFragment extends Fragment {
private Post.Gallery galleryVideo; private Post.Gallery galleryVideo;
private String subredditName; private String subredditName;
private boolean isNsfw; private boolean isNsfw;
private SimpleExoPlayer player; private ExoPlayer player;
private DataSource.Factory dataSourceFactory; private DataSource.Factory dataSourceFactory;
private boolean wasPlaying = false; private boolean wasPlaying = false;
private boolean isMute = false; private boolean isMute = false;
@ -91,6 +90,8 @@ public class ViewRedditGalleryVideoFragment extends Fragment {
@Inject @Inject
@Named("default") @Named("default")
SharedPreferences mSharedPreferences; SharedPreferences mSharedPreferences;
@Inject
SimpleCache mSimpleCache;
public ViewRedditGalleryVideoFragment() { public ViewRedditGalleryVideoFragment() {
// Required empty public constructor // Required empty public constructor
@ -153,13 +154,13 @@ public class ViewRedditGalleryVideoFragment extends Fragment {
} }
}); });
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(); TrackSelector trackSelector = new DefaultTrackSelector(activity);
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); player = new ExoPlayer.Builder(activity).setTrackSelector(trackSelector).build();
player = ExoPlayerFactory.newSimpleInstance(activity, trackSelector);
videoPlayerView.setPlayer(player); videoPlayerView.setPlayer(player);
dataSourceFactory = new DefaultDataSourceFactory(activity, dataSourceFactory = new CacheDataSource.Factory().setCache(mSimpleCache)
Util.getUserAgent(activity, "Infinity")); .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(activity)));
player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(galleryVideo.url))); player.prepare();
player.setMediaSource(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(galleryVideo.url)));
if (savedInstanceState != null) { if (savedInstanceState != null) {
playbackSpeed = savedInstanceState.getInt(PLAYBACK_SPEED_STATE); playbackSpeed = savedInstanceState.getInt(PLAYBACK_SPEED_STATE);
@ -290,12 +291,13 @@ public class ViewRedditGalleryVideoFragment extends Fragment {
muteButton.setImageResource(R.drawable.ic_unmute_24dp); muteButton.setImageResource(R.drawable.ic_unmute_24dp);
} }
player.addListener(new Player.EventListener() { player.addListener(new Player.Listener() {
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (!trackGroups.isEmpty()) { if (!trackGroups.isEmpty()) {
for (int i = 0; i < trackGroups.length; i++) { for (int i = 0; i < trackGroups.size(); i++) {
String mimeType = trackGroups.get(i).getFormat(0).sampleMimeType; String mimeType = trackGroups.get(i).getTrackFormat(0).sampleMimeType;
if (mimeType != null && mimeType.contains("audio")) { if (mimeType != null && mimeType.contains("audio")) {
muteButton.setVisibility(View.VISIBLE); muteButton.setVisibility(View.VISIBLE);
muteButton.setOnClickListener(view -> { muteButton.setOnClickListener(view -> {

View File

@ -29,15 +29,11 @@ import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory.ExtensionRendererMode; import com.google.android.exoplayer2.DefaultRenderersFactory.ExtensionRendererMode;
import com.google.android.exoplayer2.LoadControl; import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.SimpleExoPlayer; 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.source.MediaSource;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.cache.Cache; 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 * Necessary configuration for {@link ExoCreator} to produces {@link SimpleExoPlayer} and
* {@link MediaSource}. Instance of this class must be construct using {@link Builder}. * {@link MediaSource}. Instance of this class must be construct using {@link Builder}.
@ -61,7 +57,6 @@ public final class Config {
@NonNull final MediaSourceBuilder mediaSourceBuilder; @NonNull final MediaSourceBuilder mediaSourceBuilder;
// Nullable options // Nullable options
@Nullable final DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
@Nullable final Cache cache; // null by default @Nullable final Cache cache; // null by default
// If null, ExoCreator must come up with a default one. // If null, ExoCreator must come up with a default one.
// This is to help customizing the Data source, for example using OkHttp extension. // This is to help customizing the Data source, for example using OkHttp extension.
@ -69,17 +64,16 @@ public final class Config {
@SuppressWarnings("WeakerAccess") // @SuppressWarnings("WeakerAccess") //
Config(@Nullable Context context, int extensionMode, @NonNull BaseMeter meter, Config(@Nullable Context context, int extensionMode, @NonNull BaseMeter meter,
@NonNull LoadControl loadControl, @NonNull LoadControl loadControl,
@Nullable DataSource.Factory dataSourceFactory, @Nullable DataSource.Factory dataSourceFactory,
@NonNull MediaSourceBuilder mediaSourceBuilder, @NonNull MediaSourceBuilder mediaSourceBuilder,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable Cache cache) { @Nullable Cache cache) {
this.context = context != null ? context.getApplicationContext() : null; this.context = context != null ? context.getApplicationContext() : null;
this.extensionMode = extensionMode; this.extensionMode = extensionMode;
this.meter = meter; this.meter = meter;
this.loadControl = loadControl; this.loadControl = loadControl;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.mediaSourceBuilder = mediaSourceBuilder; this.mediaSourceBuilder = mediaSourceBuilder;
this.drmSessionManager = drmSessionManager;
this.cache = cache; this.cache = cache;
} }
@ -93,7 +87,6 @@ public final class Config {
if (!meter.equals(config.meter)) return false; if (!meter.equals(config.meter)) return false;
if (!loadControl.equals(config.loadControl)) return false; if (!loadControl.equals(config.loadControl)) return false;
if (!mediaSourceBuilder.equals(config.mediaSourceBuilder)) 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; if (!ObjectsCompat.equals(cache, config.cache)) return false;
return ObjectsCompat.equals(dataSourceFactory, config.dataSourceFactory); return ObjectsCompat.equals(dataSourceFactory, config.dataSourceFactory);
} }
@ -103,7 +96,6 @@ public final class Config {
result = 31 * result + meter.hashCode(); result = 31 * result + meter.hashCode();
result = 31 * result + loadControl.hashCode(); result = 31 * result + loadControl.hashCode();
result = 31 * result + mediaSourceBuilder.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 + (cache != null ? cache.hashCode() : 0);
result = 31 * result + (dataSourceFactory != null ? dataSourceFactory.hashCode() : 0); result = 31 * result + (dataSourceFactory != null ? dataSourceFactory.hashCode() : 0);
return result; return result;
@ -111,7 +103,6 @@ public final class Config {
@SuppressWarnings("unused") public Builder newBuilder() { @SuppressWarnings("unused") public Builder newBuilder() {
return new Builder(context).setCache(this.cache) return new Builder(context).setCache(this.cache)
.setDrmSessionManager(this.drmSessionManager)
.setExtensionMode(this.extensionMode) .setExtensionMode(this.extensionMode)
.setLoadControl(this.loadControl) .setLoadControl(this.loadControl)
.setMediaSourceBuilder(this.mediaSourceBuilder) .setMediaSourceBuilder(this.mediaSourceBuilder)
@ -145,7 +136,6 @@ public final class Config {
private LoadControl loadControl = new DefaultLoadControl(); private LoadControl loadControl = new DefaultLoadControl();
private DataSource.Factory dataSourceFactory = null; private DataSource.Factory dataSourceFactory = null;
private MediaSourceBuilder mediaSourceBuilder = MediaSourceBuilder.DEFAULT; private MediaSourceBuilder mediaSourceBuilder = MediaSourceBuilder.DEFAULT;
private DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
private Cache cache = null; private Cache cache = null;
public Builder setExtensionMode(@ExtensionRendererMode int extensionMode) { public Builder setExtensionMode(@ExtensionRendererMode int extensionMode) {
@ -175,13 +165,6 @@ public final class Config {
return this; return this;
} }
@Beta //
public Builder setDrmSessionManager(
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
this.drmSessionManager = drmSessionManager;
return this;
}
public Builder setCache(@Nullable Cache cache) { public Builder setCache(@Nullable Cache cache) {
this.cache = cache; this.cache = cache;
return this; return this;
@ -189,7 +172,7 @@ public final class Config {
public Config build() { public Config build() {
return new Config(context, extensionMode, meter, loadControl, dataSourceFactory, return new Config(context, extensionMode, meter, loadControl, dataSourceFactory,
mediaSourceBuilder, drmSessionManager, cache); mediaSourceBuilder, cache);
} }
} }
} }

View File

@ -27,22 +27,26 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.LoadControl; import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.RenderersFactory; 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.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
/** /**
* Usage: use this as-it or inheritance. * Usage: use this as-it or inheritance.
* *
@ -50,144 +54,139 @@ import java.io.IOException;
* @since 3.4.0 * @since 3.4.0
*/ */
@SuppressWarnings({ "unused", "WeakerAccess" }) // @SuppressWarnings({"unused", "WeakerAccess"}) //
public class DefaultExoCreator implements ExoCreator, MediaSourceEventListener { public class DefaultExoCreator implements ExoCreator, MediaSourceEventListener {
final ToroExo toro; // per application final ToroExo toro; // per application
final Config config; final Config config;
private final TrackSelector trackSelector; // 'maybe' stateless private final TrackSelector trackSelector; // 'maybe' stateless
private final LoadControl loadControl; // stateless private final LoadControl loadControl; // stateless
private final MediaSourceBuilder mediaSourceBuilder; // stateless private final MediaSourceBuilder mediaSourceBuilder; // stateless
private final RenderersFactory renderersFactory; // stateless private final RenderersFactory renderersFactory; // stateless
private final DataSource.Factory mediaDataSourceFactory; // stateless private final DataSource.Factory mediaDataSourceFactory; // stateless
private final DataSource.Factory manifestDataSourceFactory; // stateless private final DataSource.Factory manifestDataSourceFactory; // stateless
public DefaultExoCreator(@NonNull ToroExo toro, @NonNull Config config) { public DefaultExoCreator(@NonNull ToroExo toro, @NonNull Config config) {
this.toro = checkNotNull(toro); this.toro = checkNotNull(toro);
this.config = checkNotNull(config); this.config = checkNotNull(config);
trackSelector = new DefaultTrackSelector(); trackSelector = new DefaultTrackSelector();
loadControl = config.loadControl; loadControl = config.loadControl;
mediaSourceBuilder = config.mediaSourceBuilder; mediaSourceBuilder = config.mediaSourceBuilder;
DefaultRenderersFactory tempFactory = new DefaultRenderersFactory(this.toro.context); DefaultRenderersFactory tempFactory = new DefaultRenderersFactory(this.toro.context);
tempFactory.setExtensionRendererMode(config.extensionMode); tempFactory.setExtensionRendererMode(config.extensionMode);
renderersFactory = tempFactory; renderersFactory = tempFactory;
DataSource.Factory baseFactory = config.dataSourceFactory; DataSource.Factory baseFactory = config.dataSourceFactory;
if (baseFactory == null) { if (baseFactory == null) {
baseFactory = new DefaultHttpDataSourceFactory(toro.appName, config.meter); 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);
} }
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) { public DefaultExoCreator(Context context, Config config) {
this(with(context), config); this(with(context), config);
} }
@SuppressWarnings("SimplifiableIfStatement") @SuppressWarnings("SimplifiableIfStatement")
@Override public boolean equals(Object o) { @Override
if (this == o) return true; public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DefaultExoCreator that = (DefaultExoCreator) o; DefaultExoCreator that = (DefaultExoCreator) o;
if (!toro.equals(that.toro)) return false; if (!toro.equals(that.toro)) return false;
if (!trackSelector.equals(that.trackSelector)) return false; if (!trackSelector.equals(that.trackSelector)) return false;
if (!loadControl.equals(that.loadControl)) return false; if (!loadControl.equals(that.loadControl)) return false;
if (!mediaSourceBuilder.equals(that.mediaSourceBuilder)) return false; if (!mediaSourceBuilder.equals(that.mediaSourceBuilder)) return false;
if (!renderersFactory.equals(that.renderersFactory)) return false; if (!renderersFactory.equals(that.renderersFactory)) return false;
if (!mediaDataSourceFactory.equals(that.mediaDataSourceFactory)) return false; if (!mediaDataSourceFactory.equals(that.mediaDataSourceFactory)) return false;
return manifestDataSourceFactory.equals(that.manifestDataSourceFactory); return manifestDataSourceFactory.equals(that.manifestDataSourceFactory);
} }
@Override public int hashCode() { @Override
int result = toro.hashCode(); public int hashCode() {
result = 31 * result + trackSelector.hashCode(); int result = toro.hashCode();
result = 31 * result + loadControl.hashCode(); result = 31 * result + trackSelector.hashCode();
result = 31 * result + mediaSourceBuilder.hashCode(); result = 31 * result + loadControl.hashCode();
result = 31 * result + renderersFactory.hashCode(); result = 31 * result + mediaSourceBuilder.hashCode();
result = 31 * result + mediaDataSourceFactory.hashCode(); result = 31 * result + renderersFactory.hashCode();
result = 31 * result + manifestDataSourceFactory.hashCode(); result = 31 * result + mediaDataSourceFactory.hashCode();
return result; result = 31 * result + manifestDataSourceFactory.hashCode();
} return result;
}
final TrackSelector getTrackSelector() { final TrackSelector getTrackSelector() {
return trackSelector; return trackSelector;
} }
@Nullable @Override public Context getContext() { @Nullable
return toro.context; @Override
} public Context getContext() {
return toro.context;
}
@NonNull @Override public SimpleExoPlayer createPlayer() { @NonNull
return new ToroExoPlayer(toro.context, renderersFactory, trackSelector, loadControl, @Override
new DefaultBandwidthMeter.Builder(toro.context).build(), config.drmSessionManager, public ExoPlayer createPlayer() {
Util.getLooper()); 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) { @NonNull
return mediaSourceBuilder.buildMediaSource(this.toro.context, uri, fileExt, new Handler(), @Override
manifestDataSourceFactory, mediaDataSourceFactory, this); public MediaSource createMediaSource(@NonNull Uri uri, String fileExt) {
} return mediaSourceBuilder.buildMediaSource(this.toro.context, uri, fileExt, new Handler(),
manifestDataSourceFactory, mediaDataSourceFactory, this);
}
@NonNull @Override // @NonNull
public Playable createPlayable(@NonNull Uri uri, String fileExt) { @Override //
return new PlayableImpl(this, uri, fileExt); public Playable createPlayable(@NonNull Uri uri, String fileExt) {
} return new PlayableImpl(this, uri, fileExt);
}
/// MediaSourceEventListener /// MediaSourceEventListener
@Override @Override
public void onLoadStarted(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, public void onLoadStarted(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) { LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
// no-ops // no-ops
} }
@Override @Override
public void onLoadCompleted(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, public void onLoadCompleted(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) { LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
// no-ops // no-ops
} }
@Override @Override
public void onLoadCanceled(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, public void onLoadCanceled(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) { LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
// no-ops // no-ops
} }
@Override @Override
public void onLoadError(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, public void onLoadError(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId,
LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData, IOException error, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData, IOException error,
boolean wasCanceled) { boolean wasCanceled) {
// no-ops // no-ops
} }
@Override public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { @Override
// no-ops public void onUpstreamDiscarded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId,
} MediaLoadData mediaLoadData) {
// no-ops
}
@Override @Override
public void onUpstreamDiscarded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, public void onDownstreamFormatChanged(int windowIndex,
MediaLoadData mediaLoadData) { @Nullable MediaSource.MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
// no-ops // 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
}
} }

View File

@ -22,6 +22,7 @@ import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
@ -52,7 +53,8 @@ public interface ExoCreator {
* *
* @return a new {@link SimpleExoPlayer} instance. * @return a new {@link SimpleExoPlayer} instance.
*/ */
@NonNull SimpleExoPlayer createPlayer(); @NonNull
ExoPlayer createPlayer();
/** /**
* Create a {@link MediaSource} from media {@link Uri}. * Create a {@link MediaSource} from media {@link Uri}.

View File

@ -17,7 +17,6 @@
package ml.docilealligator.infinityforreddit.videoautoplay; package ml.docilealligator.infinityforreddit.videoautoplay;
import static com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS; import static com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS;
import static ml.docilealligator.infinityforreddit.videoautoplay.ToroExo.toro; import static ml.docilealligator.infinityforreddit.videoautoplay.ToroExo.toro;
import android.net.Uri; import android.net.Uri;
@ -28,16 +27,16 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; 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.SimpleExoPlayer;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.source.BehindLiveWindowException; 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.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; 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.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.PlayerView;
import com.google.common.collect.ImmutableList;
import ml.docilealligator.infinityforreddit.R; import ml.docilealligator.infinityforreddit.R;
@ -52,154 +51,132 @@ import ml.docilealligator.infinityforreddit.R;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class ExoPlayable extends PlayableImpl { public class ExoPlayable extends PlayableImpl {
@SuppressWarnings("unused") private static final String TAG = "ToroExo:Playable"; @SuppressWarnings("unused")
private static final String TAG = "ToroExo:Playable";
private EventListener listener; private EventListener listener;
// Adapt from ExoPlayer demo. // Adapt from ExoPlayer demo.
protected boolean inErrorState = false; protected boolean inErrorState = false;
protected TrackGroupArray lastSeenTrackGroupArray; protected ImmutableList<Tracks.Group> lastSeenTrackGroupArray;
/** /**
* Construct an instance of {@link ExoPlayable} from an {@link ExoCreator} and {@link Uri}. The * 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} * {@link ExoCreator} is used to request {@link SimpleExoPlayer} instance, while {@link Uri}
* defines the media to play. * defines the media to play.
* *
* @param creator the {@link ExoCreator} instance. * @param creator the {@link ExoCreator} instance.
* @param uri the {@link Uri} of the media. * @param uri the {@link Uri} of the media.
* @param fileExt the custom extension of the media Uri. * @param fileExt the custom extension of the media Uri.
*/ */
public ExoPlayable(ExoCreator creator, Uri uri, String fileExt) { public ExoPlayable(ExoCreator creator, Uri uri, String fileExt) {
super(creator, uri, 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;
}
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;
}
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();
}
}
class Listener extends DefaultEventListener {
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void prepare(boolean prepareSource) {
super.onTracksChanged(trackGroups, trackSelections); if (listener == null) {
if (trackGroups == lastSeenTrackGroupArray) return; listener = new Listener();
lastSeenTrackGroupArray = trackGroups; super.addEventListener(listener);
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));
}
} }
} super.prepare(prepareSource);
this.lastSeenTrackGroupArray = null;
this.inErrorState = false;
} }
@Override public void onPlayerError(ExoPlaybackException error) { @Override
/// Adapt from ExoPlayer Demo public void setPlayerView(@Nullable PlayerView playerView) {
String errorString = null; // This will also clear these flags
if (error.type == ExoPlaybackException.TYPE_RENDERER) { if (playerView != this.playerView) {
Exception cause = error.getRendererException(); this.lastSeenTrackGroupArray = null;
if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { this.inErrorState = false;
// Special case for decoder initialization failures. }
MediaCodecRenderer.DecoderInitializationException decoderInitializationException = super.setPlayerView(playerView);
(MediaCodecRenderer.DecoderInitializationException) cause; }
if (decoderInitializationException.decoderName == null) {
if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) { @Override
errorString = toro.getString(R.string.error_querying_decoders); public void reset() {
} else if (decoderInitializationException.secureDecoderRequired) { super.reset();
errorString = toro.getString(R.string.error_no_secure_decoder, this.lastSeenTrackGroupArray = null;
decoderInitializationException.mimeType); this.inErrorState = false;
} else { }
errorString = toro.getString(R.string.error_no_decoder,
decoderInitializationException.mimeType); @Override
public void release() {
if (listener != null) {
super.removeEventListener(listener);
listener = null;
}
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();
}
}
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; super.onPlayerError(error);
if (isBehindLiveWindow(error)) { }
ExoPlayable.super.reset();
} else {
ExoPlayable.super.updatePlaybackInfo();
}
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) { static boolean isBehindLiveWindow(PlaybackException error) {
if (inErrorState) { if (error instanceof ExoPlaybackException && ((ExoPlaybackException) error).type != ExoPlaybackException.TYPE_SOURCE) return false;
// Adapt from ExoPlayer demo. Throwable cause = error.getCause();
// "This will only occur if the user has performed a seek whilst in the error state. Update while (cause != null) {
// the resume position so that if the user then retries, playback will resume from the if (cause instanceof BehindLiveWindowException) return true;
// position to which they seek." - ExoPlayer cause = cause.getCause();
ExoPlayable.super.updatePlaybackInfo(); }
} return false;
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();
}
return false;
}
} }

View File

@ -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;
}
}
}

View File

@ -28,6 +28,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.C.ContentType; 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.LoopingMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.MediaSourceEventListener;
@ -46,59 +47,62 @@ import com.google.android.exoplayer2.upstream.DataSource;
public interface MediaSourceBuilder { public interface MediaSourceBuilder {
@NonNull MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri, @NonNull
@Nullable String fileExt, @Nullable Handler handler, MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri,
@NonNull DataSource.Factory manifestDataSourceFactory, @Nullable String fileExt, @Nullable Handler handler,
@NonNull DataSource.Factory mediaDataSourceFactory, @NonNull DataSource.Factory manifestDataSourceFactory,
@Nullable MediaSourceEventListener listener); @NonNull DataSource.Factory mediaDataSourceFactory,
@Nullable MediaSourceEventListener listener);
MediaSourceBuilder DEFAULT = new MediaSourceBuilder() { MediaSourceBuilder DEFAULT = new MediaSourceBuilder() {
@NonNull @Override @NonNull
public MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri, @Override
@Nullable String ext, @Nullable Handler handler, public MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri,
@NonNull DataSource.Factory manifestDataSourceFactory, @Nullable String ext, @Nullable Handler handler,
@NonNull DataSource.Factory mediaDataSourceFactory, MediaSourceEventListener listener) { @NonNull DataSource.Factory manifestDataSourceFactory,
@ContentType int type = isEmpty(ext) ? inferContentType(uri) : inferContentType("." + ext); @NonNull DataSource.Factory mediaDataSourceFactory, MediaSourceEventListener listener) {
MediaSource result; @ContentType int type = isEmpty(ext) ? inferContentType(uri) : inferContentType("." + ext);
switch (type) { MediaSource result;
case C.TYPE_SS: switch (type) {
result = new SsMediaSource.Factory( case C.CONTENT_TYPE_SS:
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), manifestDataSourceFactory) result = new SsMediaSource.Factory(
.createMediaSource(uri); new DefaultSsChunkSource.Factory(mediaDataSourceFactory), manifestDataSourceFactory)
break; .createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_DASH: break;
result = new DashMediaSource.Factory( case C.CONTENT_TYPE_DASH:
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), manifestDataSourceFactory) result = new DashMediaSource.Factory(
.createMediaSource(uri); new DefaultDashChunkSource.Factory(mediaDataSourceFactory), manifestDataSourceFactory)
break; .createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_HLS: break;
result = new HlsMediaSource.Factory(mediaDataSourceFactory) // case C.CONTENT_TYPE_HLS:
.createMediaSource(uri); result = new HlsMediaSource.Factory(mediaDataSourceFactory) //
break; .createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_OTHER: break;
result = new ProgressiveMediaSource.Factory(mediaDataSourceFactory) // case C.CONTENT_TYPE_OTHER:
.createMediaSource(uri); result = new ProgressiveMediaSource.Factory(mediaDataSourceFactory) //
break; .createMediaSource(MediaItem.fromUri(uri));
default: break;
throw new IllegalStateException("Unsupported type: " + type); default:
} throw new IllegalStateException("Unsupported type: " + type);
}
result.addEventListener(handler, listener); result.addEventListener(handler, listener);
return result; return result;
} }
}; };
MediaSourceBuilder LOOPING = new MediaSourceBuilder() { MediaSourceBuilder LOOPING = new MediaSourceBuilder() {
@NonNull @Override @NonNull
public MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri, @Override
@Nullable String fileExt, @Nullable Handler handler, public MediaSource buildMediaSource(@NonNull Context context, @NonNull Uri uri,
@NonNull DataSource.Factory manifestDataSourceFactory, @Nullable String fileExt, @Nullable Handler handler,
@NonNull DataSource.Factory mediaDataSourceFactory, @NonNull DataSource.Factory manifestDataSourceFactory,
@Nullable MediaSourceEventListener listener) { @NonNull DataSource.Factory mediaDataSourceFactory,
return new LoopingMediaSource( @Nullable MediaSourceEventListener listener) {
DEFAULT.buildMediaSource(context, uri, fileExt, handler, manifestDataSourceFactory, return new LoopingMediaSource(
mediaDataSourceFactory, listener)); DEFAULT.buildMediaSource(context, uri, fileExt, handler, manifestDataSourceFactory,
} mediaDataSourceFactory, listener));
}; }
};
} }

View File

@ -20,20 +20,20 @@ import androidx.annotation.FloatRange;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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.PlaybackParameters;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline; 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.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.source.MediaSource; 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.Cue;
import com.google.android.exoplayer2.text.CueGroup;
import com.google.android.exoplayer2.text.TextOutput; 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.ui.PlayerView;
import com.google.android.exoplayer2.video.VideoListener; import com.google.android.exoplayer2.video.VideoSize;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet; 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}. * 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 * 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 * strong reference to Activity, and if it supports any kind of that, make sure to implicitly clean
* it up. * it up.
@ -56,310 +56,350 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
@SuppressWarnings("unused") // @SuppressWarnings("unused") //
public interface Playable { public interface Playable {
/** /**
* Prepare the resource for a {@link SimpleExoPlayer}. This method should: * Prepare the resource for a {@link SimpleExoPlayer}. This method should:
* - Request for new {@link SimpleExoPlayer} instance if there is not a usable one. * - Request for new {@link SimpleExoPlayer} instance if there is not a usable one.
* - Configure {@link EventListener} for it. * - Configure {@link EventListener} for it.
* - If there is non-trivial PlaybackInfo, update it to the SimpleExoPlayer. * - If there is non-trivial PlaybackInfo, update it to the SimpleExoPlayer.
* - If client request to prepare MediaSource, then prepare it. * - If client request to prepare MediaSource, then prepare it.
* * <p>
* This method must be called before {@link #setPlayerView(PlayerView)}. * This method must be called before {@link #setPlayerView(PlayerView)}.
* *
* @param prepareSource if {@code true}, also prepare the MediaSource when preparing the Player, * @param prepareSource if {@code true}, also prepare the MediaSource when preparing the Player,
* if {@code false} just do nothing for the MediaSource. * if {@code false} just do nothing for the MediaSource.
*/ */
void prepare(boolean prepareSource); void prepare(boolean prepareSource);
/** /**
* Set the {@link PlayerView} for this Playable. It is expected that a playback doesn't require a * 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, * 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()}. * that is after {@link #prepare(boolean)} and before {@link #release()}.
* * <p>
* Changing the PlayerView during playback is expected, though not always recommended, especially * Changing the PlayerView during playback is expected, though not always recommended, especially
* on old Devices with low Android API. * on old Devices with low Android API.
* *
* @param playerView the PlayerView to set to the SimpleExoPlayer. * @param playerView the PlayerView to set to the SimpleExoPlayer.
*/ */
void setPlayerView(@Nullable PlayerView playerView); void setPlayerView(@Nullable PlayerView playerView);
/** /**
* Get current {@link PlayerView} of this Playable. * Get current {@link PlayerView} of this Playable.
* *
* @return current PlayerView instance of this Playable. * @return current PlayerView instance of this Playable.
*/ */
@Nullable PlayerView getPlayerView(); @Nullable
PlayerView getPlayerView();
/** /**
* Start the playback. If the {@link MediaSource} is not prepared, then also prepare it. * Start the playback. If the {@link MediaSource} is not prepared, then also prepare it.
*/ */
void play(); void play();
/** /**
* Pause the playback. * Pause the playback.
*/ */
void pause(); void pause();
/** /**
* Reset all resource, so that the playback can start all over again. This is to cleanup the * 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 * playback for reuse. The SimpleExoPlayer instance must be still usable without calling
* {@link #prepare(boolean)}. * {@link #prepare(boolean)}.
*/ */
void reset(); void reset();
/** /**
* Release all resource. After this, the SimpleExoPlayer is released to the Player pool and the * 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. * Playable must call {@link #prepare(boolean)} again to use it again.
*/ */
void release(); void release();
/** /**
* Get current {@link PlaybackInfo} of the playback. * Get current {@link PlaybackInfo} of the playback.
* *
* @return current PlaybackInfo of the playback. * @return current PlaybackInfo of the playback.
*/ */
@NonNull @NonNull
PlaybackInfo getPlaybackInfo(); PlaybackInfo getPlaybackInfo();
/** /**
* Set the custom {@link PlaybackInfo} for this playback. This could suggest a seek. * Set the custom {@link PlaybackInfo} for this playback. This could suggest a seek.
* *
* @param playbackInfo the PlaybackInfo to set for this playback. * @param playbackInfo the PlaybackInfo to set for this playback.
*/ */
void setPlaybackInfo(@NonNull PlaybackInfo playbackInfo); void setPlaybackInfo(@NonNull PlaybackInfo playbackInfo);
/** /**
* Add a new {@link EventListener} to this Playable. As calling {@link #prepare(boolean)} also * 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 * triggers some internal events, this method should be called before {@link #prepare(boolean)} so
* that Client could received them all. * that Client could received them all.
* *
* @param listener the EventListener to add, must be not {@code null}. * @param listener the EventListener to add, must be not {@code null}.
*/ */
void addEventListener(@NonNull EventListener listener); void addEventListener(@NonNull EventListener listener);
/** /**
* Remove an {@link EventListener} from this Playable. * Remove an {@link EventListener} from this Playable.
* *
* @param listener the EventListener to be removed. If null, nothing happens. * @param listener the EventListener to be removed. If null, nothing happens.
*/ */
void removeEventListener(EventListener listener); void removeEventListener(EventListener listener);
/** /**
* !This must only work if the Player in use is a {@link ToroExoPlayer}. * !This must only work if the Player in use is a {@link ToroExoPlayer}.
*/ */
void addOnVolumeChangeListener(@NonNull ToroPlayer.OnVolumeChangeListener listener); void addOnVolumeChangeListener(@NonNull ToroPlayer.OnVolumeChangeListener listener);
void removeOnVolumeChangeListener(@Nullable ToroPlayer.OnVolumeChangeListener listener); void removeOnVolumeChangeListener(@Nullable ToroPlayer.OnVolumeChangeListener listener);
/** /**
* Check if current Playable is playing or not. * Check if current Playable is playing or not.
* *
* @return {@code true} if this Playable is playing, {@code false} otherwise. * @return {@code true} if this Playable is playing, {@code false} otherwise.
*/ */
boolean isPlaying(); boolean isPlaying();
/** /**
* Change the volume of current playback. * 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. * @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. * @deprecated use {@link #setVolumeInfo(VolumeInfo)} instead.
*/ */
@RemoveIn(version = "3.6.0") @Deprecated // @RemoveIn(version = "3.6.0")
void setVolume(@FloatRange(from = 0.0, to = 1.0) float volume); @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. * Obtain current volume value. The returned value is a {@code float} of range from 0 to 1.
* *
* @return current volume value. * @return current volume value.
* @deprecated use {@link #getVolumeInfo()} instead. * @deprecated use {@link #getVolumeInfo()} instead.
*/ */
@RemoveIn(version = "3.6.0") @Deprecated // @RemoveIn(version = "3.6.0")
@FloatRange(from = 0.0, to = 1.0) float getVolume(); @Deprecated //
@FloatRange(from = 0.0, to = 1.0)
float getVolume();
/** /**
* Update playback's volume. * Update playback's volume.
* *
* @param volumeInfo the {@link VolumeInfo} to update to. * @param volumeInfo the {@link VolumeInfo} to update to.
* @return {@code true} if current Volume info is updated, {@code false} otherwise. * @return {@code true} if current Volume info is updated, {@code false} otherwise.
*/ */
boolean setVolumeInfo(@NonNull VolumeInfo volumeInfo); boolean setVolumeInfo(@NonNull VolumeInfo volumeInfo);
/** /**
* Get current {@link VolumeInfo}. * Get current {@link VolumeInfo}.
*/ */
@NonNull VolumeInfo getVolumeInfo(); @NonNull
VolumeInfo getVolumeInfo();
/** /**
* Same as {@link Player#setPlaybackParameters(PlaybackParameters)} * Same as {@link Player#setPlaybackParameters(PlaybackParameters)}
*/ */
void setParameters(@Nullable PlaybackParameters parameters); void setParameters(@Nullable PlaybackParameters parameters);
/** /**
* Same as {@link Player#getPlaybackParameters()} * Same as {@link Player#getPlaybackParameters()}
*/ */
@Nullable PlaybackParameters getParameters(); @Nullable
PlaybackParameters getParameters();
void addErrorListener(@NonNull ToroPlayer.OnErrorListener listener); void addErrorListener(@NonNull ToroPlayer.OnErrorListener listener);
void removeErrorListener(@Nullable ToroPlayer.OnErrorListener listener); void removeErrorListener(@Nullable ToroPlayer.OnErrorListener listener);
// Combine necessary interfaces. // Combine necessary interfaces.
interface EventListener extends Player.EventListener, VideoListener, TextOutput, MetadataOutput { interface EventListener extends Player.Listener, TextOutput, MetadataOutput {
} @Override
default void onCues(@NonNull List<Cue> cues) {
/** Default empty implementation */ }
class DefaultEventListener implements EventListener {
@Override public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { @Override
default void onCues(@NonNull CueGroup cueGroup) {
}
@Override
default void onMetadata(@NonNull Metadata metadata) {
}
} }
@Override /**
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { * Default empty implementation
*/
class DefaultEventListener implements EventListener {
@Override
public void onTimelineChanged(@NonNull Timeline timeline, int reason) {
}
@Override
public void onTracksChanged(@NonNull Tracks tracks) {
}
@Override
public void onIsLoadingChanged(boolean isLoading) {
}
@Override
public void onPlaybackStateChanged(int playbackState) {
}
@Override
public void onRepeatModeChanged(int repeatMode) {
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
}
@Override
public void onPlayerError(@NonNull PlaybackException error) {
}
@Override
public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
}
@Override
public void onPlaybackParametersChanged(@NonNull PlaybackParameters playbackParameters) {
}
@Override
public void onSeekProcessed() {
}
@Override
public void onVideoSizeChanged(@NonNull VideoSize videoSize) {
EventListener.super.onVideoSizeChanged(videoSize);
}
@Override
public void onRenderedFirstFrame() {
}
@Override
public void onCues(@NonNull CueGroup cueGroup) {
EventListener.super.onCues(cueGroup);
}
@Override
public void onMetadata(@NonNull Metadata metadata) {
}
} }
@Override public void onLoadingChanged(boolean isLoading) { /**
* 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);
}
}
} }
@Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
}
@Override public void onRepeatModeChanged(int repeatMode) {
}
@Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
}
@Override public void onPlayerError(ExoPlaybackException error) {
}
@Override public void onPositionDiscontinuity(int reason) {
}
@Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
}
@Override public void onSeekProcessed() {
}
@Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) {
}
@Override public void onRenderedFirstFrame() {
}
@Override public void onCues(List<Cue> cues) {
}
@Override public void onMetadata(Metadata metadata) {
}
}
/** List of EventListener */
class EventListeners extends CopyOnWriteArraySet<EventListener> implements EventListener {
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 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 onCues(List<Cue> cues) {
for (EventListener eventListener : this) {
eventListener.onCues(cues);
}
}
@Override public void onMetadata(Metadata metadata) {
for (EventListener eventListener : this) {
eventListener.onMetadata(metadata);
}
}
}
} }

View File

@ -30,7 +30,6 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.PlayerView;
@ -39,9 +38,9 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
/** /**
* [20180225] * [20180225]
* * <p>
* Default implementation of {@link Playable}. * Default implementation of {@link Playable}.
* * <p>
* Instance of {@link Playable} should be reusable. Retaining instance of Playable across config * 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. * 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") // @SuppressWarnings("WeakerAccess") //
class PlayableImpl implements Playable { 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 EventListeners listeners = new EventListeners(); // original listener.
protected final ToroPlayer.VolumeChangeListeners volumeChangeListeners = new ToroPlayer.VolumeChangeListeners(); protected final ToroPlayer.VolumeChangeListeners volumeChangeListeners = new ToroPlayer.VolumeChangeListeners();
protected final ToroPlayer.ErrorListeners errorListeners = new ToroPlayer.ErrorListeners(); protected final ToroPlayer.ErrorListeners errorListeners = new ToroPlayer.ErrorListeners();
protected final Uri mediaUri; // immutable, parcelable protected final Uri mediaUri; // immutable, parcelable
protected final String fileExt; protected final String fileExt;
protected final ExoCreator creator; // required, cached protected final ExoCreator creator; // required, cached
protected SimpleExoPlayer player; // on-demand, cached protected ToroExoPlayer player; // on-demand, cached
protected MediaSource mediaSource; // on-demand, since we do not reuse MediaSource now. protected MediaSource mediaSource; // on-demand, since we do not reuse MediaSource now.
protected PlayerView playerView; // on-demand, not always required. protected PlayerView playerView; // on-demand, not always required.
private boolean sourcePrepared = false; private boolean sourcePrepared = false;
private boolean listenerApplied = false; private boolean listenerApplied = false;
PlayableImpl(ExoCreator creator, Uri uri, String fileExt) { PlayableImpl(ExoCreator creator, Uri uri, String fileExt) {
this.creator = creator; this.creator = creator;
this.mediaUri = uri; this.mediaUri = uri;
this.fileExt = fileExt; this.fileExt = fileExt;
}
@CallSuper @Override public void prepare(boolean prepareSource) {
if (prepareSource) {
ensureMediaSource();
ensurePlayerView();
}
}
@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);
}
} }
this.playerView = playerView; @CallSuper
} @Override
public void prepare(boolean prepareSource) {
@Override public final PlayerView getPlayerView() { if (prepareSource) {
return this.playerView; ensureMediaSource();
} ensurePlayerView();
@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);
}
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);
} }
listenerApplied = false;
}
with(checkNotNull(creator.getContext(), "ExoCreator has no Context")) //
.releasePlayer(this.creator, this.player);
}
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 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());
}
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);
} }
if (!sourcePrepared) { @CallSuper
ensurePlayer(); // sourcePrepared is set to false only when player is null. @Override
beforePrepareMediaSource(); public void setPlayerView(@Nullable PlayerView playerView) {
player.prepare(mediaSource, playbackInfo.getResumeWindow() == C.INDEX_UNSET, false); if (this.playerView == playerView) return;
sourcePrepared = true; if (playerView == null) {
} this.playerView.setPlayer(null);
} } else {
if (this.player != null) {
PlayerView.switchTargetView(this.player.getPlayer(), this.playerView, playerView);
}
}
private void ensurePlayer() { this.playerView = playerView;
if (player == null) {
sourcePrepared = false;
player = with(checkNotNull(creator.getContext(), "ExoCreator has no Context")) //
.requestPlayer(creator);
listenerApplied = false;
} }
if (!listenerApplied) { @Override
if (player instanceof ToroExoPlayer) { public final PlayerView getPlayerView() {
((ToroExoPlayer) player).addOnVolumeChangeListener(volumeChangeListeners); return this.playerView;
}
player.addListener(listeners);
player.addVideoListener(listeners);
player.addTextOutput(listeners);
player.addMetadataOutput(listeners);
listenerApplied = true;
} }
ToroExo.setVolumeInfo(player, this.playbackInfo.getVolumeInfo()); @CallSuper
boolean haveResumePosition = playbackInfo.getResumeWindow() != C.INDEX_UNSET; @Override
if (haveResumePosition) { public void play() {
player.seekTo(playbackInfo.getResumeWindow(), playbackInfo.getResumePosition()); ensureMediaSource();
ensurePlayerView();
checkNotNull(player, "Playable#play(): Player is null!");
player.getPlayer().setPlayWhenReady(true);
} }
}
// Trick to inject to the Player creation event. @CallSuper
// Required for AdsLoader to set Player. @Override
protected void beforePrepareMediaSource() { 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());
}
}
}
@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.getPlayer(), "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());
}
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);
}
@Override
public PlaybackParameters getParameters() {
return checkNotNull(player.getPlayer(), "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.getPlayer().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.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));
}
private void ensurePlayerView() {
if (playerView != null && playerView.getPlayer() != player.getPlayer()) playerView.setPlayer(player.getPlayer());
}
// 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() {
}
} }

View File

@ -16,34 +16,21 @@
package ml.docilealligator.infinityforreddit.videoautoplay; 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 java.lang.Runtime.getRuntime;
import static ml.docilealligator.infinityforreddit.videoautoplay.ToroUtil.checkNotNull; import static ml.docilealligator.infinityforreddit.videoautoplay.ToroUtil.checkNotNull;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.core.util.Pools; import androidx.core.util.Pools;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.SimpleExoPlayer; 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 com.google.android.exoplayer2.util.Util;
import java.net.CookieHandler; import java.net.CookieHandler;
@ -52,18 +39,15 @@ import java.net.CookiePolicy;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.utils.APIUtils; import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.videoautoplay.media.DrmMedia;
import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo; import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
/** /**
* Global helper class to manage {@link ExoCreator} and {@link SimpleExoPlayer} instances. * Global helper class to manage {@link ExoCreator} and {@link SimpleExoPlayer} instances.
* In this setup, {@link ExoCreator} and SimpleExoPlayer pools are cached. A {@link Config} * In this setup, {@link ExoCreator} and SimpleExoPlayer pools are cached. A {@link Config}
* is a key for each {@link ExoCreator}. * is a key for each {@link ExoCreator}.
* * <p>
* A suggested usage is as below: * A suggested usage is as below:
* <pre><code> * <pre><code>
* ExoCreator creator = ToroExo.with(this).getDefaultCreator(); * ExoCreator creator = ToroExo.with(this).getDefaultCreator();
@ -78,220 +62,152 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
public final class ToroExo { 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. // Magic number: Build.VERSION.SDK_INT / 6 --> API 16 ~ 18 will set pool size to 2, etc.
@SuppressWarnings("WeakerAccess") // @SuppressWarnings("WeakerAccess") //
static final int MAX_POOL_SIZE = Math.max(Util.SDK_INT / 6, getRuntime().availableProcessors()); static final int MAX_POOL_SIZE = Math.max(Util.SDK_INT / 6, getRuntime().availableProcessors());
@SuppressLint("StaticFieldLeak") // @SuppressLint("StaticFieldLeak") //
static volatile ToroExo toro; static volatile ToroExo toro;
public static ToroExo with(Context context) { public static ToroExo with(Context context) {
if (toro == null) { if (toro == null) {
synchronized (ToroExo.class) { synchronized (ToroExo.class) {
if (toro == null) toro = new ToroExo(context.getApplicationContext()); if (toro == null) toro = new ToroExo(context.getApplicationContext());
} }
}
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.
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);
}
}
/**
* 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);
}
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();
}
}
/// 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);
}
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);
}
/**
* 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();
}
} }
} return toro;
} }
if (drmSessionManager == null) { @NonNull
String error = TextUtils.isEmpty(subString) ? context.getString(errorStringId) final String appName;
: context.getString(errorStringId) + ": " + subString; @NonNull
Toast.makeText(context, error, LENGTH_SHORT).show(); 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);
}
} }
return drmSessionManager; /**
} * 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);
}
@RequiresApi(18) private static DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManagerV18( return creator;
@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]);
}
} }
return new DefaultDrmSessionManager<>(uuid, FrameworkMediaDrm.newInstance(uuid), drmCallback,
null, multiSession);
}
// Share the code of setting Volume. For use inside library only. @SuppressWarnings("WeakerAccess")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // public final Config getDefaultConfig() {
public static void setVolumeInfo(@NonNull SimpleExoPlayer player, if (defaultConfig == null) defaultConfig = new Config.Builder(context).build();
@NonNull VolumeInfo volumeInfo) { return defaultConfig;
if (player instanceof ToroExoPlayer) {
((ToroExoPlayer) player).setVolumeInfo(volumeInfo);
} else {
if (volumeInfo.isMute()) {
player.setVolume(0f);
} else {
player.setVolume(volumeInfo.getVolume());
}
} }
}
@SuppressWarnings("WeakerAccess") @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // /**
public static VolumeInfo getVolumeInfo(SimpleExoPlayer player) { * Get the default {@link ExoCreator}. This ExoCreator is configured by {@link #defaultConfig}.
if (player instanceof ToroExoPlayer) { */
return new VolumeInfo(((ToroExoPlayer) player).getVolumeInfo()); public final ExoCreator getDefaultCreator() {
} else { return getCreator(getDefaultConfig());
float volume = player.getVolume();
return new VolumeInfo(volume == 0, volume);
} }
}
@SuppressWarnings("SameParameterValue") /**
private static String getUserAgent() { * Request an instance of {@link SimpleExoPlayer}. It can be an existing instance cached by Pool
return APIUtils.USER_AGENT; * 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);
}
/**
* 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);
}
/**
* 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();
}
}
/// 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 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);
}
// 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(ToroExoPlayer player) {
return new VolumeInfo(player.getVolumeInfo());
}
@SuppressWarnings("SameParameterValue")
private static String getUserAgent() {
return APIUtils.USER_AGENT;
}
} }

View File

@ -23,13 +23,11 @@ import android.os.Looper;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.NonNull; 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.LoadControl;
import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer; 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.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.BandwidthMeter;
@ -41,53 +39,65 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
* @author eneim (2018/03/27). * @author eneim (2018/03/27).
*/ */
@SuppressWarnings("WeakerAccess") // @SuppressWarnings("WeakerAccess") //
public class ToroExoPlayer extends SimpleExoPlayer { public class ToroExoPlayer {
protected ToroExoPlayer(Context context, RenderersFactory renderersFactory, private ExoPlayer player;
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 ToroExoPlayer(Context context, RenderersFactory renderersFactory,
TrackSelector trackSelector, LoadControl loadControl, BandwidthMeter bandwidthMeter,
public final void addOnVolumeChangeListener(@NonNull ToroPlayer.OnVolumeChangeListener listener) { Looper looper) {
if (this.listeners == null) this.listeners = new ToroPlayer.VolumeChangeListeners(); player = new ExoPlayer.Builder(context).setRenderersFactory(renderersFactory).setTrackSelector(trackSelector).setLoadControl(loadControl).setBandwidthMeter(bandwidthMeter).setLooper(looper).build();
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);
}
}
} }
return changed; public ToroExoPlayer(ExoPlayer exoPlayer) {
} this.player = exoPlayer;
}
@SuppressWarnings("unused") @NonNull public final VolumeInfo getVolumeInfo() { private ToroPlayer.VolumeChangeListeners listeners;
return volumeInfo;
} 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;
}
@SuppressWarnings("unused")
@NonNull
public final VolumeInfo getVolumeInfo() {
return volumeInfo;
}
public ExoPlayer getPlayer() {
return player;
}
} }

View File

@ -34,82 +34,85 @@ import ml.docilealligator.infinityforreddit.videoautoplay.widget.Container;
public final class ToroUtil { public final class ToroUtil {
@SuppressWarnings("unused") private static final String TAG = "ToroLib:Util"; @SuppressWarnings("unused")
private static final String TAG = "ToroLib:Util";
private ToroUtil() { private ToroUtil() {
throw new RuntimeException("Meh!"); 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;
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());
float offset = 0.f;
if (visible && drawArea > 0) {
int visibleArea = playerRect.height() * playerRect.width();
offset = visibleArea / (float) drawArea;
} }
return offset;
}
/** /**
* Ensures that an object reference passed as a parameter to the calling * Get the ratio in range of 0.0 ~ 1.0 the visible area of a {@link ToroPlayer}'s playerView.
* method is not null. *
* * @param player the {@link ToroPlayer} need to investigate.
* @param reference an object reference * @param container the {@link ViewParent} that holds the {@link ToroPlayer}. If {@code null}
* @return the non-null reference that was validated * then this method must returns 0.0f;
* @throws NullPointerException if {@code reference} is null * @return the value in range of 0.0 ~ 1.0 of the visible area.
*/ */
public static @NonNull <T> T checkNotNull(final T reference) { @FloatRange(from = 0.0, to = 1.0) //
if (reference == null) { public static float visibleAreaOffset(@NonNull ToroPlayer player, ViewParent container) {
throw new NullPointerException(); if (container == null) return 0.0f;
}
return reference;
}
/** View playerView = player.getPlayerView();
* Ensures that an object reference passed as a parameter to the calling Rect drawRect = new Rect();
* method is not null. playerView.getDrawingRect(drawRect);
* int drawArea = drawRect.width() * drawRect.height();
* @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;
}
@SuppressWarnings("unchecked") // Rect playerRect = new Rect();
public static void wrapParamBehavior(@NonNull final Container container, boolean visible = playerView.getGlobalVisibleRect(playerRect, new Point());
final Container.BehaviorCallback callback) {
container.setBehaviorCallback(callback); float offset = 0.f;
ViewGroup.LayoutParams params = container.getLayoutParams(); if (visible && drawArea > 0) {
if (params instanceof CoordinatorLayout.LayoutParams) { int visibleArea = playerRect.height() * playerRect.width();
CoordinatorLayout.Behavior temp = ((CoordinatorLayout.LayoutParams) params).getBehavior(); offset = visibleArea / (float) drawArea;
if (temp != null) { }
((CoordinatorLayout.LayoutParams) params).setBehavior(new Container.Behavior(temp)); 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();
}
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));
}
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));
}
}
} }
}
} }

View File

@ -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();
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -1297,13 +1297,6 @@
<string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string> <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="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_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="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> <string name="storage_permission_denied">Permission to access storage was denied</string>