sortedVideos,
+ String playbackQuality) {
+ return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality);
+ }
+ };
}
/*//////////////////////////////////////////////////////////////////////////
@@ -678,7 +752,6 @@ public final class MainVideoPlayer extends AppCompatActivity
@Override
public void onBuffering() {
super.onBuffering();
- animatePlayButtons(false, 100);
getRootView().setKeepScreenOn(true);
}
@@ -728,6 +801,13 @@ public final class MainVideoPlayer extends AppCompatActivity
// Utils
//////////////////////////////////////////////////////////////////////////*/
+ private void setInitialGestureValues() {
+ if (getAudioReactor() != null) {
+ final float currentVolumeNormalized = (float) getAudioReactor().getVolume() / getAudioReactor().getMaxVolume();
+ volumeProgressBar.setProgress((int) (volumeProgressBar.getMax() * currentVolumeNormalized));
+ }
+ }
+
@Override
public void showControlsThenHide() {
if (queueVisible) return;
@@ -831,12 +911,28 @@ public final class MainVideoPlayer extends AppCompatActivity
return channelTextView;
}
- public TextView getVolumeTextView() {
- return volumeTextView;
+ public RelativeLayout getVolumeRelativeLayout() {
+ return volumeRelativeLayout;
}
- public TextView getBrightnessTextView() {
- return brightnessTextView;
+ public ProgressBar getVolumeProgressBar() {
+ return volumeProgressBar;
+ }
+
+ public ImageView getVolumeImageView() {
+ return volumeImageView;
+ }
+
+ public RelativeLayout getBrightnessRelativeLayout() {
+ return brightnessRelativeLayout;
+ }
+
+ public ProgressBar getBrightnessProgressBar() {
+ return brightnessProgressBar;
+ }
+
+ public ImageView getBrightnessImageView() {
+ return brightnessImageView;
}
public ImageButton getRepeatButton() {
@@ -846,15 +942,18 @@ public final class MainVideoPlayer extends AppCompatActivity
public ImageButton getPlayPauseButton() {
return playPauseButton;
}
+
+ public int getMaxGestureLength() {
+ return maxGestureLength;
+ }
}
- private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
+ private class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
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());
- if (!playerImpl.isPlaying()) return false;
if (e.getX() > playerImpl.getRootView().getWidth() * 2 / 3) {
playerImpl.onFastForward();
@@ -888,91 +987,91 @@ public final class MainVideoPlayer extends AppCompatActivity
return super.onDown(e);
}
+ private static final int MOVEMENT_THRESHOLD = 40;
+
private final boolean isPlayerGestureEnabled = PlayerHelper.isPlayerGestureEnabled(getApplicationContext());
+ private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume();
- private final float stepsBrightness = 15, stepBrightness = (1f / stepsBrightness), minBrightness = .01f;
- private float currentBrightness = getWindow().getAttributes().screenBrightness > 0
- ? getWindow().getAttributes().screenBrightness
- : 0.5f;
-
- private int currentVolume, maxVolume = playerImpl.getAudioReactor().getMaxVolume();
- private final float stepsVolume = 15, stepVolume = (float) Math.ceil(maxVolume / stepsVolume), minVolume = 0;
-
- private final String brightnessUnicode = new String(Character.toChars(0x2600));
- private final String volumeUnicode = new String(Character.toChars(0x1F508));
-
- private final int MOVEMENT_THRESHOLD = 40;
- private final int eventsThreshold = 8;
- private boolean triggered = false;
- private int eventsNum;
-
- // TODO: Improve video gesture controls
@Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) {
if (!isPlayerGestureEnabled) return false;
//noinspection PointlessBooleanExpression
if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " +
- ", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
- ", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
+ ", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" +
+ ", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]");
- float abs = Math.abs(e2.getY() - e1.getY());
- if (!triggered) {
- triggered = abs > 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;
}
- if (eventsNum++ % eventsThreshold != 0 || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) return false;
isMoving = true;
-// boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top
- boolean up = distanceY > 0;
-
- if (e1.getX() > playerImpl.getRootView().getWidth() / 2) {
- double floor = Math.floor(up ? stepVolume : -stepVolume);
- currentVolume = (int) (playerImpl.getAudioReactor().getVolume() + floor);
- if (currentVolume >= maxVolume) currentVolume = maxVolume;
- if (currentVolume <= minVolume) currentVolume = (int) minVolume;
+ if (initialEvent.getX() > playerImpl.getRootView().getWidth() / 2) {
+ playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
+ float currentProgressPercent =
+ (float) playerImpl.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
+ int currentVolume = (int) (maxVolume * currentProgressPercent);
playerImpl.getAudioReactor().setVolume(currentVolume);
- currentVolume = playerImpl.getAudioReactor().getVolume();
if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
- final String volumeText = volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%";
- playerImpl.getVolumeTextView().setText(volumeText);
- if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getVolumeTextView(), true, 200);
- if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);
+ 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)
+ );
+
+ if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
+ animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
+ }
+ if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
+ playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
+ }
} else {
- WindowManager.LayoutParams lp = getWindow().getAttributes();
- currentBrightness += up ? stepBrightness : -stepBrightness;
- if (currentBrightness >= 1f) currentBrightness = 1f;
- if (currentBrightness <= minBrightness) currentBrightness = minBrightness;
+ playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY);
+ float currentProgressPercent =
+ (float) playerImpl.getBrightnessProgressBar().getProgress() / playerImpl.getMaxGestureLength();
+ WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
+ layoutParams.screenBrightness = currentProgressPercent;
+ getWindow().setAttributes(layoutParams);
- lp.screenBrightness = currentBrightness;
- getWindow().setAttributes(lp);
- if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentBrightness);
- int brightnessNormalized = Math.round(currentBrightness * 100);
+ if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentProgressPercent);
- final String brightnessText = brightnessUnicode + " " + (brightnessNormalized == 1 ? 0 : brightnessNormalized) + "%";
- playerImpl.getBrightnessTextView().setText(brightnessText);
+ 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;
- if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getBrightnessTextView(), true, 200);
- if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
+ playerImpl.getBrightnessImageView().setImageDrawable(
+ AppCompatResources.getDrawable(getApplicationContext(), resId)
+ );
+
+ if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
+ animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
+ }
+ if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
+ playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
+ }
}
return true;
}
private void onScrollEnd() {
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
- triggered = false;
- eventsNum = 0;
- /* if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
- if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);*/
- if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) {
- animateView(playerImpl.getVolumeTextView(), false, 200, 200);
+
+ if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
+ animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
}
- if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) {
- animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
+ if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
+ animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerState.java b/app/src/main/java/org/schabi/newpipe/player/PlayerState.java
index 8ffcb6b29..359159809 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PlayerState.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PlayerState.java
@@ -14,21 +14,26 @@ public class PlayerState implements Serializable {
private final float playbackSpeed;
private final float playbackPitch;
@Nullable private final String playbackQuality;
+ private final boolean playbackSkipSilence;
private final boolean wasPlaying;
PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode,
- final float playbackSpeed, final float playbackPitch, final boolean wasPlaying) {
- this(playQueue, repeatMode, playbackSpeed, playbackPitch, null, wasPlaying);
+ final float playbackSpeed, final float playbackPitch,
+ final boolean playbackSkipSilence, final boolean wasPlaying) {
+ this(playQueue, repeatMode, playbackSpeed, playbackPitch, null,
+ playbackSkipSilence, wasPlaying);
}
PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode,
final float playbackSpeed, final float playbackPitch,
- @Nullable final String playbackQuality, final boolean wasPlaying) {
+ @Nullable final String playbackQuality, final boolean playbackSkipSilence,
+ final boolean wasPlaying) {
this.playQueue = playQueue;
this.repeatMode = repeatMode;
this.playbackSpeed = playbackSpeed;
this.playbackPitch = playbackPitch;
this.playbackQuality = playbackQuality;
+ this.playbackSkipSilence = playbackSkipSilence;
this.wasPlaying = wasPlaying;
}
@@ -62,6 +67,10 @@ public class PlayerState implements Serializable {
return playbackQuality;
}
+ public boolean isPlaybackSkipSilence() {
+ return playbackSkipSilence;
+ }
+
public boolean wasPlaying() {
return wasPlaying;
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
index f9892ecb5..0e7328020 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
@@ -19,6 +19,8 @@
package org.schabi.newpipe.player;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -34,7 +36,7 @@ import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
+import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.NotificationCompat;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -42,7 +44,9 @@ import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.animation.AnticipateInterpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.PopupMenu;
@@ -56,17 +60,17 @@ 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.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.BuildConfig;
-import org.schabi.newpipe.CheckForNewAppVersionTask;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.old.PlayVideoActivity;
-import org.schabi.newpipe.player.playqueue.PlayQueueItem;
+import org.schabi.newpipe.player.resolver.MediaSourceTag;
+import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -99,11 +103,19 @@ public final class PopupVideoPlayer extends Service {
private static final int MINIMUM_SHOW_EXTRA_WIDTH_DP = 300;
- private WindowManager windowManager;
- private WindowManager.LayoutParams windowLayoutParams;
- private GestureDetector gestureDetector;
+ 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;
+ private GestureDetector popupGestureDetector;
+
+ private View closeOverlayView;
+ private FloatingActionButton closeOverlayButton;
+ private WindowManager.LayoutParams closeOverlayLayoutParams;
- private int shutdownFlingVelocity;
private int tossFlingVelocity;
private float screenWidth, screenHeight;
@@ -118,6 +130,7 @@ public final class PopupVideoPlayer extends Service {
private VideoPlayerImpl playerImpl;
private LockManager lockManager;
+ private boolean isPopupClosing = false;
/*//////////////////////////////////////////////////////////////////////////
// Service-Activity Binder
@@ -146,7 +159,10 @@ public final class PopupVideoPlayer extends Service {
public int onStartCommand(final Intent intent, int flags, int startId) {
if (DEBUG)
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
- if (playerImpl.getPlayer() == null) initPopup();
+ if (playerImpl.getPlayer() == null) {
+ initPopup();
+ initPopupCloseOverlay();
+ }
if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
playerImpl.handleIntent(intent);
@@ -156,15 +172,16 @@ public final class PopupVideoPlayer extends Service {
@Override
public void onConfigurationChanged(Configuration newConfig) {
+ if (DEBUG) Log.d(TAG, "onConfigurationChanged() called with: newConfig = [" + newConfig + "]");
updateScreenSize();
- updatePopupSize(windowLayoutParams.width, -1);
- checkPositionBounds();
+ updatePopupSize(popupLayoutParams.width, -1);
+ checkPopupPositionBounds();
}
@Override
public void onDestroy() {
if (DEBUG) Log.d(TAG, "onDestroy() called");
- onClose();
+ closePopup();
}
@Override
@@ -182,7 +199,6 @@ public final class PopupVideoPlayer extends Service {
View rootView = View.inflate(this, R.layout.player_popup, null);
playerImpl.setup(rootView);
- shutdownFlingVelocity = PlayerHelper.getShutdownFlingVelocity(this);
tossFlingVelocity = PlayerHelper.getTossFlingVelocity(this);
updateScreenSize();
@@ -192,28 +208,56 @@ public final class PopupVideoPlayer extends Service {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
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;
- windowLayoutParams = new WindowManager.LayoutParams(
+ popupLayoutParams = new WindowManager.LayoutParams(
(int) popupWidth, (int) getMinimumVideoHeight(popupWidth),
layoutParamType,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ IDLE_WINDOW_FLAGS,
PixelFormat.TRANSLUCENT);
- windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+ popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+ popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
- windowLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
- windowLayoutParams.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;
- checkPositionBounds();
+ checkPopupPositionBounds();
- MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
- gestureDetector = new GestureDetector(this, listener);
+ PopupWindowGestureListener listener = new PopupWindowGestureListener();
+ popupGestureDetector = new GestureDetector(this, listener);
rootView.setOnTouchListener(listener);
- playerImpl.getLoadingPanel().setMinimumWidth(windowLayoutParams.width);
- playerImpl.getLoadingPanel().setMinimumHeight(windowLayoutParams.height);
- windowManager.addView(rootView, windowLayoutParams);
+
+ playerImpl.getLoadingPanel().setMinimumWidth(popupLayoutParams.width);
+ playerImpl.getLoadingPanel().setMinimumHeight(popupLayoutParams.height);
+ windowManager.addView(rootView, popupLayoutParams);
+ }
+
+ @SuppressLint("RtlHardcoded")
+ private void initPopupCloseOverlay() {
+ 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
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+
+ closeOverlayLayoutParams = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
+ layoutParamType,
+ flags,
+ PixelFormat.TRANSLUCENT);
+ closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+ closeOverlayLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+
+ closeOverlayButton.setVisibility(View.GONE);
+ windowManager.addView(closeOverlayView, closeOverlayLayoutParams);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -229,6 +273,7 @@ public final class PopupVideoPlayer extends Service {
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));
@@ -244,11 +289,15 @@ public final class PopupVideoPlayer extends Service {
setRepeatModeRemote(notRemoteView, playerImpl.getRepeatMode());
- return 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)
.setContent(notRemoteView);
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ builder.setPriority(NotificationCompat.PRIORITY_MAX);
+ }
+ return builder;
}
/**
@@ -268,44 +317,105 @@ public final class PopupVideoPlayer extends Service {
// Misc
//////////////////////////////////////////////////////////////////////////*/
- public void onClose() {
- if (DEBUG) Log.d(TAG, "onClose() called");
+ public void closePopup() {
+ if (DEBUG) Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing);
+ if (isPopupClosing) return;
+ isPopupClosing = true;
if (playerImpl != null) {
if (playerImpl.getRootView() != null) {
windowManager.removeView(playerImpl.getRootView());
- playerImpl.setRootView(null);
}
+ playerImpl.setRootView(null);
playerImpl.stopActivityBinding();
playerImpl.destroy();
+ playerImpl = null;
}
+
+ mBinder = null;
if (lockManager != null) lockManager.releaseWifiAndCpu();
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
- mBinder = null;
- playerImpl = null;
- stopForeground(true);
- stopSelf();
+ animateOverlayAndFinishService();
+ }
+
+ private void animateOverlayAndFinishService() {
+ final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight() - closeOverlayButton.getY());
+
+ closeOverlayButton.animate().setListener(null).cancel();
+ closeOverlayButton.animate()
+ .setInterpolator(new AnticipateInterpolator())
+ .translationY(targetTranslationY)
+ .setDuration(400)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ end();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ end();
+ }
+
+ private void end() {
+ windowManager.removeView(closeOverlayView);
+
+ stopForeground(true);
+ stopSelf();
+ }
+ }).start();
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
- private void checkPositionBounds() {
- if (windowLayoutParams.x > screenWidth - windowLayoutParams.width)
- windowLayoutParams.x = (int) (screenWidth - windowLayoutParams.width);
- if (windowLayoutParams.x < 0) windowLayoutParams.x = 0;
- if (windowLayoutParams.y > screenHeight - windowLayoutParams.height)
- windowLayoutParams.y = (int) (screenHeight - windowLayoutParams.height);
- if (windowLayoutParams.y < 0) windowLayoutParams.y = 0;
+ /**
+ * @see #checkPopupPositionBounds(float, float)
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ private boolean checkPopupPositionBounds() {
+ return checkPopupPositionBounds(screenWidth, screenHeight);
+ }
+
+ /**
+ * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary that goes from (0,0) to (boundaryWidth,boundaryHeight).
+ *
+ * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed and {@code true} is returned
+ * to represent this change.
+ *
+ * @return if the popup was out of bounds and have been moved back to it
+ */
+ private boolean checkPopupPositionBounds(final float boundaryWidth, final float boundaryHeight) {
+ if (DEBUG) {
+ Log.d(TAG, "checkPopupPositionBounds() called with: boundaryWidth = [" + boundaryWidth + "], boundaryHeight = [" + boundaryHeight + "]");
+ }
+
+ if (popupLayoutParams.x < 0) {
+ popupLayoutParams.x = 0;
+ return true;
+ } else if (popupLayoutParams.x > boundaryWidth - popupLayoutParams.width) {
+ popupLayoutParams.x = (int) (boundaryWidth - popupLayoutParams.width);
+ return true;
+ }
+
+ if (popupLayoutParams.y < 0) {
+ popupLayoutParams.y = 0;
+ return true;
+ } else if (popupLayoutParams.y > boundaryHeight - popupLayoutParams.height) {
+ popupLayoutParams.y = (int) (boundaryHeight - popupLayoutParams.height);
+ return true;
+ }
+
+ return false;
}
private void savePositionAndSize() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(PopupVideoPlayer.this);
- sharedPreferences.edit().putInt(POPUP_SAVED_X, windowLayoutParams.x).apply();
- sharedPreferences.edit().putInt(POPUP_SAVED_Y, windowLayoutParams.y).apply();
- sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, windowLayoutParams.width).apply();
+ 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) {
@@ -340,13 +450,13 @@ public final class PopupVideoPlayer extends Service {
if (height == -1) height = (int) getMinimumVideoHeight(width);
else height = (int) (height > maximumHeight ? maximumHeight : height < minimumHeight ? minimumHeight : height);
- windowLayoutParams.width = width;
- windowLayoutParams.height = height;
+ popupLayoutParams.width = width;
+ popupLayoutParams.height = height;
popupWidth = width;
popupHeight = height;
if (DEBUG) Log.d(TAG, "updatePopupSize() updated values: width = [" + width + "], height = [" + height + "]");
- windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
+ windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
}
protected void setRepeatModeRemote(final RemoteViews remoteViews, final int repeatMode) {
@@ -367,6 +477,12 @@ public final class PopupVideoPlayer extends Service {
}
}
+ private void updateWindowFlags(final int flags) {
+ if (popupLayoutParams == null || windowManager == null || playerImpl == null) return;
+
+ popupLayoutParams.flags = flags;
+ windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
+ }
///////////////////////////////////////////////////////////////////////////
protected class VideoPlayerImpl extends VideoPlayer implements View.OnLayoutChangeListener {
@@ -375,6 +491,7 @@ public final class PopupVideoPlayer extends Service {
private ImageView videoPlayPause;
private View extraOptionsView;
+ private View closingOverlayView;
@Override
public void handleIntent(Intent intent) {
@@ -395,12 +512,18 @@ public final class PopupVideoPlayer extends Service {
fullScreenButton = rootView.findViewById(R.id.fullScreenButton);
fullScreenButton.setOnClickListener(v -> onFullScreenButtonClicked());
videoPlayPause = rootView.findViewById(R.id.videoPlayPause);
- videoPlayPause.setOnClickListener(this::onPlayPauseButtonPressed);
extraOptionsView = rootView.findViewById(R.id.extraOptionsView);
+ closingOverlayView = rootView.findViewById(R.id.closingOverlay);
rootView.addOnLayoutChangeListener(this);
}
+ @Override
+ public void initListeners() {
+ super.initListeners();
+ videoPlayPause.setOnClickListener(v -> onPlayPause());
+ }
+
@Override
protected void setupSubtitleView(@NonNull SubtitleView view,
final float captionScale,
@@ -411,10 +534,6 @@ public final class PopupVideoPlayer extends Service {
view.setStyle(captionStyle);
}
- private void onPlayPauseButtonPressed(View ib) {
- onPlayPause();
- }
-
@Override
public void onLayoutChange(final View view, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
@@ -429,21 +548,6 @@ public final class PopupVideoPlayer extends Service {
super.destroy();
}
- @Override
- public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
- super.onLoadingComplete(imageUri, view, loadedImage);
- if (loadedImage != null) {
- // rebuild notification here since remote view does not release bitmaps, causing memory leaks
- notBuilder = createNotification();
-
- if (notRemoteView != null) {
- notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
- }
-
- updateNotification(-1);
- }
- }
-
@Override
public void onFullScreenButtonClicked() {
super.onFullScreenButtonClicked();
@@ -460,6 +564,7 @@ public final class PopupVideoPlayer extends Service {
this.getRepeatMode(),
this.getPlaybackSpeed(),
this.getPlaybackPitch(),
+ this.getPlaybackSkipSilence(),
this.getPlaybackQuality()
);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -472,7 +577,7 @@ public final class PopupVideoPlayer extends Service {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
- onClose();
+ closePopup();
}
@Override
@@ -511,14 +616,47 @@ public final class PopupVideoPlayer extends Service {
}
@Override
- protected int getDefaultResolutionIndex(final List sortedVideos) {
- return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
+ protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
+ return new VideoPlaybackResolver.QualityResolver() {
+ @Override
+ public int getDefaultResolutionIndex(List sortedVideos) {
+ return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
+ }
+
+ @Override
+ public int getOverrideResolutionIndex(List sortedVideos,
+ String playbackQuality) {
+ return ListHelper.getPopupResolutionIndex(context, sortedVideos,
+ playbackQuality);
+ }
+ };
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Thumbnail Loading
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
+ super.onLoadingComplete(imageUri, view, loadedImage);
+ // rebuild notification here since remote view does not release bitmaps,
+ // causing memory leaks
+ resetNotification();
+ updateNotification(-1);
}
@Override
- protected int getOverrideResolutionIndex(final List sortedVideos,
- final String playbackQuality) {
- return ListHelper.getPopupResolutionIndex(context, sortedVideos, playbackQuality);
+ public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
+ super.onLoadingFailed(imageUri, view, failReason);
+ resetNotification();
+ updateNotification(-1);
+ }
+
+ @Override
+ public void onLoadingCancelled(String imageUri, View view) {
+ super.onLoadingCancelled(imageUri, view);
+ resetNotification();
+ updateNotification(-1);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -539,8 +677,8 @@ public final class PopupVideoPlayer extends Service {
}
private void updateMetadata() {
- if (activityListener != null && currentInfo != null) {
- activityListener.onMetadataUpdate(currentInfo);
+ if (activityListener != null && getCurrentMetadata() != null) {
+ activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
}
}
@@ -572,8 +710,9 @@ public final class PopupVideoPlayer extends Service {
public void onRepeatModeChanged(int i) {
super.onRepeatModeChanged(i);
setRepeatModeRemote(notRemoteView, i);
- updateNotification(-1);
updatePlayback();
+ resetNotification();
+ updateNotification(-1);
}
@Override
@@ -586,18 +725,17 @@ public final class PopupVideoPlayer extends Service {
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
- protected void onMetadataChanged(@NonNull final PlayQueueItem item,
- @Nullable final StreamInfo info,
- final int newPlayQueueIndex,
- final boolean hasPlayQueueItemChanged) {
- super.onMetadataChanged(item, info, newPlayQueueIndex, false);
+ protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
+ super.onMetadataChanged(tag);
+ resetNotification();
+ updateNotification(-1);
updateMetadata();
}
@Override
public void onPlaybackShutdown() {
super.onPlaybackShutdown();
- onClose();
+ closePopup();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -623,7 +761,7 @@ public final class PopupVideoPlayer extends Service {
if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
switch (intent.getAction()) {
case ACTION_CLOSE:
- onClose();
+ closePopup();
break;
case ACTION_PLAY_PAUSE:
onPlayPause();
@@ -653,49 +791,70 @@ public final class PopupVideoPlayer extends Service {
@Override
public void onBlocked() {
super.onBlocked();
+ resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
public void onPlaying() {
super.onPlaying();
- updateNotification(R.drawable.ic_pause_white);
- videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
- lockManager.acquireWifiAndCpu();
+ updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
+
+ resetNotification();
+ updateNotification(R.drawable.ic_pause_white);
+
+ videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
- // Check for new version
- //new CheckForNewAppVersionTask().execute();
+ startForeground(NOTIFICATION_ID, notBuilder.build());
+ lockManager.acquireWifiAndCpu();
}
@Override
public void onBuffering() {
super.onBuffering();
+ resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
public void onPaused() {
super.onPaused();
+
+ updateWindowFlags(IDLE_WINDOW_FLAGS);
+
+ resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
+
videoPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_white);
lockManager.releaseWifiAndCpu();
+
+ stopForeground(false);
}
@Override
public void onPausedSeek() {
super.onPausedSeek();
- videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
+ resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
+
+ videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
}
@Override
public void onCompleted() {
super.onCompleted();
+
+ updateWindowFlags(IDLE_WINDOW_FLAGS);
+
+ resetNotification();
updateNotification(R.drawable.ic_replay_white);
+
videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white);
lockManager.releaseWifiAndCpu();
+
+ stopForeground(false);
}
@Override
@@ -713,16 +872,15 @@ public final class PopupVideoPlayer extends Service {
super.hideControlsAndButton(duration, delay, videoPlayPause);
}
-
-
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
/*package-private*/ void enableVideoRenderer(final boolean enable) {
final int videoRendererIndex = getRendererIndex(C.TRACK_TYPE_VIDEO);
- if (trackSelector != null && videoRendererIndex != RENDERER_UNAVAILABLE) {
- trackSelector.setRendererDisabled(videoRendererIndex, !enable);
+ if (videoRendererIndex != RENDERER_UNAVAILABLE) {
+ trackSelector.setParameters(trackSelector.buildUponParameters()
+ .setRendererDisabled(videoRendererIndex, !enable));
}
}
@@ -734,12 +892,15 @@ public final class PopupVideoPlayer extends Service {
public TextView getResizingIndicator() {
return resizingIndicator;
}
+
+ public View getClosingOverlayView() {
+ return closingOverlayView;
+ }
}
- private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
+ private class PopupWindowGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
private int initialPopupX, initialPopupY;
private boolean isMoving;
-
private boolean isResizing;
@Override
@@ -775,10 +936,15 @@ public final class PopupVideoPlayer extends Service {
@Override
public boolean onDown(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]");
- initialPopupX = windowLayoutParams.x;
- initialPopupY = windowLayoutParams.y;
- popupWidth = windowLayoutParams.width;
- popupHeight = windowLayoutParams.height;
+
+ // 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).
+ checkPopupPositionBounds(closeOverlayView.getWidth(), closeOverlayView.getHeight());
+
+ initialPopupX = popupLayoutParams.x;
+ initialPopupY = popupLayoutParams.y;
+ popupWidth = popupLayoutParams.width;
+ popupHeight = popupLayoutParams.height;
return super.onDown(e);
}
@@ -786,20 +952,22 @@ public final class PopupVideoPlayer extends Service {
public void onLongPress(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
updateScreenSize();
- checkPositionBounds();
+ checkPopupPositionBounds();
updatePopupSize((int) screenWidth, -1);
}
@Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- if (isResizing || playerImpl == null) return super.onScroll(e1, e2, distanceX, distanceY);
+ public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) {
+ if (isResizing || playerImpl == null) return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
+
+ if (!isMoving) {
+ animateView(closeOverlayButton, true, 200);
+ }
- if (playerImpl.getCurrentState() != BasePlayer.STATE_BUFFERING
- && (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f)) playerImpl.showControls(0);
isMoving = true;
- float diffX = (int) (e2.getRawX() - e1.getRawX()), posX = (int) (initialPopupX + diffX);
- float diffY = (int) (e2.getRawY() - e1.getRawY()), posY = (int) (initialPopupY + diffY);
+ float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX()), posX = (int) (initialPopupX + diffX);
+ float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY()), posY = (int) (initialPopupY + diffY);
if (posX > (screenWidth - popupWidth)) posX = (int) (screenWidth - popupWidth);
else if (posX < 0) posX = 0;
@@ -807,26 +975,49 @@ public final class PopupVideoPlayer extends Service {
if (posY > (screenHeight - popupHeight)) posY = (int) (screenHeight - popupHeight);
else if (posY < 0) posY = 0;
- windowLayoutParams.x = (int) posX;
- windowLayoutParams.y = (int) posY;
+ popupLayoutParams.x = (int) posX;
+ popupLayoutParams.y = (int) posY;
+
+ final View closingOverlayView = playerImpl.getClosingOverlayView();
+ if (isInsideClosingRadius(movingEvent)) {
+ if (closingOverlayView.getVisibility() == View.GONE) {
+ animateView(closingOverlayView, true, 250);
+ }
+ } else {
+ if (closingOverlayView.getVisibility() == View.VISIBLE) {
+ animateView(closingOverlayView, false, 0);
+ }
+ }
//noinspection PointlessBooleanExpression
- if (DEBUG && false) Log.d(TAG, "PopupVideoPlayer.onScroll = " +
- ", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
- ", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
- ", distanceXy = [" + distanceX + ", " + distanceY + "]" +
- ", posXy = [" + posX + ", " + posY + "]" +
- ", popupWh = [" + popupWidth + " x " + popupHeight + "]");
- windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
+ 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 + "]");
+ }
+ windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
return true;
}
- private void onScrollEnd() {
+ private void onScrollEnd(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);
}
+
+ if (isInsideClosingRadius(event)) {
+ closePopup();
+ } else {
+ animateView(playerImpl.getClosingOverlayView(), false, 0);
+
+ if (!isPopupClosing) {
+ animateView(closeOverlayButton, false, 200);
+ }
+ }
}
@Override
@@ -836,14 +1027,11 @@ public final class PopupVideoPlayer extends Service {
final float absVelocityX = Math.abs(velocityX);
final float absVelocityY = Math.abs(velocityY);
- if (absVelocityX > shutdownFlingVelocity) {
- onClose();
- return true;
- } else if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
- if (absVelocityX > tossFlingVelocity) windowLayoutParams.x = (int) velocityX;
- if (absVelocityY > tossFlingVelocity) windowLayoutParams.y = (int) velocityY;
- checkPositionBounds();
- windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
+ if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
+ if (absVelocityX > tossFlingVelocity) popupLayoutParams.x = (int) velocityX;
+ if (absVelocityY > tossFlingVelocity) popupLayoutParams.y = (int) velocityY;
+ checkPopupPositionBounds();
+ windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
return true;
}
return false;
@@ -851,7 +1039,7 @@ public final class PopupVideoPlayer extends Service {
@Override
public boolean onTouch(View v, MotionEvent event) {
- gestureDetector.onTouchEvent(event);
+ popupGestureDetector.onTouchEvent(event);
if (playerImpl == null) return false;
if (event.getPointerCount() == 2 && !isResizing) {
if (DEBUG) Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
@@ -874,7 +1062,7 @@ public final class PopupVideoPlayer extends Service {
Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
if (isMoving) {
isMoving = false;
- onScrollEnd();
+ onScrollEnd(event);
}
if (isResizing) {
@@ -882,7 +1070,10 @@ public final class PopupVideoPlayer extends Service {
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
playerImpl.changeState(playerImpl.getCurrentState());
}
- savePositionAndSize();
+
+ if (!isPopupClosing) {
+ savePositionAndSize();
+ }
}
v.performClick();
@@ -898,13 +1089,13 @@ public final class PopupVideoPlayer extends Service {
final float diff = Math.abs(firstPointerX - secondPointerX);
if (firstPointerX > secondPointerX) {
// second pointer is the anchor (the leftmost pointer)
- windowLayoutParams.x = (int) (event.getRawX() - diff);
+ popupLayoutParams.x = (int) (event.getRawX() - diff);
} else {
// first pointer is the anchor
- windowLayoutParams.x = (int) event.getRawX();
+ popupLayoutParams.x = (int) event.getRawX();
}
- checkPositionBounds();
+ checkPopupPositionBounds();
updateScreenSize();
final int width = (int) Math.min(screenWidth, diff);
@@ -912,5 +1103,29 @@ public final class PopupVideoPlayer extends Service {
return true;
}
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private int distanceFromCloseButton(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));
+ }
+
+ private float getClosingRadius() {
+ final int buttonRadius = closeOverlayButton.getWidth() / 2;
+ // 20% wider than the button itself
+ return buttonRadius * 1.2f;
+ }
+
+ private boolean isInsideClosingRadius(MotionEvent popupMotionEvent) {
+ return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius();
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
index 8b96b651e..94305e6c4 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
@@ -16,6 +16,7 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
@@ -187,6 +188,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
this.player.getRepeatMode(),
this.player.getPlaybackSpeed(),
this.player.getPlaybackPitch(),
+ this.player.getPlaybackSkipSilence(),
null
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@@ -340,6 +342,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return true;
});
+ final MenuItem share = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/3,
+ Menu.NONE, R.string.share);
+ share.setOnMenuItemClickListener(menuItem -> {
+ shareUrl(item.getTitle(), item.getUrl());
+ return true;
+ });
+
menu.show();
}
@@ -459,13 +468,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void openPlaybackParameterDialog() {
if (player == null) return;
- PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(),
- player.getPlaybackPitch()).show(getSupportFragmentManager(), getTag());
+ PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
+ player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag());
}
@Override
- public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) {
- if (player != null) player.setPlaybackParameters(playbackTempo, playbackPitch);
+ public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
+ boolean playbackSkipSilence) {
+ if (player != null) {
+ player.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
+ }
}
////////////////////////////////////////////////////////////////////////////
@@ -509,6 +521,18 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
.show(getSupportFragmentManager(), getTag());
}
+ ////////////////////////////////////////////////////////////////////////////
+ // Share
+ ////////////////////////////////////////////////////////////////////////////
+
+ private void shareUrl(String subject, String url) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ intent.putExtra(Intent.EXTRA_TEXT, url);
+ startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
+ }
+
////////////////////////////////////////////////////////////////////////////
// Binding Service Listener
////////////////////////////////////////////////////////////////////////////
@@ -539,6 +563,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
if (player != null) {
progressLiveSync.setClickable(!player.isLiveEdge());
}
+
+ // this will make shure progressCurrentTime has the same width as progressEndTime
+ final ViewGroup.LayoutParams endTimeParams = progressEndTime.getLayoutParams();
+ final ViewGroup.LayoutParams currentTimeParams = progressCurrentTime.getLayoutParams();
+ currentTimeParams.width = progressEndTime.getWidth();
+ progressCurrentTime.setLayoutParams(currentTimeParams);
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
index 5ea1c74a0..679fc6645 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
@@ -29,7 +29,6 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
-import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.NonNull;
@@ -47,11 +46,9 @@ import android.widget.SeekBar;
import android.widget.TextView;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
@@ -62,21 +59,17 @@ import com.google.android.exoplayer2.video.VideoListener;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
-import org.schabi.newpipe.extractor.Subtitles;
-import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
-import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
+import org.schabi.newpipe.player.resolver.MediaSourceTag;
+import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.AnimationUtils;
-import org.schabi.newpipe.util.ListHelper;
import java.util.ArrayList;
import java.util.List;
-import static com.google.android.exoplayer2.C.SELECTION_FLAG_AUTOSELECT;
-import static com.google.android.exoplayer2.C.TIME_UNSET;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -105,13 +98,12 @@ public abstract class VideoPlayer extends BasePlayer
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
- private ArrayList availableStreams;
+ private List availableStreams;
private int selectedStreamIndex;
- protected String playbackQuality;
-
protected boolean wasPlaying = false;
+ @NonNull final private VideoPlaybackResolver resolver;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
@@ -162,6 +154,7 @@ public abstract class VideoPlayer extends BasePlayer
public VideoPlayer(String debugTag, Context context) {
super(context);
this.TAG = debugTag;
+ this.resolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
}
public void setup(View rootView) {
@@ -241,7 +234,8 @@ public abstract class VideoPlayer extends BasePlayer
// Setup audio session with onboard equalizer
if (Build.VERSION.SDK_INT >= 21) {
- trackSelector.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context));
+ trackSelector.setParameters(trackSelector.buildUponParameters()
+ .setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
}
}
@@ -297,8 +291,9 @@ public abstract class VideoPlayer extends BasePlayer
0, Menu.NONE, R.string.caption_none);
captionOffItem.setOnMenuItemClickListener(menuItem -> {
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
- if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) {
- trackSelector.setRendererDisabled(textRendererIndex, true);
+ if (textRendererIndex != RENDERER_UNAVAILABLE) {
+ trackSelector.setParameters(trackSelector.buildUponParameters()
+ .setRendererDisabled(textRendererIndex, true));
}
return true;
});
@@ -310,68 +305,61 @@ public abstract class VideoPlayer extends BasePlayer
i + 1, Menu.NONE, captionLanguage);
captionItem.setOnMenuItemClickListener(menuItem -> {
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
- if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) {
+ if (textRendererIndex != RENDERER_UNAVAILABLE) {
trackSelector.setPreferredTextLanguage(captionLanguage);
- trackSelector.setRendererDisabled(textRendererIndex, false);
+ trackSelector.setParameters(trackSelector.buildUponParameters()
+ .setRendererDisabled(textRendererIndex, false));
}
return true;
});
}
captionPopupMenu.setOnDismissListener(this);
}
- /*//////////////////////////////////////////////////////////////////////////
- // Playback Listener
- //////////////////////////////////////////////////////////////////////////*/
- protected abstract int getDefaultResolutionIndex(final List sortedVideos);
- protected abstract int getOverrideResolutionIndex(final List sortedVideos, final String playbackQuality);
+ private void updateStreamRelatedViews() {
+ if (getCurrentMetadata() == null) return;
+
+ final MediaSourceTag tag = getCurrentMetadata();
+ final StreamInfo metadata = tag.getMetadata();
- protected void onMetadataChanged(@NonNull final PlayQueueItem item,
- @Nullable final StreamInfo info,
- final int newPlayQueueIndex,
- final boolean hasPlayQueueItemChanged) {
qualityTextView.setVisibility(View.GONE);
playbackSpeedTextView.setVisibility(View.GONE);
playbackEndTime.setVisibility(View.GONE);
playbackLiveSync.setVisibility(View.GONE);
- final StreamType streamType = info == null ? StreamType.NONE : info.getStreamType();
-
- switch (streamType) {
+ switch (metadata.getStreamType()) {
case AUDIO_STREAM:
surfaceView.setVisibility(View.GONE);
+ endScreen.setVisibility(View.VISIBLE);
playbackEndTime.setVisibility(View.VISIBLE);
break;
case AUDIO_LIVE_STREAM:
surfaceView.setVisibility(View.GONE);
+ endScreen.setVisibility(View.VISIBLE);
playbackLiveSync.setVisibility(View.VISIBLE);
break;
case LIVE_STREAM:
surfaceView.setVisibility(View.VISIBLE);
+ endScreen.setVisibility(View.GONE);
playbackLiveSync.setVisibility(View.VISIBLE);
break;
case VIDEO_STREAM:
- if (info.getVideoStreams().size() + info.getVideoOnlyStreams().size() == 0) break;
-
- final List videos = ListHelper.getSortedStreamVideosList(context,
- info.getVideoStreams(), info.getVideoOnlyStreams(), false);
- availableStreams = new ArrayList<>(videos);
- if (playbackQuality == null) {
- selectedStreamIndex = getDefaultResolutionIndex(videos);
- } else {
- selectedStreamIndex = getOverrideResolutionIndex(videos, getPlaybackQuality());
- }
+ if (metadata.getVideoStreams().size() + metadata.getVideoOnlyStreams().size() == 0)
+ break;
+ availableStreams = tag.getSortedAvailableVideoStreams();
+ selectedStreamIndex = tag.getSelectedVideoStreamIndex();
buildQualityMenu();
- qualityTextView.setVisibility(View.VISIBLE);
+ qualityTextView.setVisibility(View.VISIBLE);
surfaceView.setVisibility(View.VISIBLE);
default:
+ endScreen.setVisibility(View.GONE);
playbackEndTime.setVisibility(View.VISIBLE);
break;
}
@@ -379,69 +367,21 @@ public abstract class VideoPlayer extends BasePlayer
buildPlaybackSpeedMenu();
playbackSpeedTextView.setVisibility(View.VISIBLE);
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // Playback Listener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver();
+
+ protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
+ super.onMetadataChanged(tag);
+ updateStreamRelatedViews();
+ }
@Override
@Nullable
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
- final MediaSource liveSource = super.sourceOf(item, info);
- if (liveSource != null) return liveSource;
-
- List mediaSources = new ArrayList<>();
-
- // Create video stream source
- final List videos = ListHelper.getSortedStreamVideosList(context,
- info.getVideoStreams(), info.getVideoOnlyStreams(), false);
- final int index;
- if (videos.isEmpty()) {
- index = -1;
- } else if (playbackQuality == null) {
- index = getDefaultResolutionIndex(videos);
- } else {
- index = getOverrideResolutionIndex(videos, getPlaybackQuality());
- }
- final VideoStream video = index >= 0 && index < videos.size() ? videos.get(index) : null;
- if (video != null) {
- final MediaSource streamSource = buildMediaSource(video.getUrl(),
- PlayerHelper.cacheKeyOf(info, video),
- MediaFormat.getSuffixById(video.getFormatId()));
- mediaSources.add(streamSource);
- }
-
- // Create optional audio stream source
- final List audioStreams = info.getAudioStreams();
- final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get(
- 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)) {
- final MediaSource audioSource = buildMediaSource(audio.getUrl(),
- PlayerHelper.cacheKeyOf(info, audio),
- MediaFormat.getSuffixById(audio.getFormatId()));
- mediaSources.add(audioSource);
- }
-
- // If there is no audio or video sources, then this media source cannot be played back
- if (mediaSources.isEmpty()) return null;
- // Below are auxiliary media sources
-
- // Create subtitle sources
- for (final Subtitles subtitle : info.getSubtitles()) {
- final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType());
- if (mimeType == null) continue;
-
- final Format textFormat = Format.createTextSampleFormat(null, mimeType,
- SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle));
- final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
- .createMediaSource(Uri.parse(subtitle.getURL()), textFormat, TIME_UNSET);
- mediaSources.add(textSource);
- }
-
- if (mediaSources.size() == 1) {
- return mediaSources.get(0);
- } else {
- return new MergingMediaSource(mediaSources.toArray(
- new MediaSource[mediaSources.size()]));
- }
+ return resolver.resolve(info);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -460,7 +400,6 @@ public abstract class VideoPlayer extends BasePlayer
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
- animateView(endScreen, false, 0);
loadingPanel.setBackgroundColor(Color.BLACK);
animateView(loadingPanel, true, 0);
animateView(surfaceForeground, true, 100);
@@ -470,6 +409,8 @@ public abstract class VideoPlayer extends BasePlayer
public void onPlaying() {
super.onPlaying();
+ updateStreamRelatedViews();
+
showAndAnimateControl(-1, true);
playbackSeekBar.setEnabled(true);
@@ -480,14 +421,12 @@ public abstract class VideoPlayer extends BasePlayer
loadingPanel.setVisibility(View.GONE);
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
- animateView(endScreen, false, 0);
}
@Override
public void onBuffering() {
if (DEBUG) Log.d(TAG, "onBuffering() called");
loadingPanel.setBackgroundColor(Color.TRANSPARENT);
- animateView(loadingPanel, true, 500);
}
@Override
@@ -552,8 +491,7 @@ public abstract class VideoPlayer extends BasePlayer
final int textRenderer = getRendererIndex(C.TRACK_TYPE_TEXT);
if (captionTextView == null) return;
- if (trackSelector == null || trackSelector.getCurrentMappedTrackInfo() == null ||
- textRenderer == RENDERER_UNAVAILABLE) {
+ if (trackSelector.getCurrentMappedTrackInfo() == null || textRenderer == RENDERER_UNAVAILABLE) {
captionTextView.setVisibility(View.GONE);
return;
}
@@ -575,8 +513,8 @@ public abstract class VideoPlayer extends BasePlayer
// Build UI
buildCaptionMenu(availableLanguages);
- if (trackSelector.getRendererDisabled(textRenderer) || preferredLanguage == null ||
- !availableLanguages.contains(preferredLanguage)) {
+ if (trackSelector.getParameters().getRendererDisabled(textRenderer) ||
+ preferredLanguage == null || !availableLanguages.contains(preferredLanguage)) {
captionTextView.setText(R.string.caption_none);
} else {
captionTextView.setText(preferredLanguage);
@@ -905,11 +843,12 @@ public abstract class VideoPlayer extends BasePlayer
//////////////////////////////////////////////////////////////////////////*/
public void setPlaybackQuality(final String quality) {
- this.playbackQuality = quality;
+ this.resolver.setPlaybackQuality(quality);
}
+ @Nullable
public String getPlaybackQuality() {
- return playbackQuality;
+ return resolver.getPlaybackQuality();
}
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java
index b174ed3ed..63c0bf333 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java
@@ -39,10 +39,13 @@ public class MediaSessionManager {
return MediaButtonReceiver.handleIntent(mediaSession, intent);
}
+ /**
+ * Should be called on player destruction to prevent leakage.
+ * */
public void dispose() {
this.sessionConnector.setPlayer(null, null);
this.sessionConnector.setQueueNavigator(null);
this.mediaSession.setActive(false);
this.mediaSession.release();
- }
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
index 7c7d87791..d6453f579 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
@@ -21,25 +21,34 @@ import static org.schabi.newpipe.player.BasePlayer.DEBUG;
public class PlaybackParameterDialog extends DialogFragment {
@NonNull private static final String TAG = "PlaybackParameterDialog";
- public static final double MINIMUM_PLAYBACK_VALUE = 0.25f;
+ // 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 PLAYBACK_STEP_VALUE = 0.05f;
- public static final double NIGHTCORE_TEMPO = 1.20f;
- public static final double NIGHTCORE_PITCH_LOWER = 1.15f;
- public static final double NIGHTCORE_PITCH_UPPER = 1.25f;
+ 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 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);
+ void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch,
+ final boolean playbackSkipSilence);
}
@Nullable private Callback callback;
@@ -50,6 +59,11 @@ public class PlaybackParameterDialog extends DialogFragment {
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 tempoMinimumText;
@@ -65,16 +79,26 @@ public class PlaybackParameterDialog extends DialogFragment {
@Nullable private TextView pitchStepDownText;
@Nullable private TextView pitchStepUpText;
- @Nullable private CheckBox unhookingCheckbox;
+ @Nullable private TextView stepSizeOnePercentText;
+ @Nullable private TextView stepSizeFivePercentText;
+ @Nullable private TextView stepSizeTenPercentText;
+ @Nullable private TextView stepSizeTwentyFivePercentText;
+ @Nullable private TextView stepSizeOneHundredPercentText;
- @Nullable private TextView nightCorePresetText;
- @Nullable private TextView resetPresetText;
+ @Nullable private CheckBox unhookingCheckbox;
+ @Nullable private CheckBox skipSilenceCheckbox;
public static PlaybackParameterDialog newInstance(final double playbackTempo,
- final double playbackPitch) {
+ final double playbackPitch,
+ final boolean playbackSkipSilence) {
PlaybackParameterDialog dialog = new PlaybackParameterDialog();
dialog.initialTempo = playbackTempo;
dialog.initialPitch = playbackPitch;
+
+ dialog.tempo = playbackTempo;
+ dialog.pitch = playbackPitch;
+
+ dialog.initialSkipSilence = playbackSkipSilence;
return dialog;
}
@@ -98,6 +122,10 @@ public class PlaybackParameterDialog extends DialogFragment {
if (savedInstanceState != null) {
initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO);
initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH);
+
+ tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO);
+ pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH);
+ stepSize = savedInstanceState.getDouble(STEP_SIZE_KEY, DEFAULT_STEP);
}
}
@@ -106,6 +134,10 @@ public class PlaybackParameterDialog extends DialogFragment {
super.onSaveInstanceState(outState);
outState.putDouble(INITIAL_TEMPO_KEY, initialTempo);
outState.putDouble(INITIAL_PITCH_KEY, initialPitch);
+
+ outState.putDouble(TEMPO_KEY, getCurrentTempo());
+ outState.putDouble(PITCH_KEY, getCurrentPitch());
+ outState.putDouble(STEP_SIZE_KEY, getCurrentStepSize());
}
/*//////////////////////////////////////////////////////////////////////////
@@ -123,7 +155,9 @@ public class PlaybackParameterDialog extends DialogFragment {
.setView(view)
.setCancelable(true)
.setNegativeButton(R.string.cancel, (dialogInterface, i) ->
- setPlaybackParameters(initialTempo, initialPitch))
+ setPlaybackParameters(initialTempo, initialPitch, initialSkipSilence))
+ .setNeutralButton(R.string.playback_reset, (dialogInterface, i) ->
+ setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, DEFAULT_SKIP_SILENCE))
.setPositiveButton(R.string.finish, (dialogInterface, i) ->
setCurrentPlaybackParameters());
@@ -136,9 +170,13 @@ public class PlaybackParameterDialog extends DialogFragment {
private void setupControlViews(@NonNull View rootView) {
setupHookingControl(rootView);
+ setupSkipSilenceControl(rootView);
+
setupTempoControl(rootView);
setupPitchControl(rootView);
- setupPresetControl(rootView);
+
+ changeStepSize(stepSize);
+ setupStepSizeSelector(rootView);
}
private void setupTempoControl(@NonNull View rootView) {
@@ -150,31 +188,15 @@ public class PlaybackParameterDialog extends DialogFragment {
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
if (tempoCurrentText != null)
- tempoCurrentText.setText(PlayerHelper.formatSpeed(initialTempo));
+ tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
if (tempoMaximumText != null)
tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE));
if (tempoMinimumText != null)
tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE));
- if (tempoStepUpText != null) {
- tempoStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
- tempoStepUpText.setOnClickListener(view -> {
- onTempoSliderUpdated(getCurrentTempo() + PLAYBACK_STEP_VALUE);
- setCurrentPlaybackParameters();
- });
- }
-
- if (tempoStepDownText != null) {
- tempoStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
- tempoStepDownText.setOnClickListener(view -> {
- onTempoSliderUpdated(getCurrentTempo() - PLAYBACK_STEP_VALUE);
- setCurrentPlaybackParameters();
- });
- }
-
if (tempoSlider != null) {
tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
- tempoSlider.setProgress(strategy.progressOf(initialTempo));
+ tempoSlider.setProgress(strategy.progressOf(tempo));
tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener());
}
}
@@ -188,31 +210,15 @@ public class PlaybackParameterDialog extends DialogFragment {
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
if (pitchCurrentText != null)
- pitchCurrentText.setText(PlayerHelper.formatPitch(initialPitch));
+ pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
if (pitchMaximumText != null)
pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE));
if (pitchMinimumText != null)
pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE));
- if (pitchStepUpText != null) {
- pitchStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
- pitchStepUpText.setOnClickListener(view -> {
- onPitchSliderUpdated(getCurrentPitch() + PLAYBACK_STEP_VALUE);
- setCurrentPlaybackParameters();
- });
- }
-
- if (pitchStepDownText != null) {
- pitchStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
- pitchStepDownText.setOnClickListener(view -> {
- onPitchSliderUpdated(getCurrentPitch() - PLAYBACK_STEP_VALUE);
- setCurrentPlaybackParameters();
- });
- }
-
if (pitchSlider != null) {
pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
- pitchSlider.setProgress(strategy.progressOf(initialPitch));
+ pitchSlider.setProgress(strategy.progressOf(pitch));
pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener());
}
}
@@ -220,7 +226,7 @@ public class PlaybackParameterDialog extends DialogFragment {
private void setupHookingControl(@NonNull View rootView) {
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
if (unhookingCheckbox != null) {
- unhookingCheckbox.setChecked(initialPitch != initialTempo);
+ unhookingCheckbox.setChecked(pitch != tempo);
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
if (isChecked) return;
// When unchecked, slide back to the minimum of current tempo or pitch
@@ -231,24 +237,84 @@ public class PlaybackParameterDialog extends DialogFragment {
}
}
- private void setupPresetControl(@NonNull View rootView) {
- nightCorePresetText = rootView.findViewById(R.id.presetNightcore);
- if (nightCorePresetText != null) {
- nightCorePresetText.setOnClickListener(view -> {
- final double randomPitch = NIGHTCORE_PITCH_LOWER +
- Math.random() * (NIGHTCORE_PITCH_UPPER - NIGHTCORE_PITCH_LOWER);
+ private void setupSkipSilenceControl(@NonNull View rootView) {
+ skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox);
+ if (skipSilenceCheckbox != null) {
+ skipSilenceCheckbox.setChecked(initialSkipSilence);
+ skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) ->
+ setCurrentPlaybackParameters());
+ }
+ }
- setTempoSlider(NIGHTCORE_TEMPO);
- setPitchSlider(randomPitch);
+ private void setupStepSizeSelector(@NonNull final View rootView) {
+ stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
+ stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
+ stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
+ stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent);
+ stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent);
+
+ if (stepSizeOnePercentText != null) {
+ stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE));
+ stepSizeOnePercentText.setOnClickListener(view ->
+ changeStepSize(STEP_ONE_PERCENT_VALUE));
+ }
+
+ if (stepSizeFivePercentText != null) {
+ stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE));
+ stepSizeFivePercentText.setOnClickListener(view ->
+ changeStepSize(STEP_FIVE_PERCENT_VALUE));
+ }
+
+ if (stepSizeTenPercentText != null) {
+ stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE));
+ stepSizeTenPercentText.setOnClickListener(view ->
+ changeStepSize(STEP_TEN_PERCENT_VALUE));
+ }
+
+ if (stepSizeTwentyFivePercentText != null) {
+ stepSizeTwentyFivePercentText.setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE));
+ stepSizeTwentyFivePercentText.setOnClickListener(view ->
+ changeStepSize(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));
+ }
+ }
+
+ private void changeStepSize(final double stepSize) {
+ this.stepSize = stepSize;
+
+ if (tempoStepUpText != null) {
+ tempoStepUpText.setText(getStepUpPercentString(stepSize));
+ tempoStepUpText.setOnClickListener(view -> {
+ onTempoSliderUpdated(getCurrentTempo() + stepSize);
setCurrentPlaybackParameters();
});
}
- resetPresetText = rootView.findViewById(R.id.presetReset);
- if (resetPresetText != null) {
- resetPresetText.setOnClickListener(view -> {
- setTempoSlider(DEFAULT_TEMPO);
- setPitchSlider(DEFAULT_PITCH);
+ if (tempoStepDownText != null) {
+ tempoStepDownText.setText(getStepDownPercentString(stepSize));
+ tempoStepDownText.setOnClickListener(view -> {
+ onTempoSliderUpdated(getCurrentTempo() - stepSize);
+ setCurrentPlaybackParameters();
+ });
+ }
+
+ if (pitchStepUpText != null) {
+ pitchStepUpText.setText(getStepUpPercentString(stepSize));
+ pitchStepUpText.setOnClickListener(view -> {
+ onPitchSliderUpdated(getCurrentPitch() + stepSize);
+ setCurrentPlaybackParameters();
+ });
+ }
+
+ if (pitchStepDownText != null) {
+ pitchStepDownText.setText(getStepDownPercentString(stepSize));
+ pitchStepDownText.setOnClickListener(view -> {
+ onPitchSliderUpdated(getCurrentPitch() - stepSize);
setCurrentPlaybackParameters();
});
}
@@ -342,10 +408,11 @@ public class PlaybackParameterDialog extends DialogFragment {
//////////////////////////////////////////////////////////////////////////*/
private void setCurrentPlaybackParameters() {
- setPlaybackParameters(getCurrentTempo(), getCurrentPitch());
+ setPlaybackParameters(getCurrentTempo(), getCurrentPitch(), getCurrentSkipSilence());
}
- private void setPlaybackParameters(final double tempo, final double pitch) {
+ private void setPlaybackParameters(final double tempo, final double pitch,
+ final boolean skipSilence) {
if (callback != null && tempoCurrentText != null && pitchCurrentText != null) {
if (DEBUG) Log.d(TAG, "Setting playback parameters to " +
"tempo=[" + tempo + "], " +
@@ -353,27 +420,40 @@ public class PlaybackParameterDialog extends DialogFragment {
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
- callback.onPlaybackParameterChanged((float) tempo, (float) pitch);
+ callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence);
}
}
private double getCurrentTempo() {
- return tempoSlider == null ? initialTempo : strategy.valueOf(
+ return tempoSlider == null ? tempo : strategy.valueOf(
tempoSlider.getProgress());
}
private double getCurrentPitch() {
- return pitchSlider == null ? initialPitch : strategy.valueOf(
+ return pitchSlider == null ? pitch : strategy.valueOf(
pitchSlider.getProgress());
}
+ private double getCurrentStepSize() {
+ return stepSize;
+ }
+
+ private boolean getCurrentSkipSilence() {
+ return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked();
+ }
+
@NonNull
private static String getStepUpPercentString(final double percent) {
- return STEP_UP_SIGN + PlayerHelper.formatPitch(percent);
+ return STEP_UP_SIGN + getPercentString(percent);
}
@NonNull
private static String getStepDownPercentString(final double percent) {
- return STEP_DOWN_SIGN + PlayerHelper.formatPitch(percent);
+ return STEP_DOWN_SIGN + getPercentString(percent);
+ }
+
+ @NonNull
+ private static String getPercentString(final double percent) {
+ return PlayerHelper.formatPitch(percent);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
index dbe0e9f46..ae187a834 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
+import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.accessibility.CaptioningManager;
@@ -28,6 +29,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
+import java.lang.annotation.Retention;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
@@ -42,6 +44,8 @@ import java.util.concurrent.TimeUnit;
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FILL;
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT;
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.*;
public class PlayerHelper {
private PlayerHelper() {}
@@ -51,6 +55,14 @@ public class PlayerHelper {
private static final NumberFormat speedFormatter = new DecimalFormat("0.##x");
private static final NumberFormat pitchFormatter = new DecimalFormat("##%");
+ @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
////////////////////////////////////////////////////////////////////////////
@@ -173,6 +185,22 @@ public class PlayerHelper {
return isAutoQueueEnabled(context, false);
}
+ @MinimizeMode
+ public static int getMinimizeOnExitAction(@NonNull final Context context) {
+ final String defaultAction = context.getString(R.string.minimize_on_exit_none_key);
+ final String popupAction = context.getString(R.string.minimize_on_exit_popup_key);
+ final String backgroundAction = context.getString(R.string.minimize_on_exit_background_key);
+
+ final String action = getMinimizeOnExitAction(context, defaultAction);
+ if (action.equals(popupAction)) {
+ return MINIMIZE_ON_EXIT_MODE_POPUP;
+ } else if (action.equals(backgroundAction)) {
+ return MINIMIZE_ON_EXIT_MODE_BACKGROUND;
+ } else {
+ return MINIMIZE_ON_EXIT_MODE_NONE;
+ }
+ }
+
@NonNull
public static SeekParameters getSeekParameters(@NonNull final Context context) {
return isUsingInexactSeek(context, false) ?
@@ -213,7 +241,6 @@ public class PlayerHelper {
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context,
@NonNull final BandwidthMeter meter) {
return new AdaptiveTrackSelection.Factory(meter,
- AdaptiveTrackSelection.DEFAULT_MAX_INITIAL_BITRATE,
/*bufferDurationRequiredForQualityIncrease=*/1000,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
@@ -224,10 +251,6 @@ public class PlayerHelper {
return true;
}
- public static int getShutdownFlingVelocity(@NonNull final Context context) {
- return 10000;
- }
-
public static int getTossFlingVelocity(@NonNull final Context context) {
return 2500;
}
@@ -249,7 +272,6 @@ public class PlayerHelper {
* System font scaling:
* Very small - 0.25f, Small - 0.5f, Normal - 1.0f, Large - 1.5f, Very Large - 2.0f
* */
- @NonNull
public static float getCaptionScale(@NonNull final Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return 1f;
@@ -322,4 +344,10 @@ public class PlayerHelper {
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);
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
index 8d498a9bf..2f233c464 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
@@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
import android.util.Log;
import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.source.BaseMediaSource;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.upstream.Allocator;
@@ -11,7 +12,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import java.io.IOException;
-public class FailedMediaSource implements ManagedMediaSource {
+public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSource {
private final String TAG = "FailedMediaSource@" + Integer.toHexString(hashCode());
public static class FailedMediaSourceException extends Exception {
@@ -72,11 +73,6 @@ public class FailedMediaSource implements ManagedMediaSource {
return System.currentTimeMillis() >= retryTimestamp;
}
- @Override
- public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
- Log.e(TAG, "Loading failed source: ", error);
- }
-
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
throw new IOException(error);
@@ -90,8 +86,14 @@ public class FailedMediaSource implements ManagedMediaSource {
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {}
+
@Override
- public void releaseSource() {}
+ protected void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {
+ Log.e(TAG, "Loading failed source: ", error);
+ }
+
+ @Override
+ protected void releaseSourceInternal() {}
@Override
public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity,
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java
index 1a9cfeb4d..c39b0a03d 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java
@@ -1,10 +1,12 @@
package org.schabi.newpipe.player.mediasource;
+import android.os.Handler;
import android.support.annotation.NonNull;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.upstream.Allocator;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
@@ -34,7 +36,8 @@ public class LoadedMediaSource implements ManagedMediaSource {
}
@Override
- public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
+ public void prepareSource(ExoPlayer player, boolean isTopLevelSource,
+ SourceInfoRefreshListener listener) {
source.prepareSource(player, isTopLevelSource, listener);
}
@@ -54,8 +57,18 @@ public class LoadedMediaSource implements ManagedMediaSource {
}
@Override
- public void releaseSource() {
- source.releaseSource();
+ public void releaseSource(SourceInfoRefreshListener listener) {
+ source.releaseSource(listener);
+ }
+
+ @Override
+ public void addEventListener(Handler handler, MediaSourceEventListener eventListener) {
+ source.addEventListener(handler, eventListener);
+ }
+
+ @Override
+ public void removeEventListener(MediaSourceEventListener eventListener) {
+ source.removeEventListener(eventListener);
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java
index 310f1062b..5fe107657 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java
@@ -3,14 +3,14 @@ package org.schabi.newpipe.player.mediasource;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
+import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.ShuffleOrder;
public class ManagedMediaSourcePlaylist {
- @NonNull private final DynamicConcatenatingMediaSource internalSource;
+ @NonNull private final ConcatenatingMediaSource internalSource;
public ManagedMediaSourcePlaylist() {
- internalSource = new DynamicConcatenatingMediaSource(/*isPlaylistAtomic=*/false,
+ internalSource = new ConcatenatingMediaSource(/*isPlaylistAtomic=*/false,
new ShuffleOrder.UnshuffledShuffleOrder(0));
}
@@ -32,12 +32,8 @@ public class ManagedMediaSourcePlaylist {
null : (ManagedMediaSource) internalSource.getMediaSource(index);
}
- public void dispose() {
- internalSource.releaseSource();
- }
-
@NonNull
- public DynamicConcatenatingMediaSource getParentMediaSource() {
+ public ConcatenatingMediaSource getParentMediaSource() {
return internalSource;
}
@@ -46,7 +42,7 @@ public class ManagedMediaSourcePlaylist {
//////////////////////////////////////////////////////////////////////////*/
/**
- * Expands the {@link DynamicConcatenatingMediaSource} by appending it with a
+ * Expands the {@link ConcatenatingMediaSource} by appending it with a
* {@link PlaceholderMediaSource}.
*
* @see #append(ManagedMediaSource)
@@ -56,17 +52,17 @@ public class ManagedMediaSourcePlaylist {
}
/**
- * Appends a {@link ManagedMediaSource} to the end of {@link DynamicConcatenatingMediaSource}.
- * @see DynamicConcatenatingMediaSource#addMediaSource
+ * Appends a {@link ManagedMediaSource} to the end of {@link ConcatenatingMediaSource}.
+ * @see ConcatenatingMediaSource#addMediaSource
* */
public synchronized void append(@NonNull final ManagedMediaSource source) {
internalSource.addMediaSource(source);
}
/**
- * Removes a {@link ManagedMediaSource} from {@link DynamicConcatenatingMediaSource}
+ * Removes a {@link ManagedMediaSource} from {@link ConcatenatingMediaSource}
* at the given index. If this index is out of bound, then the removal is ignored.
- * @see DynamicConcatenatingMediaSource#removeMediaSource(int)
+ * @see ConcatenatingMediaSource#removeMediaSource(int)
* */
public synchronized void remove(final int index) {
if (index < 0 || index > internalSource.getSize()) return;
@@ -75,10 +71,10 @@ public class ManagedMediaSourcePlaylist {
}
/**
- * Moves a {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
+ * 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 DynamicConcatenatingMediaSource#moveMediaSource(int, int)
+ * @see ConcatenatingMediaSource#moveMediaSource(int, int)
* */
public synchronized void move(final int source, final int target) {
if (source < 0 || target < 0) return;
@@ -99,7 +95,7 @@ public class ManagedMediaSourcePlaylist {
}
/**
- * Updates the {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
+ * Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
* at the given index with a given {@link ManagedMediaSource}.
* @see #update(int, ManagedMediaSource, Runnable)
* */
@@ -108,11 +104,11 @@ public class ManagedMediaSourcePlaylist {
}
/**
- * Updates the {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
+ * 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 DynamicConcatenatingMediaSource#addMediaSource
- * @see DynamicConcatenatingMediaSource#removeMediaSource(int, Runnable)
+ * @see ConcatenatingMediaSource#addMediaSource
+ * @see ConcatenatingMediaSource#removeMediaSource(int, Runnable)
* */
public synchronized void update(final int index, @NonNull final ManagedMediaSource source,
@Nullable final Runnable finalizingAction) {
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java
index 318f9a316..bfd734393 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java
@@ -3,20 +3,19 @@ package org.schabi.newpipe.player.mediasource;
import android.support.annotation.NonNull;
import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.source.BaseMediaSource;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.upstream.Allocator;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
-import java.io.IOException;
-
-public class PlaceholderMediaSource implements ManagedMediaSource {
+public class PlaceholderMediaSource extends BaseMediaSource implements ManagedMediaSource {
// Do nothing, so this will stall the playback
- @Override public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {}
- @Override public void maybeThrowSourceInfoRefreshError() throws IOException {}
+ @Override public void maybeThrowSourceInfoRefreshError() {}
@Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { return null; }
@Override public void releasePeriod(MediaPeriod mediaPeriod) {}
- @Override public void releaseSource() {}
+ @Override protected void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {}
+ @Override protected void releaseSourceInternal() {}
@Override
public boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity,
diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java
index 8ab3cba98..b27dc3dd6 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java
@@ -5,12 +5,10 @@ import android.support.annotation.Nullable;
import android.support.v4.util.ArraySet;
import android.util.Log;
-import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
-import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.mediasource.FailedMediaSource;
import org.schabi.newpipe.player.mediasource.LoadedMediaSource;
import org.schabi.newpipe.player.mediasource.ManagedMediaSource;
@@ -24,10 +22,8 @@ import org.schabi.newpipe.player.playqueue.events.RemoveEvent;
import org.schabi.newpipe.player.playqueue.events.ReorderEvent;
import org.schabi.newpipe.util.ServiceHelper;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -37,8 +33,6 @@ import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
-import io.reactivex.disposables.SerialDisposable;
-import io.reactivex.functions.Consumer;
import io.reactivex.internal.subscriptions.EmptySubscription;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
@@ -104,7 +98,6 @@ public class MediaSourceManager {
private final static int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
@NonNull private final CompositeDisposable loaderReactor;
@NonNull private final Set loadingItems;
- @NonNull private final SerialDisposable syncReactor;
@NonNull private final AtomicBoolean isBlocked;
@@ -144,7 +137,6 @@ public class MediaSourceManager {
this.playQueueReactor = EmptySubscription.INSTANCE;
this.loaderReactor = new CompositeDisposable();
- this.syncReactor = new SerialDisposable();
this.isBlocked = new AtomicBoolean(false);
@@ -171,8 +163,6 @@ public class MediaSourceManager {
playQueueReactor.cancel();
loaderReactor.dispose();
- syncReactor.dispose();
- playlist.dispose();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -311,21 +301,7 @@ public class MediaSourceManager {
final PlayQueueItem currentItem = playQueue.getItem();
if (isBlocked.get() || currentItem == null) return;
- final Consumer onSuccess = info -> syncInternal(currentItem, info);
- final Consumer onError = throwable -> syncInternal(currentItem, null);
-
- final Disposable sync = currentItem.getStream()
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(onSuccess, onError);
- syncReactor.set(sync);
- }
-
- private void syncInternal(@NonNull final PlayQueueItem item,
- @Nullable final StreamInfo info) {
- // Ensure the current item is up to date with the play queue
- if (playQueue.getItem() == item) {
- playbackListener.onPlaybackSynchronize(item, info);
- }
+ playbackListener.onPlaybackSynchronize(currentItem);
}
private synchronized void maybeSynchronizePlayer() {
@@ -424,7 +400,8 @@ public class MediaSourceManager {
}
/**
- * Checks if the corresponding MediaSource in {@link DynamicConcatenatingMediaSource}
+ * Checks if the corresponding MediaSource in
+ * {@link com.google.android.exoplayer2.source.ConcatenatingMediaSource}
* for a given {@link PlayQueueItem} needs replacement, either due to gapless playback
* readiness or playlist desynchronization.
*
@@ -481,8 +458,6 @@ public class MediaSourceManager {
private void resetSources() {
if (DEBUG) Log.d(TAG, "resetSources() called.");
-
- playlist.dispose();
playlist = new ManagedMediaSourcePlaylist();
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java
index 4dcb30aa3..238bdfcd0 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java
@@ -45,7 +45,7 @@ public interface PlaybackListener {
*
* May be called anytime at any amount once unblock is called.
* */
- void onPlaybackSynchronize(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info);
+ void onPlaybackSynchronize(@NonNull final PlayQueueItem item);
/**
* Requests the listener to resolve a stream info into a media source
diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java
index a21560abd..c9e07c96a 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java
@@ -6,6 +6,7 @@ import android.util.Log;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
+import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
import org.schabi.newpipe.player.playqueue.events.InitEvent;
@@ -41,7 +42,7 @@ import io.reactivex.subjects.BehaviorSubject;
public abstract class PlayQueue implements Serializable {
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
- public static final boolean DEBUG = true;
+ public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
private ArrayList backup;
private ArrayList streams;
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java
new file mode 100644
index 000000000..6bb556850
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java
@@ -0,0 +1,41 @@
+package org.schabi.newpipe.player.resolver;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.google.android.exoplayer2.source.MediaSource;
+
+import org.schabi.newpipe.extractor.MediaFormat;
+import org.schabi.newpipe.extractor.stream.AudioStream;
+import org.schabi.newpipe.extractor.stream.StreamInfo;
+import org.schabi.newpipe.player.helper.PlayerDataSource;
+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;
+
+ public AudioPlaybackResolver(@NonNull final Context context,
+ @NonNull final PlayerDataSource dataSource) {
+ this.context = context;
+ this.dataSource = dataSource;
+ }
+
+ @Override
+ @Nullable
+ public MediaSource resolve(@NonNull StreamInfo info) {
+ final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
+ if (liveSource != null) return liveSource;
+
+ final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
+ if (index < 0 || index >= info.getAudioStreams().size()) return null;
+
+ final AudioStream audio = info.getAudioStreams().get(index);
+ final MediaSourceTag tag = new MediaSourceTag(info);
+ return buildMediaSource(dataSource, audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio),
+ MediaFormat.getSuffixById(audio.getFormatId()), tag);
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/MediaSourceTag.java b/app/src/main/java/org/schabi/newpipe/player/resolver/MediaSourceTag.java
new file mode 100644
index 000000000..bbe5d33ca
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/MediaSourceTag.java
@@ -0,0 +1,51 @@
+package org.schabi.newpipe.player.resolver;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.schabi.newpipe.extractor.stream.StreamInfo;
+import org.schabi.newpipe.extractor.stream.VideoStream;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+public class MediaSourceTag implements Serializable {
+ @NonNull private final StreamInfo metadata;
+
+ @NonNull private final List sortedAvailableVideoStreams;
+ private final int selectedVideoStreamIndex;
+
+ public MediaSourceTag(@NonNull final StreamInfo metadata,
+ @NonNull final List sortedAvailableVideoStreams,
+ final int selectedVideoStreamIndex) {
+ this.metadata = metadata;
+ this.sortedAvailableVideoStreams = sortedAvailableVideoStreams;
+ this.selectedVideoStreamIndex = selectedVideoStreamIndex;
+ }
+
+ public MediaSourceTag(@NonNull final StreamInfo metadata) {
+ this(metadata, Collections.emptyList(), /*indexNotAvailable=*/-1);
+ }
+
+ @NonNull
+ public StreamInfo getMetadata() {
+ return metadata;
+ }
+
+ @NonNull
+ public List getSortedAvailableVideoStreams() {
+ return sortedAvailableVideoStreams;
+ }
+
+ public int getSelectedVideoStreamIndex() {
+ return selectedVideoStreamIndex;
+ }
+
+ @Nullable
+ public VideoStream getSelectedVideoStream() {
+ return selectedVideoStreamIndex < 0 ||
+ selectedVideoStreamIndex >= sortedAvailableVideoStreams.size() ? null :
+ sortedAvailableVideoStreams.get(selectedVideoStreamIndex);
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java
new file mode 100644
index 000000000..1da3ec211
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java
@@ -0,0 +1,84 @@
+package org.schabi.newpipe.player.resolver;
+
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.util.Util;
+
+import org.schabi.newpipe.extractor.stream.StreamInfo;
+import org.schabi.newpipe.extractor.stream.StreamType;
+import org.schabi.newpipe.player.helper.PlayerDataSource;
+
+public interface PlaybackResolver extends Resolver {
+
+ @Nullable
+ default MediaSource maybeBuildLiveMediaSource(@NonNull final PlayerDataSource dataSource,
+ @NonNull final StreamInfo info) {
+ final StreamType streamType = info.getStreamType();
+ if (!(streamType == StreamType.AUDIO_LIVE_STREAM || streamType == StreamType.LIVE_STREAM)) {
+ return null;
+ }
+
+ final MediaSourceTag tag = new MediaSourceTag(info);
+ if (!info.getHlsUrl().isEmpty()) {
+ return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.TYPE_HLS, tag);
+ } else if (!info.getDashMpdUrl().isEmpty()) {
+ return buildLiveMediaSource(dataSource, info.getDashMpdUrl(), C.TYPE_DASH, tag);
+ }
+
+ return null;
+ }
+
+ @NonNull
+ default MediaSource buildLiveMediaSource(@NonNull final PlayerDataSource dataSource,
+ @NonNull final String sourceUrl,
+ @C.ContentType final int type,
+ @NonNull final MediaSourceTag metadata) {
+ final Uri uri = Uri.parse(sourceUrl);
+ switch (type) {
+ case C.TYPE_SS:
+ return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
+ .createMediaSource(uri);
+ case C.TYPE_DASH:
+ return dataSource.getLiveDashMediaSourceFactory().setTag(metadata)
+ .createMediaSource(uri);
+ case C.TYPE_HLS:
+ return dataSource.getLiveHlsMediaSourceFactory().setTag(metadata)
+ .createMediaSource(uri);
+ default:
+ throw new IllegalStateException("Unsupported type: " + type);
+ }
+ }
+
+ @NonNull
+ default MediaSource buildMediaSource(@NonNull final PlayerDataSource dataSource,
+ @NonNull final String sourceUrl,
+ @NonNull final String cacheKey,
+ @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);
+
+ switch (type) {
+ case C.TYPE_SS:
+ return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
+ .createMediaSource(uri);
+ case C.TYPE_DASH:
+ return dataSource.getDashMediaSourceFactory().setTag(metadata)
+ .createMediaSource(uri);
+ case C.TYPE_HLS:
+ return dataSource.getHlsMediaSourceFactory().setTag(metadata)
+ .createMediaSource(uri);
+ case C.TYPE_OTHER:
+ return dataSource.getExtractorMediaSourceFactory(cacheKey).setTag(metadata)
+ .createMediaSource(uri);
+ default:
+ throw new IllegalStateException("Unsupported type: " + type);
+ }
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java
new file mode 100644
index 000000000..4bd795574
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java
@@ -0,0 +1,8 @@
+package org.schabi.newpipe.player.resolver;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+public interface Resolver