Improve code style to be more consistent

This commit is contained in:
wb9688 2020-03-31 19:20:15 +02:00
parent 819e52cab3
commit fda5405e48
244 changed files with 10116 additions and 7222 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
package org.schabi.newpipe.player;
import android.os.Binder;
import androidx.annotation.NonNull;
class PlayerServiceBinder extends Binder {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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