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'
/** 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-dash:$exoplayerVersion"
implementation "com.google.android.exoplayer:exoplayer-hls:$exoplayerVersion"
implementation "com.google.android.exoplayer:exoplayer-ui:$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 **/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,22 +27,26 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
/**
* Usage: use this as-it or inheritance.
*
@ -75,13 +79,13 @@ public class DefaultExoCreator implements ExoCreator, MediaSourceEventListener {
DataSource.Factory baseFactory = config.dataSourceFactory;
if (baseFactory == null) {
baseFactory = new DefaultHttpDataSourceFactory(toro.appName, config.meter);
baseFactory = new DefaultHttpDataSource.Factory().setUserAgent(APIUtils.getRedgifsUserAgent(getContext()));
}
DataSource.Factory factory = new DefaultDataSourceFactory(this.toro.context, //
config.meter, baseFactory);
if (config.cache != null) factory = new CacheDataSourceFactory(config.cache, factory);
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 DefaultDataSourceFactory(this.toro.context, this.toro.appName);
manifestDataSourceFactory = new DefaultDataSource.Factory(this.toro.context);
}
public DefaultExoCreator(Context context, Config config) {
@ -89,7 +93,8 @@ public class DefaultExoCreator implements ExoCreator, MediaSourceEventListener {
}
@SuppressWarnings("SimplifiableIfStatement")
@Override public boolean equals(Object o) {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@ -104,7 +109,8 @@ public class DefaultExoCreator implements ExoCreator, MediaSourceEventListener {
return manifestDataSourceFactory.equals(that.manifestDataSourceFactory);
}
@Override public int hashCode() {
@Override
public int hashCode() {
int result = toro.hashCode();
result = 31 * result + trackSelector.hashCode();
result = 31 * result + loadControl.hashCode();
@ -119,22 +125,28 @@ public class DefaultExoCreator implements ExoCreator, MediaSourceEventListener {
return trackSelector;
}
@Nullable @Override public Context getContext() {
@Nullable
@Override
public Context getContext() {
return toro.context;
}
@NonNull @Override public SimpleExoPlayer createPlayer() {
@NonNull
@Override
public ExoPlayer createPlayer() {
return new ToroExoPlayer(toro.context, renderersFactory, trackSelector, loadControl,
new DefaultBandwidthMeter.Builder(toro.context).build(), config.drmSessionManager,
Util.getLooper());
new DefaultBandwidthMeter.Builder(toro.context).build(), Util.getCurrentOrMainLooper()).getPlayer();
}
@NonNull @Override public MediaSource createMediaSource(@NonNull Uri uri, String fileExt) {
@NonNull
@Override
public MediaSource createMediaSource(@NonNull Uri uri, String fileExt) {
return mediaSourceBuilder.buildMediaSource(this.toro.context, uri, fileExt, new Handler(),
manifestDataSourceFactory, mediaDataSourceFactory, this);
}
@NonNull @Override //
@NonNull
@Override //
public Playable createPlayable(@NonNull Uri uri, String fileExt) {
return new PlayableImpl(this, uri, fileExt);
}
@ -166,28 +178,15 @@ public class DefaultExoCreator implements ExoCreator, MediaSourceEventListener {
// no-ops
}
@Override public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
// no-ops
}
@Override
public void onUpstreamDiscarded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId,
MediaLoadData mediaLoadData) {
// no-ops
}
@Override public void onDownstreamFormatChanged(int windowIndex,
@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.Nullable;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
@ -52,7 +53,8 @@ public interface ExoCreator {
*
* @return a new {@link SimpleExoPlayer} instance.
*/
@NonNull SimpleExoPlayer createPlayer();
@NonNull
ExoPlayer createPlayer();
/**
* Create a {@link MediaSource} from media {@link Uri}.

View File

@ -17,7 +17,6 @@
package ml.docilealligator.infinityforreddit.videoautoplay;
import static com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS;
import static ml.docilealligator.infinityforreddit.videoautoplay.ToroExo.toro;
import android.net.Uri;
@ -28,16 +27,16 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.common.collect.ImmutableList;
import ml.docilealligator.infinityforreddit.R;
@ -52,13 +51,14 @@ import ml.docilealligator.infinityforreddit.R;
@SuppressWarnings("WeakerAccess")
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;
// Adapt from ExoPlayer demo.
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
@ -73,7 +73,8 @@ public class ExoPlayable extends PlayableImpl {
super(creator, uri, fileExt);
}
@Override public void prepare(boolean prepareSource) {
@Override
public void prepare(boolean prepareSource) {
if (listener == null) {
listener = new Listener();
super.addEventListener(listener);
@ -83,7 +84,8 @@ public class ExoPlayable extends PlayableImpl {
this.inErrorState = false;
}
@Override public void setPlayerView(@Nullable PlayerView playerView) {
@Override
public void setPlayerView(@Nullable PlayerView playerView) {
// This will also clear these flags
if (playerView != this.playerView) {
this.lastSeenTrackGroupArray = null;
@ -92,13 +94,15 @@ public class ExoPlayable extends PlayableImpl {
super.setPlayerView(playerView);
}
@Override public void reset() {
@Override
public void reset() {
super.reset();
this.lastSeenTrackGroupArray = null;
this.inErrorState = false;
}
@Override public void release() {
@Override
public void release() {
if (listener != null) {
super.removeEventListener(listener);
listener = null;
@ -120,10 +124,9 @@ public class ExoPlayable extends PlayableImpl {
}
class Listener extends DefaultEventListener {
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
super.onTracksChanged(trackGroups, trackSelections);
public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (trackGroups == lastSeenTrackGroupArray) return;
lastSeenTrackGroupArray = trackGroups;
if (!(creator instanceof DefaultExoCreator)) return;
@ -142,34 +145,8 @@ public class ExoPlayable extends PlayableImpl {
}
}
@Override public void onPlayerError(ExoPlaybackException error) {
/// Adapt from ExoPlayer Demo
String errorString = null;
if (error.type == ExoPlaybackException.TYPE_RENDERER) {
Exception cause = error.getRendererException();
if (cause instanceof MediaCodecRenderer.DecoderInitializationException) {
// Special case for decoder initialization failures.
MediaCodecRenderer.DecoderInitializationException decoderInitializationException =
(MediaCodecRenderer.DecoderInitializationException) cause;
if (decoderInitializationException.decoderName == null) {
if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) {
errorString = toro.getString(R.string.error_querying_decoders);
} else if (decoderInitializationException.secureDecoderRequired) {
errorString = toro.getString(R.string.error_no_secure_decoder,
decoderInitializationException.mimeType);
} else {
errorString = toro.getString(R.string.error_no_decoder,
decoderInitializationException.mimeType);
}
} 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();
@ -180,7 +157,8 @@ public class ExoPlayable extends PlayableImpl {
super.onPlayerError(error);
}
@Override public void onPositionDiscontinuity(int reason) {
@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
@ -188,14 +166,13 @@ public class ExoPlayable extends PlayableImpl {
// position to which they seek." - ExoPlayer
ExoPlayable.super.updatePlaybackInfo();
}
super.onPositionDiscontinuity(reason);
super.onPositionDiscontinuity(oldPosition, newPosition, reason);
}
}
static boolean isBehindLiveWindow(ExoPlaybackException error) {
if (error.type != ExoPlaybackException.TYPE_SOURCE) return false;
Throwable cause = error.getSourceException();
static boolean isBehindLiveWindow(PlaybackException error) {
if (error instanceof ExoPlaybackException && ((ExoPlaybackException) error).type != ExoPlaybackException.TYPE_SOURCE) return false;
Throwable cause = error.getCause();
while (cause != null) {
if (cause instanceof BehindLiveWindowException) return true;
cause = cause.getCause();

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

View File

@ -20,20 +20,20 @@ import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.CueGroup;
import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.video.VideoListener;
import com.google.android.exoplayer2.video.VideoSize;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
@ -44,7 +44,7 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
/**
* Define an interface to control a playback, specific for {@link SimpleExoPlayer} and {@link PlayerView}.
*
* <p>
* This interface is designed to be reused across Config change. Implementation must not hold any
* strong reference to Activity, and if it supports any kind of that, make sure to implicitly clean
* it up.
@ -62,7 +62,7 @@ public interface Playable {
* - Configure {@link EventListener} for it.
* - If there is non-trivial PlaybackInfo, update it to the SimpleExoPlayer.
* - If client request to prepare MediaSource, then prepare it.
*
* <p>
* This method must be called before {@link #setPlayerView(PlayerView)}.
*
* @param prepareSource if {@code true}, also prepare the MediaSource when preparing the Player,
@ -74,7 +74,7 @@ public interface Playable {
* Set the {@link PlayerView} for this Playable. It is expected that a playback doesn't require a
* UI, so this setup is optional. But it must be called after the SimpleExoPlayer is prepared,
* that is after {@link #prepare(boolean)} and before {@link #release()}.
*
* <p>
* Changing the PlayerView during playback is expected, though not always recommended, especially
* on old Devices with low Android API.
*
@ -87,7 +87,8 @@ public interface 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.
@ -163,7 +164,9 @@ public interface Playable {
* @param volume the volume value to be set. Must be a {@code float} of range from 0 to 1.
* @deprecated use {@link #setVolumeInfo(VolumeInfo)} instead.
*/
@RemoveIn(version = "3.6.0") @Deprecated //
@RemoveIn(version = "3.6.0")
@Deprecated
//
void setVolume(@FloatRange(from = 0.0, to = 1.0) float volume);
/**
@ -172,8 +175,10 @@ public interface Playable {
* @return current volume value.
* @deprecated use {@link #getVolumeInfo()} instead.
*/
@RemoveIn(version = "3.6.0") @Deprecated //
@FloatRange(from = 0.0, to = 1.0) float getVolume();
@RemoveIn(version = "3.6.0")
@Deprecated //
@FloatRange(from = 0.0, to = 1.0)
float getVolume();
/**
* Update playback's volume.
@ -186,7 +191,8 @@ public interface Playable {
/**
* Get current {@link VolumeInfo}.
*/
@NonNull VolumeInfo getVolumeInfo();
@NonNull
VolumeInfo getVolumeInfo();
/**
* Same as {@link Player#setPlaybackParameters(PlaybackParameters)}
@ -196,167 +202,201 @@ public interface Playable {
/**
* Same as {@link Player#getPlaybackParameters()}
*/
@Nullable PlaybackParameters getParameters();
@Nullable
PlaybackParameters getParameters();
void addErrorListener(@NonNull ToroPlayer.OnErrorListener listener);
void removeErrorListener(@Nullable ToroPlayer.OnErrorListener listener);
// Combine necessary interfaces.
interface EventListener extends Player.EventListener, VideoListener, TextOutput, MetadataOutput {
interface EventListener extends Player.Listener, TextOutput, MetadataOutput {
}
/** Default empty implementation */
class DefaultEventListener implements EventListener {
@Override public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
@Override
default void onCues(@NonNull List<Cue> cues) {
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
default void onCues(@NonNull CueGroup cueGroup) {
}
@Override public void onLoadingChanged(boolean isLoading) {
}
@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) {
@Override
default void onMetadata(@NonNull Metadata metadata) {
}
}
/** List of EventListener */
/**
* 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) {
}
}
/**
* List of EventListener
*/
class EventListeners extends CopyOnWriteArraySet<EventListener> implements EventListener {
EventListeners() {
}
@Override public void onVideoSizeChanged(int width, int height, int unAppliedRotationDegrees,
float pixelWidthHeightRatio) {
@Override
public void onVideoSizeChanged(@NonNull VideoSize videoSize) {
for (EventListener eventListener : this) {
eventListener.onVideoSizeChanged(width, height, unAppliedRotationDegrees,
pixelWidthHeightRatio);
eventListener.onVideoSizeChanged(videoSize);
}
}
@Override public void onRenderedFirstFrame() {
@Override
public void onRenderedFirstFrame() {
for (EventListener eventListener : this) {
eventListener.onRenderedFirstFrame();
}
}
@Override public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
@Override
public void onTimelineChanged(@NonNull Timeline timeline, int reason) {
for (EventListener eventListener : this) {
eventListener.onTimelineChanged(timeline, manifest, reason);
eventListener.onTimelineChanged(timeline, reason);
}
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
public void onTracksChanged(@NonNull Tracks tracks) {
for (EventListener eventListener : this) {
eventListener.onTracksChanged(trackGroups, trackSelections);
eventListener.onTracksChanged(tracks);
}
}
@Override public void onLoadingChanged(boolean isLoading) {
@Override
public void onIsLoadingChanged(boolean isLoading) {
for (EventListener eventListener : this) {
eventListener.onLoadingChanged(isLoading);
eventListener.onIsLoadingChanged(isLoading);
}
}
@Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
@Override
public void onPlaybackStateChanged(int playbackState) {
for (EventListener eventListener : this) {
eventListener.onPlayerStateChanged(playWhenReady, playbackState);
eventListener.onPlaybackStateChanged(playbackState);
}
}
@Override public void onRepeatModeChanged(int repeatMode) {
@Override
public void onRepeatModeChanged(int repeatMode) {
for (EventListener eventListener : this) {
eventListener.onRepeatModeChanged(repeatMode);
}
}
@Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
for (EventListener eventListener : this) {
eventListener.onShuffleModeEnabledChanged(shuffleModeEnabled);
}
}
@Override public void onPlayerError(ExoPlaybackException error) {
@Override
public void onPlayerError(@NonNull PlaybackException error) {
for (EventListener eventListener : this) {
eventListener.onPlayerError(error);
}
}
@Override public void onPositionDiscontinuity(int reason) {
@Override
public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
for (EventListener eventListener : this) {
eventListener.onPositionDiscontinuity(reason);
eventListener.onPositionDiscontinuity(oldPosition, newPosition, reason);
}
}
@Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
@Override
public void onPlaybackParametersChanged(@NonNull PlaybackParameters playbackParameters) {
for (EventListener eventListener : this) {
eventListener.onPlaybackParametersChanged(playbackParameters);
}
}
@Override public void onSeekProcessed() {
@Override
public void onCues(@NonNull CueGroup cueGroup) {
for (EventListener eventListener : this) {
eventListener.onSeekProcessed();
eventListener.onCues(cueGroup);
}
}
@Override public void onCues(List<Cue> cues) {
for (EventListener eventListener : this) {
eventListener.onCues(cues);
}
}
@Override public void onMetadata(Metadata metadata) {
@Override
public void onMetadata(@NonNull 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.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.ui.PlayerView;
@ -39,9 +38,9 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
/**
* [20180225]
*
* <p>
* Default implementation of {@link Playable}.
*
* <p>
* Instance of {@link Playable} should be reusable. Retaining instance of Playable across config
* change must guarantee that all {@link EventListener} are cleaned up on config change.
*
@ -60,7 +59,7 @@ class PlayableImpl implements Playable {
protected final String fileExt;
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 PlayerView playerView; // on-demand, not always required.
@ -73,84 +72,101 @@ class PlayableImpl implements Playable {
this.fileExt = fileExt;
}
@CallSuper @Override public void prepare(boolean prepareSource) {
@CallSuper
@Override
public void prepare(boolean prepareSource) {
if (prepareSource) {
ensureMediaSource();
ensurePlayerView();
}
}
@CallSuper @Override public void setPlayerView(@Nullable PlayerView playerView) {
@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);
PlayerView.switchTargetView(this.player.getPlayer(), this.playerView, playerView);
}
}
this.playerView = playerView;
}
@Override public final PlayerView getPlayerView() {
@Override
public final PlayerView getPlayerView() {
return this.playerView;
}
@CallSuper @Override public void play() {
@CallSuper
@Override
public void play() {
ensureMediaSource();
ensurePlayerView();
checkNotNull(player, "Playable#play(): Player is null!");
player.setPlayWhenReady(true);
player.getPlayer().setPlayWhenReady(true);
}
@CallSuper @Override public void pause() {
@CallSuper
@Override
public void pause() {
// Player is not required to be non-null here.
if (player != null) player.setPlayWhenReady(false);
if (player != null) player.getPlayer().setPlayWhenReady(false);
}
@CallSuper @Override public void reset() {
@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);
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() {
@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);
player.getPlayer().stop();
player.getPlayer().clearMediaItems();
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);
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);
.releasePlayer(this.creator, this.player.getPlayer());
}
this.player = null;
this.mediaSource = null;
this.sourcePrepared = false;
}
@CallSuper @NonNull @Override public PlaybackInfo getPlaybackInfo() {
@CallSuper
@NonNull
@Override
public PlaybackInfo getPlaybackInfo() {
updatePlaybackInfo();
return new PlaybackInfo(playbackInfo.getResumeWindow(), playbackInfo.getResumePosition(),
playbackInfo.getVolumeInfo());
}
@CallSuper @Override public void setPlaybackInfo(@NonNull PlaybackInfo playbackInfo) {
@CallSuper
@Override
public void setPlaybackInfo(@NonNull PlaybackInfo playbackInfo) {
this.playbackInfo.setResumeWindow(playbackInfo.getResumeWindow());
this.playbackInfo.setResumePosition(playbackInfo.getResumePosition());
this.setVolumeInfo(playbackInfo.getVolumeInfo());
@ -159,31 +175,38 @@ class PlayableImpl implements Playable {
ToroExo.setVolumeInfo(player, this.playbackInfo.getVolumeInfo());
boolean haveResumePosition = this.playbackInfo.getResumeWindow() != INDEX_UNSET;
if (haveResumePosition) {
player.seekTo(this.playbackInfo.getResumeWindow(), this.playbackInfo.getResumePosition());
player.getPlayer().seekTo(this.playbackInfo.getResumeWindow(), this.playbackInfo.getResumePosition());
}
}
}
@Override public final void addEventListener(@NonNull EventListener listener) {
@Override
public final void addEventListener(@NonNull EventListener listener) {
//noinspection ConstantConditions
if (listener != null) this.listeners.add(listener);
}
@Override public final void removeEventListener(EventListener listener) {
@Override
public final void removeEventListener(EventListener listener) {
this.listeners.remove(listener);
}
@CallSuper @Override public void setVolume(float volume) {
@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();
@CallSuper
@Override
public float getVolume() {
return checkNotNull(player.getPlayer(), "Playable#getVolume(): Player is null!").getVolume();
}
@Override public boolean setVolumeInfo(@NonNull VolumeInfo volumeInfo) {
@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());
@ -192,17 +215,21 @@ class PlayableImpl implements Playable {
return changed;
}
@NonNull @Override public VolumeInfo getVolumeInfo() {
@NonNull
@Override
public VolumeInfo getVolumeInfo() {
return this.playbackInfo.getVolumeInfo();
}
@Override public void setParameters(@Nullable PlaybackParameters parameters) {
checkNotNull(player, "Playable#setParameters(PlaybackParameters): Player is null") //
@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, "Playable#getParameters(): Player is null").getPlaybackParameters();
@Override
public PlaybackParameters getParameters() {
return checkNotNull(player.getPlayer(), "Playable#getParameters(): Player is null").getPlaybackParameters();
}
@Override
@ -215,28 +242,31 @@ class PlayableImpl implements Playable {
volumeChangeListeners.remove(listener);
}
@Override public boolean isPlaying() {
return player != null && player.getPlayWhenReady();
@Override
public boolean isPlaying() {
return player != null && player.getPlayer().getPlayWhenReady();
}
@Override public void addErrorListener(@NonNull ToroPlayer.OnErrorListener listener) {
@Override
public void addErrorListener(@NonNull ToroPlayer.OnErrorListener listener) {
this.errorListeners.add(checkNotNull(listener));
}
@Override public void removeErrorListener(@Nullable ToroPlayer.OnErrorListener 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);
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) playerView.setPlayer(player);
if (playerView != null && playerView.getPlayer() != player.getPlayer()) playerView.setPlayer(player.getPlayer());
}
// TODO [20180822] Double check this.
@ -249,7 +279,7 @@ class PlayableImpl implements Playable {
if (!sourcePrepared) {
ensurePlayer(); // sourcePrepared is set to false only when player is null.
beforePrepareMediaSource();
player.prepare(mediaSource, playbackInfo.getResumeWindow() == C.INDEX_UNSET, false);
player.getPlayer().prepare(mediaSource, playbackInfo.getResumeWindow() == C.INDEX_UNSET, false);
sourcePrepared = true;
}
}
@ -263,20 +293,15 @@ class PlayableImpl implements Playable {
}
if (!listenerApplied) {
if (player instanceof ToroExoPlayer) {
((ToroExoPlayer) player).addOnVolumeChangeListener(volumeChangeListeners);
}
player.addListener(listeners);
player.addVideoListener(listeners);
player.addTextOutput(listeners);
player.addMetadataOutput(listeners);
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.seekTo(playbackInfo.getResumeWindow(), playbackInfo.getResumePosition());
player.getPlayer().seekTo(playbackInfo.getResumeWindow(), playbackInfo.getResumePosition());
}
}

View File

@ -16,34 +16,21 @@
package ml.docilealligator.infinityforreddit.videoautoplay;
import static android.widget.Toast.LENGTH_SHORT;
import static com.google.android.exoplayer2.drm.UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME;
import static com.google.android.exoplayer2.util.Util.getDrmUuid;
import static java.lang.Runtime.getRuntime;
import static ml.docilealligator.infinityforreddit.videoautoplay.ToroUtil.checkNotNull;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes;
import androidx.core.util.Pools;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util;
import java.net.CookieHandler;
@ -52,18 +39,15 @@ import java.net.CookiePolicy;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.videoautoplay.media.DrmMedia;
import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
/**
* Global helper class to manage {@link ExoCreator} and {@link SimpleExoPlayer} instances.
* In this setup, {@link ExoCreator} and SimpleExoPlayer pools are cached. A {@link Config}
* is a key for each {@link ExoCreator}.
*
* <p>
* A suggested usage is as below:
* <pre><code>
* ExoCreator creator = ToroExo.with(this).getDefaultCreator();
@ -95,10 +79,14 @@ public final class ToroExo {
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;
@NonNull
final String appName;
@NonNull
final Context context; // Application context
@NonNull
private final Map<Config, ExoCreator> creators;
@NonNull
private final Map<ExoCreator, Pools.Pool<ExoPlayer>> playerPools;
private Config defaultConfig; // will be created on the first time it is used.
@ -129,7 +117,8 @@ public final class ToroExo {
return creator;
}
@SuppressWarnings("WeakerAccess") public final Config getDefaultConfig() {
@SuppressWarnings("WeakerAccess")
public final Config getDefaultConfig() {
if (defaultConfig == null) defaultConfig = new Config.Builder(context).build();
return defaultConfig;
}
@ -144,7 +133,7 @@ public final class ToroExo {
/**
* Request an instance of {@link SimpleExoPlayer}. It can be an existing instance cached by Pool
* or new one.
*
* <p>
* The creator may or may not be the one created by either {@link #getCreator(Config)} or
* {@link #getDefaultCreator()}.
*
@ -152,10 +141,10 @@ public final class ToroExo {
* @return an usable {@link SimpleExoPlayer} instance.
*/
@NonNull //
public final SimpleExoPlayer requestPlayer(@NonNull ExoCreator creator) {
SimpleExoPlayer player = getPool(checkNotNull(creator)).acquire();
public final ToroExoPlayer requestPlayer(@NonNull ExoCreator creator) {
ExoPlayer player = getPool(checkNotNull(creator)).acquire();
if (player == null) player = creator.createPlayer();
return player;
return new ToroExoPlayer(player);
}
/**
@ -166,7 +155,7 @@ public final class ToroExo {
* @return true if player is released to relevant Pool, false otherwise.
*/
@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"}) //
public final boolean releasePlayer(@NonNull ExoCreator creator, @NonNull SimpleExoPlayer player) {
public final boolean releasePlayer(@NonNull ExoCreator creator, @NonNull ExoPlayer player) {
return getPool(checkNotNull(creator)).release(player);
}
@ -176,18 +165,18 @@ public final class ToroExo {
*/
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 =
for (Iterator<Map.Entry<ExoCreator, Pools.Pool<ExoPlayer>>> it =
playerPools.entrySet().iterator(); it.hasNext(); ) {
Pools.Pool<SimpleExoPlayer> pool = it.next().getValue();
SimpleExoPlayer item;
Pools.Pool<ExoPlayer> pool = it.next().getValue();
ExoPlayer 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);
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);
@ -204,90 +193,17 @@ public final class ToroExo {
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();
}
}
}
}
if (drmSessionManager == null) {
String error = TextUtils.isEmpty(subString) ? context.getString(errorStringId)
: context.getString(errorStringId) + ": " + subString;
Toast.makeText(context, error, LENGTH_SHORT).show();
}
return drmSessionManager;
}
@RequiresApi(18) private static DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManagerV18(
@NonNull UUID uuid, @Nullable String licenseUrl, @Nullable String[] keyRequestPropertiesArray,
boolean multiSession, @NonNull HttpDataSource.Factory httpDataSourceFactory)
throws UnsupportedDrmException {
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory);
if (keyRequestPropertiesArray != null) {
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
keyRequestPropertiesArray[i + 1]);
}
}
return new DefaultDrmSessionManager<>(uuid, FrameworkMediaDrm.newInstance(uuid), drmCallback,
null, multiSession);
}
// Share the code of setting Volume. For use inside library only.
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) //
public static void setVolumeInfo(@NonNull SimpleExoPlayer player,
public static void setVolumeInfo(@NonNull ToroExoPlayer player,
@NonNull VolumeInfo volumeInfo) {
if (player instanceof ToroExoPlayer) {
((ToroExoPlayer) player).setVolumeInfo(volumeInfo);
} else {
if (volumeInfo.isMute()) {
player.setVolume(0f);
} else {
player.setVolume(volumeInfo.getVolume());
}
}
player.setVolumeInfo(volumeInfo);
}
@SuppressWarnings("WeakerAccess") @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) //
public static VolumeInfo getVolumeInfo(SimpleExoPlayer player) {
if (player instanceof ToroExoPlayer) {
return new VolumeInfo(((ToroExoPlayer) player).getVolumeInfo());
} else {
float volume = player.getVolume();
return new VolumeInfo(volume == 0, volume);
}
@SuppressWarnings("WeakerAccess")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) //
public static VolumeInfo getVolumeInfo(ToroExoPlayer player) {
return new VolumeInfo(player.getVolumeInfo());
}
@SuppressWarnings("SameParameterValue")

View File

@ -23,13 +23,11 @@ import android.os.Looper;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
@ -41,13 +39,18 @@ import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
* @author eneim (2018/03/27).
*/
@SuppressWarnings("WeakerAccess") //
public class ToroExoPlayer extends SimpleExoPlayer {
public class ToroExoPlayer {
protected ToroExoPlayer(Context context, RenderersFactory renderersFactory,
private ExoPlayer player;
public ToroExoPlayer(Context context, RenderersFactory renderersFactory,
TrackSelector trackSelector, LoadControl loadControl, BandwidthMeter bandwidthMeter,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, Looper looper) {
super(context, renderersFactory, trackSelector, loadControl, bandwidthMeter, drmSessionManager,
looper);
Looper looper) {
player = new ExoPlayer.Builder(context).setRenderersFactory(renderersFactory).setTrackSelector(trackSelector).setLoadControl(loadControl).setBandwidthMeter(bandwidthMeter).setLooper(looper).build();
}
public ToroExoPlayer(ExoPlayer exoPlayer) {
this.player = exoPlayer;
}
private ToroPlayer.VolumeChangeListeners listeners;
@ -65,7 +68,8 @@ public class ToroExoPlayer extends SimpleExoPlayer {
if (this.listeners != null) this.listeners.clear();
}
@CallSuper @Override public void setVolume(float audioVolume) {
@CallSuper
public void setVolume(float audioVolume) {
this.setVolumeInfo(new VolumeInfo(audioVolume == 0, audioVolume));
}
@ -76,7 +80,7 @@ public class ToroExoPlayer extends SimpleExoPlayer {
boolean changed = !this.volumeInfo.equals(volumeInfo);
if (changed) {
this.volumeInfo.setTo(volumeInfo.isMute(), volumeInfo.getVolume());
super.setVolume(volumeInfo.isMute() ? 0 : volumeInfo.getVolume());
player.setVolume(volumeInfo.isMute() ? 0 : volumeInfo.getVolume());
if (listeners != null) {
for (ToroPlayer.OnVolumeChangeListener listener : this.listeners) {
listener.onVolumeChanged(volumeInfo);
@ -87,7 +91,13 @@ public class ToroExoPlayer extends SimpleExoPlayer {
return changed;
}
@SuppressWarnings("unused") @NonNull public final VolumeInfo getVolumeInfo() {
@SuppressWarnings("unused")
@NonNull
public final VolumeInfo getVolumeInfo() {
return volumeInfo;
}
public ExoPlayer getPlayer() {
return player;
}
}

View File

@ -34,7 +34,8 @@ import ml.docilealligator.infinityforreddit.videoautoplay.widget.Container;
public final class ToroUtil {
@SuppressWarnings("unused") private static final String TAG = "ToroLib:Util";
@SuppressWarnings("unused")
private static final String TAG = "ToroLib:Util";
private ToroUtil() {
throw new RuntimeException("Meh!");
@ -76,7 +77,8 @@ public final class ToroUtil {
* @return the non-null reference that was validated
* @throws NullPointerException if {@code reference} is null
*/
public static @NonNull <T> T checkNotNull(final T reference) {
public static @NonNull
<T> T checkNotNull(final T reference) {
if (reference == null) {
throw new NullPointerException();
}
@ -93,7 +95,8 @@ public final class ToroUtil {
* @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) {
public static @NonNull
<T> T checkNotNull(final T reference, final Object errorMessage) {
if (reference == null) {
throw new NullPointerException(String.valueOf(errorMessage));
}

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="enable_random_adaptation">Enable random adaptation</string>
<string name="error_drm_not_supported">Protected content not supported on API levels below 18</string>
<string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string>
<string name="error_drm_unknown">An unknown DRM error occurred</string>
<string name="error_no_decoder">This device does not provide a decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
<string name="error_no_secure_decoder">This device does not provide a secure decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
<string name="error_querying_decoders">Unable to query device decoders</string>
<string name="error_instantiating_decoder">Unable to instantiate decoder <xliff:g id="decoder_name">%1$s</xliff:g></string>
<string name="error_unsupported_video">Media includes video tracks, but none are playable by this device</string>
<string name="error_unsupported_audio">Media includes audio tracks, but none are playable by this device</string>
<string name="storage_permission_denied">Permission to access storage was denied</string>