diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
index 58047639c..73d0b9180 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
@@ -68,6 +68,7 @@ import org.schabi.newpipe.player.playback.PlaybackListener;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueAdapter;
import org.schabi.newpipe.playlist.PlayQueueItem;
+import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.util.SerializedCache;
import java.io.IOException;
@@ -818,22 +819,32 @@ public abstract class BasePlayer implements
initThumbnail(info == null ? item.getThumbnailUrl() : info.getThumbnailUrl());
}
- final int currentSourceIndex = playQueue.indexOf(item);
- onMetadataChanged(item, info, currentSourceIndex, hasPlayQueueItemChanged);
+ final int currentPlayQueueIndex = playQueue.indexOf(item);
+ onMetadataChanged(item, info, currentPlayQueueIndex, hasPlayQueueItemChanged);
- // Check if on wrong window
if (simpleExoPlayer == null) return;
- if (currentSourceIndex != playQueue.getIndex()) {
- Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex +
- "], queue index=[" + playQueue.getIndex() + "]");
+ final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex();
+ // Check if on wrong window
+ if (currentPlayQueueIndex != playQueue.getIndex()) {
+ Log.e(TAG, "Play Queue may be desynchronized: item " +
+ "index=[" + currentPlayQueueIndex + "], " +
+ "queue index=[" + playQueue.getIndex() + "]");
// on metadata changed
- } else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) {
- final long startPos = info != null ? info.start_position : 0;
- if (DEBUG) Log.d(TAG, "Rewinding to correct window=[" + currentSourceIndex + "]," +
+ } else if (currentPlaylistIndex != currentPlayQueueIndex || !isPlaying()) {
+ final long startPos = info != null ? info.start_position : C.TIME_UNSET;
+ if (DEBUG) Log.d(TAG, "Rewinding to correct" +
+ " window=[" + currentPlayQueueIndex + "]," +
" at=[" + getTimeString((int)startPos) + "]," +
" from=[" + simpleExoPlayer.getCurrentWindowIndex() + "].");
- simpleExoPlayer.seekTo(currentSourceIndex, startPos);
+ simpleExoPlayer.seekTo(currentPlayQueueIndex, startPos);
+ }
+
+ // when starting playback on the last item, maybe auto queue
+ if (info != null && currentPlayQueueIndex == playQueue.size() - 1 &&
+ PlayerHelper.isAutoQueueEnabled(context)) {
+ final PlayQueue autoQueue = PlayerHelper.autoQueueOf(info, playQueue.getStreams());
+ if (autoQueue != null) playQueue.append(autoQueue.getStreams());
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
index 0bfdcd32a..146b511c4 100644
--- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
@@ -129,6 +129,7 @@ public final class MainVideoPlayer extends Activity {
super.onSaveInstanceState(outState);
if (this.playerImpl == null) return;
+ playerImpl.setRecovery();
final Intent intent = NavigationHelper.getPlayerIntent(
getApplicationContext(),
this.getClass(),
@@ -156,31 +157,27 @@ public final class MainVideoPlayer extends Activity {
}
@Override
- protected void onStop() {
- super.onStop();
- if (DEBUG) Log.d(TAG, "onStop() called");
- activityPaused = true;
+ protected void onPause() {
+ super.onPause();
+ if (DEBUG) Log.d(TAG, "onPause() called");
- if (playerImpl.getPlayer() != null) {
- playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady();
- playerImpl.setRecovery();
- playerImpl.destroyPlayer();
+ if (playerImpl.getPlayer() != null && playerImpl.isPlaying() && !activityPaused) {
+ playerImpl.wasPlaying = true;
+ playerImpl.onVideoPlayPause();
}
+ activityPaused = true;
}
@Override
protected void onResume() {
super.onResume();
if (DEBUG) Log.d(TAG, "onResume() called");
- if (activityPaused) {
- playerImpl.initPlayer();
- playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
-
- playerImpl.getPlayer().setPlayWhenReady(playerImpl.wasPlaying);
- playerImpl.initPlayback(playerImpl.playQueue);
-
- activityPaused = false;
+ if (playerImpl.getPlayer() != null && playerImpl.wasPlaying()
+ && !playerImpl.isPlaying() && activityPaused) {
+ playerImpl.onVideoPlayPause();
}
+ activityPaused = false;
+
if(globalScreenOrientationLocked()) {
boolean lastOrientationWasLandscape
= defaultPreferences.getBoolean(getString(R.string.last_orientation_landscape_key), false);
@@ -873,6 +870,13 @@ public final class MainVideoPlayer extends Activity {
return true;
}
+ @Override
+ public boolean onDown(MotionEvent e) {
+ if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]");
+
+ return super.onDown(e);
+ }
+
private final boolean isPlayerGestureEnabled = PlayerHelper.isPlayerGestureEnabled(getApplicationContext());
private final float stepsBrightness = 15, stepBrightness = (1f / stepsBrightness), minBrightness = .01f;
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 553163d21..87b0f701f 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,22 +4,33 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.util.MimeTypes;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.InfoItem;
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.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.SubtitlesFormat;
import org.schabi.newpipe.extractor.stream.VideoStream;
+import org.schabi.newpipe.playlist.PlayQueue;
+import org.schabi.newpipe.playlist.PlayQueueItem;
+import org.schabi.newpipe.playlist.SinglePlayQueue;
import java.text.DecimalFormat;
import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Formatter;
+import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
+import java.util.Set;
import javax.annotation.Nonnull;
@@ -76,6 +87,7 @@ public class PlayerHelper {
return displayName + (subtitles.isAutoGenerated() ? " (" + context.getString(R.string.caption_auto_generated)+ ")" : "");
}
+ @NonNull
public static String resizeTypeOf(@NonNull final Context context,
@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
switch (resizeMode) {
@@ -86,14 +98,58 @@ public class PlayerHelper {
}
}
+ @NonNull
public static String cacheKeyOf(@NonNull final StreamInfo info, @NonNull VideoStream video) {
return info.getUrl() + video.getResolution() + video.getFormat().getName();
}
+ @NonNull
public static String cacheKeyOf(@NonNull final StreamInfo info, @NonNull AudioStream audio) {
return info.getUrl() + audio.getAverageBitrate() + audio.getFormat().getName();
}
+ /**
+ * Given a {@link StreamInfo} and the existing queue items, provide the
+ * {@link SinglePlayQueue} consisting of the next video for auto queuing.
+ *
+ * This method detects and prevents cycle by naively checking if a
+ * candidate next video's url already exists in the existing items.
+ *
+ * To select the next video, {@link StreamInfo#getNextVideo()} is first
+ * checked. If it is nonnull and is not part of the existing items, then
+ * it will be used as the next video. Otherwise, an random item with
+ * non-repeating url will be selected from the {@link StreamInfo#getRelatedStreams()}.
+ * */
+ @Nullable
+ public static PlayQueue autoQueueOf(@NonNull final StreamInfo info,
+ @NonNull final List existingItems) {
+ Set urls = new HashSet<>(existingItems.size());
+ for (final PlayQueueItem item : existingItems) {
+ urls.add(item.getUrl());
+ }
+
+ final StreamInfoItem nextVideo = info.getNextVideo();
+ if (nextVideo != null && !urls.contains(nextVideo.getUrl())) {
+ return new SinglePlayQueue(nextVideo);
+ }
+
+ final List relatedItems = info.getRelatedStreams();
+ if (relatedItems == null) return null;
+
+ List autoQueueItems = new ArrayList<>();
+ for (final InfoItem item : info.getRelatedStreams()) {
+ if (item instanceof StreamInfoItem && !urls.contains(item.getUrl())) {
+ autoQueueItems.add((StreamInfoItem) item);
+ }
+ }
+ Collections.shuffle(autoQueueItems);
+ return autoQueueItems.isEmpty() ? null : new SinglePlayQueue(autoQueueItems.get(0));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Settings Resolution
+ ////////////////////////////////////////////////////////////////////////////
+
public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) {
return isResumeAfterAudioFocusGain(context, false);
}
@@ -110,6 +166,10 @@ public class PlayerHelper {
return isRememberingPopupDimensions(context, true);
}
+ public static boolean isAutoQueueEnabled(@NonNull final Context context) {
+ return isAutoQueueEnabled(context, false);
+ }
+
@NonNull
public static SeekParameters getSeekParameters(@NonNull final Context context) {
return isUsingInexactSeek(context, false) ?
@@ -187,4 +247,8 @@ public class PlayerHelper {
private static boolean isUsingInexactSeek(@NonNull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), b);
}
+
+ private static boolean isAutoQueueEnabled(@NonNull final Context context, final boolean b) {
+ return getPreferences(context).getBoolean(context.getString(R.string.auto_queue_key), b);
+ }
}
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index c207ed712..a897aa185 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -21,6 +21,7 @@
resume_on_audio_focus_gain
popup_remember_size_pos_key
use_inexact_seek_key
+ auto_queue_key
default_resolution
360p
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6a8123bde..d875036e4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -74,6 +74,8 @@
Remember last size and position of popup
Use fast inexact seek
Inexact seek allows the player to seek to positions faster with reduced precision
+ Auto-queue next stream
+ Automatically append a related stream when playback starts on the last stream in play queue.
Player gesture controls
Use gestures to control the brightness and volume of the player
Search suggestions
diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml
index 20099d5c0..c8c1efb12 100644
--- a/app/src/main/res/xml/content_settings.xml
+++ b/app/src/main/res/xml/content_settings.xml
@@ -30,6 +30,13 @@
android:key="@string/show_search_suggestions_key"
android:summary="@string/show_search_suggestions_summary"
android:title="@string/show_search_suggestions_title"/>
+
+
+