Improve code style to be more consistent
This commit is contained in:
parent
819e52cab3
commit
fda5405e48
244 changed files with 10116 additions and 7222 deletions
|
|
@ -11,20 +11,19 @@ import android.content.ContextWrapper;
|
|||
* https://gist.github.com/jankovd/891d96f476f7a9ce24e2
|
||||
*/
|
||||
public class AudioServiceLeakFix extends ContextWrapper {
|
||||
AudioServiceLeakFix(final Context base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
AudioServiceLeakFix(Context base) {
|
||||
super(base);
|
||||
}
|
||||
public static ContextWrapper preventLeakOf(final Context base) {
|
||||
return new AudioServiceLeakFix(base);
|
||||
}
|
||||
|
||||
public static ContextWrapper preventLeakOf(Context base) {
|
||||
return new AudioServiceLeakFix(base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSystemService(String name) {
|
||||
if (Context.AUDIO_SERVICE.equals(name)) {
|
||||
return getApplicationContext().getSystemService(name);
|
||||
}
|
||||
return super.getSystemService(name);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Object getSystemService(final String name) {
|
||||
if (Context.AUDIO_SERVICE.equals(name)) {
|
||||
return getApplicationContext().getSystemService(name);
|
||||
}
|
||||
return super.getSystemService(name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,48 +61,49 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
|||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
/**
|
||||
* Base players joining the common properties
|
||||
* Service Background Player implementing {@link VideoPlayer}.
|
||||
*
|
||||
* @author mauriciocolli
|
||||
*/
|
||||
public final class BackgroundPlayer extends Service {
|
||||
private static final String TAG = "BackgroundPlayer";
|
||||
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||
|
||||
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
|
||||
public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
|
||||
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
|
||||
public static final String ACTION_PLAY_NEXT = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT";
|
||||
public static final String ACTION_PLAY_PREVIOUS = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS";
|
||||
public static final String ACTION_FAST_REWIND = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
|
||||
public static final String ACTION_FAST_FORWARD = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
|
||||
public static final String ACTION_CLOSE
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
|
||||
public static final String ACTION_PLAY_PAUSE
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
|
||||
public static final String ACTION_REPEAT
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
|
||||
public static final String ACTION_PLAY_NEXT
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT";
|
||||
public static final String ACTION_PLAY_PREVIOUS
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS";
|
||||
public static final String ACTION_FAST_REWIND
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
|
||||
public static final String ACTION_FAST_FORWARD
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
|
||||
|
||||
public static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
|
||||
|
||||
private static final String TAG = "BackgroundPlayer";
|
||||
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||
private static final int NOTIFICATION_ID = 123789;
|
||||
private static final int NOTIFICATION_UPDATES_BEFORE_RESET = 60;
|
||||
private BasePlayerImpl basePlayerImpl;
|
||||
private LockManager lockManager;
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Service-Activity Binder
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private PlayerEventListener activityListener;
|
||||
private IBinder mBinder;
|
||||
private LockManager lockManager;
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Notification
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static final int NOTIFICATION_ID = 123789;
|
||||
private PlayerEventListener activityListener;
|
||||
private IBinder mBinder;
|
||||
private NotificationManager notificationManager;
|
||||
private NotificationCompat.Builder notBuilder;
|
||||
private RemoteViews notRemoteView;
|
||||
private RemoteViews bigNotRemoteView;
|
||||
|
||||
private boolean shouldUpdateOnProgress;
|
||||
|
||||
private static final int NOTIFICATION_UPDATES_BEFORE_RESET = 60;
|
||||
private int timesNotificationUpdated;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -111,7 +112,9 @@ public final class BackgroundPlayer extends Service {
|
|||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
if (DEBUG) Log.d(TAG, "onCreate() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onCreate() called");
|
||||
}
|
||||
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
||||
lockManager = new LockManager(this);
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
|
|
@ -125,9 +128,11 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent +
|
||||
"], flags = [" + flags + "], startId = [" + startId + "]");
|
||||
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], "
|
||||
+ "flags = [" + flags + "], startId = [" + startId + "]");
|
||||
}
|
||||
basePlayerImpl.handleIntent(intent);
|
||||
if (basePlayerImpl.mediaSessionManager != null) {
|
||||
basePlayerImpl.mediaSessionManager.handleMediaButtonIntent(intent);
|
||||
|
|
@ -137,17 +142,19 @@ public final class BackgroundPlayer extends Service {
|
|||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (DEBUG) Log.d(TAG, "destroy() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "destroy() called");
|
||||
}
|
||||
onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
protected void attachBaseContext(final Context base) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
public IBinder onBind(final Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +162,9 @@ public final class BackgroundPlayer extends Service {
|
|||
// Actions
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private void onClose() {
|
||||
if (DEBUG) Log.d(TAG, "onClose() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onClose() called");
|
||||
}
|
||||
|
||||
if (lockManager != null) {
|
||||
lockManager.releaseWifiAndCpu();
|
||||
|
|
@ -165,7 +174,9 @@ public final class BackgroundPlayer extends Service {
|
|||
basePlayerImpl.stopActivityBinding();
|
||||
basePlayerImpl.destroy();
|
||||
}
|
||||
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
|
||||
if (notificationManager != null) {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
mBinder = null;
|
||||
basePlayerImpl = null;
|
||||
lockManager = null;
|
||||
|
|
@ -174,8 +185,10 @@ public final class BackgroundPlayer extends Service {
|
|||
stopSelf();
|
||||
}
|
||||
|
||||
private void onScreenOnOff(boolean on) {
|
||||
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
|
||||
private void onScreenOnOff(final boolean on) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
|
||||
}
|
||||
shouldUpdateOnProgress = on;
|
||||
basePlayerImpl.triggerProgressUpdate();
|
||||
if (on) {
|
||||
|
|
@ -196,12 +209,14 @@ public final class BackgroundPlayer extends Service {
|
|||
|
||||
private NotificationCompat.Builder createNotification() {
|
||||
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
|
||||
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded);
|
||||
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
||||
R.layout.player_notification_expanded);
|
||||
|
||||
setupNotification(notRemoteView);
|
||||
setupNotification(bigNotRemoteView);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
|
||||
NotificationCompat.Builder builder = new NotificationCompat
|
||||
.Builder(this, getString(R.string.notification_channel_id))
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
|
|
@ -219,11 +234,9 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
private void setLockScreenThumbnail(NotificationCompat.Builder builder) {
|
||||
private void setLockScreenThumbnail(final NotificationCompat.Builder builder) {
|
||||
boolean isLockScreenThumbnailEnabled = sharedPreferences.getBoolean(
|
||||
getString(R.string.enable_lock_screen_video_thumbnail_key),
|
||||
true
|
||||
);
|
||||
getString(R.string.enable_lock_screen_video_thumbnail_key), true);
|
||||
|
||||
if (isLockScreenThumbnailEnabled) {
|
||||
basePlayerImpl.mediaSessionManager.setLockScreenArt(
|
||||
|
|
@ -237,47 +250,58 @@ public final class BackgroundPlayer extends Service {
|
|||
|
||||
@Nullable
|
||||
private Bitmap getCenteredThumbnailBitmap() {
|
||||
int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
|
||||
int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
|
||||
final int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
|
||||
final int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
|
||||
|
||||
return BitmapUtils.centerCrop(
|
||||
basePlayerImpl.getThumbnail(),
|
||||
screenWidth,
|
||||
screenHeight);
|
||||
return BitmapUtils.centerCrop(basePlayerImpl.getThumbnail(), screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
private void setupNotification(RemoteViews remoteViews) {
|
||||
if (basePlayerImpl == null) return;
|
||||
private void setupNotification(final RemoteViews remoteViews) {
|
||||
if (basePlayerImpl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
|
||||
remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName());
|
||||
|
||||
remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||
new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
remoteViews.setOnClickPendingIntent(R.id.notificationStop,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||
new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||
new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
|
||||
// Starts background player activity -- attempts to unlock lockscreen
|
||||
final Intent intent = NavigationHelper.getBackgroundPlayerActivityIntent(this);
|
||||
remoteViews.setOnClickPendingIntent(R.id.notificationContent,
|
||||
PendingIntent.getActivity(this, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getActivity(this, NOTIFICATION_ID, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
|
||||
if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) {
|
||||
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_previous);
|
||||
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_next);
|
||||
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
|
||||
R.drawable.exo_controls_previous);
|
||||
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
|
||||
R.drawable.exo_controls_next);
|
||||
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||
new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||
new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
} else {
|
||||
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_rewind);
|
||||
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_fastforward);
|
||||
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
|
||||
R.drawable.exo_controls_rewind);
|
||||
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
|
||||
R.drawable.exo_controls_fastforward);
|
||||
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||
new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
|
||||
new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
}
|
||||
|
||||
setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode());
|
||||
|
|
@ -289,14 +313,20 @@ public final class BackgroundPlayer extends Service {
|
|||
*
|
||||
* @param drawableId if != -1, sets the drawable with that id on the play/pause button
|
||||
*/
|
||||
private synchronized void updateNotification(int drawableId) {
|
||||
//if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
|
||||
if (notBuilder == null) return;
|
||||
private synchronized void updateNotification(final int drawableId) {
|
||||
// if (DEBUG) {
|
||||
// Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
|
||||
// }
|
||||
if (notBuilder == null) {
|
||||
return;
|
||||
}
|
||||
if (drawableId != -1) {
|
||||
if (notRemoteView != null)
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
||||
if (bigNotRemoteView != null)
|
||||
}
|
||||
if (bigNotRemoteView != null) {
|
||||
bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
||||
}
|
||||
}
|
||||
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
|
||||
timesNotificationUpdated++;
|
||||
|
|
@ -309,44 +339,48 @@ public final class BackgroundPlayer extends Service {
|
|||
private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) {
|
||||
switch (repeatMode) {
|
||||
case Player.REPEAT_MODE_OFF:
|
||||
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_off);
|
||||
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
|
||||
R.drawable.exo_controls_repeat_off);
|
||||
break;
|
||||
case Player.REPEAT_MODE_ONE:
|
||||
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_one);
|
||||
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
|
||||
R.drawable.exo_controls_repeat_one);
|
||||
break;
|
||||
case Player.REPEAT_MODE_ALL:
|
||||
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_all);
|
||||
remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD,
|
||||
R.drawable.exo_controls_repeat_all);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected class BasePlayerImpl extends BasePlayer {
|
||||
|
||||
@NonNull
|
||||
final private AudioPlaybackResolver resolver;
|
||||
private final AudioPlaybackResolver resolver;
|
||||
private int cachedDuration;
|
||||
private String cachedDurationString;
|
||||
|
||||
BasePlayerImpl(Context context) {
|
||||
BasePlayerImpl(final Context context) {
|
||||
super(context);
|
||||
this.resolver = new AudioPlaybackResolver(context, dataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initPlayer(boolean playOnReady) {
|
||||
public void initPlayer(final boolean playOnReady) {
|
||||
super.initPlayer(playOnReady);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleIntent(final Intent intent) {
|
||||
super.handleIntent(intent);
|
||||
|
||||
|
||||
resetNotification();
|
||||
if (bigNotRemoteView != null)
|
||||
if (bigNotRemoteView != null) {
|
||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
|
||||
if (notRemoteView != null)
|
||||
}
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
|
||||
}
|
||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
||||
}
|
||||
|
||||
|
|
@ -355,7 +389,9 @@ public final class BackgroundPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void updateNotificationThumbnail() {
|
||||
if (basePlayerImpl == null) return;
|
||||
if (basePlayerImpl == null) {
|
||||
return;
|
||||
}
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setImageViewBitmap(R.id.notificationCover,
|
||||
basePlayerImpl.getThumbnail());
|
||||
|
|
@ -367,7 +403,8 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
public void onLoadingComplete(final String imageUri, final View view,
|
||||
final Bitmap loadedImage) {
|
||||
super.onLoadingComplete(imageUri, view, loadedImage);
|
||||
resetNotification();
|
||||
updateNotificationThumbnail();
|
||||
|
|
@ -375,7 +412,8 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||
public void onLoadingFailed(final String imageUri, final View view,
|
||||
final FailReason failReason) {
|
||||
super.onLoadingFailed(imageUri, view, failReason);
|
||||
resetNotification();
|
||||
updateNotificationThumbnail();
|
||||
|
|
@ -387,7 +425,7 @@ public final class BackgroundPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onPrepared(boolean playWhenReady) {
|
||||
public void onPrepared(final boolean playWhenReady) {
|
||||
super.onPrepared(playWhenReady);
|
||||
}
|
||||
|
||||
|
|
@ -404,10 +442,13 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
|
||||
public void onUpdateProgress(final int currentProgress, final int duration,
|
||||
final int bufferPercent) {
|
||||
updateProgress(currentProgress, duration, bufferPercent);
|
||||
|
||||
if (!shouldUpdateOnProgress) return;
|
||||
if (!shouldUpdateOnProgress) {
|
||||
return;
|
||||
}
|
||||
if (timesNotificationUpdated > NOTIFICATION_UPDATES_BEFORE_RESET) {
|
||||
resetNotification();
|
||||
|
||||
|
|
@ -420,11 +461,14 @@ public final class BackgroundPlayer extends Service {
|
|||
cachedDuration = duration;
|
||||
cachedDurationString = getTimeString(duration);
|
||||
}
|
||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
|
||||
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + cachedDurationString);
|
||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration,
|
||||
currentProgress, false);
|
||||
bigNotRemoteView.setTextViewText(R.id.notificationTime,
|
||||
getTimeString(currentProgress) + " / " + cachedDurationString);
|
||||
}
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
|
||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration,
|
||||
currentProgress, false);
|
||||
}
|
||||
updateNotification(-1);
|
||||
}
|
||||
|
|
@ -444,10 +488,12 @@ public final class BackgroundPlayer extends Service {
|
|||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
if (notRemoteView != null)
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
|
||||
if (bigNotRemoteView != null)
|
||||
}
|
||||
if (bigNotRemoteView != null) {
|
||||
bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, null);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -455,18 +501,18 @@ public final class BackgroundPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||
public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
|
||||
super.onPlaybackParametersChanged(playbackParameters);
|
||||
updatePlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingChanged(boolean isLoading) {
|
||||
public void onLoadingChanged(final boolean isLoading) {
|
||||
// Disable default behavior
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(int i) {
|
||||
public void onRepeatModeChanged(final int i) {
|
||||
resetNotification();
|
||||
updateNotification(-1);
|
||||
updatePlayback();
|
||||
|
|
@ -500,14 +546,14 @@ public final class BackgroundPlayer extends Service {
|
|||
// Activity Event Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/*package-private*/ void setActivityListener(PlayerEventListener listener) {
|
||||
/*package-private*/ void setActivityListener(final PlayerEventListener listener) {
|
||||
activityListener = listener;
|
||||
updateMetadata();
|
||||
updatePlayback();
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
/*package-private*/ void removeActivityListener(PlayerEventListener listener) {
|
||||
/*package-private*/ void removeActivityListener(final PlayerEventListener listener) {
|
||||
if (activityListener == listener) {
|
||||
activityListener = null;
|
||||
}
|
||||
|
|
@ -526,7 +572,8 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
private void updateProgress(int currentProgress, int duration, int bufferPercent) {
|
||||
private void updateProgress(final int currentProgress, final int duration,
|
||||
final int bufferPercent) {
|
||||
if (activityListener != null) {
|
||||
activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
|
||||
}
|
||||
|
|
@ -544,27 +591,31 @@ public final class BackgroundPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected void setupBroadcastReceiver(IntentFilter intentFilter) {
|
||||
super.setupBroadcastReceiver(intentFilter);
|
||||
intentFilter.addAction(ACTION_CLOSE);
|
||||
intentFilter.addAction(ACTION_PLAY_PAUSE);
|
||||
intentFilter.addAction(ACTION_REPEAT);
|
||||
intentFilter.addAction(ACTION_PLAY_PREVIOUS);
|
||||
intentFilter.addAction(ACTION_PLAY_NEXT);
|
||||
intentFilter.addAction(ACTION_FAST_REWIND);
|
||||
intentFilter.addAction(ACTION_FAST_FORWARD);
|
||||
protected void setupBroadcastReceiver(final IntentFilter intentFltr) {
|
||||
super.setupBroadcastReceiver(intentFltr);
|
||||
intentFltr.addAction(ACTION_CLOSE);
|
||||
intentFltr.addAction(ACTION_PLAY_PAUSE);
|
||||
intentFltr.addAction(ACTION_REPEAT);
|
||||
intentFltr.addAction(ACTION_PLAY_PREVIOUS);
|
||||
intentFltr.addAction(ACTION_PLAY_NEXT);
|
||||
intentFltr.addAction(ACTION_FAST_REWIND);
|
||||
intentFltr.addAction(ACTION_FAST_FORWARD);
|
||||
|
||||
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
|
||||
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
intentFltr.addAction(Intent.ACTION_SCREEN_ON);
|
||||
intentFltr.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
|
||||
intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
|
||||
intentFltr.addAction(Intent.ACTION_HEADSET_PLUG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBroadcastReceived(Intent intent) {
|
||||
public void onBroadcastReceived(final Intent intent) {
|
||||
super.onBroadcastReceived(intent);
|
||||
if (intent == null || intent.getAction() == null) return;
|
||||
if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
|
||||
if (intent == null || intent.getAction() == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
|
||||
}
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_CLOSE:
|
||||
onClose();
|
||||
|
|
@ -601,7 +652,7 @@ public final class BackgroundPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void changeState(int state) {
|
||||
public void changeState(final int state) {
|
||||
super.changeState(state);
|
||||
updatePlayback();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onPlayerOptionSelected(MenuItem item) {
|
||||
public boolean onPlayerOptionSelected(final MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_switch_popup) {
|
||||
|
||||
if (!PermissionHelper.isPopupEnabled(this)) {
|
||||
|
|
@ -58,8 +58,8 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
|||
this.player.setRecovery();
|
||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
||||
getApplicationContext().startService(
|
||||
getSwitchIntent(PopupVideoPlayer.class)
|
||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
|
||||
getSwitchIntent(PopupVideoPlayer.class)
|
||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -34,17 +34,6 @@ import android.os.Bundle;
|
|||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
|
|
@ -64,6 +53,15 @@ import android.widget.SeekBar;
|
|||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
|
|
@ -104,7 +102,7 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|||
import static org.schabi.newpipe.util.StateSaver.KEY_SAVED_STATE;
|
||||
|
||||
/**
|
||||
* Activity Player implementing VideoPlayer
|
||||
* Activity Player implementing {@link VideoPlayer}.
|
||||
*
|
||||
* @author mauriciocolli
|
||||
*/
|
||||
|
|
@ -131,16 +129,19 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
assureCorrectAppLanguage(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onCreate() called with: "
|
||||
+ "savedInstanceState = [" + savedInstanceState + "]");
|
||||
}
|
||||
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
ThemeHelper.setTheme(this);
|
||||
getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
getWindow().setStatusBarColor(Color.BLACK);
|
||||
}
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
|
||||
WindowManager.LayoutParams lp = getWindow().getAttributes();
|
||||
|
|
@ -166,11 +167,11 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
rotationObserver = new ContentObserver(new Handler()) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
public void onChange(final boolean selfChange) {
|
||||
super.onChange(selfChange);
|
||||
if (globalScreenOrientationLocked()) {
|
||||
final boolean lastOrientationWasLandscape = defaultPreferences.getBoolean(
|
||||
getString(R.string.last_orientation_landscape_key), false);
|
||||
final boolean lastOrientationWasLandscape = defaultPreferences
|
||||
.getBoolean(getString(R.string.last_orientation_landscape_key), false);
|
||||
setLandscape(lastOrientationWasLandscape);
|
||||
} else {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
|
|
@ -183,15 +184,19 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle bundle) {
|
||||
if (DEBUG) Log.d(TAG, "onRestoreInstanceState() called");
|
||||
protected void onRestoreInstanceState(@NonNull final Bundle bundle) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onRestoreInstanceState() called");
|
||||
}
|
||||
super.onRestoreInstanceState(bundle);
|
||||
StateSaver.tryToRestore(bundle, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
|
||||
protected void onNewIntent(final Intent intent) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
|
||||
}
|
||||
super.onNewIntent(intent);
|
||||
if (intent != null) {
|
||||
playerState = null;
|
||||
|
|
@ -201,13 +206,15 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
if (DEBUG) Log.d(TAG, "onResume() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onResume() called");
|
||||
}
|
||||
assureCorrectAppLanguage(this);
|
||||
super.onResume();
|
||||
|
||||
if (globalScreenOrientationLocked()) {
|
||||
boolean lastOrientationWasLandscape = defaultPreferences.getBoolean(
|
||||
getString(R.string.last_orientation_landscape_key), false);
|
||||
boolean lastOrientationWasLandscape = defaultPreferences
|
||||
.getBoolean(getString(R.string.last_orientation_landscape_key), false);
|
||||
setLandscape(lastOrientationWasLandscape);
|
||||
}
|
||||
|
||||
|
|
@ -219,19 +226,22 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
// since the first onResume needs to restore the player.
|
||||
// Subsequent onResume calls while multiwindow mode remains the same and the player is
|
||||
// prepared should be ignored.
|
||||
if (isInMultiWindow) return;
|
||||
if (isInMultiWindow) {
|
||||
return;
|
||||
}
|
||||
isInMultiWindow = isInMultiWindow();
|
||||
|
||||
if (playerState != null) {
|
||||
playerImpl.setPlaybackQuality(playerState.getPlaybackQuality());
|
||||
playerImpl.initPlayback(playerState.getPlayQueue(), playerState.getRepeatMode(),
|
||||
playerState.getPlaybackSpeed(), playerState.getPlaybackPitch(),
|
||||
playerState.isPlaybackSkipSilence(), playerState.wasPlaying(), playerImpl.isMuted());
|
||||
playerState.isPlaybackSkipSilence(), playerState.wasPlaying(),
|
||||
playerImpl.isMuted());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
public void onConfigurationChanged(final Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
assureCorrectAppLanguage(this);
|
||||
|
||||
|
|
@ -248,10 +258,14 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
if (DEBUG) Log.d(TAG, "onSaveInstanceState() called");
|
||||
protected void onSaveInstanceState(final Bundle outState) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onSaveInstanceState() called");
|
||||
}
|
||||
super.onSaveInstanceState(outState);
|
||||
if (playerImpl == null) return;
|
||||
if (playerImpl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
playerImpl.setRecovery();
|
||||
if (!playerImpl.gotDestroyed()) {
|
||||
|
|
@ -262,27 +276,32 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
if (DEBUG) Log.d(TAG, "onStop() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStop() called");
|
||||
}
|
||||
super.onStop();
|
||||
PlayerHelper.setScreenBrightness(getApplicationContext(),
|
||||
getWindow().getAttributes().screenBrightness);
|
||||
|
||||
if (playerImpl == null) return;
|
||||
if (playerImpl == null) {
|
||||
return;
|
||||
}
|
||||
if (!isBackPressed) {
|
||||
playerImpl.minimize();
|
||||
}
|
||||
playerState = createPlayerState();
|
||||
playerImpl.destroy();
|
||||
|
||||
if (rotationObserver != null)
|
||||
if (rotationObserver != null) {
|
||||
getContentResolver().unregisterContentObserver(rotationObserver);
|
||||
}
|
||||
|
||||
isInMultiWindow = false;
|
||||
isBackPressed = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context newBase) {
|
||||
protected void attachBaseContext(final Context newBase) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(newBase));
|
||||
}
|
||||
|
||||
|
|
@ -309,14 +328,16 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(Queue<Object> objectsToSave) {
|
||||
if (objectsToSave == null) return;
|
||||
public void writeTo(final Queue<Object> objectsToSave) {
|
||||
if (objectsToSave == null) {
|
||||
return;
|
||||
}
|
||||
objectsToSave.add(playerState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readFrom(@NonNull Queue<Object> savedObjects) {
|
||||
public void readFrom(@NonNull final Queue<Object> savedObjects) {
|
||||
playerState = (PlayerState) savedObjects.poll();
|
||||
}
|
||||
|
||||
|
|
@ -325,8 +346,12 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void showSystemUi() {
|
||||
if (DEBUG) Log.d(TAG, "showSystemUi() called");
|
||||
if (playerImpl != null && playerImpl.queueVisible) return;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "showSystemUi() called");
|
||||
}
|
||||
if (playerImpl != null && playerImpl.queueVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
|
|
@ -344,7 +369,9 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
private void hideSystemUi() {
|
||||
if (DEBUG) Log.d(TAG, "hideSystemUi() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "hideSystemUi() called");
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
|
|
@ -368,10 +395,11 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
private boolean isLandscape() {
|
||||
return getResources().getDisplayMetrics().heightPixels < getResources().getDisplayMetrics().widthPixels;
|
||||
return getResources().getDisplayMetrics().heightPixels
|
||||
< getResources().getDisplayMetrics().widthPixels;
|
||||
}
|
||||
|
||||
private void setLandscape(boolean v) {
|
||||
private void setLandscape(final boolean v) {
|
||||
setRequestedOrientation(v
|
||||
? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
: ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
|
||||
|
|
@ -380,7 +408,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
private boolean globalScreenOrientationLocked() {
|
||||
// 1: Screen orientation changes using accelerometer
|
||||
// 0: Screen orientation is locked
|
||||
return !(android.provider.Settings.System.getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 1);
|
||||
return !(android.provider.Settings.System
|
||||
.getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 1);
|
||||
}
|
||||
|
||||
protected void setRepeatModeButton(final ImageButton imageButton, final int repeatMode) {
|
||||
|
|
@ -403,8 +432,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
protected void setMuteButton(final ImageButton muteButton, final boolean isMuted) {
|
||||
muteButton.setImageDrawable(AppCompatResources.getDrawable(getApplicationContext(),
|
||||
isMuted ? R.drawable.ic_volume_off_white_72dp : R.drawable.ic_volume_up_white_72dp));
|
||||
muteButton.setImageDrawable(AppCompatResources.getDrawable(getApplicationContext(), isMuted
|
||||
? R.drawable.ic_volume_off_white_72dp : R.drawable.ic_volume_up_white_72dp));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -417,8 +446,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
|
||||
boolean playbackSkipSilence) {
|
||||
public void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch,
|
||||
final boolean playbackSkipSilence) {
|
||||
if (playerImpl != null) {
|
||||
playerImpl.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
|
||||
}
|
||||
|
|
@ -428,7 +457,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
private class VideoPlayerImpl extends VideoPlayer {
|
||||
private final float MAX_GESTURE_LENGTH = 0.75f;
|
||||
private static final float MAX_GESTURE_LENGTH = 0.75f;
|
||||
|
||||
private TextView titleTextView;
|
||||
private TextView channelTextView;
|
||||
|
|
@ -472,33 +501,33 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public void initViews(View rootView) {
|
||||
super.initViews(rootView);
|
||||
this.titleTextView = rootView.findViewById(R.id.titleTextView);
|
||||
this.channelTextView = rootView.findViewById(R.id.channelTextView);
|
||||
this.volumeRelativeLayout = rootView.findViewById(R.id.volumeRelativeLayout);
|
||||
this.volumeProgressBar = rootView.findViewById(R.id.volumeProgressBar);
|
||||
this.volumeImageView = rootView.findViewById(R.id.volumeImageView);
|
||||
this.brightnessRelativeLayout = rootView.findViewById(R.id.brightnessRelativeLayout);
|
||||
this.brightnessProgressBar = rootView.findViewById(R.id.brightnessProgressBar);
|
||||
this.brightnessImageView = rootView.findViewById(R.id.brightnessImageView);
|
||||
this.queueButton = rootView.findViewById(R.id.queueButton);
|
||||
this.repeatButton = rootView.findViewById(R.id.repeatButton);
|
||||
this.shuffleButton = rootView.findViewById(R.id.shuffleButton);
|
||||
public void initViews(final View view) {
|
||||
super.initViews(view);
|
||||
this.titleTextView = view.findViewById(R.id.titleTextView);
|
||||
this.channelTextView = view.findViewById(R.id.channelTextView);
|
||||
this.volumeRelativeLayout = view.findViewById(R.id.volumeRelativeLayout);
|
||||
this.volumeProgressBar = view.findViewById(R.id.volumeProgressBar);
|
||||
this.volumeImageView = view.findViewById(R.id.volumeImageView);
|
||||
this.brightnessRelativeLayout = view.findViewById(R.id.brightnessRelativeLayout);
|
||||
this.brightnessProgressBar = view.findViewById(R.id.brightnessProgressBar);
|
||||
this.brightnessImageView = view.findViewById(R.id.brightnessImageView);
|
||||
this.queueButton = view.findViewById(R.id.queueButton);
|
||||
this.repeatButton = view.findViewById(R.id.repeatButton);
|
||||
this.shuffleButton = view.findViewById(R.id.shuffleButton);
|
||||
|
||||
this.playPauseButton = rootView.findViewById(R.id.playPauseButton);
|
||||
this.playPreviousButton = rootView.findViewById(R.id.playPreviousButton);
|
||||
this.playNextButton = rootView.findViewById(R.id.playNextButton);
|
||||
this.closeButton = rootView.findViewById(R.id.closeButton);
|
||||
this.playPauseButton = view.findViewById(R.id.playPauseButton);
|
||||
this.playPreviousButton = view.findViewById(R.id.playPreviousButton);
|
||||
this.playNextButton = view.findViewById(R.id.playNextButton);
|
||||
this.closeButton = view.findViewById(R.id.closeButton);
|
||||
|
||||
this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton);
|
||||
this.secondaryControls = rootView.findViewById(R.id.secondaryControls);
|
||||
this.kodiButton = rootView.findViewById(R.id.kodi);
|
||||
this.shareButton = rootView.findViewById(R.id.share);
|
||||
this.toggleOrientationButton = rootView.findViewById(R.id.toggleOrientation);
|
||||
this.switchBackgroundButton = rootView.findViewById(R.id.switchBackground);
|
||||
this.muteButton = rootView.findViewById(R.id.switchMute);
|
||||
this.switchPopupButton = rootView.findViewById(R.id.switchPopup);
|
||||
this.moreOptionsButton = view.findViewById(R.id.moreOptionsButton);
|
||||
this.secondaryControls = view.findViewById(R.id.secondaryControls);
|
||||
this.kodiButton = view.findViewById(R.id.kodi);
|
||||
this.shareButton = view.findViewById(R.id.share);
|
||||
this.toggleOrientationButton = view.findViewById(R.id.toggleOrientation);
|
||||
this.switchBackgroundButton = view.findViewById(R.id.switchBackground);
|
||||
this.muteButton = view.findViewById(R.id.switchMute);
|
||||
this.switchPopupButton = view.findViewById(R.id.switchPopup);
|
||||
|
||||
this.queueLayout = findViewById(R.id.playQueuePanel);
|
||||
this.itemsListCloseButton = findViewById(R.id.playQueueClose);
|
||||
|
|
@ -506,15 +535,15 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
titleTextView.setSelected(true);
|
||||
channelTextView.setSelected(true);
|
||||
boolean showKodiButton = PreferenceManager.getDefaultSharedPreferences(this.context).getBoolean(
|
||||
this.context.getString(R.string.show_play_with_kodi_key), false);
|
||||
boolean showKodiButton = PreferenceManager.getDefaultSharedPreferences(this.context)
|
||||
.getBoolean(this.context.getString(R.string.show_play_with_kodi_key), false);
|
||||
kodiButton.setVisibility(showKodiButton ? View.VISIBLE : View.GONE);
|
||||
|
||||
getRootView().setKeepScreenOn(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupSubtitleView(@NonNull SubtitleView view,
|
||||
protected void setupSubtitleView(@NonNull final SubtitleView view,
|
||||
final float captionScale,
|
||||
@NonNull final CaptionStyleCompat captionStyle) {
|
||||
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
|
|
@ -556,10 +585,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
if (l != ol || t != ot || r != or || b != ob) {
|
||||
// Use smaller value to be consistent between screen orientations
|
||||
// (and to make usage easier)
|
||||
int width = r - l, height = b - t;
|
||||
int width = r - l;
|
||||
int height = b - t;
|
||||
maxGestureLength = (int) (Math.min(width, height) * MAX_GESTURE_LENGTH);
|
||||
|
||||
if (DEBUG) Log.d(TAG, "maxGestureLength = " + maxGestureLength);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "maxGestureLength = " + maxGestureLength);
|
||||
}
|
||||
|
||||
volumeProgressBar.setMax(maxGestureLength);
|
||||
brightnessProgressBar.setMax(maxGestureLength);
|
||||
|
|
@ -571,11 +603,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
queueLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
|
||||
public WindowInsets onApplyWindowInsets(final View view,
|
||||
final WindowInsets windowInsets) {
|
||||
final DisplayCutout cutout = windowInsets.getDisplayCutout();
|
||||
if (cutout != null)
|
||||
if (cutout != null) {
|
||||
view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
|
||||
cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
|
||||
}
|
||||
return windowInsets;
|
||||
}
|
||||
});
|
||||
|
|
@ -602,7 +636,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(int i) {
|
||||
public void onRepeatModeChanged(final int i) {
|
||||
super.onRepeatModeChanged(i);
|
||||
updatePlaybackButtons();
|
||||
}
|
||||
|
|
@ -633,10 +667,12 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
public void onKodiShare() {
|
||||
onPause();
|
||||
try {
|
||||
NavigationHelper.playWithKore(this.context, Uri.parse(
|
||||
playerImpl.getVideoUrl().replace("https", "http")));
|
||||
NavigationHelper.playWithKore(this.context,
|
||||
Uri.parse(playerImpl.getVideoUrl().replace("https", "http")));
|
||||
} catch (Exception e) {
|
||||
if (DEBUG) Log.i(TAG, "Failed to start kore", e);
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Failed to start kore", e);
|
||||
}
|
||||
KoreUtil.showInstallKoreDialog(this.context);
|
||||
}
|
||||
}
|
||||
|
|
@ -649,8 +685,12 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
public void onFullScreenButtonClicked() {
|
||||
super.onFullScreenButtonClicked();
|
||||
|
||||
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
|
||||
if (simpleExoPlayer == null) return;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onFullScreenButtonClicked() called");
|
||||
}
|
||||
if (simpleExoPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PermissionHelper.isPopupEnabled(context)) {
|
||||
PermissionHelper.showPopupEnablementToast(context);
|
||||
|
|
@ -679,8 +719,12 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
public void onPlayBackgroundButtonClicked() {
|
||||
if (DEBUG) Log.d(TAG, "onPlayBackgroundButtonClicked() called");
|
||||
if (playerImpl.getPlayer() == null) return;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onPlayBackgroundButtonClicked() called");
|
||||
}
|
||||
if (playerImpl.getPlayer() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRecovery();
|
||||
final Intent intent = NavigationHelper.getPlayerIntent(
|
||||
|
|
@ -711,17 +755,14 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
public void onClick(final View v) {
|
||||
super.onClick(v);
|
||||
if (v.getId() == playPauseButton.getId()) {
|
||||
onPlayPause();
|
||||
|
||||
} else if (v.getId() == playPreviousButton.getId()) {
|
||||
onPlayPrevious();
|
||||
|
||||
} else if (v.getId() == playNextButton.getId()) {
|
||||
onPlayNext();
|
||||
|
||||
} else if (v.getId() == queueButton.getId()) {
|
||||
onQueueClicked();
|
||||
return;
|
||||
|
|
@ -733,22 +774,16 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
return;
|
||||
} else if (v.getId() == moreOptionsButton.getId()) {
|
||||
onMoreOptionsClicked();
|
||||
|
||||
} else if (v.getId() == shareButton.getId()) {
|
||||
onShareClicked();
|
||||
|
||||
} else if (v.getId() == toggleOrientationButton.getId()) {
|
||||
onScreenRotationClicked();
|
||||
|
||||
} else if (v.getId() == switchPopupButton.getId()) {
|
||||
onFullScreenButtonClicked();
|
||||
|
||||
} else if (v.getId() == switchBackgroundButton.getId()) {
|
||||
onPlayBackgroundButtonClicked();
|
||||
|
||||
} else if (v.getId() == muteButton.getId()) {
|
||||
onMuteUnmuteButtonClicked();
|
||||
|
||||
} else if (v.getId() == closeButton.getId()) {
|
||||
onPlaybackShutdown();
|
||||
return;
|
||||
|
|
@ -774,22 +809,23 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
updatePlaybackButtons();
|
||||
|
||||
getControlsRoot().setVisibility(View.INVISIBLE);
|
||||
animateView(queueLayout, SLIDE_AND_ALPHA, /*visible=*/true,
|
||||
DEFAULT_CONTROLS_DURATION);
|
||||
animateView(queueLayout, SLIDE_AND_ALPHA, true, DEFAULT_CONTROLS_DURATION);
|
||||
|
||||
itemsList.scrollToPosition(playQueue.getIndex());
|
||||
}
|
||||
|
||||
private void onQueueClosed() {
|
||||
animateView(queueLayout, SLIDE_AND_ALPHA, /*visible=*/false,
|
||||
DEFAULT_CONTROLS_DURATION);
|
||||
animateView(queueLayout, SLIDE_AND_ALPHA, false, DEFAULT_CONTROLS_DURATION);
|
||||
queueVisible = false;
|
||||
}
|
||||
|
||||
private void onMoreOptionsClicked() {
|
||||
if (DEBUG) Log.d(TAG, "onMoreOptionsClicked() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onMoreOptionsClicked() called");
|
||||
}
|
||||
|
||||
final boolean isMoreControlsVisible = secondaryControls.getVisibility() == View.VISIBLE;
|
||||
final boolean isMoreControlsVisible
|
||||
= secondaryControls.getVisibility() == View.VISIBLE;
|
||||
|
||||
animateRotation(moreOptionsButton, DEFAULT_CONTROLS_DURATION,
|
||||
isMoreControlsVisible ? 0 : 180);
|
||||
|
|
@ -801,13 +837,15 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
private void onShareClicked() {
|
||||
// share video at the current time (youtube.com/watch?v=ID&t=SECONDS)
|
||||
ShareUtils.shareUrl(MainVideoPlayer.this,
|
||||
playerImpl.getVideoTitle(),
|
||||
playerImpl.getVideoUrl() + "&t=" + String.valueOf(playerImpl.getPlaybackSeekBar().getProgress() / 1000));
|
||||
ShareUtils.shareUrl(MainVideoPlayer.this, playerImpl.getVideoTitle(),
|
||||
playerImpl.getVideoUrl()
|
||||
+ "&t=" + playerImpl.getPlaybackSeekBar().getProgress() / 1000);
|
||||
}
|
||||
|
||||
private void onScreenRotationClicked() {
|
||||
if (DEBUG) Log.d(TAG, "onScreenRotationClicked() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScreenRotationClicked() called");
|
||||
}
|
||||
toggleOrientation();
|
||||
showControlsThenHide();
|
||||
}
|
||||
|
|
@ -820,20 +858,24 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
super.onStopTrackingTouch(seekBar);
|
||||
if (wasPlaying()) showControlsThenHide();
|
||||
if (wasPlaying()) {
|
||||
showControlsThenHide();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(PopupMenu menu) {
|
||||
public void onDismiss(final PopupMenu menu) {
|
||||
super.onDismiss(menu);
|
||||
if (isPlaying()) hideControls(DEFAULT_CONTROLS_DURATION, 0);
|
||||
if (isPlaying()) {
|
||||
hideControls(DEFAULT_CONTROLS_DURATION, 0);
|
||||
}
|
||||
hideSystemUi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int nextResizeMode(int currentResizeMode) {
|
||||
protected int nextResizeMode(final int currentResizeMode) {
|
||||
final int newResizeMode;
|
||||
switch (currentResizeMode) {
|
||||
case AspectRatioFrameLayout.RESIZE_MODE_FIT:
|
||||
|
|
@ -851,7 +893,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
return newResizeMode;
|
||||
}
|
||||
|
||||
private void storeResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) {
|
||||
private void storeResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
|
||||
defaultPreferences.edit()
|
||||
.putInt(getString(R.string.last_resize_mode), resizeMode)
|
||||
.apply();
|
||||
|
|
@ -861,13 +903,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
|
||||
return new VideoPlaybackResolver.QualityResolver() {
|
||||
@Override
|
||||
public int getDefaultResolutionIndex(List<VideoStream> sortedVideos) {
|
||||
public int getDefaultResolutionIndex(final List<VideoStream> sortedVideos) {
|
||||
return ListHelper.getDefaultResolutionIndex(context, sortedVideos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOverrideResolutionIndex(List<VideoStream> sortedVideos,
|
||||
String playbackQuality) {
|
||||
public int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
||||
final String playbackQuality) {
|
||||
return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality);
|
||||
}
|
||||
};
|
||||
|
|
@ -948,40 +990,52 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
private void setInitialGestureValues() {
|
||||
if (getAudioReactor() != null) {
|
||||
final float currentVolumeNormalized = (float) getAudioReactor().getVolume() / getAudioReactor().getMaxVolume();
|
||||
volumeProgressBar.setProgress((int) (volumeProgressBar.getMax() * currentVolumeNormalized));
|
||||
final float currentVolumeNormalized
|
||||
= (float) getAudioReactor().getVolume() / getAudioReactor().getMaxVolume();
|
||||
volumeProgressBar.setProgress(
|
||||
(int) (volumeProgressBar.getMax() * currentVolumeNormalized));
|
||||
}
|
||||
|
||||
float screenBrightness = getWindow().getAttributes().screenBrightness;
|
||||
if (screenBrightness < 0)
|
||||
if (screenBrightness < 0) {
|
||||
screenBrightness = Settings.System.getInt(getContentResolver(),
|
||||
Settings.System.SCREEN_BRIGHTNESS, 0) / 255.0f;
|
||||
}
|
||||
|
||||
brightnessProgressBar.setProgress((int) (brightnessProgressBar.getMax() * screenBrightness));
|
||||
brightnessProgressBar.setProgress(
|
||||
(int) (brightnessProgressBar.getMax() * screenBrightness));
|
||||
|
||||
if (DEBUG) Log.d(TAG, "setInitialGestureValues: volumeProgressBar.getProgress() ["
|
||||
+ volumeProgressBar.getProgress() + "] "
|
||||
+ "brightnessProgressBar.getProgress() ["
|
||||
+ brightnessProgressBar.getProgress() + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setInitialGestureValues: volumeProgressBar.getProgress() ["
|
||||
+ volumeProgressBar.getProgress() + "] "
|
||||
+ "brightnessProgressBar.getProgress() ["
|
||||
+ brightnessProgressBar.getProgress() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showControlsThenHide() {
|
||||
if (queueVisible) return;
|
||||
if (queueVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.showControlsThenHide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showControls(long duration) {
|
||||
if (queueVisible) return;
|
||||
public void showControls(final long duration) {
|
||||
if (queueVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.showControls(duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideControls(final long duration, long delay) {
|
||||
if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
|
||||
public void hideControls(final long duration, final long delay) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
|
||||
}
|
||||
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
|
||||
getControlsVisibilityHandler().postDelayed(() ->
|
||||
animateView(getControlsRoot(), false, duration, 0,
|
||||
|
|
@ -991,8 +1045,10 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
private void updatePlaybackButtons() {
|
||||
if (repeatButton == null || shuffleButton == null ||
|
||||
simpleExoPlayer == null || playQueue == null) return;
|
||||
if (repeatButton == null || shuffleButton == null
|
||||
|| simpleExoPlayer == null || playQueue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRepeatModeButton(repeatButton, getRepeatMode());
|
||||
setShuffleButton(shuffleButton, playQueue.isShuffled());
|
||||
|
|
@ -1017,7 +1073,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
private OnScrollBelowItemsListener getQueueScrollListener() {
|
||||
return new OnScrollBelowItemsListener() {
|
||||
@Override
|
||||
public void onScrolledDown(RecyclerView recyclerView) {
|
||||
public void onScrolledDown(final RecyclerView recyclerView) {
|
||||
if (playQueue != null && !playQueue.isComplete()) {
|
||||
playQueue.fetch();
|
||||
} else if (itemsList != null) {
|
||||
|
|
@ -1030,13 +1086,17 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
||||
return new PlayQueueItemTouchCallback() {
|
||||
@Override
|
||||
public void onMove(int sourceIndex, int targetIndex) {
|
||||
if (playQueue != null) playQueue.move(sourceIndex, targetIndex);
|
||||
public void onMove(final int sourceIndex, final int targetIndex) {
|
||||
if (playQueue != null) {
|
||||
playQueue.move(sourceIndex, targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(int index) {
|
||||
if (index != -1) playQueue.remove(index);
|
||||
public void onSwiped(final int index) {
|
||||
if (index != -1) {
|
||||
playQueue.remove(index);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1044,19 +1104,23 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() {
|
||||
return new PlayQueueItemBuilder.OnSelectedListener() {
|
||||
@Override
|
||||
public void selected(PlayQueueItem item, View view) {
|
||||
public void selected(final PlayQueueItem item, final View view) {
|
||||
onSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(PlayQueueItem item, View view) {
|
||||
public void held(final PlayQueueItem item, final View view) {
|
||||
final int index = playQueue.indexOf(item);
|
||||
if (index != -1) playQueue.remove(index);
|
||||
if (index != -1) {
|
||||
playQueue.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartDrag(PlayQueueItemHolder viewHolder) {
|
||||
if (itemTouchHelper != null) itemTouchHelper.startDrag(viewHolder);
|
||||
public void onStartDrag(final PlayQueueItemHolder viewHolder) {
|
||||
if (itemTouchHelper != null) {
|
||||
itemTouchHelper.startDrag(viewHolder);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1114,13 +1178,24 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
private class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
|
||||
private class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener
|
||||
implements View.OnTouchListener {
|
||||
private static final int MOVEMENT_THRESHOLD = 40;
|
||||
private final boolean isVolumeGestureEnabled = PlayerHelper
|
||||
.isVolumeGestureEnabled(getApplicationContext());
|
||||
private final boolean isBrightnessGestureEnabled = PlayerHelper
|
||||
.isBrightnessGestureEnabled(getApplicationContext());
|
||||
private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume();
|
||||
private boolean isMoving;
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
|
||||
public boolean onDoubleTap(final MotionEvent e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDoubleTap() called with: "
|
||||
+ "e = [" + e + "], "
|
||||
+ "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", "
|
||||
+ "xy = " + e.getX() + ", " + e.getY());
|
||||
}
|
||||
|
||||
if (e.getX() > playerImpl.getRootView().getWidth() * 2 / 3) {
|
||||
playerImpl.onFastForward();
|
||||
|
|
@ -1134,9 +1209,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
|
||||
if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) return true;
|
||||
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
|
||||
}
|
||||
if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (playerImpl.isControlsVisible()) {
|
||||
playerImpl.hideControls(150, 0);
|
||||
|
|
@ -1148,30 +1227,32 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]");
|
||||
public boolean onDown(final MotionEvent e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDown() called with: e = [" + e + "]");
|
||||
}
|
||||
|
||||
return super.onDown(e);
|
||||
}
|
||||
|
||||
private static final int MOVEMENT_THRESHOLD = 40;
|
||||
|
||||
private final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(getApplicationContext());
|
||||
private final boolean isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(getApplicationContext());
|
||||
|
||||
private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume();
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) {
|
||||
if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) return false;
|
||||
public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent,
|
||||
final float distanceX, final float distanceY) {
|
||||
if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression
|
||||
if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " +
|
||||
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" +
|
||||
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
|
||||
", distanceXy = [" + distanceX + ", " + distanceY + "]");
|
||||
// if (DEBUG) {
|
||||
// Log.d(TAG, "MainVideoPlayer.onScroll = " +
|
||||
// "e1.getRaw = [" + initialEvent.getRawX() + ", "
|
||||
// + initialEvent.getRawY() + "], " +
|
||||
// "e2.getRaw = [" + movingEvent.getRawX() + ", "
|
||||
// + movingEvent.getRawY() + "], " +
|
||||
// "distanceXy = [" + distanceX + ", " + distanceY + "]");
|
||||
// }
|
||||
|
||||
final boolean insideThreshold = Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
|
||||
final boolean insideThreshold
|
||||
= Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
|
||||
if (!isMoving && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY))
|
||||
|| playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
|
||||
return false;
|
||||
|
|
@ -1180,23 +1261,29 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
isMoving = true;
|
||||
|
||||
boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
|
||||
boolean acceptVolumeArea = acceptAnyArea || initialEvent.getX() > playerImpl.getRootView().getWidth() / 2;
|
||||
boolean acceptVolumeArea = acceptAnyArea
|
||||
|| initialEvent.getX() > playerImpl.getRootView().getWidth() / 2;
|
||||
boolean acceptBrightnessArea = acceptAnyArea || !acceptVolumeArea;
|
||||
|
||||
if (isVolumeGestureEnabled && acceptVolumeArea) {
|
||||
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
|
||||
float currentProgressPercent =
|
||||
(float) playerImpl.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
|
||||
(float) playerImpl.getVolumeProgressBar().getProgress()
|
||||
/ playerImpl.getMaxGestureLength();
|
||||
int currentVolume = (int) (maxVolume * currentProgressPercent);
|
||||
playerImpl.getAudioReactor().setVolume(currentVolume);
|
||||
|
||||
if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
|
||||
}
|
||||
|
||||
final int resId =
|
||||
currentProgressPercent <= 0 ? R.drawable.ic_volume_off_white_72dp
|
||||
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_72dp
|
||||
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_72dp
|
||||
: R.drawable.ic_volume_up_white_72dp;
|
||||
final int resId = currentProgressPercent <= 0
|
||||
? R.drawable.ic_volume_off_white_72dp
|
||||
: currentProgressPercent < 0.25
|
||||
? R.drawable.ic_volume_mute_white_72dp
|
||||
: currentProgressPercent < 0.75
|
||||
? R.drawable.ic_volume_down_white_72dp
|
||||
: R.drawable.ic_volume_up_white_72dp;
|
||||
|
||||
playerImpl.getVolumeImageView().setImageDrawable(
|
||||
AppCompatResources.getDrawable(getApplicationContext(), resId)
|
||||
|
|
@ -1210,18 +1297,22 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
} else if (isBrightnessGestureEnabled && acceptBrightnessArea) {
|
||||
playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY);
|
||||
float currentProgressPercent =
|
||||
(float) playerImpl.getBrightnessProgressBar().getProgress() / playerImpl.getMaxGestureLength();
|
||||
float currentProgressPercent
|
||||
= (float) playerImpl.getBrightnessProgressBar().getProgress()
|
||||
/ playerImpl.getMaxGestureLength();
|
||||
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
|
||||
layoutParams.screenBrightness = currentProgressPercent;
|
||||
getWindow().setAttributes(layoutParams);
|
||||
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentProgressPercent);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScroll().brightnessControl, currentBrightness = "
|
||||
+ currentProgressPercent);
|
||||
}
|
||||
|
||||
final int resId =
|
||||
currentProgressPercent < 0.25 ? R.drawable.ic_brightness_low_white_72dp
|
||||
: currentProgressPercent < 0.75 ? R.drawable.ic_brightness_medium_white_72dp
|
||||
final int resId = currentProgressPercent < 0.25
|
||||
? R.drawable.ic_brightness_low_white_72dp
|
||||
: currentProgressPercent < 0.75
|
||||
? R.drawable.ic_brightness_medium_white_72dp
|
||||
: R.drawable.ic_brightness_high_white_72dp;
|
||||
|
||||
playerImpl.getBrightnessImageView().setImageDrawable(
|
||||
|
|
@ -1229,7 +1320,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
);
|
||||
|
||||
if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
|
||||
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
|
||||
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true,
|
||||
200);
|
||||
}
|
||||
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||
playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
|
||||
|
|
@ -1239,13 +1331,17 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
private void onScrollEnd() {
|
||||
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScrollEnd() called");
|
||||
}
|
||||
|
||||
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
|
||||
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false,
|
||||
200, 200);
|
||||
}
|
||||
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
|
||||
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false,
|
||||
200, 200);
|
||||
}
|
||||
|
||||
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
|
||||
|
|
@ -1254,10 +1350,10 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
//noinspection PointlessBooleanExpression
|
||||
if (DEBUG && false)
|
||||
Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]");
|
||||
public boolean onTouch(final View v, final MotionEvent event) {
|
||||
// if (DEBUG) {
|
||||
// Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]");
|
||||
// }
|
||||
gestureDetector.onTouchEvent(event);
|
||||
if (event.getAction() == MotionEvent.ACTION_UP && isMoving) {
|
||||
isMoving = false;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.player;
|
||||
|
||||
import android.os.Binder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
class PlayerServiceBinder extends Binder {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ import java.io.Serializable;
|
|||
|
||||
public class PlayerState implements Serializable {
|
||||
|
||||
@NonNull private final PlayQueue playQueue;
|
||||
@NonNull
|
||||
private final PlayQueue playQueue;
|
||||
private final int repeatMode;
|
||||
private final float playbackSpeed;
|
||||
private final float playbackPitch;
|
||||
@Nullable private final String playbackQuality;
|
||||
@Nullable
|
||||
private final String playbackQuality;
|
||||
private final boolean playbackSkipSilence;
|
||||
private final boolean wasPlaying;
|
||||
|
||||
|
|
|
|||
|
|
@ -35,9 +35,6 @@ import android.graphics.PixelFormat;
|
|||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
|
|
@ -54,12 +51,16 @@ import android.widget.RemoteViews;
|
|||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
|
|
@ -83,29 +84,28 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
|||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
/**
|
||||
* Service Popup Player implementing VideoPlayer
|
||||
* Service Popup Player implementing {@link VideoPlayer}.
|
||||
*
|
||||
* @author mauriciocolli
|
||||
*/
|
||||
public final class PopupVideoPlayer extends Service {
|
||||
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
|
||||
public static final String ACTION_PLAY_PAUSE
|
||||
= "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE";
|
||||
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
|
||||
private static final String TAG = ".PopupVideoPlayer";
|
||||
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||
|
||||
private static final int NOTIFICATION_ID = 40028922;
|
||||
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
|
||||
public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE";
|
||||
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
|
||||
|
||||
private static final String POPUP_SAVED_WIDTH = "popup_saved_width";
|
||||
private static final String POPUP_SAVED_X = "popup_saved_x";
|
||||
private static final String POPUP_SAVED_Y = "popup_saved_y";
|
||||
|
||||
private static final int MINIMUM_SHOW_EXTRA_WIDTH_DP = 300;
|
||||
|
||||
private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
|
||||
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
|
||||
private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS |
|
||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
|
||||
private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
|
||||
private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS
|
||||
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
|
||||
|
||||
private WindowManager windowManager;
|
||||
private WindowManager.LayoutParams popupLayoutParams;
|
||||
|
|
@ -116,11 +116,15 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
private int tossFlingVelocity;
|
||||
|
||||
private float screenWidth, screenHeight;
|
||||
private float popupWidth, popupHeight;
|
||||
private float screenWidth;
|
||||
private float screenHeight;
|
||||
private float popupWidth;
|
||||
private float popupHeight;
|
||||
|
||||
private float minimumWidth, minimumHeight;
|
||||
private float maximumWidth, maximumHeight;
|
||||
private float minimumWidth;
|
||||
private float minimumHeight;
|
||||
private float maximumWidth;
|
||||
private float maximumHeight;
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
private NotificationCompat.Builder notBuilder;
|
||||
|
|
@ -155,14 +159,18 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(final Intent intent, int flags, int startId) {
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
|
||||
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], "
|
||||
+ "flags = [" + flags + "], startId = [" + startId + "]");
|
||||
}
|
||||
if (playerImpl.getPlayer() == null) {
|
||||
initPopup();
|
||||
initPopupCloseOverlay();
|
||||
}
|
||||
if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
|
||||
if (!playerImpl.isPlaying()) {
|
||||
playerImpl.getPlayer().setPlayWhenReady(true);
|
||||
}
|
||||
|
||||
playerImpl.handleIntent(intent);
|
||||
|
||||
|
|
@ -170,9 +178,12 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
public void onConfigurationChanged(final Configuration newConfig) {
|
||||
assureCorrectAppLanguage(this);
|
||||
if (DEBUG) Log.d(TAG, "onConfigurationChanged() called with: newConfig = [" + newConfig + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onConfigurationChanged() called with: "
|
||||
+ "newConfig = [" + newConfig + "]");
|
||||
}
|
||||
updateScreenSize();
|
||||
updatePopupSize(popupLayoutParams.width, -1);
|
||||
checkPopupPositionBounds();
|
||||
|
|
@ -180,17 +191,19 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (DEBUG) Log.d(TAG, "onDestroy() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDestroy() called");
|
||||
}
|
||||
closePopup();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
protected void attachBaseContext(final Context base) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
public IBinder onBind(final Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
|
|
@ -200,7 +213,9 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
private void initPopup() {
|
||||
if (DEBUG) Log.d(TAG, "initPopup() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "initPopup() called");
|
||||
}
|
||||
View rootView = View.inflate(this, R.layout.player_popup, null);
|
||||
playerImpl.setup(rootView);
|
||||
|
||||
|
|
@ -211,11 +226,12 @@ public final class PopupVideoPlayer extends Service {
|
|||
final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(this);
|
||||
final float defaultSize = getResources().getDimension(R.dimen.popup_default_width);
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize;
|
||||
popupWidth = popupRememberSizeAndPos
|
||||
? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize;
|
||||
|
||||
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ?
|
||||
WindowManager.LayoutParams.TYPE_PHONE :
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O
|
||||
? WindowManager.LayoutParams.TYPE_PHONE
|
||||
: WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
|
||||
popupLayoutParams = new WindowManager.LayoutParams(
|
||||
(int) popupWidth, (int) getMinimumVideoHeight(popupWidth),
|
||||
|
|
@ -227,8 +243,10 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
|
||||
int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
|
||||
popupLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
|
||||
popupLayoutParams.y = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
|
||||
popupLayoutParams.x = popupRememberSizeAndPos
|
||||
? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
|
||||
popupLayoutParams.y = popupRememberSizeAndPos
|
||||
? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
|
||||
|
||||
checkPopupPositionBounds();
|
||||
|
||||
|
|
@ -243,14 +261,17 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
private void initPopupCloseOverlay() {
|
||||
if (DEBUG) Log.d(TAG, "initPopupCloseOverlay() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "initPopupCloseOverlay() called");
|
||||
}
|
||||
closeOverlayView = View.inflate(this, R.layout.player_popup_close_overlay, null);
|
||||
closeOverlayButton = closeOverlayView.findViewById(R.id.closeButton);
|
||||
|
||||
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ?
|
||||
WindowManager.LayoutParams.TYPE_PHONE :
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|
||||
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O
|
||||
? WindowManager.LayoutParams.TYPE_PHONE
|
||||
: WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|
||||
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
|
||||
|
||||
WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams(
|
||||
|
|
@ -259,7 +280,8 @@ public final class PopupVideoPlayer extends Service {
|
|||
flags,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
|
||||
closeOverlayLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
|
||||
closeOverlayLayoutParams.softInputMode = WindowManager
|
||||
.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
|
||||
|
||||
closeOverlayButton.setVisibility(View.GONE);
|
||||
windowManager.addView(closeOverlayView, closeOverlayLayoutParams);
|
||||
|
|
@ -274,27 +296,33 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
private NotificationCompat.Builder createNotification() {
|
||||
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_popup_notification);
|
||||
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
||||
R.layout.player_popup_notification);
|
||||
|
||||
notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
|
||||
notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getUploaderName());
|
||||
notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getThumbnail());
|
||||
|
||||
notRemoteView.setOnClickPendingIntent(R.id.notificationPlayPause,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
notRemoteView.setOnClickPendingIntent(R.id.notificationStop,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
notRemoteView.setOnClickPendingIntent(R.id.notificationRepeat,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
|
||||
// Starts popup player activity -- attempts to unlock lockscreen
|
||||
final Intent intent = NavigationHelper.getPopupPlayerActivityIntent(this);
|
||||
notRemoteView.setOnClickPendingIntent(R.id.notificationContent,
|
||||
PendingIntent.getActivity(this, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
PendingIntent.getActivity(this, NOTIFICATION_ID, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
|
||||
setRepeatModeRemote(notRemoteView, playerImpl.getRepeatMode());
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
|
||||
NotificationCompat.Builder builder = new NotificationCompat
|
||||
.Builder(this, getString(R.string.notification_channel_id))
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
|
|
@ -311,10 +339,16 @@ public final class PopupVideoPlayer extends Service {
|
|||
*
|
||||
* @param drawableId if != -1, sets the drawable with that id on the play/pause button
|
||||
*/
|
||||
private void updateNotification(int drawableId) {
|
||||
if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
|
||||
if (notBuilder == null || notRemoteView == null) return;
|
||||
if (drawableId != -1) notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
||||
private void updateNotification(final int drawableId) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
|
||||
}
|
||||
if (notBuilder == null || notRemoteView == null) {
|
||||
return;
|
||||
}
|
||||
if (drawableId != -1) {
|
||||
notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
||||
}
|
||||
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
|
||||
}
|
||||
|
||||
|
|
@ -323,8 +357,12 @@ public final class PopupVideoPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void closePopup() {
|
||||
if (DEBUG) Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing);
|
||||
if (isPopupClosing) return;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing);
|
||||
}
|
||||
if (isPopupClosing) {
|
||||
return;
|
||||
}
|
||||
isPopupClosing = true;
|
||||
|
||||
if (playerImpl != null) {
|
||||
|
|
@ -339,14 +377,19 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
mBinder = null;
|
||||
if (lockManager != null) lockManager.releaseWifiAndCpu();
|
||||
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
|
||||
if (lockManager != null) {
|
||||
lockManager.releaseWifiAndCpu();
|
||||
}
|
||||
if (notificationManager != null) {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
animateOverlayAndFinishService();
|
||||
}
|
||||
|
||||
private void animateOverlayAndFinishService() {
|
||||
final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight() - closeOverlayButton.getY());
|
||||
final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight()
|
||||
- closeOverlayButton.getY());
|
||||
|
||||
closeOverlayButton.animate().setListener(null).cancel();
|
||||
closeOverlayButton.animate()
|
||||
|
|
@ -355,12 +398,12 @@ public final class PopupVideoPlayer extends Service {
|
|||
.setDuration(400)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
public void onAnimationCancel(final Animator animation) {
|
||||
end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
end();
|
||||
}
|
||||
|
||||
|
|
@ -379,6 +422,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
/**
|
||||
* @see #checkPopupPositionBounds(float, float)
|
||||
* @return if the popup was out of bounds and have been moved back to it
|
||||
*/
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
private boolean checkPopupPositionBounds() {
|
||||
|
|
@ -386,16 +430,23 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if {@link #popupLayoutParams}' position is within a arbitrary boundary that goes from (0,0) to (boundaryWidth,boundaryHeight).
|
||||
* Check if {@link #popupLayoutParams}' position is within a arbitrary boundary
|
||||
* that goes from (0, 0) to (boundaryWidth, boundaryHeight).
|
||||
* <p>
|
||||
* If it's out of these boundaries, {@link #popupLayoutParams}' position is changed and {@code true} is returned
|
||||
* to represent this change.
|
||||
* If it's out of these boundaries, {@link #popupLayoutParams}' position is changed
|
||||
* and {@code true} is returned to represent this change.
|
||||
* </p>
|
||||
*
|
||||
* @param boundaryWidth width of the boundary
|
||||
* @param boundaryHeight height of the boundary
|
||||
* @return if the popup was out of bounds and have been moved back to it
|
||||
*/
|
||||
private boolean checkPopupPositionBounds(final float boundaryWidth, final float boundaryHeight) {
|
||||
private boolean checkPopupPositionBounds(final float boundaryWidth,
|
||||
final float boundaryHeight) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "checkPopupPositionBounds() called with: boundaryWidth = [" + boundaryWidth + "], boundaryHeight = [" + boundaryHeight + "]");
|
||||
Log.d(TAG, "checkPopupPositionBounds() called with: "
|
||||
+ "boundaryWidth = [" + boundaryWidth + "], "
|
||||
+ "boundaryHeight = [" + boundaryHeight + "]");
|
||||
}
|
||||
|
||||
if (popupLayoutParams.x < 0) {
|
||||
|
|
@ -418,15 +469,20 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
private void savePositionAndSize() {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(PopupVideoPlayer.this);
|
||||
SharedPreferences sharedPreferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(PopupVideoPlayer.this);
|
||||
sharedPreferences.edit().putInt(POPUP_SAVED_X, popupLayoutParams.x).apply();
|
||||
sharedPreferences.edit().putInt(POPUP_SAVED_Y, popupLayoutParams.y).apply();
|
||||
sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, popupLayoutParams.width).apply();
|
||||
}
|
||||
|
||||
private float getMinimumVideoHeight(float width) {
|
||||
//if (DEBUG) Log.d(TAG, "getMinimumVideoHeight() called with: width = [" + width + "], returned: " + height);
|
||||
return width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
|
||||
private float getMinimumVideoHeight(final float width) {
|
||||
final float height = width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
|
||||
// if (DEBUG) {
|
||||
// Log.d(TAG, "getMinimumVideoHeight() called with: width = [" + width + "], "
|
||||
// + "returned: " + height);
|
||||
// }
|
||||
return height;
|
||||
}
|
||||
|
||||
private void updateScreenSize() {
|
||||
|
|
@ -435,7 +491,10 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
screenWidth = metrics.widthPixels;
|
||||
screenHeight = metrics.heightPixels;
|
||||
if (DEBUG) Log.d(TAG, "updateScreenSize() called > screenWidth = " + screenWidth + ", screenHeight = " + screenHeight);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "updateScreenSize() called > screenWidth = " + screenWidth + ", "
|
||||
+ "screenHeight = " + screenHeight);
|
||||
}
|
||||
|
||||
popupWidth = getResources().getDimension(R.dimen.popup_default_width);
|
||||
popupHeight = getMinimumVideoHeight(popupWidth);
|
||||
|
|
@ -447,44 +506,65 @@ public final class PopupVideoPlayer extends Service {
|
|||
maximumHeight = screenHeight;
|
||||
}
|
||||
|
||||
private void updatePopupSize(int width, int height) {
|
||||
if (playerImpl == null) return;
|
||||
if (DEBUG) Log.d(TAG, "updatePopupSize() called with: width = [" + width + "], height = [" + height + "]");
|
||||
private void updatePopupSize(final int width, final int height) {
|
||||
if (playerImpl == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "updatePopupSize() called with: "
|
||||
+ "width = [" + width + "], height = [" + height + "]");
|
||||
}
|
||||
|
||||
width = (int) (width > maximumWidth ? maximumWidth : width < minimumWidth ? minimumWidth : width);
|
||||
final int actualWidth = (int) (width > maximumWidth ? maximumWidth
|
||||
: width < minimumWidth ? minimumWidth : width);
|
||||
|
||||
if (height == -1) height = (int) getMinimumVideoHeight(width);
|
||||
else height = (int) (height > maximumHeight ? maximumHeight : height < minimumHeight ? minimumHeight : height);
|
||||
final int actualHeight;
|
||||
if (height == -1) {
|
||||
actualHeight = (int) getMinimumVideoHeight(width);
|
||||
} else {
|
||||
actualHeight = (int) (height > maximumHeight ? maximumHeight
|
||||
: height < minimumHeight ? minimumHeight : height);
|
||||
}
|
||||
|
||||
popupLayoutParams.width = width;
|
||||
popupLayoutParams.height = height;
|
||||
popupWidth = width;
|
||||
popupHeight = height;
|
||||
popupLayoutParams.width = actualWidth;
|
||||
popupLayoutParams.height = actualHeight;
|
||||
popupWidth = actualWidth;
|
||||
popupHeight = actualHeight;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "updatePopupSize() updated values: width = [" + width + "], height = [" + height + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "updatePopupSize() updated values: "
|
||||
+ "width = [" + actualWidth + "], height = [" + actualHeight + "]");
|
||||
}
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
|
||||
}
|
||||
|
||||
protected void setRepeatModeRemote(final RemoteViews remoteViews, final int repeatMode) {
|
||||
final String methodName = "setImageResource";
|
||||
|
||||
if (remoteViews == null) return;
|
||||
if (remoteViews == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (repeatMode) {
|
||||
case Player.REPEAT_MODE_OFF:
|
||||
remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_off);
|
||||
remoteViews.setInt(R.id.notificationRepeat, methodName,
|
||||
R.drawable.exo_controls_repeat_off);
|
||||
break;
|
||||
case Player.REPEAT_MODE_ONE:
|
||||
remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_one);
|
||||
remoteViews.setInt(R.id.notificationRepeat, methodName,
|
||||
R.drawable.exo_controls_repeat_one);
|
||||
break;
|
||||
case Player.REPEAT_MODE_ALL:
|
||||
remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_all);
|
||||
remoteViews.setInt(R.id.notificationRepeat, methodName,
|
||||
R.drawable.exo_controls_repeat_all);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWindowFlags(final int flags) {
|
||||
if (popupLayoutParams == null || windowManager == null || playerImpl == null) return;
|
||||
if (popupLayoutParams == null || windowManager == null || playerImpl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
popupLayoutParams.flags = flags;
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
|
||||
|
|
@ -499,29 +579,29 @@ public final class PopupVideoPlayer extends Service {
|
|||
private View extraOptionsView;
|
||||
private View closingOverlayView;
|
||||
|
||||
VideoPlayerImpl(final Context context) {
|
||||
super("VideoPlayerImpl" + PopupVideoPlayer.TAG, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleIntent(Intent intent) {
|
||||
public void handleIntent(final Intent intent) {
|
||||
super.handleIntent(intent);
|
||||
|
||||
resetNotification();
|
||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
||||
}
|
||||
|
||||
VideoPlayerImpl(final Context context) {
|
||||
super("VideoPlayerImpl" + PopupVideoPlayer.TAG, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initViews(View rootView) {
|
||||
super.initViews(rootView);
|
||||
resizingIndicator = rootView.findViewById(R.id.resizing_indicator);
|
||||
fullScreenButton = rootView.findViewById(R.id.fullScreenButton);
|
||||
public void initViews(final View view) {
|
||||
super.initViews(view);
|
||||
resizingIndicator = view.findViewById(R.id.resizing_indicator);
|
||||
fullScreenButton = view.findViewById(R.id.fullScreenButton);
|
||||
fullScreenButton.setOnClickListener(v -> onFullScreenButtonClicked());
|
||||
videoPlayPause = rootView.findViewById(R.id.videoPlayPause);
|
||||
videoPlayPause = view.findViewById(R.id.videoPlayPause);
|
||||
|
||||
extraOptionsView = rootView.findViewById(R.id.extraOptionsView);
|
||||
closingOverlayView = rootView.findViewById(R.id.closingOverlay);
|
||||
rootView.addOnLayoutChangeListener(this);
|
||||
extraOptionsView = view.findViewById(R.id.extraOptionsView);
|
||||
closingOverlayView = view.findViewById(R.id.closingOverlay);
|
||||
view.addOnLayoutChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -531,8 +611,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void setupSubtitleView(@NonNull SubtitleView view,
|
||||
final float captionScale,
|
||||
protected void setupSubtitleView(@NonNull final SubtitleView view, final float captionScale,
|
||||
@NonNull final CaptionStyleCompat captionStyle) {
|
||||
float captionRatio = (captionScale - 1f) / 5f + 1f;
|
||||
view.setFractionalTextSize(SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio);
|
||||
|
|
@ -541,8 +620,9 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChange(final View view, int left, int top, int right, int bottom,
|
||||
int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
||||
public void onLayoutChange(final View view, final int left, final int top, final int right,
|
||||
final int bottom, final int oldLeft, final int oldTop,
|
||||
final int oldRight, final int oldBottom) {
|
||||
float widthDp = Math.abs(right - left) / getResources().getDisplayMetrics().density;
|
||||
final int visibility = widthDp > MINIMUM_SHOW_EXTRA_WIDTH_DP ? View.VISIBLE : View.GONE;
|
||||
extraOptionsView.setVisibility(visibility);
|
||||
|
|
@ -550,7 +630,9 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
|
|
@ -558,7 +640,9 @@ public final class PopupVideoPlayer extends Service {
|
|||
public void onFullScreenButtonClicked() {
|
||||
super.onFullScreenButtonClicked();
|
||||
|
||||
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onFullScreenButtonClicked() called");
|
||||
}
|
||||
|
||||
setRecovery();
|
||||
final Intent intent = NavigationHelper.getPlayerIntent(
|
||||
|
|
@ -580,13 +664,15 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(PopupMenu menu) {
|
||||
public void onDismiss(final PopupMenu menu) {
|
||||
super.onDismiss(menu);
|
||||
if (isPlaying()) hideControls(500, 0);
|
||||
if (isPlaying()) {
|
||||
hideControls(500, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int nextResizeMode(int resizeMode) {
|
||||
protected int nextResizeMode(final int resizeMode) {
|
||||
if (resizeMode == AspectRatioFrameLayout.RESIZE_MODE_FILL) {
|
||||
return AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
||||
} else {
|
||||
|
|
@ -595,7 +681,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
super.onStopTrackingTouch(seekBar);
|
||||
if (wasPlaying()) {
|
||||
hideControls(100, 0);
|
||||
|
|
@ -615,7 +701,8 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
|
||||
public void onUpdateProgress(final int currentProgress, final int duration,
|
||||
final int bufferPercent) {
|
||||
updateProgress(currentProgress, duration, bufferPercent);
|
||||
super.onUpdateProgress(currentProgress, duration, bufferPercent);
|
||||
}
|
||||
|
|
@ -624,13 +711,13 @@ public final class PopupVideoPlayer extends Service {
|
|||
protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
|
||||
return new VideoPlaybackResolver.QualityResolver() {
|
||||
@Override
|
||||
public int getDefaultResolutionIndex(List<VideoStream> sortedVideos) {
|
||||
public int getDefaultResolutionIndex(final List<VideoStream> sortedVideos) {
|
||||
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOverrideResolutionIndex(List<VideoStream> sortedVideos,
|
||||
String playbackQuality) {
|
||||
public int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
||||
final String playbackQuality) {
|
||||
return ListHelper.getPopupResolutionIndex(context, sortedVideos,
|
||||
playbackQuality);
|
||||
}
|
||||
|
|
@ -642,9 +729,12 @@ public final class PopupVideoPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
public void onLoadingComplete(final String imageUri, final View view,
|
||||
final Bitmap loadedImage) {
|
||||
super.onLoadingComplete(imageUri, view, loadedImage);
|
||||
if (playerImpl == null) return;
|
||||
if (playerImpl == null) {
|
||||
return;
|
||||
}
|
||||
// rebuild notification here since remote view does not release bitmaps,
|
||||
// causing memory leaks
|
||||
resetNotification();
|
||||
|
|
@ -652,14 +742,15 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||
public void onLoadingFailed(final String imageUri, final View view,
|
||||
final FailReason failReason) {
|
||||
super.onLoadingFailed(imageUri, view, failReason);
|
||||
resetNotification();
|
||||
updateNotification(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingCancelled(String imageUri, View view) {
|
||||
public void onLoadingCancelled(final String imageUri, final View view) {
|
||||
super.onLoadingCancelled(imageUri, view);
|
||||
resetNotification();
|
||||
updateNotification(-1);
|
||||
|
|
@ -669,14 +760,14 @@ public final class PopupVideoPlayer extends Service {
|
|||
// Activity Event Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/*package-private*/ void setActivityListener(PlayerEventListener listener) {
|
||||
/*package-private*/ void setActivityListener(final PlayerEventListener listener) {
|
||||
activityListener = listener;
|
||||
updateMetadata();
|
||||
updatePlayback();
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
/*package-private*/ void removeActivityListener(PlayerEventListener listener) {
|
||||
/*package-private*/ void removeActivityListener(final PlayerEventListener listener) {
|
||||
if (activityListener == listener) {
|
||||
activityListener = null;
|
||||
}
|
||||
|
|
@ -695,7 +786,8 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
private void updateProgress(int currentProgress, int duration, int bufferPercent) {
|
||||
private void updateProgress(final int currentProgress, final int duration,
|
||||
final int bufferPercent) {
|
||||
if (activityListener != null) {
|
||||
activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
|
||||
}
|
||||
|
|
@ -713,7 +805,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(int i) {
|
||||
public void onRepeatModeChanged(final int i) {
|
||||
super.onRepeatModeChanged(i);
|
||||
setRepeatModeRemote(notRemoteView, i);
|
||||
updatePlayback();
|
||||
|
|
@ -722,7 +814,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||
public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
|
||||
super.onPlaybackParametersChanged(playbackParameters);
|
||||
updatePlayback();
|
||||
}
|
||||
|
|
@ -749,22 +841,29 @@ public final class PopupVideoPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected void setupBroadcastReceiver(IntentFilter intentFilter) {
|
||||
super.setupBroadcastReceiver(intentFilter);
|
||||
if (DEBUG) Log.d(TAG, "setupBroadcastReceiver() called with: intentFilter = [" + intentFilter + "]");
|
||||
intentFilter.addAction(ACTION_CLOSE);
|
||||
intentFilter.addAction(ACTION_PLAY_PAUSE);
|
||||
intentFilter.addAction(ACTION_REPEAT);
|
||||
protected void setupBroadcastReceiver(final IntentFilter intentFltr) {
|
||||
super.setupBroadcastReceiver(intentFltr);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setupBroadcastReceiver() called with: "
|
||||
+ "intentFilter = [" + intentFltr + "]");
|
||||
}
|
||||
intentFltr.addAction(ACTION_CLOSE);
|
||||
intentFltr.addAction(ACTION_PLAY_PAUSE);
|
||||
intentFltr.addAction(ACTION_REPEAT);
|
||||
|
||||
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
|
||||
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
intentFltr.addAction(Intent.ACTION_SCREEN_ON);
|
||||
intentFltr.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBroadcastReceived(Intent intent) {
|
||||
public void onBroadcastReceived(final Intent intent) {
|
||||
super.onBroadcastReceived(intent);
|
||||
if (intent == null || intent.getAction() == null) return;
|
||||
if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
|
||||
if (intent == null || intent.getAction() == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
|
||||
}
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_CLOSE:
|
||||
closePopup();
|
||||
|
|
@ -789,7 +888,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void changeState(int state) {
|
||||
public void changeState(final int state) {
|
||||
super.changeState(state);
|
||||
updatePlayback();
|
||||
}
|
||||
|
|
@ -869,12 +968,12 @@ public final class PopupVideoPlayer extends Service {
|
|||
super.showControlsThenHide();
|
||||
}
|
||||
|
||||
public void showControls(long duration) {
|
||||
public void showControls(final long duration) {
|
||||
videoPlayPause.setVisibility(View.VISIBLE);
|
||||
super.showControls(duration);
|
||||
}
|
||||
|
||||
public void hideControls(final long duration, long delay) {
|
||||
public void hideControls(final long duration, final long delay) {
|
||||
super.hideControlsAndButton(duration, delay, videoPlayPause);
|
||||
}
|
||||
|
||||
|
|
@ -904,16 +1003,23 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
private class PopupWindowGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
|
||||
private int initialPopupX, initialPopupY;
|
||||
private class PopupWindowGestureListener extends GestureDetector.SimpleOnGestureListener
|
||||
implements View.OnTouchListener {
|
||||
private int initialPopupX;
|
||||
private int initialPopupY;
|
||||
private boolean isMoving;
|
||||
private boolean isResizing;
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
|
||||
if (playerImpl == null || !playerImpl.isPlaying()) return false;
|
||||
public boolean onDoubleTap(final MotionEvent e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "], "
|
||||
+ "rawXy = " + e.getRawX() + ", " + e.getRawY()
|
||||
+ ", xy = " + e.getX() + ", " + e.getY());
|
||||
}
|
||||
if (playerImpl == null || !playerImpl.isPlaying()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
playerImpl.hideControls(0, 0);
|
||||
|
||||
|
|
@ -927,9 +1033,13 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
|
||||
if (playerImpl == null || playerImpl.getPlayer() == null) return false;
|
||||
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
|
||||
}
|
||||
if (playerImpl == null || playerImpl.getPlayer() == null) {
|
||||
return false;
|
||||
}
|
||||
if (playerImpl.isControlsVisible()) {
|
||||
playerImpl.hideControls(100, 100);
|
||||
} else {
|
||||
|
|
@ -940,8 +1050,10 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]");
|
||||
public boolean onDown(final MotionEvent e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDown() called with: e = [" + e + "]");
|
||||
}
|
||||
|
||||
// Fix popup position when the user touch it, it may have the wrong one
|
||||
// because the soft input is visible (the draggable area is currently resized).
|
||||
|
|
@ -955,16 +1067,21 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
if (DEBUG) Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
|
||||
public void onLongPress(final MotionEvent e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
|
||||
}
|
||||
updateScreenSize();
|
||||
checkPopupPositionBounds();
|
||||
updatePopupSize((int) screenWidth, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) {
|
||||
if (isResizing || playerImpl == null) return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
|
||||
public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent,
|
||||
final float distanceX, final float distanceY) {
|
||||
if (isResizing || playerImpl == null) {
|
||||
return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
|
||||
}
|
||||
|
||||
if (!isMoving) {
|
||||
animateView(closeOverlayButton, true, 200);
|
||||
|
|
@ -972,14 +1089,22 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
isMoving = true;
|
||||
|
||||
float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX()), posX = (int) (initialPopupX + diffX);
|
||||
float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY()), posY = (int) (initialPopupY + diffY);
|
||||
float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX());
|
||||
float posX = (int) (initialPopupX + diffX);
|
||||
float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY());
|
||||
float posY = (int) (initialPopupY + diffY);
|
||||
|
||||
if (posX > (screenWidth - popupWidth)) posX = (int) (screenWidth - popupWidth);
|
||||
else if (posX < 0) posX = 0;
|
||||
if (posX > (screenWidth - popupWidth)) {
|
||||
posX = (int) (screenWidth - popupWidth);
|
||||
} else if (posX < 0) {
|
||||
posX = 0;
|
||||
}
|
||||
|
||||
if (posY > (screenHeight - popupHeight)) posY = (int) (screenHeight - popupHeight);
|
||||
else if (posY < 0) posY = 0;
|
||||
if (posY > (screenHeight - popupHeight)) {
|
||||
posY = (int) (screenHeight - popupHeight);
|
||||
} else if (posY < 0) {
|
||||
posY = 0;
|
||||
}
|
||||
|
||||
popupLayoutParams.x = (int) posX;
|
||||
popupLayoutParams.y = (int) posY;
|
||||
|
|
@ -995,22 +1120,30 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression
|
||||
if (DEBUG && false) {
|
||||
Log.d(TAG, "PopupVideoPlayer.onScroll = " +
|
||||
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" + ", e1.getX,Y = [" + initialEvent.getX() + ", " + initialEvent.getY() + "]" +
|
||||
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" + ", e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "]" +
|
||||
", distanceX,Y = [" + distanceX + ", " + distanceY + "]" +
|
||||
", posX,Y = [" + posX + ", " + posY + "]" +
|
||||
", popupW,H = [" + popupWidth + " x " + popupHeight + "]");
|
||||
}
|
||||
// if (DEBUG) {
|
||||
// Log.d(TAG, "PopupVideoPlayer.onScroll = "
|
||||
// + "e1.getRaw = [" + initialEvent.getRawX() + ", "
|
||||
// + initialEvent.getRawY() + "], "
|
||||
// + "e1.getX,Y = [" + initialEvent.getX() + ", "
|
||||
// + initialEvent.getY() + "], "
|
||||
// + "e2.getRaw = [" + movingEvent.getRawX() + ", "
|
||||
// + movingEvent.getRawY() + "], "
|
||||
// + "e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "], "
|
||||
// + "distanceX,Y = [" + distanceX + ", " + distanceY + "], "
|
||||
// + "posX,Y = [" + posX + ", " + posY + "], "
|
||||
// + "popupW,H = [" + popupWidth + " x " + popupHeight + "]");
|
||||
// }
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onScrollEnd(MotionEvent event) {
|
||||
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
|
||||
if (playerImpl == null) return;
|
||||
private void onScrollEnd(final MotionEvent event) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScrollEnd() called");
|
||||
}
|
||||
if (playerImpl == null) {
|
||||
return;
|
||||
}
|
||||
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
|
||||
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
||||
}
|
||||
|
|
@ -1027,15 +1160,24 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
if (DEBUG) Log.d(TAG, "Fling velocity: dX=[" + velocityX + "], dY=[" + velocityY + "]");
|
||||
if (playerImpl == null) return false;
|
||||
public boolean onFling(final MotionEvent e1, final MotionEvent e2,
|
||||
final float velocityX, final float velocityY) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Fling velocity: dX=[" + velocityX + "], dY=[" + velocityY + "]");
|
||||
}
|
||||
if (playerImpl == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final float absVelocityX = Math.abs(velocityX);
|
||||
final float absVelocityY = Math.abs(velocityY);
|
||||
if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
|
||||
if (absVelocityX > tossFlingVelocity) popupLayoutParams.x = (int) velocityX;
|
||||
if (absVelocityY > tossFlingVelocity) popupLayoutParams.y = (int) velocityY;
|
||||
if (absVelocityX > tossFlingVelocity) {
|
||||
popupLayoutParams.x = (int) velocityX;
|
||||
}
|
||||
if (absVelocityY > tossFlingVelocity) {
|
||||
popupLayoutParams.y = (int) velocityY;
|
||||
}
|
||||
checkPopupPositionBounds();
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
|
||||
return true;
|
||||
|
|
@ -1044,11 +1186,15 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
public boolean onTouch(final View v, final MotionEvent event) {
|
||||
popupGestureDetector.onTouchEvent(event);
|
||||
if (playerImpl == null) return false;
|
||||
if (playerImpl == null) {
|
||||
return false;
|
||||
}
|
||||
if (event.getPointerCount() == 2 && !isMoving && !isResizing) {
|
||||
if (DEBUG) Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
|
||||
}
|
||||
playerImpl.showAndAnimateControl(-1, true);
|
||||
playerImpl.getLoadingPanel().setVisibility(View.GONE);
|
||||
|
||||
|
|
@ -1059,13 +1205,18 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
if (event.getAction() == MotionEvent.ACTION_MOVE && !isMoving && isResizing) {
|
||||
if (DEBUG) Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], "
|
||||
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
|
||||
}
|
||||
return handleMultiDrag(event);
|
||||
}
|
||||
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], "
|
||||
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
|
||||
}
|
||||
if (isMoving) {
|
||||
isMoving = false;
|
||||
onScrollEnd(event);
|
||||
|
|
@ -1087,7 +1238,9 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
private boolean handleMultiDrag(final MotionEvent event) {
|
||||
if (event.getPointerCount() != 2) return false;
|
||||
if (event.getPointerCount() != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final float firstPointerX = event.getX(0);
|
||||
final float secondPointerX = event.getX(1);
|
||||
|
|
@ -1114,14 +1267,17 @@ public final class PopupVideoPlayer extends Service {
|
|||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private int distanceFromCloseButton(MotionEvent popupMotionEvent) {
|
||||
final int closeOverlayButtonX = closeOverlayButton.getLeft() + closeOverlayButton.getWidth() / 2;
|
||||
final int closeOverlayButtonY = closeOverlayButton.getTop() + closeOverlayButton.getHeight() / 2;
|
||||
private int distanceFromCloseButton(final MotionEvent popupMotionEvent) {
|
||||
final int closeOverlayButtonX = closeOverlayButton.getLeft()
|
||||
+ closeOverlayButton.getWidth() / 2;
|
||||
final int closeOverlayButtonY = closeOverlayButton.getTop()
|
||||
+ closeOverlayButton.getHeight() / 2;
|
||||
|
||||
float fingerX = popupLayoutParams.x + popupMotionEvent.getX();
|
||||
float fingerY = popupLayoutParams.y + popupMotionEvent.getY();
|
||||
|
||||
return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) + Math.pow(closeOverlayButtonY - fingerY, 2));
|
||||
return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2)
|
||||
+ Math.pow(closeOverlayButtonY - fingerY, 2));
|
||||
}
|
||||
|
||||
private float getClosingRadius() {
|
||||
|
|
@ -1130,7 +1286,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
return buttonRadius * 1.2f;
|
||||
}
|
||||
|
||||
private boolean isInsideClosingRadius(MotionEvent popupMotionEvent) {
|
||||
private boolean isInsideClosingRadius(final MotionEvent popupMotionEvent) {
|
||||
return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,13 +46,13 @@ public final class PopupVideoPlayerActivity extends ServicePlayerActivity {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onPlayerOptionSelected(MenuItem item) {
|
||||
public boolean onPlayerOptionSelected(final MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_switch_background) {
|
||||
this.player.setRecovery();
|
||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
||||
getApplicationContext().startService(
|
||||
getSwitchIntent(BackgroundPlayer.class)
|
||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
|
||||
getSwitchIntent(BackgroundPlayer.class)
|
||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,22 +52,16 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|||
public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener,
|
||||
View.OnClickListener, PlaybackParameterDialog.Callback {
|
||||
|
||||
private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47;
|
||||
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
|
||||
protected BasePlayer player;
|
||||
private boolean serviceBound;
|
||||
private ServiceConnection serviceConnection;
|
||||
|
||||
protected BasePlayer player;
|
||||
|
||||
private boolean seeking;
|
||||
private boolean redraw;
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47;
|
||||
|
||||
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
|
||||
|
||||
private boolean seeking;
|
||||
private boolean redraw;
|
||||
private View rootView;
|
||||
|
||||
private RecyclerView itemsList;
|
||||
|
|
@ -119,7 +113,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
assureCorrectAppLanguage(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeHelper.setTheme(this);
|
||||
|
|
@ -147,16 +141,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
this.menu = menu;
|
||||
getMenuInflater().inflate(R.menu.menu_play_queue, menu);
|
||||
getMenuInflater().inflate(getPlayerOptionMenuResource(), menu);
|
||||
public boolean onCreateOptionsMenu(final Menu m) {
|
||||
this.menu = m;
|
||||
getMenuInflater().inflate(R.menu.menu_play_queue, m);
|
||||
getMenuInflater().inflate(getPlayerOptionMenuResource(), m);
|
||||
onMaybeMuteChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
|
|
@ -192,19 +186,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
protected Intent getSwitchIntent(final Class clazz) {
|
||||
return NavigationHelper.getPlayerIntent(
|
||||
getApplicationContext(),
|
||||
clazz,
|
||||
this.player.getPlayQueue(),
|
||||
this.player.getRepeatMode(),
|
||||
this.player.getPlaybackSpeed(),
|
||||
this.player.getPlaybackPitch(),
|
||||
this.player.getPlaybackSkipSilence(),
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
this.player.isMuted()
|
||||
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
return NavigationHelper.getPlayerIntent(getApplicationContext(), clazz,
|
||||
this.player.getPlayQueue(), this.player.getRepeatMode(),
|
||||
this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(),
|
||||
this.player.getPlaybackSkipSilence(), null, false, false, this.player.isMuted())
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying());
|
||||
}
|
||||
|
||||
|
|
@ -229,8 +215,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
if (player != null && player.getPlayQueueAdapter() != null) {
|
||||
player.getPlayQueueAdapter().unsetSelectedListener();
|
||||
}
|
||||
if (itemsList != null) itemsList.setAdapter(null);
|
||||
if (itemTouchHelper != null) itemTouchHelper.attachToRecyclerView(null);
|
||||
if (itemsList != null) {
|
||||
itemsList.setAdapter(null);
|
||||
}
|
||||
if (itemTouchHelper != null) {
|
||||
itemTouchHelper.attachToRecyclerView(null);
|
||||
}
|
||||
|
||||
itemsList = null;
|
||||
itemTouchHelper = null;
|
||||
|
|
@ -241,20 +231,20 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
private ServiceConnection getServiceConnection() {
|
||||
return new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
public void onServiceDisconnected(final ComponentName name) {
|
||||
Log.d(getTag(), "Player service is disconnected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
public void onServiceConnected(final ComponentName name, final IBinder service) {
|
||||
Log.d(getTag(), "Player service is connected");
|
||||
|
||||
if (service instanceof PlayerServiceBinder) {
|
||||
player = ((PlayerServiceBinder) service).getPlayerInstance();
|
||||
}
|
||||
|
||||
if (player == null || player.getPlayQueue() == null ||
|
||||
player.getPlayQueueAdapter() == null || player.getPlayer() == null) {
|
||||
if (player == null || player.getPlayQueue() == null
|
||||
|| player.getPlayQueueAdapter() == null || player.getPlayer() == null) {
|
||||
unbind();
|
||||
finish();
|
||||
} else {
|
||||
|
|
@ -332,39 +322,43 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
|
||||
final PopupMenu menu = new PopupMenu(this, view);
|
||||
final MenuItem remove = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/0,
|
||||
final PopupMenu popupMenu = new PopupMenu(this, view);
|
||||
final MenuItem remove = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 0,
|
||||
Menu.NONE, R.string.play_queue_remove);
|
||||
remove.setOnMenuItemClickListener(menuItem -> {
|
||||
if (player == null) return false;
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int index = player.getPlayQueue().indexOf(item);
|
||||
if (index != -1) player.getPlayQueue().remove(index);
|
||||
if (index != -1) {
|
||||
player.getPlayQueue().remove(index);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
final MenuItem detail = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/1,
|
||||
final MenuItem detail = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 1,
|
||||
Menu.NONE, R.string.play_queue_stream_detail);
|
||||
detail.setOnMenuItemClickListener(menuItem -> {
|
||||
onOpenDetail(item.getServiceId(), item.getUrl(), item.getTitle());
|
||||
return true;
|
||||
});
|
||||
|
||||
final MenuItem append = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/2,
|
||||
final MenuItem append = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 2,
|
||||
Menu.NONE, R.string.append_playlist);
|
||||
append.setOnMenuItemClickListener(menuItem -> {
|
||||
openPlaylistAppendDialog(Collections.singletonList(item));
|
||||
return true;
|
||||
});
|
||||
|
||||
final MenuItem share = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/3,
|
||||
final MenuItem share = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 3,
|
||||
Menu.NONE, R.string.share);
|
||||
share.setOnMenuItemClickListener(menuItem -> {
|
||||
shareUrl(item.getTitle(), item.getUrl());
|
||||
return true;
|
||||
});
|
||||
|
||||
menu.show();
|
||||
popupMenu.show();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -374,8 +368,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
private OnScrollBelowItemsListener getQueueScrollListener() {
|
||||
return new OnScrollBelowItemsListener() {
|
||||
@Override
|
||||
public void onScrolledDown(RecyclerView recyclerView) {
|
||||
if (player != null && player.getPlayQueue() != null && !player.getPlayQueue().isComplete()) {
|
||||
public void onScrolledDown(final RecyclerView recyclerView) {
|
||||
if (player != null && player.getPlayQueue() != null
|
||||
&& !player.getPlayQueue().isComplete()) {
|
||||
player.getPlayQueue().fetch();
|
||||
} else if (itemsList != null) {
|
||||
itemsList.clearOnScrollListeners();
|
||||
|
|
@ -387,13 +382,17 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
||||
return new PlayQueueItemTouchCallback() {
|
||||
@Override
|
||||
public void onMove(int sourceIndex, int targetIndex) {
|
||||
if (player != null) player.getPlayQueue().move(sourceIndex, targetIndex);
|
||||
public void onMove(final int sourceIndex, final int targetIndex) {
|
||||
if (player != null) {
|
||||
player.getPlayQueue().move(sourceIndex, targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(int index) {
|
||||
if (index != -1) player.getPlayQueue().remove(index);
|
||||
public void onSwiped(final int index) {
|
||||
if (index != -1) {
|
||||
player.getPlayQueue().remove(index);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -401,31 +400,42 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() {
|
||||
return new PlayQueueItemBuilder.OnSelectedListener() {
|
||||
@Override
|
||||
public void selected(PlayQueueItem item, View view) {
|
||||
if (player != null) player.onSelected(item);
|
||||
public void selected(final PlayQueueItem item, final View view) {
|
||||
if (player != null) {
|
||||
player.onSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(PlayQueueItem item, View view) {
|
||||
if (player == null) return;
|
||||
public void held(final PlayQueueItem item, final View view) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int index = player.getPlayQueue().indexOf(item);
|
||||
if (index != -1) buildItemPopupMenu(item, view);
|
||||
if (index != -1) {
|
||||
buildItemPopupMenu(item, view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartDrag(PlayQueueItemHolder viewHolder) {
|
||||
if (itemTouchHelper != null) itemTouchHelper.startDrag(viewHolder);
|
||||
public void onStartDrag(final PlayQueueItemHolder viewHolder) {
|
||||
if (itemTouchHelper != null) {
|
||||
itemTouchHelper.startDrag(viewHolder);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void onOpenDetail(int serviceId, String videoUrl, String videoTitle) {
|
||||
private void onOpenDetail(final int serviceId, final String videoUrl,
|
||||
final String videoTitle) {
|
||||
NavigationHelper.openVideoDetail(this, serviceId, videoUrl, videoTitle);
|
||||
}
|
||||
|
||||
private void scrollToSelected() {
|
||||
if (player == null) return;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int currentPlayingIndex = player.getPlayQueue().getIndex();
|
||||
final int currentVisibleIndex;
|
||||
|
|
@ -449,36 +459,29 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (player == null) return;
|
||||
public void onClick(final View view) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.getId() == repeatButton.getId()) {
|
||||
player.onRepeatClicked();
|
||||
|
||||
} else if (view.getId() == backwardButton.getId()) {
|
||||
player.onPlayPrevious();
|
||||
|
||||
} else if (view.getId() == playPauseButton.getId()) {
|
||||
player.onPlayPause();
|
||||
|
||||
} else if (view.getId() == forwardButton.getId()) {
|
||||
player.onPlayNext();
|
||||
|
||||
} else if (view.getId() == shuffleButton.getId()) {
|
||||
player.onShuffleClicked();
|
||||
|
||||
} else if (view.getId() == playbackSpeedButton.getId()) {
|
||||
openPlaybackParameterDialog();
|
||||
|
||||
} else if (view.getId() == playbackPitchButton.getId()) {
|
||||
openPlaybackParameterDialog();
|
||||
|
||||
} else if (view.getId() == metadata.getId()) {
|
||||
scrollToSelected();
|
||||
|
||||
} else if (view.getId() == progressLiveSync.getId()) {
|
||||
player.seekToDefault();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -487,14 +490,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void openPlaybackParameterDialog() {
|
||||
if (player == null) return;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
|
||||
player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
|
||||
boolean playbackSkipSilence) {
|
||||
public void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch,
|
||||
final boolean playbackSkipSilence) {
|
||||
if (player != null) {
|
||||
player.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
|
||||
}
|
||||
|
|
@ -505,7 +510,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
public void onProgressChanged(final SeekBar seekBar, final int progress,
|
||||
final boolean fromUser) {
|
||||
if (fromUser) {
|
||||
final String seekTime = Localization.getDurationString(progress / 1000);
|
||||
progressCurrentTime.setText(seekTime);
|
||||
|
|
@ -514,14 +520,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
public void onStartTrackingTouch(final SeekBar seekBar) {
|
||||
seeking = true;
|
||||
seekDisplay.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
if (player != null) player.seekTo(seekBar.getProgress());
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
if (player != null) {
|
||||
player.seekTo(seekBar.getProgress());
|
||||
}
|
||||
seekDisplay.setVisibility(View.GONE);
|
||||
seeking = false;
|
||||
}
|
||||
|
|
@ -545,7 +553,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
// Share
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void shareUrl(String subject, String url) {
|
||||
private void shareUrl(final String subject, final String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
|
|
@ -558,7 +566,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters) {
|
||||
public void onPlaybackUpdate(final int state, final int repeatMode, final boolean shuffled,
|
||||
final PlaybackParameters parameters) {
|
||||
onStateChanged(state);
|
||||
onPlayModeChanged(repeatMode, shuffled);
|
||||
onPlaybackParameterChanged(parameters);
|
||||
|
|
@ -567,9 +576,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(int currentProgress, int duration, int bufferPercent) {
|
||||
public void onProgressUpdate(final int currentProgress, final int duration,
|
||||
final int bufferPercent) {
|
||||
// Set buffer progress
|
||||
progressSeekBar.setSecondaryProgress((int) (progressSeekBar.getMax() * ((float) bufferPercent / 100)));
|
||||
progressSeekBar.setSecondaryProgress((int) (progressSeekBar.getMax()
|
||||
* ((float) bufferPercent / 100)));
|
||||
|
||||
// Set Duration
|
||||
progressSeekBar.setMax(duration);
|
||||
|
|
@ -593,7 +604,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataUpdate(StreamInfo info) {
|
||||
public void onMetadataUpdate(final StreamInfo info) {
|
||||
if (info != null) {
|
||||
metadataTitle.setText(info.getName());
|
||||
metadataArtist.setText(info.getUploaderName());
|
||||
|
|
@ -680,7 +691,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
private void onMaybePlaybackAdapterChanged() {
|
||||
if (itemsList == null || player == null) return;
|
||||
if (itemsList == null || player == null) {
|
||||
return;
|
||||
}
|
||||
final PlayQueueAdapter maybeNewAdapter = player.getPlayQueueAdapter();
|
||||
if (maybeNewAdapter != null && itemsList.getAdapter() != maybeNewAdapter) {
|
||||
itemsList.setAdapter(maybeNewAdapter);
|
||||
|
|
@ -698,8 +711,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
//2) Icon change accordingly to current App Theme
|
||||
// using rootView.getContext() because getApplicationContext() didn't work
|
||||
item.setIcon(player.isMuted()
|
||||
? ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(), R.attr.volume_off)
|
||||
: ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(), R.attr.volume_on));
|
||||
? ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
|
||||
R.attr.volume_off)
|
||||
: ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
|
||||
R.attr.volume_on));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
|||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
/**
|
||||
* Base for <b>video</b> players
|
||||
* Base for <b>video</b> players.
|
||||
*
|
||||
* @author mauriciocolli
|
||||
*/
|
||||
|
|
@ -91,115 +91,113 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
PopupMenu.OnMenuItemClickListener,
|
||||
PopupMenu.OnDismissListener {
|
||||
public static final boolean DEBUG = BasePlayer.DEBUG;
|
||||
public final String TAG;
|
||||
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Player
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected static final int RENDERER_UNAVAILABLE = -1;
|
||||
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
|
||||
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
|
||||
|
||||
private List<VideoStream> availableStreams;
|
||||
private int selectedStreamIndex;
|
||||
|
||||
protected boolean wasPlaying = false;
|
||||
|
||||
@NonNull final private VideoPlaybackResolver resolver;
|
||||
protected static final int RENDERER_UNAVAILABLE = -1;
|
||||
public final String TAG;
|
||||
@NonNull
|
||||
private final VideoPlaybackResolver resolver;
|
||||
private final Handler controlsVisibilityHandler = new Handler();
|
||||
private final int qualityPopupMenuGroupId = 69;
|
||||
private final int playbackSpeedPopupMenuGroupId = 79;
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private final int captionPopupMenuGroupId = 89;
|
||||
protected boolean wasPlaying = false;
|
||||
boolean isSomePopupMenuVisible = false;
|
||||
private List<VideoStream> availableStreams;
|
||||
private int selectedStreamIndex;
|
||||
private View rootView;
|
||||
|
||||
private AspectRatioFrameLayout aspectRatioFrameLayout;
|
||||
private SurfaceView surfaceView;
|
||||
private View surfaceForeground;
|
||||
|
||||
private View loadingPanel;
|
||||
private ImageView endScreen;
|
||||
private ImageView controlAnimationView;
|
||||
|
||||
private View controlsRoot;
|
||||
private TextView currentDisplaySeek;
|
||||
|
||||
private View bottomControlsRoot;
|
||||
private SeekBar playbackSeekBar;
|
||||
private TextView playbackCurrentTime;
|
||||
private TextView playbackEndTime;
|
||||
private TextView playbackLiveSync;
|
||||
private TextView playbackSpeedTextView;
|
||||
|
||||
private View topControlsRoot;
|
||||
private TextView qualityTextView;
|
||||
|
||||
private SubtitleView subtitleView;
|
||||
|
||||
private TextView resizeView;
|
||||
private TextView captionTextView;
|
||||
|
||||
private ValueAnimator controlViewAnimator;
|
||||
private final Handler controlsVisibilityHandler = new Handler();
|
||||
|
||||
boolean isSomePopupMenuVisible = false;
|
||||
private final int qualityPopupMenuGroupId = 69;
|
||||
private PopupMenu qualityPopupMenu;
|
||||
|
||||
private final int playbackSpeedPopupMenuGroupId = 79;
|
||||
private PopupMenu playbackSpeedPopupMenu;
|
||||
|
||||
private final int captionPopupMenuGroupId = 89;
|
||||
private PopupMenu captionPopupMenu;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public VideoPlayer(String debugTag, Context context) {
|
||||
public VideoPlayer(final String debugTag, final Context context) {
|
||||
super(context);
|
||||
this.TAG = debugTag;
|
||||
this.resolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
|
||||
}
|
||||
|
||||
public void setup(View rootView) {
|
||||
initViews(rootView);
|
||||
// workaround to match normalized captions like english to English or deutsch to Deutsch
|
||||
private static boolean containsCaseInsensitive(final List<String> list, final String toFind) {
|
||||
for (String i : list) {
|
||||
if (i.equalsIgnoreCase(toFind)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setup(final View view) {
|
||||
initViews(view);
|
||||
setup();
|
||||
}
|
||||
|
||||
public void initViews(View rootView) {
|
||||
this.rootView = rootView;
|
||||
this.aspectRatioFrameLayout = rootView.findViewById(R.id.aspectRatioLayout);
|
||||
this.surfaceView = rootView.findViewById(R.id.surfaceView);
|
||||
this.surfaceForeground = rootView.findViewById(R.id.surfaceForeground);
|
||||
this.loadingPanel = rootView.findViewById(R.id.loading_panel);
|
||||
this.endScreen = rootView.findViewById(R.id.endScreen);
|
||||
this.controlAnimationView = rootView.findViewById(R.id.controlAnimationView);
|
||||
this.controlsRoot = rootView.findViewById(R.id.playbackControlRoot);
|
||||
this.currentDisplaySeek = rootView.findViewById(R.id.currentDisplaySeek);
|
||||
this.playbackSeekBar = rootView.findViewById(R.id.playbackSeekBar);
|
||||
this.playbackCurrentTime = rootView.findViewById(R.id.playbackCurrentTime);
|
||||
this.playbackEndTime = rootView.findViewById(R.id.playbackEndTime);
|
||||
this.playbackLiveSync = rootView.findViewById(R.id.playbackLiveSync);
|
||||
this.playbackSpeedTextView = rootView.findViewById(R.id.playbackSpeed);
|
||||
this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls);
|
||||
this.topControlsRoot = rootView.findViewById(R.id.topControls);
|
||||
this.qualityTextView = rootView.findViewById(R.id.qualityTextView);
|
||||
public void initViews(final View view) {
|
||||
this.rootView = view;
|
||||
this.aspectRatioFrameLayout = view.findViewById(R.id.aspectRatioLayout);
|
||||
this.surfaceView = view.findViewById(R.id.surfaceView);
|
||||
this.surfaceForeground = view.findViewById(R.id.surfaceForeground);
|
||||
this.loadingPanel = view.findViewById(R.id.loading_panel);
|
||||
this.endScreen = view.findViewById(R.id.endScreen);
|
||||
this.controlAnimationView = view.findViewById(R.id.controlAnimationView);
|
||||
this.controlsRoot = view.findViewById(R.id.playbackControlRoot);
|
||||
this.currentDisplaySeek = view.findViewById(R.id.currentDisplaySeek);
|
||||
this.playbackSeekBar = view.findViewById(R.id.playbackSeekBar);
|
||||
this.playbackCurrentTime = view.findViewById(R.id.playbackCurrentTime);
|
||||
this.playbackEndTime = view.findViewById(R.id.playbackEndTime);
|
||||
this.playbackLiveSync = view.findViewById(R.id.playbackLiveSync);
|
||||
this.playbackSpeedTextView = view.findViewById(R.id.playbackSpeed);
|
||||
this.bottomControlsRoot = view.findViewById(R.id.bottomControls);
|
||||
this.topControlsRoot = view.findViewById(R.id.topControls);
|
||||
this.qualityTextView = view.findViewById(R.id.qualityTextView);
|
||||
|
||||
this.subtitleView = rootView.findViewById(R.id.subtitleView);
|
||||
this.subtitleView = view.findViewById(R.id.subtitleView);
|
||||
|
||||
final float captionScale = PlayerHelper.getCaptionScale(context);
|
||||
final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context);
|
||||
setupSubtitleView(subtitleView, captionScale, captionStyle);
|
||||
|
||||
this.resizeView = rootView.findViewById(R.id.resizeTextView);
|
||||
resizeView.setText(PlayerHelper.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode()));
|
||||
this.resizeView = view.findViewById(R.id.resizeTextView);
|
||||
resizeView.setText(PlayerHelper
|
||||
.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode()));
|
||||
|
||||
this.captionTextView = rootView.findViewById(R.id.captionTextView);
|
||||
this.captionTextView = view.findViewById(R.id.captionTextView);
|
||||
|
||||
//this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
||||
this.playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
|
||||
}
|
||||
this.playbackSeekBar.getProgressDrawable().
|
||||
setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
|
||||
|
||||
this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
|
||||
this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView);
|
||||
|
|
@ -209,9 +207,8 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
.getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
|
||||
}
|
||||
|
||||
protected abstract void setupSubtitleView(@NonNull SubtitleView view,
|
||||
final float captionScale,
|
||||
@NonNull final CaptionStyleCompat captionStyle);
|
||||
protected abstract void setupSubtitleView(@NonNull SubtitleView view, float captionScale,
|
||||
@NonNull CaptionStyleCompat captionStyle);
|
||||
|
||||
@Override
|
||||
public void initListeners() {
|
||||
|
|
@ -241,9 +238,15 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// UI Builders
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void handleIntent(final Intent intent) {
|
||||
if (intent == null) return;
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (intent.hasExtra(PLAYBACK_QUALITY)) {
|
||||
setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
|
||||
|
|
@ -252,18 +255,16 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
super.handleIntent(intent);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// UI Builders
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void buildQualityMenu() {
|
||||
if (qualityPopupMenu == null) return;
|
||||
if (qualityPopupMenu == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
||||
for (int i = 0; i < availableStreams.size(); i++) {
|
||||
VideoStream videoStream = availableStreams.get(i);
|
||||
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE,
|
||||
MediaFormat.getNameById(videoStream.getFormatId()) + " " + videoStream.resolution);
|
||||
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat
|
||||
.getNameById(videoStream.getFormatId()) + " " + videoStream.resolution);
|
||||
}
|
||||
if (getSelectedVideoStream() != null) {
|
||||
qualityTextView.setText(getSelectedVideoStream().resolution);
|
||||
|
|
@ -273,11 +274,14 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
|
||||
private void buildPlaybackSpeedMenu() {
|
||||
if (playbackSpeedPopupMenu == null) return;
|
||||
if (playbackSpeedPopupMenu == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
playbackSpeedPopupMenu.getMenu().removeGroup(playbackSpeedPopupMenuGroupId);
|
||||
for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) {
|
||||
playbackSpeedPopupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE, formatSpeed(PLAYBACK_SPEEDS[i]));
|
||||
playbackSpeedPopupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE,
|
||||
formatSpeed(PLAYBACK_SPEEDS[i]));
|
||||
}
|
||||
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
|
||||
playbackSpeedPopupMenu.setOnMenuItemClickListener(this);
|
||||
|
|
@ -285,7 +289,9 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
|
||||
private void buildCaptionMenu(final List<String> availableLanguages) {
|
||||
if (captionPopupMenu == null) return;
|
||||
if (captionPopupMenu == null) {
|
||||
return;
|
||||
}
|
||||
captionPopupMenu.getMenu().removeGroup(captionPopupMenuGroupId);
|
||||
|
||||
String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
|
@ -296,8 +302,8 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
* we are only looking for "(" instead of "(auto-generated)" to hopefully get all
|
||||
* internationalized variants such as "(automatisch-erzeugt)" and so on
|
||||
*/
|
||||
boolean searchForAutogenerated = userPreferredLanguage != null &&
|
||||
!userPreferredLanguage.contains("(");
|
||||
boolean searchForAutogenerated = userPreferredLanguage != null
|
||||
&& !userPreferredLanguage.contains("(");
|
||||
|
||||
// Add option for turning off caption
|
||||
MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
|
||||
|
|
@ -324,18 +330,19 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
trackSelector.setPreferredTextLanguage(captionLanguage);
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setRendererDisabled(textRendererIndex, false));
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final SharedPreferences prefs = PreferenceManager
|
||||
.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putString(context.getString(R.string.caption_user_set_key),
|
||||
captionLanguage).commit();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// apply caption language from previous user preference
|
||||
if (userPreferredLanguage != null && (captionLanguage.equals(userPreferredLanguage) ||
|
||||
searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage) ||
|
||||
userPreferredLanguage.contains("(") &&
|
||||
captionLanguage.startsWith(userPreferredLanguage.substring(0,
|
||||
userPreferredLanguage.indexOf('('))))) {
|
||||
if (userPreferredLanguage != null && (captionLanguage.equals(userPreferredLanguage)
|
||||
|| searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage)
|
||||
|| userPreferredLanguage.contains("(") && captionLanguage.startsWith(
|
||||
userPreferredLanguage
|
||||
.substring(0, userPreferredLanguage.indexOf('('))))) {
|
||||
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
||||
if (textRendererIndex != RENDERER_UNAVAILABLE) {
|
||||
trackSelector.setPreferredTextLanguage(captionLanguage);
|
||||
|
|
@ -347,9 +354,14 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
captionPopupMenu.setOnDismissListener(this);
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void updateStreamRelatedViews() {
|
||||
if (getCurrentMetadata() == null) return;
|
||||
if (getCurrentMetadata() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MediaSourceTag tag = getCurrentMetadata();
|
||||
final StreamInfo metadata = tag.getMetadata();
|
||||
|
|
@ -380,8 +392,10 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
break;
|
||||
|
||||
case VIDEO_STREAM:
|
||||
if (metadata.getVideoStreams().size() + metadata.getVideoOnlyStreams().size() == 0)
|
||||
if (metadata.getVideoStreams().size() + metadata.getVideoOnlyStreams().size()
|
||||
== 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
availableStreams = tag.getSortedAvailableVideoStreams();
|
||||
selectedStreamIndex = tag.getSelectedVideoStreamIndex();
|
||||
|
|
@ -398,9 +412,6 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
buildPlaybackSpeedMenu();
|
||||
playbackSpeedTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver();
|
||||
|
||||
|
|
@ -409,16 +420,16 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
updateStreamRelatedViews();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// States Implementation
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
return resolver.resolve(info);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// States Implementation
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onBlocked() {
|
||||
super.onBlocked();
|
||||
|
|
@ -427,9 +438,11 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
animateView(controlsRoot, false, DEFAULT_CONTROLS_DURATION);
|
||||
|
||||
playbackSeekBar.setEnabled(false);
|
||||
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-,
|
||||
// so sets the color again
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
loadingPanel.setBackgroundColor(Color.BLACK);
|
||||
animateView(loadingPanel, true, 0);
|
||||
|
|
@ -445,9 +458,11 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
showAndAnimateControl(-1, true);
|
||||
|
||||
playbackSeekBar.setEnabled(true);
|
||||
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-,
|
||||
// so sets the color again
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
loadingPanel.setVisibility(View.GONE);
|
||||
|
||||
|
|
@ -456,23 +471,33 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
|
||||
@Override
|
||||
public void onBuffering() {
|
||||
if (DEBUG) Log.d(TAG, "onBuffering() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onBuffering() called");
|
||||
}
|
||||
loadingPanel.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaused() {
|
||||
if (DEBUG) Log.d(TAG, "onPaused() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onPaused() called");
|
||||
}
|
||||
showControls(400);
|
||||
loadingPanel.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPausedSeek() {
|
||||
if (DEBUG) Log.d(TAG, "onPausedSeek() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onPausedSeek() called");
|
||||
}
|
||||
showAndAnimateControl(-1, true);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// ExoPlayer Video Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
super.onCompleted();
|
||||
|
|
@ -485,44 +510,50 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
animateView(surfaceForeground, true, 100);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// ExoPlayer Video Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
public void onTracksChanged(final TrackGroupArray trackGroups,
|
||||
final TrackSelectionArray trackSelections) {
|
||||
super.onTracksChanged(trackGroups, trackSelections);
|
||||
onTextTrackUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||
public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
|
||||
super.onPlaybackParametersChanged(playbackParameters);
|
||||
playbackSpeedTextView.setText(formatSpeed(playbackParameters.speed));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
||||
public void onVideoSizeChanged(final int width, final int height,
|
||||
final int unappliedRotationDegrees,
|
||||
final float pixelWidthHeightRatio) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onVideoSizeChanged() called with: width / height = [" + width + " / " + height + " = " + (((float) width) / height) + "], unappliedRotationDegrees = [" + unappliedRotationDegrees + "], pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
|
||||
Log.d(TAG, "onVideoSizeChanged() called with: "
|
||||
+ "width / height = [" + width + " / " + height
|
||||
+ " = " + (((float) width) / height) + "], "
|
||||
+ "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
|
||||
+ "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
|
||||
}
|
||||
aspectRatioFrameLayout.setAspectRatio(((float) width) / height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRenderedFirstFrame() {
|
||||
animateView(surfaceForeground, false, 100);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// ExoPlayer Track Updates
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onRenderedFirstFrame() {
|
||||
animateView(surfaceForeground, false, 100);
|
||||
}
|
||||
|
||||
private void onTextTrackUpdate() {
|
||||
final int textRenderer = getRendererIndex(C.TRACK_TYPE_TEXT);
|
||||
|
||||
if (captionTextView == null) return;
|
||||
if (trackSelector.getCurrentMappedTrackInfo() == null || textRenderer == RENDERER_UNAVAILABLE) {
|
||||
if (captionTextView == null) {
|
||||
return;
|
||||
}
|
||||
if (trackSelector.getCurrentMappedTrackInfo() == null
|
||||
|| textRenderer == RENDERER_UNAVAILABLE) {
|
||||
captionTextView.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
|
@ -543,8 +574,8 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
final String preferredLanguage = trackSelector.getPreferredTextLanguage();
|
||||
// Build UI
|
||||
buildCaptionMenu(availableLanguages);
|
||||
if (trackSelector.getParameters().getRendererDisabled(textRenderer) ||
|
||||
preferredLanguage == null || (!availableLanguages.contains(preferredLanguage)
|
||||
if (trackSelector.getParameters().getRendererDisabled(textRenderer)
|
||||
|| preferredLanguage == null || (!availableLanguages.contains(preferredLanguage)
|
||||
&& !containsCaseInsensitive(availableLanguages, preferredLanguage))) {
|
||||
captionTextView.setText(R.string.caption_none);
|
||||
} else {
|
||||
|
|
@ -553,22 +584,15 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
// workaround to match normalized captions like english to English or deutsch to Deutsch
|
||||
private static boolean containsCaseInsensitive(List<String> list, String toFind) {
|
||||
for(String i : list){
|
||||
if(i.equalsIgnoreCase(toFind))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// General Player
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onPrepared(boolean playWhenReady) {
|
||||
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
|
||||
public void onPrepared(final boolean playWhenReady) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
|
||||
}
|
||||
|
||||
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
|
||||
playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
|
||||
|
|
@ -578,41 +602,56 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
|
||||
if (simpleExoPlayer.getCurrentPosition() != 0 && !isControlsVisible()) {
|
||||
controlsVisibilityHandler.removeCallbacksAndMessages(null);
|
||||
controlsVisibilityHandler.postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION);
|
||||
controlsVisibilityHandler
|
||||
.postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
if (endScreen != null) endScreen.setImageBitmap(null);
|
||||
if (endScreen != null) {
|
||||
endScreen.setImageBitmap(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
|
||||
if (!isPrepared()) return;
|
||||
public void onUpdateProgress(final int currentProgress, final int duration,
|
||||
final int bufferPercent) {
|
||||
if (!isPrepared()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (duration != playbackSeekBar.getMax()) {
|
||||
playbackEndTime.setText(getTimeString(duration));
|
||||
playbackSeekBar.setMax(duration);
|
||||
}
|
||||
if (currentState != STATE_PAUSED) {
|
||||
if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress);
|
||||
if (currentState != STATE_PAUSED_SEEK) {
|
||||
playbackSeekBar.setProgress(currentProgress);
|
||||
}
|
||||
playbackCurrentTime.setText(getTimeString(currentProgress));
|
||||
}
|
||||
if (simpleExoPlayer.isLoading() || bufferPercent > 90) {
|
||||
playbackSeekBar.setSecondaryProgress((int) (playbackSeekBar.getMax() * ((float) bufferPercent / 100)));
|
||||
playbackSeekBar.setSecondaryProgress(
|
||||
(int) (playbackSeekBar.getMax() * ((float) bufferPercent / 100)));
|
||||
}
|
||||
if (DEBUG && bufferPercent % 20 == 0) { //Limit log
|
||||
Log.d(TAG, "updateProgress() called with: isVisible = " + isControlsVisible() + ", currentProgress = [" + currentProgress + "], duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]");
|
||||
Log.d(TAG, "updateProgress() called with: "
|
||||
+ "isVisible = " + isControlsVisible() + ", "
|
||||
+ "currentProgress = [" + currentProgress + "], "
|
||||
+ "duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]");
|
||||
}
|
||||
playbackLiveSync.setClickable(!isLiveEdge());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
public void onLoadingComplete(final String imageUri, final View view,
|
||||
final Bitmap loadedImage) {
|
||||
super.onLoadingComplete(imageUri, view, loadedImage);
|
||||
if (loadedImage != null) endScreen.setImageBitmap(loadedImage);
|
||||
if (loadedImage != null) {
|
||||
endScreen.setImageBitmap(loadedImage);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onFullScreenButtonClicked() {
|
||||
|
|
@ -636,8 +675,10 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||
public void onClick(final View v) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||
}
|
||||
if (v.getId() == qualityTextView.getId()) {
|
||||
onQualitySelectorClicked();
|
||||
} else if (v.getId() == playbackSpeedTextView.getId()) {
|
||||
|
|
@ -652,17 +693,22 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
|
||||
/**
|
||||
* Called when an item of the quality selector or the playback speed selector is selected
|
||||
* Called when an item of the quality selector or the playback speed selector is selected.
|
||||
*/
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
|
||||
public boolean onMenuItemClick(final MenuItem menuItem) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onMenuItemClick() called with: "
|
||||
+ "menuItem = [" + menuItem + "], "
|
||||
+ "menuItem.getItemId = [" + menuItem.getItemId() + "]");
|
||||
}
|
||||
|
||||
if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
|
||||
final int menuItemIndex = menuItem.getItemId();
|
||||
if (selectedStreamIndex == menuItemIndex ||
|
||||
availableStreams == null || availableStreams.size() <= menuItemIndex) return true;
|
||||
if (selectedStreamIndex == menuItemIndex || availableStreams == null
|
||||
|| availableStreams.size() <= menuItemIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final String newResolution = availableStreams.get(menuItemIndex).resolution;
|
||||
setRecovery();
|
||||
|
|
@ -683,11 +729,13 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
|
||||
/**
|
||||
* Called when some popup menu is dismissed
|
||||
* Called when some popup menu is dismissed.
|
||||
*/
|
||||
@Override
|
||||
public void onDismiss(PopupMenu menu) {
|
||||
if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]");
|
||||
public void onDismiss(final PopupMenu menu) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]");
|
||||
}
|
||||
isSomePopupMenuVisible = false;
|
||||
if (getSelectedVideoStream() != null) {
|
||||
qualityTextView.setText(getSelectedVideoStream().resolution);
|
||||
|
|
@ -695,7 +743,9 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
|
||||
public void onQualitySelectorClicked() {
|
||||
if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onQualitySelectorClicked() called");
|
||||
}
|
||||
qualityPopupMenu.show();
|
||||
isSomePopupMenuVisible = true;
|
||||
showControls(DEFAULT_CONTROLS_DURATION);
|
||||
|
|
@ -711,14 +761,18 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
|
||||
public void onPlaybackSpeedClicked() {
|
||||
if (DEBUG) Log.d(TAG, "onPlaybackSpeedClicked() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onPlaybackSpeedClicked() called");
|
||||
}
|
||||
playbackSpeedPopupMenu.show();
|
||||
isSomePopupMenuVisible = true;
|
||||
showControls(DEFAULT_CONTROLS_DURATION);
|
||||
}
|
||||
|
||||
private void onCaptionClicked() {
|
||||
if (DEBUG) Log.d(TAG, "onCaptionClicked() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onCaptionClicked() called");
|
||||
}
|
||||
captionPopupMenu.show();
|
||||
isSomePopupMenuVisible = true;
|
||||
showControls(DEFAULT_CONTROLS_DURATION);
|
||||
|
|
@ -737,26 +791,38 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode));
|
||||
}
|
||||
|
||||
protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode);
|
||||
protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode);
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// SeekBar Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (DEBUG && fromUser) Log.d(TAG, "onProgressChanged() called with: seekBar = [" + seekBar + "], progress = [" + progress + "]");
|
||||
public void onProgressChanged(final SeekBar seekBar, final int progress,
|
||||
final boolean fromUser) {
|
||||
if (DEBUG && fromUser) {
|
||||
Log.d(TAG, "onProgressChanged() called with: "
|
||||
+ "seekBar = [" + seekBar + "], progress = [" + progress + "]");
|
||||
}
|
||||
//if (fromUser) playbackCurrentTime.setText(getTimeString(progress));
|
||||
if (fromUser) currentDisplaySeek.setText(getTimeString(progress));
|
||||
if (fromUser) {
|
||||
currentDisplaySeek.setText(getTimeString(progress));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
if (DEBUG) Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]");
|
||||
if (getCurrentState() != STATE_PAUSED_SEEK) changeState(STATE_PAUSED_SEEK);
|
||||
public void onStartTrackingTouch(final SeekBar seekBar) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]");
|
||||
}
|
||||
if (getCurrentState() != STATE_PAUSED_SEEK) {
|
||||
changeState(STATE_PAUSED_SEEK);
|
||||
}
|
||||
|
||||
wasPlaying = simpleExoPlayer.getPlayWhenReady();
|
||||
if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false);
|
||||
if (isPlaying()) {
|
||||
simpleExoPlayer.setPlayWhenReady(false);
|
||||
}
|
||||
|
||||
showControls(0);
|
||||
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, true,
|
||||
|
|
@ -764,17 +830,25 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
if (DEBUG) Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]");
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]");
|
||||
}
|
||||
|
||||
seekTo(seekBar.getProgress());
|
||||
if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) simpleExoPlayer.setPlayWhenReady(true);
|
||||
if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) {
|
||||
simpleExoPlayer.setPlayWhenReady(true);
|
||||
}
|
||||
|
||||
playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
|
||||
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
|
||||
|
||||
if (getCurrentState() == STATE_PAUSED_SEEK) changeState(STATE_BUFFERING);
|
||||
if (!isProgressLoopRunning()) startProgressLoop();
|
||||
if (getCurrentState() == STATE_PAUSED_SEEK) {
|
||||
changeState(STATE_BUFFERING);
|
||||
}
|
||||
if (!isProgressLoopRunning()) {
|
||||
startProgressLoop();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -782,7 +856,9 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public int getRendererIndex(final int trackIndex) {
|
||||
if (simpleExoPlayer == null) return RENDERER_UNAVAILABLE;
|
||||
if (simpleExoPlayer == null) {
|
||||
return RENDERER_UNAVAILABLE;
|
||||
}
|
||||
|
||||
for (int t = 0; t < simpleExoPlayer.getRendererCount(); t++) {
|
||||
if (simpleExoPlayer.getRendererType(t) == trackIndex) {
|
||||
|
|
@ -798,15 +874,21 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
|
||||
/**
|
||||
* Show a animation, and depending on goneOnEnd, will stay on the screen or be gone
|
||||
* Show a animation, and depending on goneOnEnd, will stay on the screen or be gone.
|
||||
*
|
||||
* @param drawableId the drawable that will be used to animate, pass -1 to clear any animation that is visible
|
||||
* @param drawableId the drawable that will be used to animate,
|
||||
* pass -1 to clear any animation that is visible
|
||||
* @param goneOnEnd will set the animation view to GONE on the end of the animation
|
||||
*/
|
||||
public void showAndAnimateControl(final int drawableId, final boolean goneOnEnd) {
|
||||
if (DEBUG) Log.d(TAG, "showAndAnimateControl() called with: drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "showAndAnimateControl() called with: "
|
||||
+ "drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]");
|
||||
}
|
||||
if (controlViewAnimator != null && controlViewAnimator.isRunning()) {
|
||||
if (DEBUG) Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning");
|
||||
}
|
||||
controlViewAnimator.end();
|
||||
}
|
||||
|
||||
|
|
@ -819,7 +901,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
).setDuration(DEFAULT_CONTROLS_DURATION);
|
||||
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
controlAnimationView.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
|
@ -828,8 +910,10 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
return;
|
||||
}
|
||||
|
||||
float scaleFrom = goneOnEnd ? 1f : 1f, scaleTo = goneOnEnd ? 1.8f : 1.4f;
|
||||
float alphaFrom = goneOnEnd ? 1f : 0f, alphaTo = goneOnEnd ? 0f : 1f;
|
||||
float scaleFrom = goneOnEnd ? 1f : 1f;
|
||||
float scaleTo = goneOnEnd ? 1.8f : 1.4f;
|
||||
float alphaFrom = goneOnEnd ? 1f : 0f;
|
||||
float alphaTo = goneOnEnd ? 0f : 1f;
|
||||
|
||||
|
||||
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
|
||||
|
|
@ -840,9 +924,12 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
controlViewAnimator.setDuration(goneOnEnd ? 1000 : 500);
|
||||
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (goneOnEnd) controlAnimationView.setVisibility(View.GONE);
|
||||
else controlAnimationView.setVisibility(View.VISIBLE);
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
if (goneOnEnd) {
|
||||
controlAnimationView.setVisibility(View.GONE);
|
||||
} else {
|
||||
controlAnimationView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -857,50 +944,58 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
|
||||
public void showControlsThenHide() {
|
||||
if (DEBUG) Log.d(TAG, "showControlsThenHide() called");
|
||||
animateView(controlsRoot, true, DEFAULT_CONTROLS_DURATION, 0,
|
||||
() -> hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME));
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "showControlsThenHide() called");
|
||||
}
|
||||
animateView(controlsRoot, true, DEFAULT_CONTROLS_DURATION, 0, () ->
|
||||
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME));
|
||||
}
|
||||
|
||||
public void showControls(long duration) {
|
||||
if (DEBUG) Log.d(TAG, "showControls() called");
|
||||
public void showControls(final long duration) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "showControls() called");
|
||||
}
|
||||
controlsVisibilityHandler.removeCallbacksAndMessages(null);
|
||||
animateView(controlsRoot, true, duration);
|
||||
}
|
||||
|
||||
public void hideControls(final long duration, long delay) {
|
||||
if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
|
||||
public void hideControls(final long duration, final long delay) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
|
||||
}
|
||||
controlsVisibilityHandler.removeCallbacksAndMessages(null);
|
||||
controlsVisibilityHandler.postDelayed(
|
||||
() -> animateView(controlsRoot, false, duration), delay);
|
||||
controlsVisibilityHandler.postDelayed(() ->
|
||||
animateView(controlsRoot, false, duration), delay);
|
||||
}
|
||||
|
||||
public void hideControlsAndButton(final long duration, long delay, View button) {
|
||||
if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
|
||||
public void hideControlsAndButton(final long duration, final long delay, final View button) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
|
||||
}
|
||||
controlsVisibilityHandler.removeCallbacksAndMessages(null);
|
||||
controlsVisibilityHandler.postDelayed(hideControlsAndButtonHandler(duration, button), delay);
|
||||
controlsVisibilityHandler
|
||||
.postDelayed(hideControlsAndButtonHandler(duration, button), delay);
|
||||
}
|
||||
|
||||
private Runnable hideControlsAndButtonHandler(long duration, View videoPlayPause)
|
||||
{
|
||||
private Runnable hideControlsAndButtonHandler(final long duration, final View videoPlayPause) {
|
||||
return () -> {
|
||||
videoPlayPause.setVisibility(View.INVISIBLE);
|
||||
animateView(controlsRoot, false,duration);
|
||||
animateView(controlsRoot, false, duration);
|
||||
};
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Getters and Setters
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void setPlaybackQuality(final String quality) {
|
||||
this.resolver.setPlaybackQuality(quality);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPlaybackQuality() {
|
||||
return resolver.getPlaybackQuality();
|
||||
}
|
||||
|
||||
public void setPlaybackQuality(final String quality) {
|
||||
this.resolver.setPlaybackQuality(quality);
|
||||
}
|
||||
|
||||
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
|
||||
return aspectRatioFrameLayout;
|
||||
}
|
||||
|
|
@ -915,9 +1010,9 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
|
||||
@Nullable
|
||||
public VideoStream getSelectedVideoStream() {
|
||||
return (selectedStreamIndex >= 0 && availableStreams != null &&
|
||||
availableStreams.size() > selectedStreamIndex) ?
|
||||
availableStreams.get(selectedStreamIndex) : null;
|
||||
return (selectedStreamIndex >= 0 && availableStreams != null
|
||||
&& availableStreams.size() > selectedStreamIndex)
|
||||
? availableStreams.get(selectedStreamIndex) : null;
|
||||
}
|
||||
|
||||
public Handler getControlsVisibilityHandler() {
|
||||
|
|
@ -928,7 +1023,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
return rootView;
|
||||
}
|
||||
|
||||
public void setRootView(View rootView) {
|
||||
public void setRootView(final View rootView) {
|
||||
this.rootView = rootView;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,12 @@ import com.google.android.exoplayer2.PlaybackParameters;
|
|||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
||||
public interface PlayerEventListener {
|
||||
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters);
|
||||
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled,
|
||||
PlaybackParameters parameters);
|
||||
|
||||
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
|
||||
|
||||
void onMetadataUpdate(StreamInfo info);
|
||||
|
||||
void onServiceStopped();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ import android.media.AudioFocusRequest;
|
|||
import android.media.AudioManager;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||
|
||||
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
AnalyticsListener {
|
||||
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, AnalyticsListener {
|
||||
|
||||
private static final String TAG = "AudioFocusReactor";
|
||||
|
||||
|
|
@ -82,20 +82,20 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
|||
return audioManager.getStreamVolume(STREAM_TYPE);
|
||||
}
|
||||
|
||||
public int getMaxVolume() {
|
||||
return audioManager.getStreamMaxVolume(STREAM_TYPE);
|
||||
}
|
||||
|
||||
public void setVolume(final int volume) {
|
||||
audioManager.setStreamVolume(STREAM_TYPE, volume, 0);
|
||||
}
|
||||
|
||||
public int getMaxVolume() {
|
||||
return audioManager.getStreamMaxVolume(STREAM_TYPE);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// AudioFocus
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
public void onAudioFocusChange(final int focusChange) {
|
||||
Log.d(TAG, "onAudioFocusChange() called with: focusChange = [" + focusChange + "]");
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_GAIN:
|
||||
|
|
@ -138,17 +138,17 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
|||
valueAnimator.setDuration(AudioReactor.DUCK_DURATION);
|
||||
valueAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
public void onAnimationStart(final Animator animation) {
|
||||
player.setVolume(from);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
public void onAnimationCancel(final Animator animation) {
|
||||
player.setVolume(to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
player.setVolume(to);
|
||||
}
|
||||
});
|
||||
|
|
@ -162,8 +162,10 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onAudioSessionId(EventTime eventTime, int audioSessionId) {
|
||||
if (!PlayerHelper.isUsingDSP(context)) return;
|
||||
public void onAudioSessionId(final EventTime eventTime, final int audioSessionId) {
|
||||
if (!PlayerHelper.isUsingDSP(context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
|
||||
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId);
|
||||
|
|
|
|||
|
|
@ -21,23 +21,22 @@ import java.io.File;
|
|||
/* package-private */ class CacheFactory implements DataSource.Factory {
|
||||
private static final String TAG = "CacheFactory";
|
||||
private static final String CACHE_FOLDER_NAME = "exoplayer";
|
||||
private static final int CACHE_FLAGS = CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR;
|
||||
|
||||
private final DefaultDataSourceFactory dataSourceFactory;
|
||||
private final File cacheDir;
|
||||
private final long maxFileSize;
|
||||
|
||||
private static final int CACHE_FLAGS = CacheDataSource.FLAG_BLOCK_ON_CACHE
|
||||
| CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR;
|
||||
// Creating cache on every instance may cause problems with multiple players when
|
||||
// sources are not ExtractorMediaSource
|
||||
// see: https://stackoverflow.com/questions/28700391/using-cache-in-exoplayer
|
||||
// todo: make this a singleton?
|
||||
private static SimpleCache cache;
|
||||
private final DefaultDataSourceFactory dataSourceFactory;
|
||||
private final File cacheDir;
|
||||
private final long maxFileSize;
|
||||
|
||||
public CacheFactory(@NonNull final Context context,
|
||||
@NonNull final String userAgent,
|
||||
@NonNull final TransferListener transferListener) {
|
||||
this(context, userAgent, transferListener, PlayerHelper.getPreferredCacheSize(context),
|
||||
PlayerHelper.getPreferredFileSize(context));
|
||||
CacheFactory(@NonNull final Context context,
|
||||
@NonNull final String userAgent,
|
||||
@NonNull final TransferListener transferListener) {
|
||||
this(context, userAgent, transferListener, PlayerHelper.getPreferredCacheSize(),
|
||||
PlayerHelper.getPreferredFileSize());
|
||||
}
|
||||
|
||||
private CacheFactory(@NonNull final Context context,
|
||||
|
|
@ -55,7 +54,8 @@ import java.io.File;
|
|||
}
|
||||
|
||||
if (cache == null) {
|
||||
final LeastRecentlyUsedCacheEvictor evictor = new LeastRecentlyUsedCacheEvictor(maxCacheSize);
|
||||
final LeastRecentlyUsedCacheEvictor evictor
|
||||
= new LeastRecentlyUsedCacheEvictor(maxCacheSize);
|
||||
cache = new SimpleCache(cacheDir, evictor, new ExoDatabaseProvider(context));
|
||||
}
|
||||
}
|
||||
|
|
@ -72,7 +72,9 @@ import java.io.File;
|
|||
}
|
||||
|
||||
public void tryDeleteCacheFiles() {
|
||||
if (!cacheDir.exists() || !cacheDir.isDirectory()) return;
|
||||
if (!cacheDir.exists() || !cacheDir.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
for (File file : cacheDir.listFiles()) {
|
||||
|
|
@ -85,4 +87,4 @@ import java.io.File;
|
|||
Log.e(TAG, "Failed to delete file.", ignored);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package org.schabi.newpipe.player.helper;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.LoadControl;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
|
|
@ -20,10 +18,10 @@ public class LoadController implements LoadControl {
|
|||
// Default Load Control
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public LoadController(final Context context) {
|
||||
this(PlayerHelper.getPlaybackStartBufferMs(context),
|
||||
PlayerHelper.getPlaybackMinimumBufferMs(context),
|
||||
PlayerHelper.getPlaybackOptimalBufferMs(context));
|
||||
public LoadController() {
|
||||
this(PlayerHelper.getPlaybackStartBufferMs(),
|
||||
PlayerHelper.getPlaybackMinimumBufferMs(),
|
||||
PlayerHelper.getPlaybackOptimalBufferMs());
|
||||
}
|
||||
|
||||
private LoadController(final int initialPlaybackBufferMs,
|
||||
|
|
@ -47,8 +45,8 @@ public class LoadController implements LoadControl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroupArray,
|
||||
TrackSelectionArray trackSelectionArray) {
|
||||
public void onTracksSelected(final Renderer[] renderers, final TrackGroupArray trackGroupArray,
|
||||
final TrackSelectionArray trackSelectionArray) {
|
||||
internalLoadControl.onTracksSelected(renderers, trackGroupArray, trackSelectionArray);
|
||||
}
|
||||
|
||||
|
|
@ -78,17 +76,18 @@ public class LoadController implements LoadControl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
|
||||
public boolean shouldContinueLoading(final long bufferedDurationUs,
|
||||
final float playbackSpeed) {
|
||||
return internalLoadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed,
|
||||
boolean rebuffering) {
|
||||
final boolean isInitialPlaybackBufferFilled = bufferedDurationUs >=
|
||||
this.initialPlaybackBufferUs * playbackSpeed;
|
||||
final boolean isInternalStartingPlayback = internalLoadControl.shouldStartPlayback(
|
||||
bufferedDurationUs, playbackSpeed, rebuffering);
|
||||
public boolean shouldStartPlayback(final long bufferedDurationUs, final float playbackSpeed,
|
||||
final boolean rebuffering) {
|
||||
final boolean isInitialPlaybackBufferFilled
|
||||
= bufferedDurationUs >= this.initialPlaybackBufferUs * playbackSpeed;
|
||||
final boolean isInternalStartingPlayback = internalLoadControl
|
||||
.shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering);
|
||||
return isInitialPlaybackBufferFilled || isInternalStartingPlayback;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,25 +18,37 @@ public class LockManager {
|
|||
private WifiManager.WifiLock wifiLock;
|
||||
|
||||
public LockManager(final Context context) {
|
||||
powerManager = ((PowerManager) context.getApplicationContext().getSystemService(POWER_SERVICE));
|
||||
wifiManager = ((WifiManager) context.getApplicationContext().getSystemService(WIFI_SERVICE));
|
||||
powerManager = ((PowerManager) context.getApplicationContext()
|
||||
.getSystemService(POWER_SERVICE));
|
||||
wifiManager = ((WifiManager) context.getApplicationContext()
|
||||
.getSystemService(WIFI_SERVICE));
|
||||
}
|
||||
|
||||
public void acquireWifiAndCpu() {
|
||||
Log.d(TAG, "acquireWifiAndCpu() called");
|
||||
if (wakeLock != null && wakeLock.isHeld() && wifiLock != null && wifiLock.isHeld()) return;
|
||||
if (wakeLock != null && wakeLock.isHeld() && wifiLock != null && wifiLock.isHeld()) {
|
||||
return;
|
||||
}
|
||||
|
||||
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
|
||||
wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
|
||||
|
||||
if (wakeLock != null) wakeLock.acquire();
|
||||
if (wifiLock != null) wifiLock.acquire();
|
||||
if (wakeLock != null) {
|
||||
wakeLock.acquire();
|
||||
}
|
||||
if (wifiLock != null) {
|
||||
wifiLock.acquire();
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseWifiAndCpu() {
|
||||
Log.d(TAG, "releaseWifiAndCpu() called");
|
||||
if (wakeLock != null && wakeLock.isHeld()) wakeLock.release();
|
||||
if (wifiLock != null && wifiLock.isHeld()) wifiLock.release();
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
if (wifiLock != null && wifiLock.isHeld()) {
|
||||
wifiLock.release();
|
||||
}
|
||||
|
||||
wakeLock = null;
|
||||
wifiLock = null;
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.media.session.MediaButtonReceiver;
|
||||
import androidx.media.app.NotificationCompat.MediaStyle;
|
||||
import androidx.media.session.MediaButtonReceiver;
|
||||
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
|
||||
|
|
@ -50,7 +50,8 @@ public class MediaSessionManager {
|
|||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public void setLockScreenArt(NotificationCompat.Builder builder, @Nullable Bitmap thumbnailBitmap) {
|
||||
public void setLockScreenArt(final NotificationCompat.Builder builder,
|
||||
@Nullable final Bitmap thumbnailBitmap) {
|
||||
if (thumbnailBitmap == null || !mediaSession.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -68,7 +69,7 @@ public class MediaSessionManager {
|
|||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public void clearLockScreenArt(NotificationCompat.Builder builder) {
|
||||
public void clearLockScreenArt(final NotificationCompat.Builder builder) {
|
||||
mediaSession.setMetadata(
|
||||
new MediaMetadataCompat.Builder()
|
||||
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, null)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,6 @@ package org.schabi.newpipe.player.helper;
|
|||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
|
@ -15,6 +10,11 @@ import android.widget.CheckBox;
|
|||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.SliderStrategy;
|
||||
|
||||
|
|
@ -22,64 +22,65 @@ import static org.schabi.newpipe.player.BasePlayer.DEBUG;
|
|||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
public class PlaybackParameterDialog extends DialogFragment {
|
||||
@NonNull private static final String TAG = "PlaybackParameterDialog";
|
||||
|
||||
// Minimum allowable range in ExoPlayer
|
||||
public static final double MINIMUM_PLAYBACK_VALUE = 0.10f;
|
||||
public static final double MAXIMUM_PLAYBACK_VALUE = 3.00f;
|
||||
|
||||
public static final char STEP_UP_SIGN = '+';
|
||||
public static final char STEP_DOWN_SIGN = '-';
|
||||
|
||||
public static final double STEP_ONE_PERCENT_VALUE = 0.01f;
|
||||
public static final double STEP_FIVE_PERCENT_VALUE = 0.05f;
|
||||
public static final double STEP_TEN_PERCENT_VALUE = 0.10f;
|
||||
public static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f;
|
||||
public static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f;
|
||||
|
||||
public static final double DEFAULT_TEMPO = 1.00f;
|
||||
public static final double DEFAULT_PITCH = 1.00f;
|
||||
public static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE;
|
||||
public static final boolean DEFAULT_SKIP_SILENCE = false;
|
||||
@NonNull
|
||||
private static final String TAG = "PlaybackParameterDialog";
|
||||
@NonNull
|
||||
private static final String INITIAL_TEMPO_KEY = "initial_tempo_key";
|
||||
@NonNull
|
||||
private static final String INITIAL_PITCH_KEY = "initial_pitch_key";
|
||||
|
||||
@NonNull private static final String INITIAL_TEMPO_KEY = "initial_tempo_key";
|
||||
@NonNull private static final String INITIAL_PITCH_KEY = "initial_pitch_key";
|
||||
|
||||
@NonNull private static final String TEMPO_KEY = "tempo_key";
|
||||
@NonNull private static final String PITCH_KEY = "pitch_key";
|
||||
@NonNull private static final String STEP_SIZE_KEY = "step_size_key";
|
||||
|
||||
public interface Callback {
|
||||
void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch,
|
||||
final boolean playbackSkipSilence);
|
||||
}
|
||||
|
||||
@Nullable private Callback callback;
|
||||
|
||||
@NonNull private final SliderStrategy strategy = new SliderStrategy.Quadratic(
|
||||
@NonNull
|
||||
private static final String TEMPO_KEY = "tempo_key";
|
||||
@NonNull
|
||||
private static final String PITCH_KEY = "pitch_key";
|
||||
@NonNull
|
||||
private static final String STEP_SIZE_KEY = "step_size_key";
|
||||
@NonNull
|
||||
private final SliderStrategy strategy = new SliderStrategy.Quadratic(
|
||||
MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE,
|
||||
/*centerAt=*/1.00f, /*sliderGranularity=*/10000);
|
||||
|
||||
@Nullable
|
||||
private Callback callback;
|
||||
private double initialTempo = DEFAULT_TEMPO;
|
||||
private double initialPitch = DEFAULT_PITCH;
|
||||
private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
|
||||
|
||||
private double tempo = DEFAULT_TEMPO;
|
||||
private double pitch = DEFAULT_PITCH;
|
||||
private double stepSize = DEFAULT_STEP;
|
||||
|
||||
@Nullable private SeekBar tempoSlider;
|
||||
@Nullable private TextView tempoCurrentText;
|
||||
@Nullable private TextView tempoStepDownText;
|
||||
@Nullable private TextView tempoStepUpText;
|
||||
|
||||
@Nullable private SeekBar pitchSlider;
|
||||
@Nullable private TextView pitchCurrentText;
|
||||
@Nullable private TextView pitchStepDownText;
|
||||
@Nullable private TextView pitchStepUpText;
|
||||
|
||||
@Nullable private CheckBox unhookingCheckbox;
|
||||
@Nullable private CheckBox skipSilenceCheckbox;
|
||||
@Nullable
|
||||
private SeekBar tempoSlider;
|
||||
@Nullable
|
||||
private TextView tempoCurrentText;
|
||||
@Nullable
|
||||
private TextView tempoStepDownText;
|
||||
@Nullable
|
||||
private TextView tempoStepUpText;
|
||||
@Nullable
|
||||
private SeekBar pitchSlider;
|
||||
@Nullable
|
||||
private TextView pitchCurrentText;
|
||||
@Nullable
|
||||
private TextView pitchStepDownText;
|
||||
@Nullable
|
||||
private TextView pitchStepUpText;
|
||||
@Nullable
|
||||
private CheckBox unhookingCheckbox;
|
||||
@Nullable
|
||||
private CheckBox skipSilenceCheckbox;
|
||||
|
||||
public static PlaybackParameterDialog newInstance(final double playbackTempo,
|
||||
final double playbackPitch,
|
||||
|
|
@ -95,12 +96,27 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
return dialog;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String getStepUpPercentString(final double percent) {
|
||||
return STEP_UP_SIGN + getPercentString(percent);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Lifecycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@NonNull
|
||||
private static String getStepDownPercentString(final double percent) {
|
||||
return STEP_DOWN_SIGN + getPercentString(percent);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String getPercentString(final double percent) {
|
||||
return PlayerHelper.formatPitch(percent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
if (context != null && context instanceof Callback) {
|
||||
callback = (Callback) context;
|
||||
|
|
@ -109,8 +125,12 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Dialog
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
assureCorrectAppLanguage(getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
|
|
@ -123,8 +143,12 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Control Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
public void onSaveInstanceState(final Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putDouble(INITIAL_TEMPO_KEY, initialTempo);
|
||||
outState.putDouble(INITIAL_PITCH_KEY, initialPitch);
|
||||
|
|
@ -134,13 +158,9 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
outState.putDouble(STEP_SIZE_KEY, getCurrentStepSize());
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Dialog
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
||||
assureCorrectAppLanguage(getContext());
|
||||
final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null);
|
||||
setupControlViews(view);
|
||||
|
|
@ -159,22 +179,18 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
return dialogBuilder.create();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Control Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void setupControlViews(@NonNull View rootView) {
|
||||
private void setupControlViews(@NonNull final View rootView) {
|
||||
setupHookingControl(rootView);
|
||||
setupSkipSilenceControl(rootView);
|
||||
|
||||
setupTempoControl(rootView);
|
||||
setupPitchControl(rootView);
|
||||
|
||||
changeStepSize(stepSize);
|
||||
setStepSize(stepSize);
|
||||
setupStepSizeSelector(rootView);
|
||||
}
|
||||
|
||||
private void setupTempoControl(@NonNull View rootView) {
|
||||
private void setupTempoControl(@NonNull final View rootView) {
|
||||
tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
|
||||
TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
|
||||
TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
|
||||
|
|
@ -182,12 +198,15 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
|
||||
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
|
||||
|
||||
if (tempoCurrentText != null)
|
||||
if (tempoCurrentText != null) {
|
||||
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
|
||||
if (tempoMaximumText != null)
|
||||
}
|
||||
if (tempoMaximumText != null) {
|
||||
tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE));
|
||||
if (tempoMinimumText != null)
|
||||
}
|
||||
if (tempoMinimumText != null) {
|
||||
tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE));
|
||||
}
|
||||
|
||||
if (tempoSlider != null) {
|
||||
tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
|
||||
|
|
@ -196,7 +215,7 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
}
|
||||
|
||||
private void setupPitchControl(@NonNull View rootView) {
|
||||
private void setupPitchControl(@NonNull final View rootView) {
|
||||
pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
|
||||
TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
|
||||
TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
|
||||
|
|
@ -204,12 +223,15 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
|
||||
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
|
||||
|
||||
if (pitchCurrentText != null)
|
||||
if (pitchCurrentText != null) {
|
||||
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
|
||||
if (pitchMaximumText != null)
|
||||
}
|
||||
if (pitchMaximumText != null) {
|
||||
pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE));
|
||||
if (pitchMinimumText != null)
|
||||
}
|
||||
if (pitchMinimumText != null) {
|
||||
pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE));
|
||||
}
|
||||
|
||||
if (pitchSlider != null) {
|
||||
pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
|
||||
|
|
@ -218,7 +240,7 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
}
|
||||
|
||||
private void setupHookingControl(@NonNull View rootView) {
|
||||
private void setupHookingControl(@NonNull final View rootView) {
|
||||
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
|
||||
if (unhookingCheckbox != null) {
|
||||
// restore whether pitch and tempo are unhooked or not
|
||||
|
|
@ -242,7 +264,7 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
}
|
||||
|
||||
private void setupSkipSilenceControl(@NonNull View rootView) {
|
||||
private void setupSkipSilenceControl(@NonNull final View rootView) {
|
||||
skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox);
|
||||
if (skipSilenceCheckbox != null) {
|
||||
skipSilenceCheckbox.setChecked(initialSkipSilence);
|
||||
|
|
@ -251,45 +273,53 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Sliders
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void setupStepSizeSelector(@NonNull final View rootView) {
|
||||
TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
|
||||
TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
|
||||
TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
|
||||
TextView stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent);
|
||||
TextView stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent);
|
||||
TextView stepSizeTwentyFivePercentText = rootView
|
||||
.findViewById(R.id.stepSizeTwentyFivePercent);
|
||||
TextView stepSizeOneHundredPercentText = rootView
|
||||
.findViewById(R.id.stepSizeOneHundredPercent);
|
||||
|
||||
if (stepSizeOnePercentText != null) {
|
||||
stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE));
|
||||
stepSizeOnePercentText.setOnClickListener(view ->
|
||||
changeStepSize(STEP_ONE_PERCENT_VALUE));
|
||||
stepSizeOnePercentText
|
||||
.setOnClickListener(view -> setStepSize(STEP_ONE_PERCENT_VALUE));
|
||||
}
|
||||
|
||||
if (stepSizeFivePercentText != null) {
|
||||
stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE));
|
||||
stepSizeFivePercentText.setOnClickListener(view ->
|
||||
changeStepSize(STEP_FIVE_PERCENT_VALUE));
|
||||
stepSizeFivePercentText
|
||||
.setOnClickListener(view -> setStepSize(STEP_FIVE_PERCENT_VALUE));
|
||||
}
|
||||
|
||||
if (stepSizeTenPercentText != null) {
|
||||
stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE));
|
||||
stepSizeTenPercentText.setOnClickListener(view ->
|
||||
changeStepSize(STEP_TEN_PERCENT_VALUE));
|
||||
stepSizeTenPercentText
|
||||
.setOnClickListener(view -> setStepSize(STEP_TEN_PERCENT_VALUE));
|
||||
}
|
||||
|
||||
if (stepSizeTwentyFivePercentText != null) {
|
||||
stepSizeTwentyFivePercentText.setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE));
|
||||
stepSizeTwentyFivePercentText.setOnClickListener(view ->
|
||||
changeStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE));
|
||||
stepSizeTwentyFivePercentText
|
||||
.setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE));
|
||||
stepSizeTwentyFivePercentText
|
||||
.setOnClickListener(view -> setStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE));
|
||||
}
|
||||
|
||||
if (stepSizeOneHundredPercentText != null) {
|
||||
stepSizeOneHundredPercentText.setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE));
|
||||
stepSizeOneHundredPercentText.setOnClickListener(view ->
|
||||
changeStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE));
|
||||
stepSizeOneHundredPercentText
|
||||
.setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE));
|
||||
stepSizeOneHundredPercentText
|
||||
.setOnClickListener(view -> setStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
private void changeStepSize(final double stepSize) {
|
||||
private void setStepSize(final double stepSize) {
|
||||
this.stepSize = stepSize;
|
||||
|
||||
if (tempoStepUpText != null) {
|
||||
|
|
@ -325,14 +355,11 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Sliders
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private SeekBar.OnSeekBarChangeListener getOnTempoChangedListener() {
|
||||
return new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
public void onProgressChanged(final SeekBar seekBar, final int progress,
|
||||
final boolean fromUser) {
|
||||
final double currentTempo = strategy.valueOf(progress);
|
||||
if (fromUser) {
|
||||
onTempoSliderUpdated(currentTempo);
|
||||
|
|
@ -341,12 +368,12 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
public void onStartTrackingTouch(final SeekBar seekBar) {
|
||||
// Do Nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
// Do Nothing.
|
||||
}
|
||||
};
|
||||
|
|
@ -355,7 +382,8 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
private SeekBar.OnSeekBarChangeListener getOnPitchChangedListener() {
|
||||
return new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
public void onProgressChanged(final SeekBar seekBar, final int progress,
|
||||
final boolean fromUser) {
|
||||
final double currentPitch = strategy.valueOf(progress);
|
||||
if (fromUser) { // this change is first in chain
|
||||
onPitchSliderUpdated(currentPitch);
|
||||
|
|
@ -364,19 +392,21 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
public void onStartTrackingTouch(final SeekBar seekBar) {
|
||||
// Do Nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
// Do Nothing.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void onTempoSliderUpdated(final double newTempo) {
|
||||
if (unhookingCheckbox == null) return;
|
||||
if (unhookingCheckbox == null) {
|
||||
return;
|
||||
}
|
||||
if (!unhookingCheckbox.isChecked()) {
|
||||
setSliders(newTempo);
|
||||
} else {
|
||||
|
|
@ -385,7 +415,9 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
private void onPitchSliderUpdated(final double newPitch) {
|
||||
if (unhookingCheckbox == null) return;
|
||||
if (unhookingCheckbox == null) {
|
||||
return;
|
||||
}
|
||||
if (!unhookingCheckbox.isChecked()) {
|
||||
setSliders(newPitch);
|
||||
} else {
|
||||
|
|
@ -398,45 +430,49 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
setPitchSlider(newValue);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Helper
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void setTempoSlider(final double newTempo) {
|
||||
if (tempoSlider == null) return;
|
||||
if (tempoSlider == null) {
|
||||
return;
|
||||
}
|
||||
tempoSlider.setProgress(strategy.progressOf(newTempo));
|
||||
}
|
||||
|
||||
private void setPitchSlider(final double newPitch) {
|
||||
if (pitchSlider == null) return;
|
||||
if (pitchSlider == null) {
|
||||
return;
|
||||
}
|
||||
pitchSlider.setProgress(strategy.progressOf(newPitch));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Helper
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void setCurrentPlaybackParameters() {
|
||||
setPlaybackParameters(getCurrentTempo(), getCurrentPitch(), getCurrentSkipSilence());
|
||||
}
|
||||
|
||||
private void setPlaybackParameters(final double tempo, final double pitch,
|
||||
private void setPlaybackParameters(final double newTempo, final double newPitch,
|
||||
final boolean skipSilence) {
|
||||
if (callback != null && tempoCurrentText != null && pitchCurrentText != null) {
|
||||
if (DEBUG) Log.d(TAG, "Setting playback parameters to " +
|
||||
"tempo=[" + tempo + "], " +
|
||||
"pitch=[" + pitch + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Setting playback parameters to "
|
||||
+ "tempo=[" + newTempo + "], "
|
||||
+ "pitch=[" + newPitch + "]");
|
||||
}
|
||||
|
||||
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
|
||||
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
|
||||
callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence);
|
||||
tempoCurrentText.setText(PlayerHelper.formatSpeed(newTempo));
|
||||
pitchCurrentText.setText(PlayerHelper.formatPitch(newPitch));
|
||||
callback.onPlaybackParameterChanged((float) newTempo, (float) newPitch, skipSilence);
|
||||
}
|
||||
}
|
||||
|
||||
private double getCurrentTempo() {
|
||||
return tempoSlider == null ? tempo : strategy.valueOf(
|
||||
tempoSlider.getProgress());
|
||||
return tempoSlider == null ? tempo : strategy.valueOf(tempoSlider.getProgress());
|
||||
}
|
||||
|
||||
private double getCurrentPitch() {
|
||||
return pitchSlider == null ? pitch : strategy.valueOf(
|
||||
pitchSlider.getProgress());
|
||||
return pitchSlider == null ? pitch : strategy.valueOf(pitchSlider.getProgress());
|
||||
}
|
||||
|
||||
private double getCurrentStepSize() {
|
||||
|
|
@ -447,18 +483,8 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String getStepUpPercentString(final double percent) {
|
||||
return STEP_UP_SIGN + getPercentString(percent);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String getStepDownPercentString(final double percent) {
|
||||
return STEP_DOWN_SIGN + getPercentString(percent);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String getPercentString(final double percent) {
|
||||
return PlayerHelper.formatPitch(percent);
|
||||
public interface Callback {
|
||||
void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
|
||||
boolean playbackSkipSilence);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,30 +24,33 @@ public class PlayerDataSource {
|
|||
private final DataSource.Factory cacheDataSourceFactory;
|
||||
private final DataSource.Factory cachelessDataSourceFactory;
|
||||
|
||||
public PlayerDataSource(@NonNull final Context context,
|
||||
@NonNull final String userAgent,
|
||||
public PlayerDataSource(@NonNull final Context context, @NonNull final String userAgent,
|
||||
@NonNull final TransferListener transferListener) {
|
||||
cacheDataSourceFactory = new CacheFactory(context, userAgent, transferListener);
|
||||
cachelessDataSourceFactory = new DefaultDataSourceFactory(context, userAgent, transferListener);
|
||||
cachelessDataSourceFactory
|
||||
= new DefaultDataSourceFactory(context, userAgent, transferListener);
|
||||
}
|
||||
|
||||
public SsMediaSource.Factory getLiveSsMediaSourceFactory() {
|
||||
return new SsMediaSource.Factory(new DefaultSsChunkSource.Factory(
|
||||
cachelessDataSourceFactory), cachelessDataSourceFactory)
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
|
||||
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
|
||||
}
|
||||
|
||||
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
|
||||
return new HlsMediaSource.Factory(cachelessDataSourceFactory)
|
||||
.setAllowChunklessPreparation(true)
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
|
||||
}
|
||||
|
||||
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
|
||||
return new DashMediaSource.Factory(new DefaultDashChunkSource.Factory(
|
||||
cachelessDataSourceFactory), cachelessDataSourceFactory)
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
|
||||
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS, true);
|
||||
}
|
||||
|
||||
|
|
@ -67,10 +70,12 @@ public class PlayerDataSource {
|
|||
|
||||
public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory() {
|
||||
return new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
|
||||
}
|
||||
|
||||
public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory(@NonNull final String key) {
|
||||
public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory(
|
||||
@NonNull final String key) {
|
||||
return getExtractorMediaSourceFactory().setCustomCacheKey(key);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.accessibility.CaptioningManager;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.accessibility.CaptioningManager;
|
||||
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
||||
|
|
@ -48,51 +49,49 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZ
|
|||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
|
||||
|
||||
public class PlayerHelper {
|
||||
private PlayerHelper() {}
|
||||
public final class PlayerHelper {
|
||||
private static final StringBuilder STRING_BUILDER = new StringBuilder();
|
||||
private static final Formatter STRING_FORMATTER
|
||||
= new Formatter(STRING_BUILDER, Locale.getDefault());
|
||||
private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x");
|
||||
private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%");
|
||||
|
||||
private static final StringBuilder stringBuilder = new StringBuilder();
|
||||
private static final Formatter stringFormatter = new Formatter(stringBuilder, Locale.getDefault());
|
||||
private static final NumberFormat speedFormatter = new DecimalFormat("0.##x");
|
||||
private static final NumberFormat pitchFormatter = new DecimalFormat("##%");
|
||||
private PlayerHelper() { }
|
||||
|
||||
@Retention(SOURCE)
|
||||
@IntDef({MINIMIZE_ON_EXIT_MODE_NONE, MINIMIZE_ON_EXIT_MODE_BACKGROUND,
|
||||
MINIMIZE_ON_EXIT_MODE_POPUP})
|
||||
public @interface MinimizeMode {
|
||||
int MINIMIZE_ON_EXIT_MODE_NONE = 0;
|
||||
int MINIMIZE_ON_EXIT_MODE_BACKGROUND = 1;
|
||||
int MINIMIZE_ON_EXIT_MODE_POPUP = 2;
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Exposed helpers
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static String getTimeString(int milliSeconds) {
|
||||
public static String getTimeString(final int milliSeconds) {
|
||||
int seconds = (milliSeconds % 60000) / 1000;
|
||||
int minutes = (milliSeconds % 3600000) / 60000;
|
||||
int hours = (milliSeconds % 86400000) / 3600000;
|
||||
int days = (milliSeconds % (86400000 * 7)) / 86400000;
|
||||
|
||||
stringBuilder.setLength(0);
|
||||
return days > 0 ? stringFormatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()
|
||||
: hours > 0 ? stringFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
|
||||
: stringFormatter.format("%02d:%02d", minutes, seconds).toString();
|
||||
STRING_BUILDER.setLength(0);
|
||||
return days > 0
|
||||
? STRING_FORMATTER.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds)
|
||||
.toString()
|
||||
: hours > 0
|
||||
? STRING_FORMATTER.format("%d:%02d:%02d", hours, minutes, seconds).toString()
|
||||
: STRING_FORMATTER.format("%02d:%02d", minutes, seconds).toString();
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Exposed helpers
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static String formatSpeed(final double speed) {
|
||||
return SPEED_FORMATTER.format(speed);
|
||||
}
|
||||
|
||||
public static String formatSpeed(double speed) {
|
||||
return speedFormatter.format(speed);
|
||||
}
|
||||
|
||||
public static String formatPitch(double pitch) {
|
||||
return pitchFormatter.format(pitch);
|
||||
public static String formatPitch(final double pitch) {
|
||||
return PITCH_FORMATTER.format(pitch);
|
||||
}
|
||||
|
||||
public static String subtitleMimeTypesOf(final MediaFormat format) {
|
||||
switch (format) {
|
||||
case VTT: return MimeTypes.TEXT_VTT;
|
||||
case TTML: return MimeTypes.APPLICATION_TTML;
|
||||
default: throw new IllegalArgumentException("Unrecognized mime type: " + format.name());
|
||||
case VTT:
|
||||
return MimeTypes.TEXT_VTT;
|
||||
case TTML:
|
||||
return MimeTypes.APPLICATION_TTML;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized mime type: " + format.name());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,42 +99,55 @@ public class PlayerHelper {
|
|||
public static String captionLanguageOf(@NonNull final Context context,
|
||||
@NonNull final SubtitlesStream subtitles) {
|
||||
final String displayName = subtitles.getDisplayLanguageName();
|
||||
return displayName + (subtitles.isAutoGenerated() ? " (" + context.getString(R.string.caption_auto_generated)+ ")" : "");
|
||||
return displayName + (subtitles.isAutoGenerated()
|
||||
? " (" + context.getString(R.string.caption_auto_generated) + ")" : "");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String resizeTypeOf(@NonNull final Context context,
|
||||
@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
|
||||
switch (resizeMode) {
|
||||
case RESIZE_MODE_FIT: return context.getResources().getString(R.string.resize_fit);
|
||||
case RESIZE_MODE_FILL: return context.getResources().getString(R.string.resize_fill);
|
||||
case RESIZE_MODE_ZOOM: return context.getResources().getString(R.string.resize_zoom);
|
||||
default: throw new IllegalArgumentException("Unrecognized resize mode: " + resizeMode);
|
||||
case RESIZE_MODE_FIT:
|
||||
return context.getResources().getString(R.string.resize_fit);
|
||||
case RESIZE_MODE_FILL:
|
||||
return context.getResources().getString(R.string.resize_fill);
|
||||
case RESIZE_MODE_ZOOM:
|
||||
return context.getResources().getString(R.string.resize_zoom);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized resize mode: " + resizeMode);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String cacheKeyOf(@NonNull final StreamInfo info, @NonNull VideoStream video) {
|
||||
public static String cacheKeyOf(@NonNull final StreamInfo info,
|
||||
@NonNull final VideoStream video) {
|
||||
return info.getUrl() + video.getResolution() + video.getFormat().getName();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String cacheKeyOf(@NonNull final StreamInfo info, @NonNull AudioStream audio) {
|
||||
public static String cacheKeyOf(@NonNull final StreamInfo info,
|
||||
@NonNull final AudioStream audio) {
|
||||
return info.getUrl() + audio.getAverageBitrate() + audio.getFormat().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a {@link StreamInfo} and the existing queue items, provide the
|
||||
* {@link SinglePlayQueue} consisting of the next video for auto queuing.
|
||||
* <br><br>
|
||||
* <p>
|
||||
* This method detects and prevents cycle by naively checking if a
|
||||
* candidate next video's url already exists in the existing items.
|
||||
* <br><br>
|
||||
* </p>
|
||||
* <p>
|
||||
* To select the next video, {@link StreamInfo#getNextVideo()} is first
|
||||
* checked. If it is nonnull and is not part of the existing items, then
|
||||
* it will be used as the next video. Otherwise, an random item with
|
||||
* non-repeating url will be selected from the {@link StreamInfo#getRelatedStreams()}.
|
||||
* */
|
||||
* </p>
|
||||
*
|
||||
* @param info currently playing stream
|
||||
* @param existingItems existing items in the queue
|
||||
* @return {@link SinglePlayQueue} with the next stream to queue
|
||||
*/
|
||||
@Nullable
|
||||
public static PlayQueue autoQueueOf(@NonNull final StreamInfo info,
|
||||
@NonNull final List<PlayQueueItem> existingItems) {
|
||||
|
|
@ -150,7 +162,9 @@ public class PlayerHelper {
|
|||
}
|
||||
|
||||
final List<InfoItem> relatedItems = info.getRelatedStreams();
|
||||
if (relatedItems == null) return null;
|
||||
if (relatedItems == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StreamInfoItem> autoQueueItems = new ArrayList<>();
|
||||
for (final InfoItem item : info.getRelatedStreams()) {
|
||||
|
|
@ -159,17 +173,18 @@ public class PlayerHelper {
|
|||
}
|
||||
}
|
||||
Collections.shuffle(autoQueueItems);
|
||||
return autoQueueItems.isEmpty() ? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
|
||||
return autoQueueItems.isEmpty()
|
||||
? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
|
||||
}
|
||||
|
||||
public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) {
|
||||
return isResumeAfterAudioFocusGain(context, false);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Settings Resolution
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) {
|
||||
return isResumeAfterAudioFocusGain(context, false);
|
||||
}
|
||||
|
||||
public static boolean isVolumeGestureEnabled(@NonNull final Context context) {
|
||||
return isVolumeGestureEnabled(context, true);
|
||||
}
|
||||
|
|
@ -204,44 +219,43 @@ public class PlayerHelper {
|
|||
|
||||
@NonNull
|
||||
public static SeekParameters getSeekParameters(@NonNull final Context context) {
|
||||
return isUsingInexactSeek(context) ?
|
||||
SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
|
||||
return isUsingInexactSeek(context) ? SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
|
||||
}
|
||||
|
||||
public static long getPreferredCacheSize(@NonNull final Context context) {
|
||||
public static long getPreferredCacheSize() {
|
||||
return 64 * 1024 * 1024L;
|
||||
}
|
||||
|
||||
public static long getPreferredFileSize(@NonNull final Context context) {
|
||||
public static long getPreferredFileSize() {
|
||||
return 512 * 1024L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of milliseconds the player buffers for before starting playback.
|
||||
* */
|
||||
public static int getPlaybackStartBufferMs(@NonNull final Context context) {
|
||||
* @return the number of milliseconds the player buffers for before starting playback
|
||||
*/
|
||||
public static int getPlaybackStartBufferMs() {
|
||||
return 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum number of milliseconds the player always buffers to after starting
|
||||
* playback.
|
||||
* */
|
||||
public static int getPlaybackMinimumBufferMs(@NonNull final Context context) {
|
||||
* @return the minimum number of milliseconds the player always buffers to
|
||||
* after starting playback.
|
||||
*/
|
||||
public static int getPlaybackMinimumBufferMs() {
|
||||
return 25000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum/optimal number of milliseconds the player will buffer to once the buffer
|
||||
* hits the point of {@link #getPlaybackMinimumBufferMs(Context)}.
|
||||
* */
|
||||
public static int getPlaybackOptimalBufferMs(@NonNull final Context context) {
|
||||
* @return the maximum/optimal number of milliseconds the player will buffer to once the buffer
|
||||
* hits the point of {@link #getPlaybackMinimumBufferMs()}.
|
||||
*/
|
||||
public static int getPlaybackOptimalBufferMs() {
|
||||
return 60000;
|
||||
}
|
||||
|
||||
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context) {
|
||||
return new AdaptiveTrackSelection.Factory(
|
||||
/*bufferDurationRequiredForQualityIncrease=*/1000,
|
||||
1000,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
|
||||
AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION);
|
||||
|
|
@ -257,7 +271,9 @@ public class PlayerHelper {
|
|||
|
||||
@NonNull
|
||||
public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return CaptionStyleCompat.DEFAULT;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return CaptionStyleCompat.DEFAULT;
|
||||
}
|
||||
|
||||
final CaptioningManager captioningManager = (CaptioningManager)
|
||||
context.getSystemService(Context.CAPTIONING_SERVICE);
|
||||
|
|
@ -269,14 +285,26 @@ public class PlayerHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* System font scaling:
|
||||
* Very small - 0.25f, Small - 0.5f, Normal - 1.0f, Large - 1.5f, Very Large - 2.0f
|
||||
* */
|
||||
* Get scaling for captions based on system font scaling.
|
||||
* <p>Options:</p>
|
||||
* <ul>
|
||||
* <li>Very small: 0.25f</li>
|
||||
* <li>Small: 0.5f</li>
|
||||
* <li>Normal: 1.0f</li>
|
||||
* <li>Large: 1.5f</li>
|
||||
* <li>Very large: 2.0f</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param context Android app context
|
||||
* @return caption scaling
|
||||
*/
|
||||
public static float getCaptionScale(@NonNull final Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return 1f;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return 1f;
|
||||
}
|
||||
|
||||
final CaptioningManager captioningManager = (CaptioningManager)
|
||||
context.getSystemService(Context.CAPTIONING_SERVICE);
|
||||
final CaptioningManager captioningManager
|
||||
= (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
|
||||
if (captioningManager == null || !captioningManager.isEnabled()) {
|
||||
return 1f;
|
||||
}
|
||||
|
|
@ -289,71 +317,96 @@ public class PlayerHelper {
|
|||
return getScreenBrightness(context, -1);
|
||||
}
|
||||
|
||||
public static void setScreenBrightness(@NonNull final Context context, final float setScreenBrightness) {
|
||||
public static void setScreenBrightness(@NonNull final Context context,
|
||||
final float setScreenBrightness) {
|
||||
setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Private helpers
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@NonNull
|
||||
private static SharedPreferences getPreferences(@NonNull final Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
private static boolean isResumeAfterAudioFocusGain(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), b);
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Private helpers
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static boolean isResumeAfterAudioFocusGain(@NonNull final Context context,
|
||||
final boolean b) {
|
||||
return getPreferences(context)
|
||||
.getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), b);
|
||||
}
|
||||
|
||||
private static boolean isVolumeGestureEnabled(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.volume_gesture_control_key), b);
|
||||
private static boolean isVolumeGestureEnabled(@NonNull final Context context,
|
||||
final boolean b) {
|
||||
return getPreferences(context)
|
||||
.getBoolean(context.getString(R.string.volume_gesture_control_key), b);
|
||||
}
|
||||
|
||||
private static boolean isBrightnessGestureEnabled(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.brightness_gesture_control_key), b);
|
||||
private static boolean isBrightnessGestureEnabled(@NonNull final Context context,
|
||||
final boolean b) {
|
||||
return getPreferences(context)
|
||||
.getBoolean(context.getString(R.string.brightness_gesture_control_key), b);
|
||||
}
|
||||
|
||||
private static boolean isRememberingPopupDimensions(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
|
||||
private static boolean isRememberingPopupDimensions(@NonNull final Context context,
|
||||
final boolean b) {
|
||||
return getPreferences(context)
|
||||
.getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
|
||||
}
|
||||
|
||||
private static boolean isUsingInexactSeek(@NonNull final Context context) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), false);
|
||||
return getPreferences(context)
|
||||
.getBoolean(context.getString(R.string.use_inexact_seek_key), false);
|
||||
}
|
||||
|
||||
private static boolean isAutoQueueEnabled(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.auto_queue_key), b);
|
||||
}
|
||||
|
||||
private static void setScreenBrightness(@NonNull final Context context, final float screenBrightness, final long timestamp) {
|
||||
private static void setScreenBrightness(@NonNull final Context context,
|
||||
final float screenBrightness, final long timestamp) {
|
||||
SharedPreferences.Editor editor = getPreferences(context).edit();
|
||||
editor.putFloat(context.getString(R.string.screen_brightness_key), screenBrightness);
|
||||
editor.putLong(context.getString(R.string.screen_brightness_timestamp_key), timestamp);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private static float getScreenBrightness(@NonNull final Context context, final float screenBrightness) {
|
||||
private static float getScreenBrightness(@NonNull final Context context,
|
||||
final float screenBrightness) {
|
||||
SharedPreferences sp = getPreferences(context);
|
||||
long timestamp = sp.getLong(context.getString(R.string.screen_brightness_timestamp_key), 0);
|
||||
// hypothesis: 4h covers a viewing block, eg evening. External lightning conditions will change in the next
|
||||
long timestamp = sp
|
||||
.getLong(context.getString(R.string.screen_brightness_timestamp_key), 0);
|
||||
// Hypothesis: 4h covers a viewing block, e.g. evening.
|
||||
// External lightning conditions will change in the next
|
||||
// viewing block so we fall back to the default brightness
|
||||
if ((System.currentTimeMillis() - timestamp) > TimeUnit.HOURS.toMillis(4)) {
|
||||
return screenBrightness;
|
||||
} else {
|
||||
return sp.getFloat(context.getString(R.string.screen_brightness_key), screenBrightness);
|
||||
return sp
|
||||
.getFloat(context.getString(R.string.screen_brightness_key), screenBrightness);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getMinimizeOnExitAction(@NonNull final Context context,
|
||||
final String key) {
|
||||
return getPreferences(context).getString(context.getString(R.string.minimize_on_exit_key),
|
||||
key);
|
||||
return getPreferences(context)
|
||||
.getString(context.getString(R.string.minimize_on_exit_key), key);
|
||||
}
|
||||
|
||||
private static SinglePlayQueue getAutoQueuedSinglePlayQueue(StreamInfoItem streamInfoItem) {
|
||||
private static SinglePlayQueue getAutoQueuedSinglePlayQueue(
|
||||
final StreamInfoItem streamInfoItem) {
|
||||
SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
|
||||
singlePlayQueue.getItem().setAutoQueued(true);
|
||||
return singlePlayQueue;
|
||||
}
|
||||
|
||||
@Retention(SOURCE)
|
||||
@IntDef({MINIMIZE_ON_EXIT_MODE_NONE, MINIMIZE_ON_EXIT_MODE_BACKGROUND,
|
||||
MINIMIZE_ON_EXIT_MODE_POPUP})
|
||||
public @interface MinimizeMode {
|
||||
int MINIMIZE_ON_EXIT_MODE_NONE = 0;
|
||||
int MINIMIZE_ON_EXIT_MODE_BACKGROUND = 1;
|
||||
int MINIMIZE_ON_EXIT_MODE_POPUP = 2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,18 @@ import android.support.v4.media.MediaDescriptionCompat;
|
|||
|
||||
public interface MediaSessionCallback {
|
||||
void onSkipToPrevious();
|
||||
|
||||
void onSkipToNext();
|
||||
void onSkipToIndex(final int index);
|
||||
|
||||
void onSkipToIndex(int index);
|
||||
|
||||
int getCurrentPlayingIndex();
|
||||
|
||||
int getQueueSize();
|
||||
MediaDescriptionCompat getQueueMetadata(final int index);
|
||||
|
||||
MediaDescriptionCompat getQueueMetadata(int index);
|
||||
|
||||
void onPlay();
|
||||
|
||||
void onPause();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_T
|
|||
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
|
||||
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
|
||||
|
||||
|
||||
public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator {
|
||||
public static final int DEFAULT_MAX_QUEUE_SIZE = 10;
|
||||
|
||||
|
|
@ -40,17 +39,17 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getSupportedQueueNavigatorActions(@Nullable Player player) {
|
||||
public long getSupportedQueueNavigatorActions(@Nullable final Player player) {
|
||||
return ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS | ACTION_SKIP_TO_QUEUE_ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Player player) {
|
||||
public void onTimelineChanged(final Player player) {
|
||||
publishFloatingQueueWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentWindowIndexChanged(Player player) {
|
||||
public void onCurrentWindowIndexChanged(final Player player) {
|
||||
if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID
|
||||
|| player.getCurrentTimeline().getWindowCount() > maxQueueSize) {
|
||||
publishFloatingQueueWindow();
|
||||
|
|
@ -60,22 +59,23 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getActiveQueueItemId(@Nullable Player player) {
|
||||
public long getActiveQueueItemId(@Nullable final Player player) {
|
||||
return callback.getCurrentPlayingIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipToPrevious(Player player, ControlDispatcher controlDispatcher) {
|
||||
public void onSkipToPrevious(final Player player, final ControlDispatcher controlDispatcher) {
|
||||
callback.onSkipToPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipToQueueItem(Player player, ControlDispatcher controlDispatcher, long id) {
|
||||
public void onSkipToQueueItem(final Player player, final ControlDispatcher controlDispatcher,
|
||||
final long id) {
|
||||
callback.onSkipToIndex((int) id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipToNext(Player player, ControlDispatcher controlDispatcher) {
|
||||
public void onSkipToNext(final Player player, final ControlDispatcher controlDispatcher) {
|
||||
callback.onSkipToNext();
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +102,8 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(Player player, ControlDispatcher controlDispatcher, String command, Bundle extras, ResultReceiver cb) {
|
||||
public boolean onCommand(final Player player, final ControlDispatcher controlDispatcher,
|
||||
final String command, final Bundle extras, final ResultReceiver cb) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public class PlayQueuePlaybackController extends DefaultControlDispatcher {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady) {
|
||||
public boolean dispatchSetPlayWhenReady(final Player player, final boolean playWhenReady) {
|
||||
if (playWhenReady) {
|
||||
callback.onPlay();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
|
|
@ -15,32 +16,8 @@ import java.io.IOException;
|
|||
|
||||
public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSource {
|
||||
private final String TAG = "FailedMediaSource@" + Integer.toHexString(hashCode());
|
||||
|
||||
public static class FailedMediaSourceException extends Exception {
|
||||
FailedMediaSourceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
FailedMediaSourceException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class MediaSourceResolutionException extends FailedMediaSourceException {
|
||||
public MediaSourceResolutionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class StreamInfoLoadException extends FailedMediaSourceException {
|
||||
public StreamInfoLoadException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
||||
private final PlayQueueItem playQueueItem;
|
||||
private final FailedMediaSourceException error;
|
||||
|
||||
private final long retryTimestamp;
|
||||
|
||||
public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem,
|
||||
|
|
@ -54,7 +31,10 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo
|
|||
/**
|
||||
* Permanently fail the play queue item associated with this source, with no hope of retrying.
|
||||
* The error will always be propagated to ExoPlayer.
|
||||
* */
|
||||
*
|
||||
* @param playQueueItem play queue item
|
||||
* @param error exception that was the reason to fail
|
||||
*/
|
||||
public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem,
|
||||
@NonNull final FailedMediaSourceException error) {
|
||||
this.playQueueItem = playQueueItem;
|
||||
|
|
@ -80,21 +60,21 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo
|
|||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||
public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator,
|
||||
final long startPositionUs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {}
|
||||
|
||||
public void releasePeriod(final MediaPeriod mediaPeriod) { }
|
||||
|
||||
@Override
|
||||
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
protected void prepareSourceInternal(@Nullable final TransferListener mediaTransferListener) {
|
||||
Log.e(TAG, "Loading failed source: ", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseSourceInternal() {}
|
||||
protected void releaseSourceInternal() { }
|
||||
|
||||
@Override
|
||||
public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity,
|
||||
|
|
@ -103,7 +83,29 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isStreamEqual(@NonNull PlayQueueItem stream) {
|
||||
public boolean isStreamEqual(@NonNull final PlayQueueItem stream) {
|
||||
return playQueueItem == stream;
|
||||
}
|
||||
|
||||
public static class FailedMediaSourceException extends Exception {
|
||||
FailedMediaSourceException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
FailedMediaSourceException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class MediaSourceResolutionException extends FailedMediaSourceException {
|
||||
public MediaSourceResolutionException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class StreamInfoLoadException extends FailedMediaSourceException {
|
||||
public StreamInfoLoadException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
|
@ -15,13 +16,11 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
|||
import java.io.IOException;
|
||||
|
||||
public class LoadedMediaSource implements ManagedMediaSource {
|
||||
|
||||
private final MediaSource source;
|
||||
private final PlayQueueItem stream;
|
||||
private final long expireTimestamp;
|
||||
|
||||
public LoadedMediaSource(@NonNull final MediaSource source,
|
||||
@NonNull final PlayQueueItem stream,
|
||||
public LoadedMediaSource(@NonNull final MediaSource source, @NonNull final PlayQueueItem stream,
|
||||
final long expireTimestamp) {
|
||||
this.source = source;
|
||||
this.stream = stream;
|
||||
|
|
@ -37,7 +36,8 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSource(SourceInfoRefreshListener listener, @Nullable TransferListener mediaTransferListener) {
|
||||
public void prepareSource(final SourceInfoRefreshListener listener,
|
||||
@Nullable final TransferListener mediaTransferListener) {
|
||||
source.prepareSource(listener, mediaTransferListener);
|
||||
}
|
||||
|
||||
|
|
@ -47,38 +47,40 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||
public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator,
|
||||
final long startPositionUs) {
|
||||
return source.createPeriod(id, allocator, startPositionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||
public void releasePeriod(final MediaPeriod mediaPeriod) {
|
||||
source.releasePeriod(mediaPeriod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseSource(SourceInfoRefreshListener listener) {
|
||||
public void releaseSource(final SourceInfoRefreshListener listener) {
|
||||
source.releaseSource(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEventListener(Handler handler, MediaSourceEventListener eventListener) {
|
||||
public void addEventListener(final Handler handler,
|
||||
final MediaSourceEventListener eventListener) {
|
||||
source.addEventListener(handler, eventListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEventListener(MediaSourceEventListener eventListener) {
|
||||
public void removeEventListener(final MediaSourceEventListener eventListener) {
|
||||
source.removeEventListener(eventListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity,
|
||||
public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity,
|
||||
final boolean isInterruptable) {
|
||||
return newIdentity != stream || (isInterruptable && isExpired());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStreamEqual(@NonNull PlayQueueItem stream) {
|
||||
return this.stream == stream;
|
||||
public boolean isStreamEqual(@NonNull final PlayQueueItem otherStream) {
|
||||
return this.stream == otherStream;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,18 +10,21 @@ public interface ManagedMediaSource extends MediaSource {
|
|||
/**
|
||||
* Determines whether or not this {@link ManagedMediaSource} can be replaced.
|
||||
*
|
||||
* @param newIdentity a stream the {@link ManagedMediaSource} should encapsulate over, if
|
||||
* it is different from the existing stream in the
|
||||
* {@link ManagedMediaSource}, then it should be replaced.
|
||||
* @param newIdentity a stream the {@link ManagedMediaSource} should encapsulate over, if
|
||||
* it is different from the existing stream in the
|
||||
* {@link ManagedMediaSource}, then it should be replaced.
|
||||
* @param isInterruptable specifies if this {@link ManagedMediaSource} potentially
|
||||
* being played.
|
||||
* */
|
||||
boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity,
|
||||
final boolean isInterruptable);
|
||||
* @return whether this could be replaces
|
||||
*/
|
||||
boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity, boolean isInterruptable);
|
||||
|
||||
/**
|
||||
* Determines if the {@link PlayQueueItem} is the one the
|
||||
* {@link ManagedMediaSource} encapsulates over.
|
||||
* */
|
||||
boolean isStreamEqual(@NonNull final PlayQueueItem stream);
|
||||
*
|
||||
* @param stream play queue item to check
|
||||
* @return whether this source is for the specified stream
|
||||
*/
|
||||
boolean isStreamEqual(@NonNull PlayQueueItem stream);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
|
@ -7,7 +9,8 @@ import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
|||
import com.google.android.exoplayer2.source.ShuffleOrder;
|
||||
|
||||
public class ManagedMediaSourcePlaylist {
|
||||
@NonNull private final ConcatenatingMediaSource internalSource;
|
||||
@NonNull
|
||||
private final ConcatenatingMediaSource internalSource;
|
||||
|
||||
public ManagedMediaSourcePlaylist() {
|
||||
internalSource = new ConcatenatingMediaSource(/*isPlaylistAtomic=*/false,
|
||||
|
|
@ -25,11 +28,14 @@ public class ManagedMediaSourcePlaylist {
|
|||
/**
|
||||
* Returns the {@link ManagedMediaSource} at the given index of the playlist.
|
||||
* If the index is invalid, then null is returned.
|
||||
* */
|
||||
*
|
||||
* @param index index of {@link ManagedMediaSource} to get from the playlist
|
||||
* @return the {@link ManagedMediaSource} at the given index of the playlist
|
||||
*/
|
||||
@Nullable
|
||||
public ManagedMediaSource get(final int index) {
|
||||
return (index < 0 || index >= size()) ?
|
||||
null : (ManagedMediaSource) internalSource.getMediaSource(index);
|
||||
return (index < 0 || index >= size())
|
||||
? null : (ManagedMediaSource) internalSource.getMediaSource(index);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
|
@ -46,15 +52,17 @@ public class ManagedMediaSourcePlaylist {
|
|||
* {@link PlaceholderMediaSource}.
|
||||
*
|
||||
* @see #append(ManagedMediaSource)
|
||||
* */
|
||||
*/
|
||||
public synchronized void expand() {
|
||||
append(new PlaceholderMediaSource());
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a {@link ManagedMediaSource} to the end of {@link ConcatenatingMediaSource}.
|
||||
*
|
||||
* @see ConcatenatingMediaSource#addMediaSource
|
||||
* */
|
||||
* @param source {@link ManagedMediaSource} to append
|
||||
*/
|
||||
public synchronized void append(@NonNull final ManagedMediaSource source) {
|
||||
internalSource.addMediaSource(source);
|
||||
}
|
||||
|
|
@ -62,10 +70,14 @@ public class ManagedMediaSourcePlaylist {
|
|||
/**
|
||||
* Removes a {@link ManagedMediaSource} from {@link ConcatenatingMediaSource}
|
||||
* at the given index. If this index is out of bound, then the removal is ignored.
|
||||
*
|
||||
* @see ConcatenatingMediaSource#removeMediaSource(int)
|
||||
* */
|
||||
* @param index of {@link ManagedMediaSource} to be removed
|
||||
*/
|
||||
public synchronized void remove(final int index) {
|
||||
if (index < 0 || index > internalSource.getSize()) return;
|
||||
if (index < 0 || index > internalSource.getSize()) {
|
||||
return;
|
||||
}
|
||||
|
||||
internalSource.removeMediaSource(index);
|
||||
}
|
||||
|
|
@ -74,11 +86,18 @@ public class ManagedMediaSourcePlaylist {
|
|||
* Moves a {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
|
||||
* from the given source index to the target index. If either index is out of bound,
|
||||
* then the call is ignored.
|
||||
*
|
||||
* @see ConcatenatingMediaSource#moveMediaSource(int, int)
|
||||
* */
|
||||
* @param source original index of {@link ManagedMediaSource}
|
||||
* @param target new index of {@link ManagedMediaSource}
|
||||
*/
|
||||
public synchronized void move(final int source, final int target) {
|
||||
if (source < 0 || target < 0) return;
|
||||
if (source >= internalSource.getSize() || target >= internalSource.getSize()) return;
|
||||
if (source < 0 || target < 0) {
|
||||
return;
|
||||
}
|
||||
if (source >= internalSource.getSize() || target >= internalSource.getSize()) {
|
||||
return;
|
||||
}
|
||||
|
||||
internalSource.moveMediaSource(source, target);
|
||||
}
|
||||
|
|
@ -86,20 +105,30 @@ public class ManagedMediaSourcePlaylist {
|
|||
/**
|
||||
* Invalidates the {@link ManagedMediaSource} at the given index by replacing it
|
||||
* with a {@link PlaceholderMediaSource}.
|
||||
*
|
||||
* @see #update(int, ManagedMediaSource, Handler, Runnable)
|
||||
* */
|
||||
* @param index index of {@link ManagedMediaSource} to invalidate
|
||||
* @param handler the {@link Handler} to run {@code finalizingAction}
|
||||
* @param finalizingAction a {@link Runnable} which is executed immediately
|
||||
* after the media source has been removed from the playlist
|
||||
*/
|
||||
public synchronized void invalidate(final int index,
|
||||
@Nullable final Handler handler,
|
||||
@Nullable final Runnable finalizingAction) {
|
||||
if (get(index) instanceof PlaceholderMediaSource) return;
|
||||
if (get(index) instanceof PlaceholderMediaSource) {
|
||||
return;
|
||||
}
|
||||
update(index, new PlaceholderMediaSource(), handler, finalizingAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
|
||||
* at the given index with a given {@link ManagedMediaSource}.
|
||||
*
|
||||
* @see #update(int, ManagedMediaSource, Handler, Runnable)
|
||||
* */
|
||||
* @param index index of {@link ManagedMediaSource} to update
|
||||
* @param source new {@link ManagedMediaSource} to use
|
||||
*/
|
||||
public synchronized void update(final int index, @NonNull final ManagedMediaSource source) {
|
||||
update(index, source, null, /*doNothing=*/null);
|
||||
}
|
||||
|
|
@ -108,13 +137,21 @@ public class ManagedMediaSourcePlaylist {
|
|||
* Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
|
||||
* at the given index with a given {@link ManagedMediaSource}. If the index is out of bound,
|
||||
* then the replacement is ignored.
|
||||
*
|
||||
* @see ConcatenatingMediaSource#addMediaSource
|
||||
* @see ConcatenatingMediaSource#removeMediaSource(int, Handler, Runnable)
|
||||
* */
|
||||
* @param index index of {@link ManagedMediaSource} to update
|
||||
* @param source new {@link ManagedMediaSource} to use
|
||||
* @param handler the {@link Handler} to run {@code finalizingAction}
|
||||
* @param finalizingAction a {@link Runnable} which is executed immediately
|
||||
* after the media source has been removed from the playlist
|
||||
*/
|
||||
public synchronized void update(final int index, @NonNull final ManagedMediaSource source,
|
||||
@Nullable final Handler handler,
|
||||
@Nullable final Runnable finalizingAction) {
|
||||
if (index < 0 || index >= internalSource.getSize()) return;
|
||||
if (index < 0 || index >= internalSource.getSize()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add and remove are sequential on the same thread, therefore here, the exoplayer
|
||||
// message queue must receive and process add before remove, effectively treating them
|
||||
|
|
|
|||
|
|
@ -12,20 +12,32 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
|||
|
||||
public class PlaceholderMediaSource extends BaseMediaSource implements ManagedMediaSource {
|
||||
// Do nothing, so this will stall the playback
|
||||
@Override public void maybeThrowSourceInfoRefreshError() {}
|
||||
@Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { return null; }
|
||||
@Override public void releasePeriod(MediaPeriod mediaPeriod) {}
|
||||
@Override protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {}
|
||||
@Override protected void releaseSourceInternal() {}
|
||||
@Override
|
||||
public void maybeThrowSourceInfoRefreshError() { }
|
||||
|
||||
@Override
|
||||
public boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity,
|
||||
public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator,
|
||||
final long startPositionUs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releasePeriod(final MediaPeriod mediaPeriod) { }
|
||||
|
||||
@Override
|
||||
protected void prepareSourceInternal(@Nullable final TransferListener mediaTransferListener) { }
|
||||
|
||||
@Override
|
||||
protected void releaseSourceInternal() { }
|
||||
|
||||
@Override
|
||||
public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity,
|
||||
final boolean isInterruptable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStreamEqual(@NonNull PlayQueueItem stream) {
|
||||
public boolean isStreamEqual(@NonNull final PlayQueueItem stream) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,25 +27,31 @@ public class BasePlayerMediaSession implements MediaSessionCallback {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onSkipToIndex(int index) {
|
||||
if (player.getPlayQueue() == null) return;
|
||||
public void onSkipToIndex(final int index) {
|
||||
if (player.getPlayQueue() == null) {
|
||||
return;
|
||||
}
|
||||
player.onSelected(player.getPlayQueue().getItem(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentPlayingIndex() {
|
||||
if (player.getPlayQueue() == null) return -1;
|
||||
if (player.getPlayQueue() == null) {
|
||||
return -1;
|
||||
}
|
||||
return player.getPlayQueue().getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getQueueSize() {
|
||||
if (player.getPlayQueue() == null) return -1;
|
||||
if (player.getPlayQueue() == null) {
|
||||
return -1;
|
||||
}
|
||||
return player.getPlayQueue().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaDescriptionCompat getQueueMetadata(int index) {
|
||||
public MediaDescriptionCompat getQueueMetadata(final int index) {
|
||||
if (player.getPlayQueue() == null || player.getPlayQueue().getItem(index) == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -60,13 +66,17 @@ public class BasePlayerMediaSession implements MediaSessionCallback {
|
|||
Bundle additionalMetadata = new Bundle();
|
||||
additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle());
|
||||
additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader());
|
||||
additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000);
|
||||
additionalMetadata
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000);
|
||||
additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1);
|
||||
additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size());
|
||||
additionalMetadata
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size());
|
||||
descriptionBuilder.setExtras(additionalMetadata);
|
||||
|
||||
final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl());
|
||||
if (thumbnailUri != null) descriptionBuilder.setIconUri(thumbnailUri);
|
||||
if (thumbnailUri != null) {
|
||||
descriptionBuilder.setIconUri(thumbnailUri);
|
||||
}
|
||||
|
||||
return descriptionBuilder.build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
|
|
@ -18,18 +17,22 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
/**
|
||||
* This class allows irregular text language labels for use when selecting text captions and
|
||||
* is mostly a copy-paste from {@link DefaultTrackSelector}.
|
||||
*
|
||||
* <p>
|
||||
* This is a hack and should be removed once ExoPlayer fixes language normalization to accept
|
||||
* a broader set of languages.
|
||||
* */
|
||||
* </p>
|
||||
*/
|
||||
public class CustomTrackSelector extends DefaultTrackSelector {
|
||||
|
||||
private String preferredTextLanguage;
|
||||
|
||||
public CustomTrackSelector(TrackSelection.Factory adaptiveTrackSelectionFactory) {
|
||||
public CustomTrackSelector(final TrackSelection.Factory adaptiveTrackSelectionFactory) {
|
||||
super(adaptiveTrackSelectionFactory);
|
||||
}
|
||||
|
||||
private static boolean formatHasLanguage(final Format format, final String language) {
|
||||
return language != null && TextUtils.equals(language, format.language);
|
||||
}
|
||||
|
||||
public String getPreferredTextLanguage() {
|
||||
return preferredTextLanguage;
|
||||
}
|
||||
|
|
@ -42,18 +45,11 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
|||
}
|
||||
}
|
||||
|
||||
private static boolean formatHasLanguage(Format format, String language) {
|
||||
return language != null && TextUtils.equals(language, format.language);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack(
|
||||
TrackGroupArray groups,
|
||||
int[][] formatSupport,
|
||||
Parameters params,
|
||||
@Nullable String selectedAudioLanguage)
|
||||
throws ExoPlaybackException {
|
||||
final TrackGroupArray groups, final int[][] formatSupport, final Parameters params,
|
||||
@Nullable final String selectedAudioLanguage) {
|
||||
TrackGroup selectedGroup = null;
|
||||
int selectedTrackIndex = C.INDEX_UNSET;
|
||||
int newPipeTrackScore = 0;
|
||||
|
|
@ -65,17 +61,16 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
|||
if (isSupported(trackFormatSupport[trackIndex],
|
||||
params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
Format format = trackGroup.getFormat(trackIndex);
|
||||
TextTrackScore trackScore =
|
||||
new TextTrackScore(
|
||||
format, params, trackFormatSupport[trackIndex], selectedAudioLanguage);
|
||||
TextTrackScore trackScore = new TextTrackScore(format, params,
|
||||
trackFormatSupport[trackIndex], selectedAudioLanguage);
|
||||
if (formatHasLanguage(format, preferredTextLanguage)) {
|
||||
selectedGroup = trackGroup;
|
||||
selectedTrackIndex = trackIndex;
|
||||
selectedTrackScore = trackScore;
|
||||
// found user selected match (perfect!)
|
||||
break;
|
||||
} else if (trackScore.isWithinConstraints
|
||||
&& (selectedTrackScore == null || trackScore.compareTo(selectedTrackScore) > 0)) {
|
||||
} else if (trackScore.isWithinConstraints && (selectedTrackScore == null
|
||||
|| trackScore.compareTo(selectedTrackScore) > 0)) {
|
||||
selectedGroup = trackGroup;
|
||||
selectedTrackIndex = trackIndex;
|
||||
selectedTrackScore = trackScore;
|
||||
|
|
@ -83,10 +78,8 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
|||
}
|
||||
}
|
||||
}
|
||||
return selectedGroup == null
|
||||
? null
|
||||
: Pair.create(
|
||||
new TrackSelection.Definition(selectedGroup, selectedTrackIndex),
|
||||
Assertions.checkNotNull(selectedTrackScore));
|
||||
return selectedGroup == null ? null
|
||||
: Pair.create(new TrackSelection.Definition(selectedGroup, selectedTrackIndex),
|
||||
Assertions.checkNotNull(selectedTrackScore));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package org.schabi.newpipe.player.playback;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
||||
|
|
@ -42,8 +44,6 @@ import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfo
|
|||
import static org.schabi.newpipe.player.playqueue.PlayQueue.DEBUG;
|
||||
|
||||
public class MediaSourceManager {
|
||||
@NonNull private final String TAG = "MediaSourceManager@" + hashCode();
|
||||
|
||||
/**
|
||||
* Determines how many streams before and after the current stream should be loaded.
|
||||
* The default value (1) ensures seamless playback under typical network settings.
|
||||
|
|
@ -52,26 +52,37 @@ public class MediaSourceManager {
|
|||
* streams before will only be cached for future usage.
|
||||
*
|
||||
* @see #onMediaSourceReceived(PlayQueueItem, ManagedMediaSource)
|
||||
* */
|
||||
private final static int WINDOW_SIZE = 1;
|
||||
|
||||
@NonNull private final PlaybackListener playbackListener;
|
||||
@NonNull private final PlayQueue playQueue;
|
||||
|
||||
*/
|
||||
private static final int WINDOW_SIZE = 1;
|
||||
/**
|
||||
* Determines the maximum number of disposables allowed in the {@link #loaderReactor}.
|
||||
* Once exceeded, new calls to {@link #loadImmediate()} will evict all disposables in the
|
||||
* {@link #loaderReactor} in order to load a new set of items.
|
||||
*
|
||||
* @see #loadImmediate()
|
||||
* @see #maybeLoadItem(PlayQueueItem)
|
||||
*/
|
||||
private static final int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
|
||||
@NonNull
|
||||
private final String TAG = "MediaSourceManager@" + hashCode();
|
||||
@NonNull
|
||||
private final PlaybackListener playbackListener;
|
||||
@NonNull
|
||||
private final PlayQueue playQueue;
|
||||
/**
|
||||
* Determines the gap time between the playback position and the playback duration which
|
||||
* the {@link #getEdgeIntervalSignal()} begins to request loading.
|
||||
*
|
||||
* @see #progressUpdateIntervalMillis
|
||||
* */
|
||||
*/
|
||||
private final long playbackNearEndGapMillis;
|
||||
/**
|
||||
* Determines the interval which the {@link #getEdgeIntervalSignal()} waits for between
|
||||
* each request for loading, once {@link #playbackNearEndGapMillis} has reached.
|
||||
* */
|
||||
*/
|
||||
private final long progressUpdateIntervalMillis;
|
||||
@NonNull private final Observable<Long> nearEndIntervalSignal;
|
||||
|
||||
@NonNull
|
||||
private final Observable<Long> nearEndIntervalSignal;
|
||||
/**
|
||||
* Process only the last load order when receiving a stream of load orders (lessens I/O).
|
||||
* <br><br>
|
||||
|
|
@ -80,34 +91,28 @@ public class MediaSourceManager {
|
|||
* Not recommended to go below 100ms.
|
||||
*
|
||||
* @see #loadDebounced()
|
||||
* */
|
||||
*/
|
||||
private final long loadDebounceMillis;
|
||||
@NonNull private final Disposable debouncedLoader;
|
||||
@NonNull private final PublishSubject<Long> debouncedSignal;
|
||||
|
||||
@NonNull private Subscription playQueueReactor;
|
||||
|
||||
/**
|
||||
* Determines the maximum number of disposables allowed in the {@link #loaderReactor}.
|
||||
* Once exceeded, new calls to {@link #loadImmediate()} will evict all disposables in the
|
||||
* {@link #loaderReactor} in order to load a new set of items.
|
||||
*
|
||||
* @see #loadImmediate()
|
||||
* @see #maybeLoadItem(PlayQueueItem)
|
||||
* */
|
||||
private final static int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
|
||||
@NonNull private final CompositeDisposable loaderReactor;
|
||||
@NonNull private final Set<PlayQueueItem> loadingItems;
|
||||
|
||||
@NonNull private final AtomicBoolean isBlocked;
|
||||
|
||||
@NonNull private ManagedMediaSourcePlaylist playlist;
|
||||
@NonNull
|
||||
private final Disposable debouncedLoader;
|
||||
@NonNull
|
||||
private final PublishSubject<Long> debouncedSignal;
|
||||
@NonNull
|
||||
private final CompositeDisposable loaderReactor;
|
||||
@NonNull
|
||||
private final Set<PlayQueueItem> loadingItems;
|
||||
@NonNull
|
||||
private final AtomicBoolean isBlocked;
|
||||
@NonNull
|
||||
private Subscription playQueueReactor;
|
||||
@NonNull
|
||||
private ManagedMediaSourcePlaylist playlist;
|
||||
|
||||
private Handler removeMediaSourceHandler = new Handler();
|
||||
|
||||
public MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue) {
|
||||
this(listener, playQueue, /*loadDebounceMillis=*/400L,
|
||||
this(listener, playQueue, 400L,
|
||||
/*playbackNearEndGapMillis=*/TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS),
|
||||
/*progressUpdateIntervalMillis*/TimeUnit.MILLISECONDS.convert(2, TimeUnit.SECONDS));
|
||||
}
|
||||
|
|
@ -121,9 +126,9 @@ public class MediaSourceManager {
|
|||
throw new IllegalArgumentException("Play Queue has not been initialized.");
|
||||
}
|
||||
if (playbackNearEndGapMillis < progressUpdateIntervalMillis) {
|
||||
throw new IllegalArgumentException("Playback end gap=[" + playbackNearEndGapMillis +
|
||||
" ms] must be longer than update interval=[ " + progressUpdateIntervalMillis +
|
||||
" ms] for them to be useful.");
|
||||
throw new IllegalArgumentException("Playback end gap=[" + playbackNearEndGapMillis
|
||||
+ " ms] must be longer than update interval=[ " + progressUpdateIntervalMillis
|
||||
+ " ms] for them to be useful.");
|
||||
}
|
||||
|
||||
this.playbackListener = listener;
|
||||
|
|
@ -154,11 +159,50 @@ public class MediaSourceManager {
|
|||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Exposed Methods
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Manager Helpers
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@Nullable
|
||||
private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue) {
|
||||
// The current item has higher priority
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
|
||||
if (currentItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The rest are just for seamless playback
|
||||
// Although timeline is not updated prior to the current index, these sources are still
|
||||
// loaded into the cache for faster retrieval at a potentially later time.
|
||||
final int leftBound = Math.max(0, currentIndex - MediaSourceManager.WINDOW_SIZE);
|
||||
final int rightLimit = currentIndex + MediaSourceManager.WINDOW_SIZE + 1;
|
||||
final int rightBound = Math.min(playQueue.size(), rightLimit);
|
||||
final Set<PlayQueueItem> neighbors = new ArraySet<>(
|
||||
playQueue.getStreams().subList(leftBound, rightBound));
|
||||
|
||||
// Do a round robin
|
||||
final int excess = rightLimit - playQueue.size();
|
||||
if (excess >= 0) {
|
||||
neighbors.addAll(playQueue.getStreams()
|
||||
.subList(0, Math.min(playQueue.size(), excess)));
|
||||
}
|
||||
neighbors.remove(currentItem);
|
||||
|
||||
return new ItemsToLoad(currentItem, neighbors);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Event Reactor
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Dispose the manager and releases all message buses and loaders.
|
||||
* */
|
||||
*/
|
||||
public void dispose() {
|
||||
if (DEBUG) Log.d(TAG, "close() called.");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "close() called.");
|
||||
}
|
||||
|
||||
debouncedSignal.onComplete();
|
||||
debouncedLoader.dispose();
|
||||
|
|
@ -167,32 +211,32 @@ public class MediaSourceManager {
|
|||
loaderReactor.dispose();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Event Reactor
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private Subscriber<PlayQueueEvent> getReactor() {
|
||||
return new Subscriber<PlayQueueEvent>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Subscription d) {
|
||||
public void onSubscribe(@NonNull final Subscription d) {
|
||||
playQueueReactor.cancel();
|
||||
playQueueReactor = d;
|
||||
playQueueReactor.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull PlayQueueEvent playQueueMessage) {
|
||||
public void onNext(@NonNull final PlayQueueEvent playQueueMessage) {
|
||||
onPlayQueueChanged(playQueueMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {}
|
||||
public void onError(@NonNull final Throwable e) { }
|
||||
|
||||
@Override
|
||||
public void onComplete() {}
|
||||
public void onComplete() { }
|
||||
};
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback Locking
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void onPlayQueueChanged(final PlayQueueEvent event) {
|
||||
if (playQueue.isEmpty() && playQueue.isComplete()) {
|
||||
playbackListener.onPlaybackShutdown();
|
||||
|
|
@ -254,29 +298,33 @@ public class MediaSourceManager {
|
|||
playQueueReactor.request(1);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback Locking
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private boolean isPlayQueueReady() {
|
||||
final boolean isWindowLoaded = playQueue.size() - playQueue.getIndex() > WINDOW_SIZE;
|
||||
return playQueue.isComplete() || isWindowLoaded;
|
||||
}
|
||||
|
||||
private boolean isPlaybackReady() {
|
||||
if (playlist.size() != playQueue.size()) return false;
|
||||
if (playlist.size() != playQueue.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ManagedMediaSource mediaSource = playlist.get(playQueue.getIndex());
|
||||
if (mediaSource == null) return false;
|
||||
if (mediaSource == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PlayQueueItem playQueueItem = playQueue.getItem();
|
||||
return mediaSource.isStreamEqual(playQueueItem);
|
||||
}
|
||||
|
||||
private void maybeBlock() {
|
||||
if (DEBUG) Log.d(TAG, "maybeBlock() called.");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "maybeBlock() called.");
|
||||
}
|
||||
|
||||
if (isBlocked.get()) return;
|
||||
if (isBlocked.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
playbackListener.onPlaybackBlock();
|
||||
resetSources();
|
||||
|
|
@ -284,8 +332,14 @@ public class MediaSourceManager {
|
|||
isBlocked.set(true);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Metadata Synchronization
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void maybeUnblock() {
|
||||
if (DEBUG) Log.d(TAG, "maybeUnblock() called.");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "maybeUnblock() called.");
|
||||
}
|
||||
|
||||
if (isBlocked.get()) {
|
||||
isBlocked.set(false);
|
||||
|
|
@ -293,19 +347,23 @@ public class MediaSourceManager {
|
|||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Metadata Synchronization
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void maybeSync() {
|
||||
if (DEBUG) Log.d(TAG, "maybeSync() called.");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "maybeSync() called.");
|
||||
}
|
||||
|
||||
final PlayQueueItem currentItem = playQueue.getItem();
|
||||
if (isBlocked.get() || currentItem == null) return;
|
||||
if (isBlocked.get() || currentItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
playbackListener.onPlaybackSynchronize(currentItem);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// MediaSource Loading
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private synchronized void maybeSynchronizePlayer() {
|
||||
if (isPlayQueueReady() && isPlaybackReady()) {
|
||||
maybeUnblock();
|
||||
|
|
@ -313,10 +371,6 @@ public class MediaSourceManager {
|
|||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// MediaSource Loading
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private Observable<Long> getEdgeIntervalSignal() {
|
||||
return Observable.interval(progressUpdateIntervalMillis, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
|
@ -337,9 +391,13 @@ public class MediaSourceManager {
|
|||
}
|
||||
|
||||
private void loadImmediate() {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - loadImmediate() called");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "MediaSource - loadImmediate() called");
|
||||
}
|
||||
final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue);
|
||||
if (itemsToLoad == null) return;
|
||||
if (itemsToLoad == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Evict the previous items being loaded to free up memory, before start loading new ones
|
||||
maybeClearLoaders();
|
||||
|
|
@ -351,12 +409,18 @@ public class MediaSourceManager {
|
|||
}
|
||||
|
||||
private void maybeLoadItem(@NonNull final PlayQueueItem item) {
|
||||
if (DEBUG) Log.d(TAG, "maybeLoadItem() called.");
|
||||
if (playQueue.indexOf(item) >= playlist.size()) return;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "maybeLoadItem() called.");
|
||||
}
|
||||
if (playQueue.indexOf(item) >= playlist.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loadingItems.contains(item) && isCorrectionNeeded(item)) {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Loading=[" + item.getTitle() +
|
||||
"] with url=[" + item.getUrl() + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "MediaSource - Loading=[" + item.getTitle() + "] "
|
||||
+ "with url=[" + item.getUrl() + "]");
|
||||
}
|
||||
|
||||
loadingItems.add(item);
|
||||
final Disposable loader = getLoadedMediaSource(item)
|
||||
|
|
@ -371,16 +435,16 @@ public class MediaSourceManager {
|
|||
return stream.getStream().map(streamInfo -> {
|
||||
final MediaSource source = playbackListener.sourceOf(stream, streamInfo);
|
||||
if (source == null) {
|
||||
final String message = "Unable to resolve source from stream info." +
|
||||
" URL: " + stream.getUrl() +
|
||||
", audio count: " + streamInfo.getAudioStreams().size() +
|
||||
", video count: " + streamInfo.getVideoOnlyStreams().size() +
|
||||
streamInfo.getVideoStreams().size();
|
||||
final String message = "Unable to resolve source from stream info. "
|
||||
+ "URL: " + stream.getUrl() + ", "
|
||||
+ "audio count: " + streamInfo.getAudioStreams().size() + ", "
|
||||
+ "video count: " + streamInfo.getVideoOnlyStreams().size() + ", "
|
||||
+ streamInfo.getVideoStreams().size();
|
||||
return new FailedMediaSource(stream, new MediaSourceResolutionException(message));
|
||||
}
|
||||
|
||||
final long expiration = System.currentTimeMillis() +
|
||||
ServiceHelper.getCacheExpirationMillis(streamInfo.getServiceId());
|
||||
final long expiration = System.currentTimeMillis()
|
||||
+ ServiceHelper.getCacheExpirationMillis(streamInfo.getServiceId());
|
||||
return new LoadedMediaSource(source, stream, expiration);
|
||||
}).onErrorReturn(throwable -> new FailedMediaSource(stream,
|
||||
new StreamInfoLoadException(throwable)));
|
||||
|
|
@ -388,17 +452,22 @@ public class MediaSourceManager {
|
|||
|
||||
private void onMediaSourceReceived(@NonNull final PlayQueueItem item,
|
||||
@NonNull final ManagedMediaSource mediaSource) {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Loaded=[" + item.getTitle() +
|
||||
"] with url=[" + item.getUrl() + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "MediaSource - Loaded=[" + item.getTitle()
|
||||
+ "] with url=[" + item.getUrl() + "]");
|
||||
}
|
||||
|
||||
loadingItems.remove(item);
|
||||
|
||||
final int itemIndex = playQueue.indexOf(item);
|
||||
// Only update the playlist timeline for items at the current index or after.
|
||||
if (isCorrectionNeeded(item)) {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Updating index=[" + itemIndex + "] with " +
|
||||
"title=[" + item.getTitle() + "] at url=[" + item.getUrl() + "]");
|
||||
playlist.update(itemIndex, mediaSource, removeMediaSourceHandler, this::maybeSynchronizePlayer);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "MediaSource - Updating index=[" + itemIndex + "] with "
|
||||
+ "title=[" + item.getTitle() + "] at url=[" + item.getUrl() + "]");
|
||||
}
|
||||
playlist.update(itemIndex, mediaSource, removeMediaSourceHandler,
|
||||
this::maybeSynchronizePlayer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -407,17 +476,21 @@ public class MediaSourceManager {
|
|||
* {@link com.google.android.exoplayer2.source.ConcatenatingMediaSource}
|
||||
* for a given {@link PlayQueueItem} needs replacement, either due to gapless playback
|
||||
* readiness or playlist desynchronization.
|
||||
* <br><br>
|
||||
* <p>
|
||||
* If the given {@link PlayQueueItem} is currently being played and is already loaded,
|
||||
* then correction is not only needed if the playlist is desynchronized. Otherwise, the
|
||||
* check depends on the status (e.g. expiration or placeholder) of the
|
||||
* {@link ManagedMediaSource}.
|
||||
* */
|
||||
* </p>
|
||||
*
|
||||
* @param item {@link PlayQueueItem} to check
|
||||
* @return whether a correction is needed
|
||||
*/
|
||||
private boolean isCorrectionNeeded(@NonNull final PlayQueueItem item) {
|
||||
final int index = playQueue.indexOf(item);
|
||||
final ManagedMediaSource mediaSource = playlist.get(index);
|
||||
return mediaSource != null && mediaSource.shouldBeReplacedWith(item,
|
||||
/*mightBeInProgress=*/index != playQueue.getIndex());
|
||||
index != playQueue.getIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -430,79 +503,62 @@ public class MediaSourceManager {
|
|||
* <br><br>
|
||||
* Under both cases, {@link #maybeSync()} will be called to ensure the listener
|
||||
* is up-to-date.
|
||||
* */
|
||||
*/
|
||||
private void maybeRenewCurrentIndex() {
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
final ManagedMediaSource currentSource = playlist.get(currentIndex);
|
||||
if (currentSource == null) return;
|
||||
if (currentSource == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PlayQueueItem currentItem = playQueue.getItem();
|
||||
if (!currentSource.shouldBeReplacedWith(currentItem, /*canInterruptOnRenew=*/true)) {
|
||||
if (!currentSource.shouldBeReplacedWith(currentItem, true)) {
|
||||
maybeSynchronizePlayer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Reloading currently playing, " +
|
||||
"index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]");
|
||||
playlist.invalidate(currentIndex, removeMediaSourceHandler, this::loadImmediate);
|
||||
}
|
||||
|
||||
private void maybeClearLoaders() {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - maybeClearLoaders() called.");
|
||||
if (!loadingItems.contains(playQueue.getItem()) &&
|
||||
loaderReactor.size() > MAXIMUM_LOADER_SIZE) {
|
||||
loaderReactor.clear();
|
||||
loadingItems.clear();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "MediaSource - Reloading currently playing, "
|
||||
+ "index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]");
|
||||
}
|
||||
playlist.invalidate(currentIndex, removeMediaSourceHandler, this::loadImmediate);
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// MediaSource Playlist Helpers
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void maybeClearLoaders() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "MediaSource - maybeClearLoaders() called.");
|
||||
}
|
||||
if (!loadingItems.contains(playQueue.getItem())
|
||||
&& loaderReactor.size() > MAXIMUM_LOADER_SIZE) {
|
||||
loaderReactor.clear();
|
||||
loadingItems.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void resetSources() {
|
||||
if (DEBUG) Log.d(TAG, "resetSources() called.");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "resetSources() called.");
|
||||
}
|
||||
playlist = new ManagedMediaSourcePlaylist();
|
||||
}
|
||||
|
||||
private void populateSources() {
|
||||
if (DEBUG) Log.d(TAG, "populateSources() called.");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "populateSources() called.");
|
||||
}
|
||||
while (playlist.size() < playQueue.size()) {
|
||||
playlist.expand();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Manager Helpers
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@Nullable
|
||||
private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue) {
|
||||
// The current item has higher priority
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
|
||||
if (currentItem == null) return null;
|
||||
|
||||
// The rest are just for seamless playback
|
||||
// Although timeline is not updated prior to the current index, these sources are still
|
||||
// loaded into the cache for faster retrieval at a potentially later time.
|
||||
final int leftBound = Math.max(0, currentIndex - MediaSourceManager.WINDOW_SIZE);
|
||||
final int rightLimit = currentIndex + MediaSourceManager.WINDOW_SIZE + 1;
|
||||
final int rightBound = Math.min(playQueue.size(), rightLimit);
|
||||
final Set<PlayQueueItem> neighbors = new ArraySet<>(
|
||||
playQueue.getStreams().subList(leftBound,rightBound));
|
||||
|
||||
// Do a round robin
|
||||
final int excess = rightLimit - playQueue.size();
|
||||
if (excess >= 0) {
|
||||
neighbors.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess)));
|
||||
}
|
||||
neighbors.remove(currentItem);
|
||||
|
||||
return new ItemsToLoad(currentItem, neighbors);
|
||||
}
|
||||
|
||||
private static class ItemsToLoad {
|
||||
@NonNull final private PlayQueueItem center;
|
||||
@NonNull final private Collection<PlayQueueItem> neighbors;
|
||||
@NonNull
|
||||
private final PlayQueueItem center;
|
||||
@NonNull
|
||||
private final Collection<PlayQueueItem> neighbors;
|
||||
|
||||
ItemsToLoad(@NonNull final PlayQueueItem center,
|
||||
@NonNull final Collection<PlayQueueItem> neighbors) {
|
||||
|
|
|
|||
|
|
@ -9,57 +9,72 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
public interface PlaybackListener {
|
||||
|
||||
/**
|
||||
* Called to check if the currently playing stream is approaching the end of its playback.
|
||||
* Implementation should return true when the current playback position is progressing within
|
||||
* timeToEndMillis or less to its playback during.
|
||||
*
|
||||
* <p>
|
||||
* May be called at any time.
|
||||
* */
|
||||
boolean isApproachingPlaybackEdge(final long timeToEndMillis);
|
||||
* </p>
|
||||
*
|
||||
* @param timeToEndMillis
|
||||
* @return whether the stream is approaching end of playback
|
||||
*/
|
||||
boolean isApproachingPlaybackEdge(long timeToEndMillis);
|
||||
|
||||
/**
|
||||
* Called when the stream at the current queue index is not ready yet.
|
||||
* Signals to the listener to block the player from playing anything and notify the source
|
||||
* is now invalid.
|
||||
*
|
||||
* <p>
|
||||
* May be called at any time.
|
||||
* */
|
||||
* </p>
|
||||
*/
|
||||
void onPlaybackBlock();
|
||||
|
||||
/**
|
||||
* Called when the stream at the current queue index is ready.
|
||||
* Signals to the listener to resume the player by preparing a new source.
|
||||
*
|
||||
* <p>
|
||||
* May be called only when the player is blocked.
|
||||
* */
|
||||
void onPlaybackUnblock(final MediaSource mediaSource);
|
||||
* </p>
|
||||
*
|
||||
* @param mediaSource
|
||||
*/
|
||||
void onPlaybackUnblock(MediaSource mediaSource);
|
||||
|
||||
/**
|
||||
* Called when the queue index is refreshed.
|
||||
* Signals to the listener to synchronize the player's window to the manager's
|
||||
* window.
|
||||
*
|
||||
* <p>
|
||||
* May be called anytime at any amount once unblock is called.
|
||||
* */
|
||||
void onPlaybackSynchronize(@NonNull final PlayQueueItem item);
|
||||
* </p>
|
||||
*
|
||||
* @param item
|
||||
*/
|
||||
void onPlaybackSynchronize(@NonNull PlayQueueItem item);
|
||||
|
||||
/**
|
||||
* Requests the listener to resolve a stream info into a media source
|
||||
* according to the listener's implementation (background, popup or main video player).
|
||||
*
|
||||
* <p>
|
||||
* May be called at any time.
|
||||
* */
|
||||
* </p>
|
||||
* @param item
|
||||
* @param info
|
||||
* @return the corresponding {@link MediaSource}
|
||||
*/
|
||||
@Nullable
|
||||
MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info);
|
||||
MediaSource sourceOf(PlayQueueItem item, StreamInfo info);
|
||||
|
||||
/**
|
||||
* Called when the play queue can no longer to played or used.
|
||||
* Currently, this means the play queue is empty and complete.
|
||||
* Signals to the listener that it should shutdown.
|
||||
*
|
||||
* <p>
|
||||
* May be called at any time.
|
||||
* */
|
||||
* </p>
|
||||
*/
|
||||
void onPlaybackShutdown();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,24 +16,20 @@ import io.reactivex.annotations.NonNull;
|
|||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> extends PlayQueue {
|
||||
boolean isInitial;
|
||||
boolean isComplete;
|
||||
|
||||
final int serviceId;
|
||||
final String baseUrl;
|
||||
boolean isInitial;
|
||||
private boolean isComplete;
|
||||
String nextUrl;
|
||||
|
||||
transient Disposable fetchReactor;
|
||||
private transient Disposable fetchReactor;
|
||||
|
||||
AbstractInfoPlayQueue(final U item) {
|
||||
this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0);
|
||||
}
|
||||
|
||||
AbstractInfoPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<StreamInfoItem> streams,
|
||||
final int index) {
|
||||
AbstractInfoPlayQueue(final int serviceId, final String url, final String nextPageUrl,
|
||||
final List<StreamInfoItem> streams, final int index) {
|
||||
super(index, extractListItems(streams));
|
||||
|
||||
this.baseUrl = url;
|
||||
|
|
@ -44,83 +40,6 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
|||
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
|
||||
}
|
||||
|
||||
abstract protected String getTag();
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
SingleObserver<T> getHeadListObserver() {
|
||||
return new SingleObserver<T>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (isComplete || !isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull T result) {
|
||||
isInitial = false;
|
||||
if (!result.hasNextPage()) isComplete = true;
|
||||
nextUrl = result.getNextPageUrl();
|
||||
|
||||
append(extractListItems(result.getRelatedItems()));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SingleObserver<ListExtractor.InfoItemsPage> getNextPageObserver() {
|
||||
return new SingleObserver<ListExtractor.InfoItemsPage>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (isComplete || isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull ListExtractor.InfoItemsPage result) {
|
||||
if (!result.hasNextPage()) isComplete = true;
|
||||
nextUrl = result.getNextPageUrl();
|
||||
|
||||
append(extractListItems(result.getItems()));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (fetchReactor != null) fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
private static List<PlayQueueItem> extractListItems(final List<StreamInfoItem> infos) {
|
||||
List<PlayQueueItem> result = new ArrayList<>();
|
||||
for (final InfoItem stream : infos) {
|
||||
|
|
@ -130,4 +49,89 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract String getTag();
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
SingleObserver<T> getHeadListObserver() {
|
||||
return new SingleObserver<T>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull final Disposable d) {
|
||||
if (isComplete || !isInitial || (fetchReactor != null
|
||||
&& !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull final T result) {
|
||||
isInitial = false;
|
||||
if (!result.hasNextPage()) {
|
||||
isComplete = true;
|
||||
}
|
||||
nextUrl = result.getNextPageUrl();
|
||||
|
||||
append(extractListItems(result.getRelatedItems()));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull final Throwable e) {
|
||||
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SingleObserver<ListExtractor.InfoItemsPage> getNextPageObserver() {
|
||||
return new SingleObserver<ListExtractor.InfoItemsPage>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull final Disposable d) {
|
||||
if (isComplete || isInitial || (fetchReactor != null
|
||||
&& !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull final ListExtractor.InfoItemsPage result) {
|
||||
if (!result.hasNextPage()) {
|
||||
isComplete = true;
|
||||
}
|
||||
nextUrl = result.getNextPageUrl();
|
||||
|
||||
append(extractListItems(result.getItems()));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull final Throwable e) {
|
||||
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (fetchReactor != null) {
|
||||
fetchReactor.dispose();
|
||||
}
|
||||
fetchReactor = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package org.schabi.newpipe.player.playqueue;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
|
|
@ -32,22 +33,20 @@ import io.reactivex.subjects.BehaviorSubject;
|
|||
/**
|
||||
* PlayQueue is responsible for keeping track of a list of streams and the index of
|
||||
* the stream that should be currently playing.
|
||||
*
|
||||
* <p>
|
||||
* This class contains basic manipulation of a playlist while also functions as a
|
||||
* message bus, providing all listeners with new updates to the play queue.
|
||||
*
|
||||
* <p>
|
||||
* This class can be serialized for passing intents, but in order to start the
|
||||
* message bus, it must be initialized.
|
||||
* */
|
||||
*/
|
||||
public abstract class PlayQueue implements Serializable {
|
||||
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
|
||||
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
|
||||
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
|
||||
@NonNull
|
||||
private final AtomicInteger queueIndex;
|
||||
private ArrayList<PlayQueueItem> backup;
|
||||
private ArrayList<PlayQueueItem> streams;
|
||||
@NonNull private final AtomicInteger queueIndex;
|
||||
|
||||
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
|
||||
private transient Flowable<PlayQueueEvent> broadcastReceiver;
|
||||
private transient Subscription reportingReactor;
|
||||
|
|
@ -65,9 +64,10 @@ public abstract class PlayQueue implements Serializable {
|
|||
|
||||
/**
|
||||
* Initializes the play queue message buses.
|
||||
*
|
||||
* <p>
|
||||
* Also starts a self reporter for logging if debug mode is enabled.
|
||||
* */
|
||||
* </p>
|
||||
*/
|
||||
public void init() {
|
||||
eventBroadcast = BehaviorSubject.create();
|
||||
|
||||
|
|
@ -75,15 +75,21 @@ public abstract class PlayQueue implements Serializable {
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.startWith(new InitEvent());
|
||||
|
||||
if (DEBUG) broadcastReceiver.subscribe(getSelfReporter());
|
||||
if (DEBUG) {
|
||||
broadcastReceiver.subscribe(getSelfReporter());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose the play queue by stopping all message buses.
|
||||
* */
|
||||
*/
|
||||
public void dispose() {
|
||||
if (eventBroadcast != null) eventBroadcast.onComplete();
|
||||
if (reportingReactor != null) reportingReactor.cancel();
|
||||
if (eventBroadcast != null) {
|
||||
eventBroadcast.onComplete();
|
||||
}
|
||||
if (reportingReactor != null) {
|
||||
reportingReactor.cancel();
|
||||
}
|
||||
|
||||
eventBroadcast = null;
|
||||
broadcastReceiver = null;
|
||||
|
|
@ -92,15 +98,18 @@ public abstract class PlayQueue implements Serializable {
|
|||
|
||||
/**
|
||||
* Checks if the queue is complete.
|
||||
*
|
||||
* <p>
|
||||
* A queue is complete if it has loaded all items in an external playlist
|
||||
* single stream or local queues are always complete.
|
||||
* */
|
||||
* </p>
|
||||
*
|
||||
* @return whether the queue is complete
|
||||
*/
|
||||
public abstract boolean isComplete();
|
||||
|
||||
/**
|
||||
* Load partial queue in the background, does nothing if the queue is complete.
|
||||
* */
|
||||
*/
|
||||
public abstract void fetch();
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -108,32 +117,64 @@ public abstract class PlayQueue implements Serializable {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Returns the current index that should be played.
|
||||
* */
|
||||
* @return the current index that should be played
|
||||
*/
|
||||
public int getIndex() {
|
||||
return queueIndex.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current item that should be played.
|
||||
* */
|
||||
* Changes the current playing index to a new index.
|
||||
* <p>
|
||||
* This method is guarded using in a circular manner for index exceeding the play queue size.
|
||||
* </p>
|
||||
* <p>
|
||||
* Will emit a {@link SelectEvent} if the index is not the current playing index.
|
||||
* </p>
|
||||
*
|
||||
* @param index the index to be set
|
||||
*/
|
||||
public synchronized void setIndex(final int index) {
|
||||
final int oldIndex = getIndex();
|
||||
|
||||
int newIndex = index;
|
||||
if (index < 0) {
|
||||
newIndex = 0;
|
||||
}
|
||||
if (index >= streams.size()) {
|
||||
newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
|
||||
}
|
||||
|
||||
queueIndex.set(newIndex);
|
||||
broadcast(new SelectEvent(oldIndex, newIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current item that should be played
|
||||
*/
|
||||
public PlayQueueItem getItem() {
|
||||
return getItem(getIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item at the given index.
|
||||
* May throw {@link IndexOutOfBoundsException}.
|
||||
* */
|
||||
public PlayQueueItem getItem(int index) {
|
||||
if (index < 0 || index >= streams.size() || streams.get(index) == null) return null;
|
||||
* @param index the index of the item to return
|
||||
* @return the item at the given index
|
||||
* @throws IndexOutOfBoundsException
|
||||
*/
|
||||
public PlayQueueItem getItem(final int index) {
|
||||
if (index < 0 || index >= streams.size() || streams.get(index) == null) {
|
||||
return null;
|
||||
}
|
||||
return streams.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the given item using referential equality.
|
||||
* May be null despite play queue contains identical item.
|
||||
* */
|
||||
*
|
||||
* @param item the item to find the index of
|
||||
* @return the index of the given item
|
||||
*/
|
||||
public int indexOf(@NonNull final PlayQueueItem item) {
|
||||
// referential equality, can't think of a better way to do this
|
||||
// todo: better than this
|
||||
|
|
@ -141,70 +182,61 @@ public abstract class PlayQueue implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the current size of play queue.
|
||||
* */
|
||||
* @return the current size of play queue.
|
||||
*/
|
||||
public int size() {
|
||||
return streams.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the play queue is empty.
|
||||
* */
|
||||
*
|
||||
* @return whether the play queue is empty
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return streams.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current play queue is shuffled.
|
||||
* */
|
||||
*
|
||||
* @return whether the play queue is shuffled
|
||||
*/
|
||||
public boolean isShuffled() {
|
||||
return backup != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable view of the play queue.
|
||||
* */
|
||||
* @return an immutable view of the play queue
|
||||
*/
|
||||
@NonNull
|
||||
public List<PlayQueueItem> getStreams() {
|
||||
return Collections.unmodifiableList(streams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the play queue's update broadcast.
|
||||
* May be null if the play queue message bus is not initialized.
|
||||
* */
|
||||
@Nullable
|
||||
public Flowable<PlayQueueEvent> getBroadcastReceiver() {
|
||||
return broadcastReceiver;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Write ops
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Changes the current playing index to a new index.
|
||||
* Returns the play queue's update broadcast.
|
||||
* May be null if the play queue message bus is not initialized.
|
||||
*
|
||||
* This method is guarded using in a circular manner for index exceeding the play queue size.
|
||||
*
|
||||
* Will emit a {@link SelectEvent} if the index is not the current playing index.
|
||||
* */
|
||||
public synchronized void setIndex(final int index) {
|
||||
final int oldIndex = getIndex();
|
||||
|
||||
int newIndex = index;
|
||||
if (index < 0) newIndex = 0;
|
||||
if (index >= streams.size()) newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
|
||||
|
||||
queueIndex.set(newIndex);
|
||||
broadcast(new SelectEvent(oldIndex, newIndex));
|
||||
* @return the play queue's update broadcast
|
||||
*/
|
||||
@Nullable
|
||||
public Flowable<PlayQueueEvent> getBroadcastReceiver() {
|
||||
return broadcastReceiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the current playing index by an offset amount.
|
||||
*
|
||||
* <p>
|
||||
* Will emit a {@link SelectEvent} if offset is non-zero.
|
||||
* */
|
||||
* </p>
|
||||
*
|
||||
* @param offset the offset relative to the current index
|
||||
*/
|
||||
public synchronized void offsetIndex(final int offset) {
|
||||
setIndex(getIndex() + offset);
|
||||
}
|
||||
|
|
@ -213,19 +245,24 @@ public abstract class PlayQueue implements Serializable {
|
|||
* Appends the given {@link PlayQueueItem}s to the current play queue.
|
||||
*
|
||||
* @see #append(List items)
|
||||
* */
|
||||
* @param items {@link PlayQueueItem}s to append
|
||||
*/
|
||||
public synchronized void append(@NonNull final PlayQueueItem... items) {
|
||||
append(Arrays.asList(items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given {@link PlayQueueItem}s to the current play queue.
|
||||
*
|
||||
* <p>
|
||||
* If the play queue is shuffled, then append the items to the backup queue as is and
|
||||
* append the shuffle items to the play queue.
|
||||
*
|
||||
* </p>
|
||||
* <p>
|
||||
* Will emit a {@link AppendEvent} on any given context.
|
||||
* */
|
||||
* </p>
|
||||
*
|
||||
* @param items {@link PlayQueueItem}s to append
|
||||
*/
|
||||
public synchronized void append(@NonNull final List<PlayQueueItem> items) {
|
||||
List<PlayQueueItem> itemList = new ArrayList<>(items);
|
||||
|
||||
|
|
@ -233,7 +270,8 @@ public abstract class PlayQueue implements Serializable {
|
|||
backup.addAll(itemList);
|
||||
Collections.shuffle(itemList);
|
||||
}
|
||||
if (!streams.isEmpty() && streams.get(streams.size() - 1).isAutoQueued() && !itemList.get(0).isAutoQueued()) {
|
||||
if (!streams.isEmpty() && streams.get(streams.size() - 1).isAutoQueued()
|
||||
&& !itemList.get(0).isAutoQueued()) {
|
||||
streams.remove(streams.size() - 1);
|
||||
}
|
||||
streams.addAll(itemList);
|
||||
|
|
@ -243,14 +281,20 @@ public abstract class PlayQueue implements Serializable {
|
|||
|
||||
/**
|
||||
* Removes the item at the given index from the play queue.
|
||||
*
|
||||
* <p>
|
||||
* The current playing index will decrement if it is greater than the index being removed.
|
||||
* On cases where the current playing index exceeds the playlist range, it is set to 0.
|
||||
*
|
||||
* </p>
|
||||
* <p>
|
||||
* Will emit a {@link RemoveEvent} if the index is within the play queue index range.
|
||||
* */
|
||||
* </p>
|
||||
*
|
||||
* @param index the index of the item to remove
|
||||
*/
|
||||
public synchronized void remove(final int index) {
|
||||
if (index >= streams.size() || index < 0) return;
|
||||
if (index >= streams.size() || index < 0) {
|
||||
return;
|
||||
}
|
||||
removeInternal(index);
|
||||
broadcast(new RemoveEvent(index, getIndex()));
|
||||
}
|
||||
|
|
@ -258,10 +302,13 @@ public abstract class PlayQueue implements Serializable {
|
|||
/**
|
||||
* Report an exception for the item at the current index in order and the course of action:
|
||||
* if the error can be skipped or the current item should be removed.
|
||||
*
|
||||
* <p>
|
||||
* This is done as a separate event as the underlying manager may have
|
||||
* different implementation regarding exceptions.
|
||||
* */
|
||||
* </p>
|
||||
*
|
||||
* @param skippable whether the error could be skipped
|
||||
*/
|
||||
public synchronized void error(final boolean skippable) {
|
||||
final int index = getIndex();
|
||||
|
||||
|
|
@ -284,29 +331,36 @@ public abstract class PlayQueue implements Serializable {
|
|||
} else if (currentIndex >= size) {
|
||||
queueIndex.set(currentIndex % (size - 1));
|
||||
|
||||
} else if (currentIndex == removeIndex && currentIndex == size - 1){
|
||||
} else if (currentIndex == removeIndex && currentIndex == size - 1) {
|
||||
queueIndex.set(0);
|
||||
}
|
||||
|
||||
if (backup != null) {
|
||||
final int backupIndex = backup.indexOf(getItem(removeIndex));
|
||||
backup.remove(backupIndex);
|
||||
backup.remove(getItem(removeIndex));
|
||||
}
|
||||
streams.remove(removeIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a queue item at the source index to the target index.
|
||||
*
|
||||
* <p>
|
||||
* If the item being moved is the currently playing, then the current playing index is set
|
||||
* to that of the target.
|
||||
* If the moved item is not the currently playing and moves to an index <b>AFTER</b> the
|
||||
* current playing index, then the current playing index is decremented.
|
||||
* Vice versa if the an item after the currently playing is moved <b>BEFORE</b>.
|
||||
* */
|
||||
* </p>
|
||||
*
|
||||
* @param source the original index of the item
|
||||
* @param target the new index of the item
|
||||
*/
|
||||
public synchronized void move(final int source, final int target) {
|
||||
if (source < 0 || target < 0) return;
|
||||
if (source >= streams.size() || target >= streams.size()) return;
|
||||
if (source < 0 || target < 0) {
|
||||
return;
|
||||
}
|
||||
if (source >= streams.size() || target >= streams.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int current = getIndex();
|
||||
if (source == current) {
|
||||
|
|
@ -325,11 +379,17 @@ public abstract class PlayQueue implements Serializable {
|
|||
|
||||
/**
|
||||
* Sets the recovery record of the item at the index.
|
||||
*
|
||||
* <p>
|
||||
* Broadcasts a recovery event.
|
||||
* */
|
||||
* </p>
|
||||
*
|
||||
* @param index index of the item
|
||||
* @param position the recovery position
|
||||
*/
|
||||
public synchronized void setRecovery(final int index, final long position) {
|
||||
if (index < 0 || index >= streams.size()) return;
|
||||
if (index < 0 || index >= streams.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
streams.get(index).setRecoveryPosition(position);
|
||||
broadcast(new RecoveryEvent(index, position));
|
||||
|
|
@ -337,22 +397,27 @@ public abstract class PlayQueue implements Serializable {
|
|||
|
||||
/**
|
||||
* Revoke the recovery record of the item at the index.
|
||||
*
|
||||
* <p>
|
||||
* Broadcasts a recovery event.
|
||||
* */
|
||||
* </p>
|
||||
*
|
||||
* @param index index of the item
|
||||
*/
|
||||
public synchronized void unsetRecovery(final int index) {
|
||||
setRecovery(index, PlayQueueItem.RECOVERY_UNSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles the current play queue.
|
||||
*
|
||||
* <p>
|
||||
* This method first backs up the existing play queue and item being played.
|
||||
* Then a newly shuffled play queue will be generated along with currently
|
||||
* playing item placed at the beginning of the queue.
|
||||
*
|
||||
* </p>
|
||||
* <p>
|
||||
* Will emit a {@link ReorderEvent} in any context.
|
||||
* */
|
||||
* </p>
|
||||
*/
|
||||
public synchronized void shuffle() {
|
||||
if (backup == null) {
|
||||
backup = new ArrayList<>(streams);
|
||||
|
|
@ -372,14 +437,18 @@ public abstract class PlayQueue implements Serializable {
|
|||
|
||||
/**
|
||||
* Unshuffles the current play queue if a backup play queue exists.
|
||||
*
|
||||
* <p>
|
||||
* This method undoes shuffling and index will be set to the previously playing item if found,
|
||||
* otherwise, the index will reset to 0.
|
||||
*
|
||||
* </p>
|
||||
* <p>
|
||||
* Will emit a {@link ReorderEvent} if a backup exists.
|
||||
* */
|
||||
* </p>
|
||||
*/
|
||||
public synchronized void unshuffle() {
|
||||
if (backup == null) return;
|
||||
if (backup == null) {
|
||||
return;
|
||||
}
|
||||
final int originIndex = getIndex();
|
||||
final PlayQueueItem current = getItem();
|
||||
|
||||
|
|
@ -410,20 +479,23 @@ public abstract class PlayQueue implements Serializable {
|
|||
private Subscriber<PlayQueueEvent> getSelfReporter() {
|
||||
return new Subscriber<PlayQueueEvent>() {
|
||||
@Override
|
||||
public void onSubscribe(Subscription s) {
|
||||
if (reportingReactor != null) reportingReactor.cancel();
|
||||
public void onSubscribe(final Subscription s) {
|
||||
if (reportingReactor != null) {
|
||||
reportingReactor.cancel();
|
||||
}
|
||||
reportingReactor = s;
|
||||
reportingReactor.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(PlayQueueEvent event) {
|
||||
Log.d(TAG, "Received broadcast: " + event.type().name() + ". Current index: " + getIndex() + ", play queue length: " + size() + ".");
|
||||
public void onNext(final PlayQueueEvent event) {
|
||||
Log.d(TAG, "Received broadcast: " + event.type().name() + ". "
|
||||
+ "Current index: " + getIndex() + ", play queue length: " + size() + ".");
|
||||
reportingReactor.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
public void onError(final Throwable t) {
|
||||
Log.e(TAG, "Received broadcast error", t);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package org.schabi.newpipe.player.playqueue;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
|
||||
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
|
||||
|
|
@ -24,20 +25,20 @@ import io.reactivex.disposables.Disposable;
|
|||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 01.08.16.
|
||||
*
|
||||
* <p>
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* InfoListAdapter.java is part of NewPipe.
|
||||
*
|
||||
* <p>
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* <p>
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
|
@ -55,14 +56,6 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
|
||||
private Disposable playQueueReactor;
|
||||
|
||||
public class HFHolder extends RecyclerView.ViewHolder {
|
||||
public HFHolder(View v) {
|
||||
super(v);
|
||||
view = v;
|
||||
}
|
||||
public View view;
|
||||
}
|
||||
|
||||
public PlayQueueAdapter(final Context context, final PlayQueue playQueue) {
|
||||
if (playQueue.getBroadcastReceiver() == null) {
|
||||
throw new IllegalStateException("Play Queue has not been initialized.");
|
||||
|
|
@ -77,18 +70,22 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
private Observer<PlayQueueEvent> getReactor() {
|
||||
return new Observer<PlayQueueEvent>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (playQueueReactor != null) playQueueReactor.dispose();
|
||||
public void onSubscribe(@NonNull final Disposable d) {
|
||||
if (playQueueReactor != null) {
|
||||
playQueueReactor.dispose();
|
||||
}
|
||||
playQueueReactor = d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull PlayQueueEvent playQueueMessage) {
|
||||
if (playQueueReactor != null) onPlayQueueChanged(playQueueMessage);
|
||||
public void onNext(@NonNull final PlayQueueEvent playQueueMessage) {
|
||||
if (playQueueReactor != null) {
|
||||
onPlayQueueChanged(playQueueMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {}
|
||||
public void onError(@NonNull final Throwable e) { }
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
|
|
@ -138,7 +135,9 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
}
|
||||
|
||||
public void dispose() {
|
||||
if (playQueueReactor != null) playQueueReactor.dispose();
|
||||
if (playQueueReactor != null) {
|
||||
playQueueReactor.dispose();
|
||||
}
|
||||
playQueueReactor = null;
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +149,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
playQueueItemBuilder.setOnSelectedListener(null);
|
||||
}
|
||||
|
||||
public void setFooter(View footer) {
|
||||
public void setFooter(final View footer) {
|
||||
this.footer = footer;
|
||||
notifyItemChanged(playQueue.size());
|
||||
}
|
||||
|
|
@ -167,13 +166,15 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
@Override
|
||||
public int getItemCount() {
|
||||
int count = playQueue.getStreams().size();
|
||||
if(footer != null && showFooter) count++;
|
||||
if (footer != null && showFooter) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if(footer != null && position == playQueue.getStreams().size() && showFooter) {
|
||||
public int getItemViewType(final int position) {
|
||||
if (footer != null && position == playQueue.getStreams().size() && showFooter) {
|
||||
return FOOTER_VIEW_TYPE_ID;
|
||||
}
|
||||
|
||||
|
|
@ -181,12 +182,13 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
|
||||
switch(type) {
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int type) {
|
||||
switch (type) {
|
||||
case FOOTER_VIEW_TYPE_ID:
|
||||
return new HFHolder(footer);
|
||||
case ITEM_VIEW_TYPE_ID:
|
||||
return new PlayQueueItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.play_queue_item, parent, false));
|
||||
return new PlayQueueItemHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.play_queue_item, parent, false));
|
||||
default:
|
||||
Log.e(TAG, "Attempting to create view holder with undefined type: " + type);
|
||||
return new FallbackViewHolder(new View(parent.getContext()));
|
||||
|
|
@ -194,19 +196,30 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
if(holder instanceof PlayQueueItemHolder) {
|
||||
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
|
||||
if (holder instanceof PlayQueueItemHolder) {
|
||||
final PlayQueueItemHolder itemHolder = (PlayQueueItemHolder) holder;
|
||||
|
||||
// Build the list item
|
||||
playQueueItemBuilder.buildStreamInfoItem(itemHolder, playQueue.getStreams().get(position));
|
||||
playQueueItemBuilder
|
||||
.buildStreamInfoItem(itemHolder, playQueue.getStreams().get(position));
|
||||
|
||||
// Check if the current item should be selected/highlighted
|
||||
final boolean isSelected = playQueue.getIndex() == position;
|
||||
itemHolder.itemSelected.setVisibility(isSelected ? View.VISIBLE : View.INVISIBLE);
|
||||
itemHolder.itemView.setSelected(isSelected);
|
||||
} else if(holder instanceof HFHolder && position == playQueue.getStreams().size() && footer != null && showFooter) {
|
||||
} else if (holder instanceof HFHolder && position == playQueue.getStreams().size()
|
||||
&& footer != null && showFooter) {
|
||||
((HFHolder) holder).view = footer;
|
||||
}
|
||||
}
|
||||
|
||||
public class HFHolder extends RecyclerView.ViewHolder {
|
||||
public View view;
|
||||
|
||||
public HFHolder(final View v) {
|
||||
super(v);
|
||||
view = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,27 +14,34 @@ import io.reactivex.Single;
|
|||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class PlayQueueItem implements Serializable {
|
||||
public final static long RECOVERY_UNSET = Long.MIN_VALUE;
|
||||
private final static String EMPTY_STRING = "";
|
||||
public static final long RECOVERY_UNSET = Long.MIN_VALUE;
|
||||
private static final String EMPTY_STRING = "";
|
||||
|
||||
@NonNull final private String title;
|
||||
@NonNull final private String url;
|
||||
final private int serviceId;
|
||||
final private long duration;
|
||||
@NonNull final private String thumbnailUrl;
|
||||
@NonNull final private String uploader;
|
||||
@NonNull final private StreamType streamType;
|
||||
@NonNull
|
||||
private final String title;
|
||||
@NonNull
|
||||
private final String url;
|
||||
private final int serviceId;
|
||||
private final long duration;
|
||||
@NonNull
|
||||
private final String thumbnailUrl;
|
||||
@NonNull
|
||||
private final String uploader;
|
||||
@NonNull
|
||||
private final StreamType streamType;
|
||||
|
||||
private boolean isAutoQueued;
|
||||
|
||||
private long recoveryPosition;
|
||||
private Throwable error;
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfo info) {
|
||||
this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(),
|
||||
info.getThumbnailUrl(), info.getUploaderName(), info.getStreamType());
|
||||
|
||||
if (info.getStartPosition() > 0)
|
||||
if (info.getStartPosition() > 0) {
|
||||
setRecoveryPosition(info.getStartPosition() * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfoItem item) {
|
||||
|
|
@ -94,6 +101,10 @@ public class PlayQueueItem implements Serializable {
|
|||
return recoveryPosition;
|
||||
}
|
||||
|
||||
/*package-private*/ void setRecoveryPosition(final long recoveryPosition) {
|
||||
this.recoveryPosition = recoveryPosition;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
|
|
@ -110,15 +121,11 @@ public class PlayQueueItem implements Serializable {
|
|||
return isAutoQueued;
|
||||
}
|
||||
|
||||
public void setAutoQueued(boolean autoQueued) {
|
||||
isAutoQueued = autoQueued;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Item States, keep external access out
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*package-private*/ void setRecoveryPosition(final long recoveryPosition) {
|
||||
this.recoveryPosition = recoveryPosition;
|
||||
public void setAutoQueued(final boolean autoQueued) {
|
||||
isAutoQueued = autoQueued;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,25 +12,20 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
|
|||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
public class PlayQueueItemBuilder {
|
||||
|
||||
private static final String TAG = PlayQueueItemBuilder.class.toString();
|
||||
|
||||
public interface OnSelectedListener {
|
||||
void selected(PlayQueueItem item, View view);
|
||||
void held(PlayQueueItem item, View view);
|
||||
void onStartDrag(PlayQueueItemHolder viewHolder);
|
||||
}
|
||||
|
||||
private OnSelectedListener onItemClickListener;
|
||||
|
||||
public PlayQueueItemBuilder(final Context context) {}
|
||||
public PlayQueueItemBuilder(final Context context) {
|
||||
}
|
||||
|
||||
public void setOnSelectedListener(OnSelectedListener listener) {
|
||||
public void setOnSelectedListener(final OnSelectedListener listener) {
|
||||
this.onItemClickListener = listener;
|
||||
}
|
||||
|
||||
public void buildStreamInfoItem(final PlayQueueItemHolder holder, final PlayQueueItem item) {
|
||||
if (!TextUtils.isEmpty(item.getTitle())) holder.itemVideoTitleView.setText(item.getTitle());
|
||||
if (!TextUtils.isEmpty(item.getTitle())) {
|
||||
holder.itemVideoTitleView.setText(item.getTitle());
|
||||
}
|
||||
holder.itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getUploader(),
|
||||
NewPipe.getNameOfService(item.getServiceId())));
|
||||
|
||||
|
|
@ -71,4 +66,12 @@ public class PlayQueueItemBuilder {
|
|||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
public interface OnSelectedListener {
|
||||
void selected(PlayQueueItem item, View view);
|
||||
|
||||
void held(PlayQueueItem item, View view);
|
||||
|
||||
void onStartDrag(PlayQueueItemHolder viewHolder);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package org.schabi.newpipe.player.playqueue;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
/**
|
||||
|
|
@ -12,29 +13,37 @@ import org.schabi.newpipe.R;
|
|||
* <p>
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* StreamInfoItemHolder.java is part of NewPipe.
|
||||
* </p>
|
||||
* <p>
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* </p>
|
||||
* <p>
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* </p>
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* </p>
|
||||
*/
|
||||
|
||||
public class PlayQueueItemHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView itemVideoTitleView;
|
||||
public final TextView itemDurationView;
|
||||
final TextView itemAdditionalDetailsView;
|
||||
|
||||
public final TextView itemVideoTitleView, itemDurationView, itemAdditionalDetailsView;
|
||||
public final ImageView itemSelected, itemThumbnailView, itemHandle;
|
||||
final ImageView itemSelected;
|
||||
public final ImageView itemThumbnailView;
|
||||
final ImageView itemHandle;
|
||||
|
||||
public final View itemRoot;
|
||||
|
||||
public PlayQueueItemHolder(View v) {
|
||||
PlayQueueItemHolder(final View v) {
|
||||
super(v);
|
||||
itemRoot = v.findViewById(R.id.itemRoot);
|
||||
itemVideoTitleView = v.findViewById(R.id.itemVideoTitleView);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package org.schabi.newpipe.player.playqueue;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleCallback {
|
||||
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 10;
|
||||
|
|
@ -11,14 +11,14 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC
|
|||
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT);
|
||||
}
|
||||
|
||||
public abstract void onMove(final int sourceIndex, final int targetIndex);
|
||||
public abstract void onMove(int sourceIndex, int targetIndex);
|
||||
|
||||
public abstract void onSwiped(int index);
|
||||
|
||||
@Override
|
||||
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,
|
||||
int viewSizeOutOfBounds, int totalSize,
|
||||
long msSinceStartScroll) {
|
||||
public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView, final int viewSize,
|
||||
final int viewSizeOutOfBounds, final int totalSize,
|
||||
final long msSinceStartScroll) {
|
||||
final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize,
|
||||
viewSizeOutOfBounds, totalSize, msSinceStartScroll);
|
||||
final int clampedAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY,
|
||||
|
|
@ -27,8 +27,8 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
|
||||
RecyclerView.ViewHolder target) {
|
||||
public boolean onMove(final RecyclerView recyclerView, final RecyclerView.ViewHolder source,
|
||||
final RecyclerView.ViewHolder target) {
|
||||
if (source.getItemViewType() != target.getItemViewType()) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
|
||||
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) {
|
||||
onSwiped(viewHolder.getAdapterPosition());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public final class SinglePlayQueue extends PlayQueue {
|
|||
super(index, playQueueItemsOf(items));
|
||||
}
|
||||
|
||||
private static List<PlayQueueItem> playQueueItemsOf(List<StreamInfoItem> items) {
|
||||
private static List<PlayQueueItem> playQueueItemsOf(final List<StreamInfoItem> items) {
|
||||
List<PlayQueueItem> playQueueItems = new ArrayList<>(items.size());
|
||||
for (final StreamInfoItem item : items) {
|
||||
playQueueItems.add(new PlayQueueItem(item));
|
||||
|
|
@ -39,5 +39,6 @@ public final class SinglePlayQueue extends PlayQueue {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void fetch() {}
|
||||
public void fetch() {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
package org.schabi.newpipe.player.playqueue.events;
|
||||
|
||||
|
||||
public class AppendEvent implements PlayQueueEvent {
|
||||
final private int amount;
|
||||
private final int amount;
|
||||
|
||||
public AppendEvent(final int amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.APPEND;
|
||||
}
|
||||
|
||||
public AppendEvent(final int amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,9 @@
|
|||
package org.schabi.newpipe.player.playqueue.events;
|
||||
|
||||
|
||||
public class ErrorEvent implements PlayQueueEvent {
|
||||
final private int errorIndex;
|
||||
final private int queueIndex;
|
||||
final private boolean skippable;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.ERROR;
|
||||
}
|
||||
private final int errorIndex;
|
||||
private final int queueIndex;
|
||||
private final boolean skippable;
|
||||
|
||||
public ErrorEvent(final int errorIndex, final int queueIndex, final boolean skippable) {
|
||||
this.errorIndex = errorIndex;
|
||||
|
|
@ -17,6 +11,11 @@ public class ErrorEvent implements PlayQueueEvent {
|
|||
this.skippable = skippable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.ERROR;
|
||||
}
|
||||
|
||||
public int getErrorIndex() {
|
||||
return errorIndex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
package org.schabi.newpipe.player.playqueue.events;
|
||||
|
||||
public class MoveEvent implements PlayQueueEvent {
|
||||
final private int fromIndex;
|
||||
final private int toIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.MOVE;
|
||||
}
|
||||
private final int fromIndex;
|
||||
private final int toIndex;
|
||||
|
||||
public MoveEvent(final int oldIndex, final int newIndex) {
|
||||
this.fromIndex = oldIndex;
|
||||
this.toIndex = newIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.MOVE;
|
||||
}
|
||||
|
||||
public int getFromIndex() {
|
||||
return fromIndex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
package org.schabi.newpipe.player.playqueue.events;
|
||||
|
||||
|
||||
public class RecoveryEvent implements PlayQueueEvent {
|
||||
final private int index;
|
||||
final private long position;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.RECOVERY;
|
||||
}
|
||||
private final int index;
|
||||
private final long position;
|
||||
|
||||
public RecoveryEvent(final int index, final long position) {
|
||||
this.index = index;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.RECOVERY;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
package org.schabi.newpipe.player.playqueue.events;
|
||||
|
||||
|
||||
public class RemoveEvent implements PlayQueueEvent {
|
||||
final private int removeIndex;
|
||||
final private int queueIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.REMOVE;
|
||||
}
|
||||
private final int removeIndex;
|
||||
private final int queueIndex;
|
||||
|
||||
public RemoveEvent(final int removeIndex, final int queueIndex) {
|
||||
this.removeIndex = removeIndex;
|
||||
this.queueIndex = queueIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.REMOVE;
|
||||
}
|
||||
|
||||
public int getQueueIndex() {
|
||||
return queueIndex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ public class ReorderEvent implements PlayQueueEvent {
|
|||
private final int fromSelectedIndex;
|
||||
private final int toSelectedIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.REORDER;
|
||||
}
|
||||
|
||||
public ReorderEvent(final int fromSelectedIndex, final int toSelectedIndex) {
|
||||
this.fromSelectedIndex = fromSelectedIndex;
|
||||
this.toSelectedIndex = toSelectedIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.REORDER;
|
||||
}
|
||||
|
||||
public int getFromSelectedIndex() {
|
||||
return fromSelectedIndex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
package org.schabi.newpipe.player.playqueue.events;
|
||||
|
||||
|
||||
public class SelectEvent implements PlayQueueEvent {
|
||||
final private int oldIndex;
|
||||
final private int newIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.SELECT;
|
||||
}
|
||||
private final int oldIndex;
|
||||
private final int newIndex;
|
||||
|
||||
public SelectEvent(final int oldIndex, final int newIndex) {
|
||||
this.oldIndex = oldIndex;
|
||||
this.newIndex = newIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.SELECT;
|
||||
}
|
||||
|
||||
public int getOldIndex() {
|
||||
return oldIndex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.player.resolver;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
|
@ -14,9 +15,10 @@ import org.schabi.newpipe.player.helper.PlayerHelper;
|
|||
import org.schabi.newpipe.util.ListHelper;
|
||||
|
||||
public class AudioPlaybackResolver implements PlaybackResolver {
|
||||
|
||||
@NonNull private final Context context;
|
||||
@NonNull private final PlayerDataSource dataSource;
|
||||
@NonNull
|
||||
private final Context context;
|
||||
@NonNull
|
||||
private final PlayerDataSource dataSource;
|
||||
|
||||
public AudioPlaybackResolver(@NonNull final Context context,
|
||||
@NonNull final PlayerDataSource dataSource) {
|
||||
|
|
@ -26,12 +28,16 @@ public class AudioPlaybackResolver implements PlaybackResolver {
|
|||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource resolve(@NonNull StreamInfo info) {
|
||||
public MediaSource resolve(@NonNull final StreamInfo info) {
|
||||
final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
|
||||
if (liveSource != null) return liveSource;
|
||||
if (liveSource != null) {
|
||||
return liveSource;
|
||||
}
|
||||
|
||||
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
|
||||
if (index < 0 || index >= info.getAudioStreams().size()) return null;
|
||||
if (index < 0 || index >= info.getAudioStreams().size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final AudioStream audio = info.getAudioStreams().get(index);
|
||||
final MediaSourceTag tag = new MediaSourceTag(info);
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
|
||||
public class MediaSourceTag implements Serializable {
|
||||
@NonNull private final StreamInfo metadata;
|
||||
@NonNull
|
||||
private final StreamInfo metadata;
|
||||
|
||||
@NonNull private final List<VideoStream> sortedAvailableVideoStreams;
|
||||
@NonNull
|
||||
private final List<VideoStream> sortedAvailableVideoStreams;
|
||||
private final int selectedVideoStreamIndex;
|
||||
|
||||
public MediaSourceTag(@NonNull final StreamInfo metadata,
|
||||
|
|
@ -44,8 +46,8 @@ public class MediaSourceTag implements Serializable {
|
|||
|
||||
@Nullable
|
||||
public VideoStream getSelectedVideoStream() {
|
||||
return selectedVideoStreamIndex < 0 ||
|
||||
selectedVideoStreamIndex >= sortedAvailableVideoStreams.size() ? null :
|
||||
sortedAvailableVideoStreams.get(selectedVideoStreamIndex);
|
||||
return selectedVideoStreamIndex < 0
|
||||
|| selectedVideoStreamIndex >= sortedAvailableVideoStreams.size()
|
||||
? null : sortedAvailableVideoStreams.get(selectedVideoStreamIndex);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package org.schabi.newpipe.player.resolver;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
|
@ -61,8 +62,8 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
|
|||
@NonNull final String overrideExtension,
|
||||
@NonNull final MediaSourceTag metadata) {
|
||||
final Uri uri = Uri.parse(sourceUrl);
|
||||
@C.ContentType final int type = TextUtils.isEmpty(overrideExtension) ?
|
||||
Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
|
||||
@C.ContentType final int type = TextUtils.isEmpty(overrideExtension)
|
||||
? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
|
||||
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface Resolver<Source, Product> {
|
||||
@Nullable Product resolve(@NonNull Source source);
|
||||
@Nullable
|
||||
Product resolve(@NonNull Source source);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.player.resolver;
|
|||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
|
@ -10,9 +11,9 @@ import com.google.android.exoplayer2.source.MediaSource;
|
|||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
|
|
@ -25,18 +26,14 @@ import static com.google.android.exoplayer2.C.SELECTION_FLAG_AUTOSELECT;
|
|||
import static com.google.android.exoplayer2.C.TIME_UNSET;
|
||||
|
||||
public class VideoPlaybackResolver implements PlaybackResolver {
|
||||
|
||||
public interface QualityResolver {
|
||||
int getDefaultResolutionIndex(final List<VideoStream> sortedVideos);
|
||||
int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
||||
final String playbackQuality);
|
||||
}
|
||||
|
||||
@NonNull private final Context context;
|
||||
@NonNull private final PlayerDataSource dataSource;
|
||||
@NonNull private final QualityResolver qualityResolver;
|
||||
|
||||
@Nullable private String playbackQuality;
|
||||
@NonNull
|
||||
private final Context context;
|
||||
@NonNull
|
||||
private final PlayerDataSource dataSource;
|
||||
@NonNull
|
||||
private final QualityResolver qualityResolver;
|
||||
@Nullable
|
||||
private String playbackQuality;
|
||||
|
||||
public VideoPlaybackResolver(@NonNull final Context context,
|
||||
@NonNull final PlayerDataSource dataSource,
|
||||
|
|
@ -48,9 +45,11 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
|||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource resolve(@NonNull StreamInfo info) {
|
||||
public MediaSource resolve(@NonNull final StreamInfo info) {
|
||||
final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
|
||||
if (liveSource != null) return liveSource;
|
||||
if (liveSource != null) {
|
||||
return liveSource;
|
||||
}
|
||||
|
||||
List<MediaSource> mediaSources = new ArrayList<>();
|
||||
|
||||
|
|
@ -81,7 +80,7 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
|||
ListHelper.getDefaultAudioFormat(context, audioStreams));
|
||||
// Use the audio stream if there is no video stream, or
|
||||
// Merge with audio stream in case if video does not contain audio
|
||||
if (audio != null && ((video != null && video.isVideoOnly) || video == null)) {
|
||||
if (audio != null && (video == null || video.isVideoOnly)) {
|
||||
final MediaSource audioSource = buildMediaSource(dataSource, audio.getUrl(),
|
||||
PlayerHelper.cacheKeyOf(info, audio),
|
||||
MediaFormat.getSuffixById(audio.getFormatId()), tag);
|
||||
|
|
@ -89,17 +88,22 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
|||
}
|
||||
|
||||
// If there is no audio or video sources, then this media source cannot be played back
|
||||
if (mediaSources.isEmpty()) return null;
|
||||
if (mediaSources.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// Below are auxiliary media sources
|
||||
|
||||
// Create subtitle sources
|
||||
if(info.getSubtitles() != null) {
|
||||
if (info.getSubtitles() != null) {
|
||||
for (final SubtitlesStream subtitle : info.getSubtitles()) {
|
||||
final String mimeType = PlayerHelper.subtitleMimeTypesOf(subtitle.getFormat());
|
||||
if (mimeType == null) continue;
|
||||
if (mimeType == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
|
||||
SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle));
|
||||
SELECTION_FLAG_AUTOSELECT,
|
||||
PlayerHelper.captionLanguageOf(context, subtitle));
|
||||
final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
|
||||
.createMediaSource(Uri.parse(subtitle.getURL()), textFormat, TIME_UNSET);
|
||||
mediaSources.add(textSource);
|
||||
|
|
@ -119,7 +123,13 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
|||
return playbackQuality;
|
||||
}
|
||||
|
||||
public void setPlaybackQuality(@Nullable String playbackQuality) {
|
||||
public void setPlaybackQuality(@Nullable final String playbackQuality) {
|
||||
this.playbackQuality = playbackQuality;
|
||||
}
|
||||
|
||||
public interface QualityResolver {
|
||||
int getDefaultResolutionIndex(List<VideoStream> sortedVideos);
|
||||
|
||||
int getOverrideResolutionIndex(List<VideoStream> sortedVideos, String playbackQuality);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue