From 772109855134f4d58483f52e2c76945340902b19 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 8 May 2018 10:18:32 -0700 Subject: [PATCH 01/22] -Updated ExoPlayer to 2.8.0 -Updated MediaSource contracts in ManagedMediaSource. -Changed PlaceholderMediaSource and FailedMediaSource to use built-in BaseMediaSource implementation. -Changed deprecated DynamicConcatenatingMediaSource to ConcatenatingMediaSource. -Removed manual playlist media source disposal in favor of player built-in disposal. --- app/build.gradle | 2 +- .../newpipe/player/helper/PlayerHelper.java | 1 - .../player/mediasource/FailedMediaSource.java | 16 +++++---- .../player/mediasource/LoadedMediaSource.java | 19 +++++++++-- .../ManagedMediaSourcePlaylist.java | 34 ++++++++----------- .../mediasource/PlaceholderMediaSource.java | 11 +++--- .../player/playback/MediaSourceManager.java | 3 -- 7 files changed, 46 insertions(+), 40 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 83abf44ab..b04daebed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { ext { supportLibVersion = '27.1.1' - exoPlayerLibVersion = '2.7.3' + exoPlayerLibVersion = '2.8.0' roomDbLibVersion = '1.1.1' leakCanaryLibVersion = '1.5.4' okHttpLibVersion = '3.10.0' 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 f8d594114..d10b99aec 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 @@ -241,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, 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..67a8debef 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 @@ -172,7 +172,6 @@ public class MediaSourceManager { playQueueReactor.cancel(); loaderReactor.dispose(); syncReactor.dispose(); - playlist.dispose(); } /*////////////////////////////////////////////////////////////////////////// @@ -481,8 +480,6 @@ public class MediaSourceManager { private void resetSources() { if (DEBUG) Log.d(TAG, "resetSources() called."); - - playlist.dispose(); playlist = new ManagedMediaSourcePlaylist(); } From 72d1e5131f526362892cc9ad6ac013210481feea Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 8 May 2018 11:24:42 -0700 Subject: [PATCH 02/22] -Added skip silence toggle to playback speed control. -Added step size selector to playback speed control. -Added skip silence flag to player intents. -Moved default preset in playback speed control to neutral dialog button, renamed as reset. -Removed nightcore preset from playback speed control. --- .../org/schabi/newpipe/player/BasePlayer.java | 23 ++- .../newpipe/player/MainVideoPlayer.java | 17 +- .../schabi/newpipe/player/PlayerState.java | 15 +- .../newpipe/player/PopupVideoPlayer.java | 1 + .../newpipe/player/ServicePlayerActivity.java | 12 +- .../helper/PlaybackParameterDialog.java | 168 ++++++++++++------ .../schabi/newpipe/util/NavigationHelper.java | 4 +- .../res/layout/dialog_playback_parameter.xml | 70 +++++++- app/src/main/res/values/strings.xml | 7 +- 9 files changed, 236 insertions(+), 81 deletions(-) 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 818f01bc0..525f0d258 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -115,6 +115,7 @@ public abstract class BasePlayer implements public static final String REPEAT_MODE = "repeat_mode"; public static final String PLAYBACK_PITCH = "playback_pitch"; public static final String PLAYBACK_SPEED = "playback_speed"; + public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence"; public static final String PLAYBACK_QUALITY = "playback_quality"; public static final String PLAY_QUEUE_KEY = "play_queue_key"; public static final String APPEND_ONLY = "append_only"; @@ -235,20 +236,23 @@ public abstract class BasePlayer implements final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode()); final float playbackSpeed = intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()); final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch()); + final boolean playbackSkipSilence = intent.getBooleanExtra(PLAYBACK_SKIP_SILENCE, false); // Good to go... - initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, /*playOnInit=*/true); + initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, + /*playOnInit=*/true); } protected void initPlayback(@NonNull final PlayQueue queue, @Player.RepeatMode final int repeatMode, final float playbackSpeed, final float playbackPitch, + final boolean playbackSkipSilence, final boolean playOnReady) { destroyPlayer(); initPlayer(playOnReady); setRepeatMode(repeatMode); - setPlaybackParameters(playbackSpeed, playbackPitch); + setPlaybackParameters(playbackSpeed, playbackPitch, playbackSkipSilence); playQueue = queue; playQueue.init(); @@ -1162,19 +1166,22 @@ public abstract class BasePlayer implements return getPlaybackParameters().pitch; } + public boolean getPlaybackSkipSilence() { + return getPlaybackParameters().skipSilence; + } + public void setPlaybackSpeed(float speed) { - setPlaybackParameters(speed, getPlaybackPitch()); + setPlaybackParameters(speed, getPlaybackPitch(), getPlaybackSkipSilence()); } public PlaybackParameters getPlaybackParameters() { - final PlaybackParameters defaultParameters = new PlaybackParameters(1f, 1f); - if (simpleExoPlayer == null) return defaultParameters; + if (simpleExoPlayer == null) return PlaybackParameters.DEFAULT; final PlaybackParameters parameters = simpleExoPlayer.getPlaybackParameters(); - return parameters == null ? defaultParameters : parameters; + return parameters == null ? PlaybackParameters.DEFAULT : parameters; } - public void setPlaybackParameters(float speed, float pitch) { - simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch)); + public void setPlaybackParameters(float speed, float pitch, boolean skipSilence) { + simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence)); } public PlayQueue getPlayQueue() { 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 df9f520e9..56c8f8855 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -181,7 +181,7 @@ public final class MainVideoPlayer extends AppCompatActivity playerImpl.setPlaybackQuality(playerState.getPlaybackQuality()); playerImpl.initPlayback(playerState.getPlayQueue(), playerState.getRepeatMode(), playerState.getPlaybackSpeed(), playerState.getPlaybackPitch(), - playerState.wasPlaying()); + playerState.isPlaybackSkipSilence(), playerState.wasPlaying()); } } @@ -210,7 +210,8 @@ public final class MainVideoPlayer extends AppCompatActivity playerImpl.setRecovery(); playerState = new PlayerState(playerImpl.getPlayQueue(), playerImpl.getRepeatMode(), playerImpl.getPlaybackSpeed(), playerImpl.getPlaybackPitch(), - playerImpl.getPlaybackQuality(), playerImpl.isPlaying()); + playerImpl.getPlaybackQuality(), playerImpl.getPlaybackSkipSilence(), + playerImpl.isPlaying()); StateSaver.tryToSave(isChangingConfigurations(), null, outState, this); } @@ -352,8 +353,11 @@ public final class MainVideoPlayer extends AppCompatActivity //////////////////////////////////////////////////////////////////////////// @Override - public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) { - if (playerImpl != null) playerImpl.setPlaybackParameters(playbackTempo, playbackPitch); + public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, + boolean playbackSkipSilence) { + if (playerImpl != null) { + playerImpl.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence); + } } /////////////////////////////////////////////////////////////////////////// @@ -533,6 +537,7 @@ public final class MainVideoPlayer extends AppCompatActivity this.getRepeatMode(), this.getPlaybackSpeed(), this.getPlaybackPitch(), + this.getPlaybackSkipSilence(), this.getPlaybackQuality() ); context.startService(intent); @@ -554,6 +559,7 @@ public final class MainVideoPlayer extends AppCompatActivity this.getRepeatMode(), this.getPlaybackSpeed(), this.getPlaybackPitch(), + this.getPlaybackSkipSilence(), this.getPlaybackQuality() ); context.startService(intent); @@ -649,7 +655,8 @@ public final class MainVideoPlayer extends AppCompatActivity @Override public void onPlaybackSpeedClicked() { - PlaybackParameterDialog.newInstance(getPlaybackSpeed(), getPlaybackPitch()) + PlaybackParameterDialog + .newInstance(getPlaybackSpeed(), getPlaybackPitch(), getPlaybackSkipSilence()) .show(getSupportFragmentManager(), TAG); } 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 3aa8d68f3..146175fb0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -459,6 +459,7 @@ public final class PopupVideoPlayer extends Service { this.getRepeatMode(), this.getPlaybackSpeed(), this.getPlaybackPitch(), + this.getPlaybackSkipSilence(), this.getPlaybackQuality() ); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 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 7674105e1..b57a710ed 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -187,6 +187,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); } @@ -466,13 +467,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); + } } //////////////////////////////////////////////////////////////////////////// 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..60e43ff7d 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,24 @@ 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; + // Maximum 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 DEFAULT_PLAYBACK_STEP_VALUE = 0.05f; public static final double DEFAULT_TEMPO = 1.00f; public static final double DEFAULT_PITCH = 1.00f; + 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"; 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 +49,7 @@ public class PlaybackParameterDialog extends DialogFragment { private double initialTempo = DEFAULT_TEMPO; private double initialPitch = DEFAULT_PITCH; + private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; @Nullable private SeekBar tempoSlider; @Nullable private TextView tempoMinimumText; @@ -65,16 +65,22 @@ 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.initialSkipSilence = playbackSkipSilence; return dialog; } @@ -123,7 +129,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 +144,13 @@ public class PlaybackParameterDialog extends DialogFragment { private void setupControlViews(@NonNull View rootView) { setupHookingControl(rootView); + setupSkipSilenceControl(rootView); + setupTempoControl(rootView); setupPitchControl(rootView); - setupPresetControl(rootView); + setupStepSize(DEFAULT_PLAYBACK_STEP_VALUE); + + setupStepSizeSelector(rootView); } private void setupTempoControl(@NonNull View rootView) { @@ -157,17 +169,17 @@ public class PlaybackParameterDialog extends DialogFragment { tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE)); if (tempoStepUpText != null) { - tempoStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE)); + tempoStepUpText.setText(getStepUpPercentString(DEFAULT_PLAYBACK_STEP_VALUE)); tempoStepUpText.setOnClickListener(view -> { - onTempoSliderUpdated(getCurrentTempo() + PLAYBACK_STEP_VALUE); + onTempoSliderUpdated(getCurrentTempo() + DEFAULT_PLAYBACK_STEP_VALUE); setCurrentPlaybackParameters(); }); } if (tempoStepDownText != null) { - tempoStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE)); + tempoStepDownText.setText(getStepDownPercentString(DEFAULT_PLAYBACK_STEP_VALUE)); tempoStepDownText.setOnClickListener(view -> { - onTempoSliderUpdated(getCurrentTempo() - PLAYBACK_STEP_VALUE); + onTempoSliderUpdated(getCurrentTempo() - DEFAULT_PLAYBACK_STEP_VALUE); setCurrentPlaybackParameters(); }); } @@ -194,22 +206,6 @@ public class PlaybackParameterDialog extends DialogFragment { 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)); @@ -231,24 +227,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) { + final double onePercent = 0.01f; + stepSizeOnePercentText.setText(getPercentString(onePercent)); + stepSizeOnePercentText.setOnClickListener(view -> setupStepSize(onePercent)); + } + + if (stepSizeFivePercentText != null) { + final double fivePercent = 0.05f; + stepSizeFivePercentText.setText(getPercentString(fivePercent)); + stepSizeFivePercentText.setOnClickListener(view -> setupStepSize(fivePercent)); + } + + if (stepSizeTenPercentText != null) { + final double tenPercent = 0.10f; + stepSizeTenPercentText.setText(getPercentString(tenPercent)); + stepSizeTenPercentText.setOnClickListener(view -> setupStepSize(tenPercent)); + } + + if (stepSizeTwentyFivePercentText != null) { + final double twentyFivePercent = 0.25f; + stepSizeTwentyFivePercentText.setText(getPercentString(twentyFivePercent)); + stepSizeTwentyFivePercentText.setOnClickListener(view -> + setupStepSize(twentyFivePercent)); + } + + if (stepSizeOneHundredPercentText != null) { + final double oneHundredPercent = 1.00f; + stepSizeOneHundredPercentText.setText(getPercentString(oneHundredPercent)); + stepSizeOneHundredPercentText.setOnClickListener(view -> + setupStepSize(oneHundredPercent)); + } + } + + private void setupStepSize(final double 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 +398,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,7 +410,7 @@ 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); } } @@ -367,13 +424,22 @@ public class PlaybackParameterDialog extends DialogFragment { pitchSlider.getProgress()); } + 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/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index ebbeb06f8..12f6856de 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -100,11 +100,13 @@ public class NavigationHelper { final int repeatMode, final float playbackSpeed, final float playbackPitch, + final boolean playbackSkipSilence, @Nullable final String playbackQuality) { return getPlayerIntent(context, targetClazz, playQueue, playbackQuality) .putExtra(BasePlayer.REPEAT_MODE, repeatMode) .putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed) - .putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch); + .putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch) + .putExtra(BasePlayer.PLAYBACK_SKIP_SILENCE, playbackSkipSilence); } public static void playOnMainPlayer(final Context context, final PlayQueue queue) { diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index a8c6a5dcd..47937e882 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -279,30 +279,88 @@ android:layout_centerHorizontal="true" android:layout_below="@id/separatorCheckbox"/> + + + android:layout_below="@id/skipSilenceCheckbox"> + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5ca88bd6f..8686d21d3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -479,9 +479,10 @@ Playback Speed Controls Tempo Pitch - Unhook (may cause distortion) - Nightcore - Default + Unlink (may cause distortion) + Fast-forward during silence + Step + Reset In order to comply with the European General Data Protection Regulation (GDPR), we herby draw your attention to NewPipe\'s privacy policy. Please read it carefully.\nYou must accept it to send us the bug report. From 3194a2bf2c769fb1fe9b4365ec3da33acc93e1ae Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Wed, 9 May 2018 11:19:03 -0700 Subject: [PATCH 03/22] -Fixed skip silence state not maintained by player on new queue. -Fixed TrackSelector deprecations. --- .../java/org/schabi/newpipe/player/BasePlayer.java | 3 ++- .../org/schabi/newpipe/player/PopupVideoPlayer.java | 3 ++- .../java/org/schabi/newpipe/player/VideoPlayer.java | 13 ++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) 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 525f0d258..11d8381dc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -236,7 +236,8 @@ public abstract class BasePlayer implements final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode()); final float playbackSpeed = intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()); final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch()); - final boolean playbackSkipSilence = intent.getBooleanExtra(PLAYBACK_SKIP_SILENCE, false); + final boolean playbackSkipSilence = intent.getBooleanExtra(PLAYBACK_SKIP_SILENCE, + getPlaybackSkipSilence()); // Good to go... initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, 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 146175fb0..8b0fc0ddc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -729,7 +729,8 @@ public final class PopupVideoPlayer extends Service { /*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); + trackSelector.setParameters(trackSelector.buildUponParameters() + .setRendererDisabled(videoRendererIndex, !enable)); } } 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..250d02b42 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -241,7 +241,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))); } } @@ -298,7 +299,8 @@ public abstract class VideoPlayer extends BasePlayer captionOffItem.setOnMenuItemClickListener(menuItem -> { final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT); if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) { - trackSelector.setRendererDisabled(textRendererIndex, true); + trackSelector.setParameters(trackSelector.buildUponParameters() + .setRendererDisabled(textRendererIndex, true)); } return true; }); @@ -312,7 +314,8 @@ public abstract class VideoPlayer extends BasePlayer final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT); if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) { trackSelector.setPreferredTextLanguage(captionLanguage); - trackSelector.setRendererDisabled(textRendererIndex, false); + trackSelector.setParameters(trackSelector.buildUponParameters() + .setRendererDisabled(textRendererIndex, false)); } return true; }); @@ -575,8 +578,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); From bc6fdf81d2405facccfb4e7f1230b664fd2586ad Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 10 May 2018 13:28:31 -0700 Subject: [PATCH 04/22] -Refactored player media source resolution into external helpers. -Baked resolved media metadata into media source for one-way data passing. --- .../newpipe/player/BackgroundPlayer.java | 36 ++-- .../org/schabi/newpipe/player/BasePlayer.java | 169 +++++++----------- .../newpipe/player/MainVideoPlayer.java | 29 +-- .../newpipe/player/PopupVideoPlayer.java | 35 ++-- .../schabi/newpipe/player/VideoPlayer.java | 110 ++---------- .../player/playback/MediaSourceManager.java | 28 +-- .../player/playback/PlaybackListener.java | 2 +- .../resolver/AudioPlaybackResolver.java | 39 ++++ .../player/resolver/MediaSourceTag.java | 51 ++++++ .../player/resolver/PlaybackResolver.java | 84 +++++++++ .../newpipe/player/resolver/Resolver.java | 7 + .../resolver/VideoPlaybackResolver.java | 122 +++++++++++++ 12 files changed, 436 insertions(+), 276 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/resolver/MediaSourceTag.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index f25c20bb2..b5135bd84 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -42,14 +42,12 @@ import com.google.android.exoplayer2.source.MediaSource; import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; -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.event.PlayerEventListener; import org.schabi.newpipe.player.helper.LockManager; -import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.playqueue.PlayQueueItem; -import org.schabi.newpipe.util.ListHelper; +import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; +import org.schabi.newpipe.player.resolver.MediaSourceTag; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ThemeHelper; @@ -279,10 +277,18 @@ public final class BackgroundPlayer extends Service { protected class BasePlayerImpl extends BasePlayer { + @Nullable private AudioPlaybackResolver resolver; + BasePlayerImpl(Context context) { super(context); } + @Override + public void initPlayer(boolean playOnReady) { + super.initPlayer(playOnReady); + resolver = new AudioPlaybackResolver(context, dataSource); + } + @Override public void handleIntent(final Intent intent) { super.handleIntent(intent); @@ -390,11 +396,9 @@ public final class BackgroundPlayer extends Service { // Playback Listener //////////////////////////////////////////////////////////////////////////*/ - protected void onMetadataChanged(@NonNull final PlayQueueItem item, - @Nullable final StreamInfo info, - final int newPlayQueueIndex, - final boolean hasPlayQueueItemChanged) { - if (shouldUpdateOnProgress || hasPlayQueueItemChanged) { + protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { + super.onMetadataChanged(tag); + if (shouldUpdateOnProgress) { resetNotification(); updateNotification(-1); updateMetadata(); @@ -404,15 +408,7 @@ public final class BackgroundPlayer extends Service { @Override @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { - final MediaSource liveSource = super.sourceOf(item, 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); - return buildMediaSource(audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio), - MediaFormat.getSuffixById(audio.getFormatId())); + return resolver == null ? null : resolver.resolve(info); } @Override @@ -439,8 +435,8 @@ public final class BackgroundPlayer extends Service { } private void updateMetadata() { - if (activityListener != null && currentInfo != null) { - activityListener.onMetadataUpdate(currentInfo); + if (activityListener != null && getCurrentMetadata() != null) { + activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata()); } } 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 11d8381dc..2cbef93e4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -25,15 +25,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.media.AudioManager; -import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Toast; -import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayerFactory; @@ -49,7 +46,6 @@ import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.util.Util; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; @@ -57,7 +53,6 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import org.schabi.newpipe.Downloader; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.LoadController; @@ -72,6 +67,7 @@ import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.player.resolver.MediaSourceTag; import org.schabi.newpipe.util.SerializedCache; import java.io.IOException; @@ -130,12 +126,12 @@ public abstract class BasePlayer implements protected PlayQueue playQueue; protected PlayQueueAdapter playQueueAdapter; - protected MediaSourceManager playbackManager; + @Nullable protected MediaSourceManager playbackManager; - protected StreamInfo currentInfo; - protected PlayQueueItem currentItem; + @Nullable private PlayQueueItem currentItem; + @Nullable private MediaSourceTag currentMetadata; - protected Toast errorToast; + @Nullable protected Toast errorToast; /*////////////////////////////////////////////////////////////////////////// // Player @@ -329,58 +325,6 @@ public abstract class BasePlayer implements if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: " + "imageUri = [" + imageUri + "], view = [" + view + "]"); } - /*////////////////////////////////////////////////////////////////////////// - // MediaSource Building - //////////////////////////////////////////////////////////////////////////*/ - - public MediaSource buildLiveMediaSource(@NonNull final String sourceUrl, - @C.ContentType final int type) { - if (DEBUG) { - Log.d(TAG, "buildLiveMediaSource() called with: url = [" + sourceUrl + - "], content type = [" + type + "]"); - } - if (dataSource == null) return null; - - final Uri uri = Uri.parse(sourceUrl); - switch (type) { - case C.TYPE_SS: - return dataSource.getLiveSsMediaSourceFactory().createMediaSource(uri); - case C.TYPE_DASH: - return dataSource.getLiveDashMediaSourceFactory().createMediaSource(uri); - case C.TYPE_HLS: - return dataSource.getLiveHlsMediaSourceFactory().createMediaSource(uri); - default: - throw new IllegalStateException("Unsupported type: " + type); - } - } - - public MediaSource buildMediaSource(@NonNull final String sourceUrl, - @NonNull final String cacheKey, - @NonNull final String overrideExtension) { - if (DEBUG) { - Log.d(TAG, "buildMediaSource() called with: url = [" + sourceUrl + - "], cacheKey = [" + cacheKey + "]" + - "], overrideExtension = [" + overrideExtension + "]"); - } - if (dataSource == null) return null; - - 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().createMediaSource(uri); - case C.TYPE_DASH: - return dataSource.getDashMediaSourceFactory().createMediaSource(uri); - case C.TYPE_HLS: - return dataSource.getHlsMediaSourceFactory().createMediaSource(uri); - case C.TYPE_OTHER: - return dataSource.getExtractorMediaSourceFactory(cacheKey).createMediaSource(uri); - default: - throw new IllegalStateException("Unsupported type: " + type); - } - } /*////////////////////////////////////////////////////////////////////////// // Broadcast Receiver @@ -614,6 +558,7 @@ public abstract class BasePlayer implements } break; case Player.STATE_READY: //3 + maybeUpdateCurrentMetadata(); maybeCorrectSeekPosition(); if (!isPrepared) { isPrepared = true; @@ -630,10 +575,12 @@ public abstract class BasePlayer implements } private void maybeCorrectSeekPosition() { - if (playQueue == null || simpleExoPlayer == null || currentInfo == null) return; + if (playQueue == null || simpleExoPlayer == null || currentMetadata == null) return; final int currentSourceIndex = playQueue.getIndex(); final PlayQueueItem currentSourceItem = playQueue.getItem(); + final StreamInfo currentInfo = currentMetadata.getMetadata(); + if (currentSourceItem == null) return; final long recoveryPositionMillis = currentSourceItem.getRecoveryPosition(); @@ -649,16 +596,15 @@ public abstract class BasePlayer implements playQueue.unsetRecovery(currentSourceIndex); } else if (isSynchronizing && isLive()) { - if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time"); // Is still synchronizing? + if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time"); seekToDefault(); } else if (isSynchronizing && presetStartPositionMillis > 0L) { + // Has another start position? if (DEBUG) Log.d(TAG, "Playback - Seeking to preset start " + "position=[" + presetStartPositionMillis + "]"); - // Has another start position? seekTo(presetStartPositionMillis); - currentInfo.setStartPosition(0); } isSynchronizing = false; @@ -732,6 +678,9 @@ public abstract class BasePlayer implements public void onPositionDiscontinuity(@Player.DiscontinuityReason final int reason) { if (DEBUG) Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with " + "reason = [" + reason + "]"); + + maybeUpdateCurrentMetadata(); + // Refresh the playback if there is a transition to the next video final int newPeriodIndex = simpleExoPlayer.getCurrentPeriodIndex(); @@ -793,7 +742,7 @@ public abstract class BasePlayer implements if (DEBUG) Log.d(TAG, "Playback - onPlaybackBlock() called"); currentItem = null; - currentInfo = null; + currentMetadata = null; simpleExoPlayer.stop(); isPrepared = false; @@ -810,42 +759,21 @@ public abstract class BasePlayer implements simpleExoPlayer.prepare(mediaSource); } - @Override - public void onPlaybackSynchronize(@NonNull final PlayQueueItem item, - @Nullable final StreamInfo info) { + public void onPlaybackSynchronize(@NonNull final PlayQueueItem item) { if (DEBUG) Log.d(TAG, "Playback - onPlaybackSynchronize() called with " + - (info != null ? "available" : "null") + " info, " + "item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]"); if (simpleExoPlayer == null || playQueue == null) return; final boolean onPlaybackInitial = currentItem == null; final boolean hasPlayQueueItemChanged = currentItem != item; - final boolean hasStreamInfoChanged = currentInfo != info; final int currentPlayQueueIndex = playQueue.indexOf(item); final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex(); final int currentPlaylistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount(); - // when starting playback on the last item when not repeating, maybe auto queue - if (info != null && currentPlayQueueIndex == playQueue.size() - 1 && - getRepeatMode() == Player.REPEAT_MODE_OFF && - PlayerHelper.isAutoQueueEnabled(context)) { - final PlayQueue autoQueue = PlayerHelper.autoQueueOf(info, playQueue.getStreams()); - if (autoQueue != null) playQueue.append(autoQueue.getStreams()); - } // If nothing to synchronize - if (!hasPlayQueueItemChanged && !hasStreamInfoChanged) { - return; - } - + if (!hasPlayQueueItemChanged) return; currentItem = item; - currentInfo = info; - if (hasPlayQueueItemChanged) { - // updates only to the stream info should not trigger another view count - registerView(); - initThumbnail(info == null ? item.getThumbnailUrl() : info.getThumbnailUrl()); - } - onMetadataChanged(item, info, currentPlayQueueIndex, hasPlayQueueItemChanged); // Check if on wrong window if (currentPlayQueueIndex != playQueue.getIndex()) { @@ -873,26 +801,21 @@ public abstract class BasePlayer implements } } - abstract protected void onMetadataChanged(@NonNull final PlayQueueItem item, - @Nullable final StreamInfo info, - final int newPlayQueueIndex, - final boolean hasPlayQueueItemChanged); + protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { + Log.d(TAG, "Playback - onMetadataChanged() called, " + + "playing: " + tag.getMetadata().getName()); + final StreamInfo info = tag.getMetadata(); - @Nullable - @Override - public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) { - final StreamType streamType = info.getStreamType(); - if (!(streamType == StreamType.AUDIO_LIVE_STREAM || streamType == StreamType.LIVE_STREAM)) { - return null; + initThumbnail(info.getThumbnailUrl()); + registerView(); + + // when starting playback on the last item when not repeating, maybe auto queue + if (playQueue.getIndex() == playQueue.size() - 1 && + getRepeatMode() == Player.REPEAT_MODE_OFF && + PlayerHelper.isAutoQueueEnabled(context)) { + final PlayQueue autoQueue = PlayerHelper.autoQueueOf(info, playQueue.getStreams()); + if (autoQueue != null) playQueue.append(autoQueue.getStreams()); } - - if (!info.getHlsUrl().isEmpty()) { - return buildLiveMediaSource(info.getHlsUrl(), C.TYPE_HLS); - } else if (!info.getDashMpdUrl().isEmpty()) { - return buildLiveMediaSource(info.getDashMpdUrl(), C.TYPE_DASH); - } - - return null; } @Override @@ -1051,7 +974,8 @@ public abstract class BasePlayer implements //////////////////////////////////////////////////////////////////////////*/ private void registerView() { - if (databaseUpdateReactor == null || currentInfo == null) return; + if (databaseUpdateReactor == null || currentMetadata == null) return; + final StreamInfo currentInfo = currentMetadata.getMetadata(); databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete() .subscribe( ignored -> {/* successful */}, @@ -1082,7 +1006,8 @@ public abstract class BasePlayer implements } private void savePlaybackState() { - if (simpleExoPlayer == null || currentInfo == null) return; + if (simpleExoPlayer == null || currentMetadata == null) return; + final StreamInfo currentInfo = currentMetadata.getMetadata(); if (simpleExoPlayer.getCurrentPosition() > RECOVERY_SKIP_THRESHOLD_MILLIS && simpleExoPlayer.getCurrentPosition() < @@ -1090,6 +1015,23 @@ public abstract class BasePlayer implements savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition()); } } + + private void maybeUpdateCurrentMetadata() { + if (simpleExoPlayer == null) return; + + final MediaSourceTag metadata; + try { + metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag(); + } catch (IndexOutOfBoundsException | ClassCastException error) { + return; + } + + if (metadata == null || currentMetadata == metadata) return; + + currentMetadata = metadata; + onMetadataChanged(metadata); + } + /*////////////////////////////////////////////////////////////////////////// // Getters and Setters //////////////////////////////////////////////////////////////////////////*/ @@ -1106,19 +1048,28 @@ public abstract class BasePlayer implements return currentState; } + @Nullable + public MediaSourceTag getCurrentMetadata() { + return currentMetadata; + } + + @NonNull public String getVideoUrl() { return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUrl(); } + @NonNull public String getVideoTitle() { return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getTitle(); } + @NonNull public String getUploaderName() { return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader(); } /** Checks if the current playback is a livestream AND is playing at or beyond the live edge */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean isLiveEdge() { if (simpleExoPlayer == null || !isLive()) return false; 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 56c8f8855..a66906c73 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -58,7 +58,6 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.SubtitleView; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.player.helper.PlaybackParameterDialog; @@ -67,6 +66,8 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder; import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder; import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback; +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 org.schabi.newpipe.util.NavigationHelper; @@ -497,11 +498,8 @@ public final class MainVideoPlayer extends AppCompatActivity // 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(@Nullable final MediaSourceTag tag) { + super.onMetadataChanged(tag); titleTextView.setText(getVideoTitle()); channelTextView.setText(getUploaderName()); @@ -686,14 +684,19 @@ public final class MainVideoPlayer extends AppCompatActivity } @Override - protected int getDefaultResolutionIndex(final List sortedVideos) { - return ListHelper.getDefaultResolutionIndex(context, sortedVideos); - } + protected VideoPlaybackResolver.QualityResolver getQualityResolver() { + return new VideoPlaybackResolver.QualityResolver() { + @Override + public int getDefaultResolutionIndex(List sortedVideos) { + return ListHelper.getDefaultResolutionIndex(context, sortedVideos); + } - @Override - protected int getOverrideResolutionIndex(final List sortedVideos, - final String playbackQuality) { - return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality); + @Override + public int getOverrideResolutionIndex(List sortedVideos, + String playbackQuality) { + return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality); + } + }; } /*////////////////////////////////////////////////////////////////////////// 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 8b0fc0ddc..476ad73ef 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -59,13 +59,13 @@ import com.google.android.exoplayer2.ui.SubtitleView; import org.schabi.newpipe.BuildConfig; 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; @@ -511,14 +511,20 @@ 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 - protected int getOverrideResolutionIndex(final List sortedVideos, - final String playbackQuality) { - return ListHelper.getPopupResolutionIndex(context, sortedVideos, playbackQuality); + @Override + public int getOverrideResolutionIndex(List sortedVideos, + String playbackQuality) { + return ListHelper.getPopupResolutionIndex(context, sortedVideos, + playbackQuality); + } + }; } /*////////////////////////////////////////////////////////////////////////// @@ -539,8 +545,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()); } } @@ -586,11 +592,8 @@ 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(@Nullable final MediaSourceTag tag) { + super.onMetadataChanged(tag); updateMetadata(); } 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 250d02b42..eaec59f65 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; + @Nullable private VideoPlaybackResolver resolver; /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ @@ -244,6 +236,8 @@ public abstract class VideoPlayer extends BasePlayer trackSelector.setParameters(trackSelector.buildUponParameters() .setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context))); } + + resolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); } @Override @@ -326,23 +320,18 @@ public abstract class VideoPlayer extends BasePlayer // Playback Listener //////////////////////////////////////////////////////////////////////////*/ - protected abstract int getDefaultResolutionIndex(final List sortedVideos); + protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver(); - protected abstract int getOverrideResolutionIndex(final List sortedVideos, final String playbackQuality); + protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { + super.onMetadataChanged(tag); - 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 (tag.getMetadata().getStreamType()) { case AUDIO_STREAM: surfaceView.setVisibility(View.GONE); playbackEndTime.setVisibility(View.VISIBLE); @@ -359,20 +348,14 @@ public abstract class VideoPlayer extends BasePlayer break; case VIDEO_STREAM: + final StreamInfo info = tag.getMetadata(); 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()); - } - + availableStreams = tag.getSortedAvailableVideoStreams(); + selectedStreamIndex = tag.getSelectedVideoStreamIndex(); buildQualityMenu(); - qualityTextView.setVisibility(View.VISIBLE); + qualityTextView.setVisibility(View.VISIBLE); surfaceView.setVisibility(View.VISIBLE); default: playbackEndTime.setVisibility(View.VISIBLE); @@ -386,65 +369,7 @@ public abstract class VideoPlayer extends BasePlayer @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 == null ? null : resolver.resolve(info); } /*////////////////////////////////////////////////////////////////////////// @@ -908,11 +833,12 @@ public abstract class VideoPlayer extends BasePlayer //////////////////////////////////////////////////////////////////////////*/ public void setPlaybackQuality(final String quality) { - this.playbackQuality = quality; + if (resolver != null) resolver.setPlaybackQuality(quality); } + @Nullable public String getPlaybackQuality() { - return playbackQuality; + return resolver == null ? null : resolver.getPlaybackQuality(); } public AspectRatioFrameLayout getAspectRatioFrameLayout() { 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 67a8debef..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,7 +163,6 @@ public class MediaSourceManager { playQueueReactor.cancel(); loaderReactor.dispose(); - syncReactor.dispose(); } /*////////////////////////////////////////////////////////////////////////// @@ -310,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() { @@ -423,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. *

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/resolver/AudioPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java new file mode 100644 index 000000000..f95f0e3bb --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java @@ -0,0 +1,39 @@ +package org.schabi.newpipe.player.resolver; + +import android.content.Context; +import android.support.annotation.NonNull; + +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 + 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..e4f81385a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java @@ -0,0 +1,7 @@ +package org.schabi.newpipe.player.resolver; + +import android.support.annotation.NonNull; + +public interface Resolver { + Produce resolve(@NonNull Source source); +} diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java new file mode 100644 index 000000000..5aa42ce3c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java @@ -0,0 +1,122 @@ +package org.schabi.newpipe.player.resolver; + +import android.content.Context; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.MergingMediaSource; + +import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.Subtitles; +import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.VideoStream; +import org.schabi.newpipe.player.helper.PlayerDataSource; +import org.schabi.newpipe.player.helper.PlayerHelper; +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; + +public class VideoPlaybackResolver implements PlaybackResolver { + + public interface QualityResolver { + int getDefaultResolutionIndex(final List sortedVideos); + int getOverrideResolutionIndex(final List sortedVideos, + final String playbackQuality); + } + + @NonNull private final Context context; + @NonNull private final PlayerDataSource dataSource; + @NonNull private final QualityResolver qualityResolver; + + @Nullable private String playbackQuality; + + public VideoPlaybackResolver(@NonNull final Context context, + @NonNull final PlayerDataSource dataSource, + @NonNull final QualityResolver qualityResolver) { + this.context = context; + this.dataSource = dataSource; + this.qualityResolver = qualityResolver; + } + + @Override + public MediaSource resolve(@NonNull StreamInfo info) { + final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, 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 = qualityResolver.getDefaultResolutionIndex(videos); + } else { + index = qualityResolver.getOverrideResolutionIndex(videos, getPlaybackQuality()); + } + final MediaSourceTag tag = new MediaSourceTag(info, videos, index); + @Nullable final VideoStream video = tag.getSelectedVideoStream(); + + if (video != null) { + final MediaSource streamSource = buildMediaSource(dataSource, video.getUrl(), + PlayerHelper.cacheKeyOf(info, video), + MediaFormat.getSuffixById(video.getFormatId()), tag); + 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(dataSource, audio.getUrl(), + PlayerHelper.cacheKeyOf(info, audio), + MediaFormat.getSuffixById(audio.getFormatId()), tag); + 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()])); + } + } + + @Nullable + public String getPlaybackQuality() { + return playbackQuality; + } + + public void setPlaybackQuality(@Nullable String playbackQuality) { + this.playbackQuality = playbackQuality; + } +} From 5c202f04e7babd401584149bc4e92c124e484867 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 10 May 2018 17:48:05 -0700 Subject: [PATCH 05/22] -[#1383]Fixed popup player caption selector not populating due to full width aspect ratio selector. -Fixed potential memory leak in media session connector containing player instance. --- .../org/schabi/newpipe/player/BasePlayer.java | 22 ++++--------------- .../player/helper/MediaSessionManager.java | 5 ++++- app/src/main/res/layout/player_popup.xml | 2 +- 3 files changed, 9 insertions(+), 20 deletions(-) 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 2cbef93e4..a0607621e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -285,9 +285,11 @@ public abstract class BasePlayer implements destroyPlayer(); unregisterBroadcastReceiver(); + if (mediaSessionManager != null) mediaSessionManager.dispose(); + trackSelector = null; - simpleExoPlayer = null; mediaSessionManager = null; + simpleExoPlayer = null; } /*////////////////////////////////////////////////////////////////////////// @@ -494,22 +496,6 @@ public abstract class BasePlayer implements (manifest == null ? "no manifest" : "available manifest") + ", " + "timeline size = [" + timeline.getWindowCount() + "], " + "reason = [" + reason + "]"); - if (playQueue == null) return; - - switch (reason) { - case Player.TIMELINE_CHANGE_REASON_RESET: // called after #block - case Player.TIMELINE_CHANGE_REASON_PREPARED: // called after #unblock - case Player.TIMELINE_CHANGE_REASON_DYNAMIC: // called after playlist changes - // Ensures MediaSourceManager#update is complete - final boolean isPlaylistStable = timeline.getWindowCount() == playQueue.size(); - // Ensure dynamic/livestream timeline changes does not cause negative position - if (isPlaylistStable && !isCurrentWindowValid() && !isSynchronizing) { - if (DEBUG) Log.d(TAG, "Playback - negative time position reached, " + - "clamping to default position."); - seekToDefault(); - } - break; - } } @Override @@ -598,7 +584,7 @@ public abstract class BasePlayer implements } else if (isSynchronizing && isLive()) { // Is still synchronizing? if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time"); - seekToDefault(); + //seekToDefault(); } else if (isSynchronizing && presetStartPositionMillis > 0L) { // Has another start position? 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/res/layout/player_popup.xml b/app/src/main/res/layout/player_popup.xml index f866cf002..001d43bf6 100644 --- a/app/src/main/res/layout/player_popup.xml +++ b/app/src/main/res/layout/player_popup.xml @@ -111,7 +111,7 @@ Date: Fri, 11 May 2018 17:20:36 -0700 Subject: [PATCH 06/22] -Fixed bookmarked playlist not updating metadata when changed. --- .../newpipe/fragments/list/playlist/PlaylistFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index ca732aa2c..161bca694 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -289,6 +289,7 @@ public class PlaylistFragment extends BaseListInfoFragment { remotePlaylistManager.getPlaylist(result) .flatMap(lists -> getUpdateProcessor(lists, result), (lists, id) -> lists) .onBackpressureLatest() + .flatMap(lists -> getUpdateProcessor(lists, result), (lists, id) -> lists) .observeOn(AndroidSchedulers.mainThread()) .subscribe(getPlaylistBookmarkSubscriber()); @@ -354,7 +355,6 @@ public class PlaylistFragment extends BaseListInfoFragment { final PlaylistRemoteEntity playlistEntity = playlists.get(0); if (playlistEntity.isIdenticalTo(result)) return noItemToUpdate; - return remotePlaylistManager.onUpdate(playlists.get(0).getUid(), result).toFlowable(); } From 19b8796cbcf827c86d69250e368cb2255869a51c Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Fri, 11 May 2018 19:21:40 -0700 Subject: [PATCH 07/22] -Fixed statistics fragment button not animating when pressed. -Removed background player notification button opacity change. --- .../newpipe/player/BackgroundPlayer.java | 27 ------------------- .../res/layout/statistic_playlist_control.xml | 5 ++-- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index b5135bd84..37b7e4c64 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -26,9 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; -import android.os.Build; import android.os.IBinder; -import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; @@ -92,7 +90,6 @@ public final class BackgroundPlayer extends Service { private NotificationCompat.Builder notBuilder; private RemoteViews notRemoteView; private RemoteViews bigNotRemoteView; - private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha"; private boolean shouldUpdateOnProgress; @@ -246,16 +243,6 @@ public final class BackgroundPlayer extends Service { } notificationManager.notify(NOTIFICATION_ID, notBuilder.build()); } - - private void setControlsOpacity(@IntRange(from = 0, to = 255) int opacity) { - if (notRemoteView != null) notRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity); - if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity); - if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity); - if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity); - if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFRewind, setAlphaMethodName, opacity); - if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFRewind, setAlphaMethodName, opacity); - } - /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -525,32 +512,20 @@ public final class BackgroundPlayer extends Service { public void changeState(int state) { super.changeState(state); updatePlayback(); - } - - @Override - public void onBlocked() { - super.onBlocked(); - - setControlsOpacity(77); updateNotification(-1); } @Override public void onPlaying() { super.onPlaying(); - - setControlsOpacity(255); updateNotification(R.drawable.ic_pause_white); - lockManager.acquireWifiAndCpu(); } @Override public void onPaused() { super.onPaused(); - updateNotification(R.drawable.ic_play_arrow_white); - lockManager.releaseWifiAndCpu(); } @@ -558,8 +533,6 @@ public final class BackgroundPlayer extends Service { public void onCompleted() { super.onCompleted(); - setControlsOpacity(255); - resetNotification(); if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false); if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false); diff --git a/app/src/main/res/layout/statistic_playlist_control.xml b/app/src/main/res/layout/statistic_playlist_control.xml index 8dc4e8c08..ff2b70524 100644 --- a/app/src/main/res/layout/statistic_playlist_control.xml +++ b/app/src/main/res/layout/statistic_playlist_control.xml @@ -4,7 +4,6 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/selectableItemBackground" android:orientation="vertical"> + android:focusable="true" + android:background="?attr/selectableItemBackground"> + Date: Fri, 11 May 2018 19:42:39 -0700 Subject: [PATCH 08/22] -Reduced fling speed required to close popup by 40%. --- .../java/org/schabi/newpipe/player/helper/PlayerHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d10b99aec..275f488e3 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 @@ -252,7 +252,7 @@ public class PlayerHelper { } public static int getShutdownFlingVelocity(@NonNull final Context context) { - return 10000; + return 6000; } public static int getTossFlingVelocity(@NonNull final Context context) { From 13587d7ab373253eae3a3f8590d9d62057e5d634 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Fri, 11 May 2018 20:09:02 -0700 Subject: [PATCH 09/22] -Fixed some typos. --- .../schabi/newpipe/player/helper/PlaybackParameterDialog.java | 2 +- .../java/org/schabi/newpipe/player/resolver/Resolver.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 60e43ff7d..c53680835 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,7 +21,7 @@ import static org.schabi.newpipe.player.BasePlayer.DEBUG; public class PlaybackParameterDialog extends DialogFragment { @NonNull private static final String TAG = "PlaybackParameterDialog"; - // Maximum allowable range in ExoPlayer + // Minimum allowable range in ExoPlayer public static final double MINIMUM_PLAYBACK_VALUE = 0.10f; public static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; 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 index e4f81385a..a7d4448e4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java @@ -2,6 +2,6 @@ package org.schabi.newpipe.player.resolver; import android.support.annotation.NonNull; -public interface Resolver { - Produce resolve(@NonNull Source source); +public interface Resolver { + Product resolve(@NonNull Source source); } From 0a2dbc468809f779edf9190bdcf18d8d4be4c9d9 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Wed, 16 May 2018 18:08:19 -0700 Subject: [PATCH 10/22] -Fixed playlist fragment infinite update cycle. -Updated Room DB version to 1.1.0. --- .../newpipe/fragments/list/playlist/PlaylistFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 161bca694..ca732aa2c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -289,7 +289,6 @@ public class PlaylistFragment extends BaseListInfoFragment { remotePlaylistManager.getPlaylist(result) .flatMap(lists -> getUpdateProcessor(lists, result), (lists, id) -> lists) .onBackpressureLatest() - .flatMap(lists -> getUpdateProcessor(lists, result), (lists, id) -> lists) .observeOn(AndroidSchedulers.mainThread()) .subscribe(getPlaylistBookmarkSubscriber()); @@ -355,6 +354,7 @@ public class PlaylistFragment extends BaseListInfoFragment { final PlaylistRemoteEntity playlistEntity = playlists.get(0); if (playlistEntity.isIdenticalTo(result)) return noItemToUpdate; + return remotePlaylistManager.onUpdate(playlists.get(0).getUid(), result).toFlowable(); } From f1f5996975cb3a5315c98dbeaa5e108105a2a4b8 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Wed, 16 May 2018 18:18:32 -0700 Subject: [PATCH 11/22] -Refactored playback resolvers and other persistent player objects to instantiate once only during player creation to enforce non-nullity. -Fixed background and popup player service staying in foreground when playback is paused or completed. -Fixed player metadata not updating on new stream. -Fixed player intent playback quality not applied. -Fixed player auto-queue not applied after stream transition or swapping. --- .../newpipe/player/BackgroundPlayer.java | 101 +++++++++++++---- .../org/schabi/newpipe/player/BasePlayer.java | 103 ++++++++++-------- .../newpipe/player/MainVideoPlayer.java | 8 +- .../newpipe/player/PopupVideoPlayer.java | 85 +++++++++++---- .../schabi/newpipe/player/VideoPlayer.java | 18 ++- .../resolver/AudioPlaybackResolver.java | 2 + .../newpipe/player/resolver/Resolver.java | 3 +- .../resolver/VideoPlaybackResolver.java | 1 + 8 files changed, 219 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 37b7e4c64..c5c9f0615 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -37,6 +37,7 @@ import android.widget.RemoteViews; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.source.MediaSource; +import com.nostra13.universalimageloader.core.assist.FailReason; import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; @@ -264,16 +265,16 @@ public final class BackgroundPlayer extends Service { protected class BasePlayerImpl extends BasePlayer { - @Nullable private AudioPlaybackResolver resolver; + @NonNull final private AudioPlaybackResolver resolver; BasePlayerImpl(Context context) { super(context); + this.resolver = new AudioPlaybackResolver(context, dataSource); } @Override public void initPlayer(boolean playOnReady) { super.initPlayer(playOnReady); - resolver = new AudioPlaybackResolver(context, dataSource); } @Override @@ -286,30 +287,65 @@ public final class BackgroundPlayer extends Service { startForeground(NOTIFICATION_ID, notBuilder.build()); } - @Override - public void initThumbnail(final String url) { + /*////////////////////////////////////////////////////////////////////////// + // Thumbnail Loading + //////////////////////////////////////////////////////////////////////////*/ + + private void setDummyRemoteViewThumbnail() { resetNotification(); - if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail); - if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail); + if (notRemoteView != null) { + notRemoteView.setImageViewResource(R.id.notificationCover, + R.drawable.dummy_thumbnail); + } + if (bigNotRemoteView != null) { + bigNotRemoteView.setImageViewResource(R.id.notificationCover, + R.drawable.dummy_thumbnail); + } updateNotification(-1); - super.initThumbnail(url); + } + + @Override + public void onLoadingStarted(String imageUri, View view) { + super.onLoadingStarted(imageUri, view); + setDummyRemoteViewThumbnail(); } @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 - resetNotification(); - - if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage); - if (bigNotRemoteView != null) bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage); - - updateNotification(-1); + if (loadedImage == null) { + setDummyRemoteViewThumbnail(); + return; } + + // rebuild notification here since remote view does not release bitmaps, + // causing memory leaks + resetNotification(); + if (notRemoteView != null) { + notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage); + } + if (bigNotRemoteView != null) { + bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage); + } + updateNotification(-1); } + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + super.onLoadingFailed(imageUri, view, failReason); + setDummyRemoteViewThumbnail(); + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + super.onLoadingCancelled(imageUri, view); + setDummyRemoteViewThumbnail(); + } + + /*////////////////////////////////////////////////////////////////////////// + // States Implementation + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onPrepared(boolean playWhenReady) { super.onPrepared(playWhenReady); @@ -385,17 +421,15 @@ public final class BackgroundPlayer extends Service { protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { super.onMetadataChanged(tag); - if (shouldUpdateOnProgress) { - resetNotification(); - updateNotification(-1); - updateMetadata(); - } + resetNotification(); + updateNotification(-1); + updateMetadata(); } @Override @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { - return resolver == null ? null : resolver.resolve(info); + return resolver.resolve(info); } @Override @@ -515,18 +549,40 @@ public final class BackgroundPlayer extends Service { updateNotification(-1); } + @Override + public void onBlocked() { + super.onBlocked(); + startForeground(NOTIFICATION_ID, notBuilder.build()); + } + + @Override + public void onBuffering() { + super.onBuffering(); + startForeground(NOTIFICATION_ID, notBuilder.build()); + } + + @Override + public void onPausedSeek() { + super.onPausedSeek(); + startForeground(NOTIFICATION_ID, notBuilder.build()); + } + @Override public void onPlaying() { super.onPlaying(); updateNotification(R.drawable.ic_pause_white); + lockManager.acquireWifiAndCpu(); + startForeground(NOTIFICATION_ID, notBuilder.build()); } @Override public void onPaused() { super.onPaused(); updateNotification(R.drawable.ic_play_arrow_white); + lockManager.releaseWifiAndCpu(); + stopForeground(false); } @Override @@ -539,6 +595,7 @@ public final class BackgroundPlayer extends Service { updateNotification(R.drawable.ic_replay_white); lockManager.releaseWifiAndCpu(); + stopForeground(false); } } } 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 a0607621e..3f50cf149 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.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.resolver.MediaSourceTag; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.SerializedCache; import java.io.IOException; @@ -78,6 +79,7 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.SerialDisposable; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION; @@ -104,6 +106,14 @@ public abstract class BasePlayer implements @NonNull final protected HistoryRecordManager recordManager; + @NonNull final protected CustomTrackSelector trackSelector; + @NonNull final protected PlayerDataSource dataSource; + + @NonNull final private LoadControl loadControl; + @NonNull final private RenderersFactory renderFactory; + + @NonNull final private SerialDisposable progressUpdateReactor; + @NonNull final private CompositeDisposable databaseUpdateReactor; /*////////////////////////////////////////////////////////////////////////// // Intent //////////////////////////////////////////////////////////////////////////*/ @@ -142,9 +152,6 @@ public abstract class BasePlayer implements protected final static int PROGRESS_LOOP_INTERVAL_MILLIS = 500; protected final static int RECOVERY_SKIP_THRESHOLD_MILLIS = 3000; // 3 seconds - protected CustomTrackSelector trackSelector; - protected PlayerDataSource dataSource; - protected SimpleExoPlayer simpleExoPlayer; protected AudioReactor audioReactor; protected MediaSessionManager mediaSessionManager; @@ -152,9 +159,6 @@ public abstract class BasePlayer implements private boolean isPrepared = false; private boolean isSynchronizing = false; - protected Disposable progressUpdateReactor; - protected CompositeDisposable databaseUpdateReactor; - //////////////////////////////////////////////////////////////////////////*/ public BasePlayer(@NonNull final Context context) { @@ -171,29 +175,32 @@ public abstract class BasePlayer implements context.registerReceiver(broadcastReceiver, intentFilter); this.recordManager = new HistoryRecordManager(context); + + this.progressUpdateReactor = new SerialDisposable(); + this.databaseUpdateReactor = new CompositeDisposable(); + + final String userAgent = Downloader.USER_AGENT; + final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); + this.dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter); + + final TrackSelection.Factory trackSelectionFactory = + PlayerHelper.getQualitySelector(context, bandwidthMeter); + this.trackSelector = new CustomTrackSelector(trackSelectionFactory); + + this.loadControl = new LoadController(context); + this.renderFactory = new DefaultRenderersFactory(context); } public void setup() { - if (simpleExoPlayer == null) initPlayer(/*playOnInit=*/true); + if (simpleExoPlayer == null) { + initPlayer(/*playOnInit=*/true); + } initListeners(); } public void initPlayer(final boolean playOnReady) { if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]"); - if (databaseUpdateReactor != null) databaseUpdateReactor.dispose(); - databaseUpdateReactor = new CompositeDisposable(); - - final String userAgent = Downloader.USER_AGENT; - final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); - dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter); - - final TrackSelection.Factory trackSelectionFactory = - PlayerHelper.getQualitySelector(context, bandwidthMeter); - trackSelector = new CustomTrackSelector(trackSelectionFactory); - - final LoadControl loadControl = new LoadController(context); - final RenderersFactory renderFactory = new DefaultRenderersFactory(context); simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, trackSelector, loadControl); simpleExoPlayer.addListener(this); simpleExoPlayer.setPlayWhenReady(playOnReady); @@ -287,7 +294,6 @@ public abstract class BasePlayer implements if (mediaSessionManager != null) mediaSessionManager.dispose(); - trackSelector = null; mediaSessionManager = null; simpleExoPlayer = null; } @@ -296,11 +302,12 @@ public abstract class BasePlayer implements // Thumbnail Loading //////////////////////////////////////////////////////////////////////////*/ - public void initThumbnail(final String url) { + private void initThumbnail(final String url) { if (DEBUG) Log.d(TAG, "Thumbnail - initThumbnail() called"); if (url == null || url.isEmpty()) return; ImageLoader.getInstance().resume(); - ImageLoader.getInstance().loadImage(url, this); + ImageLoader.getInstance().loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, + this); } @Override @@ -461,13 +468,11 @@ public abstract class BasePlayer implements public abstract void onUpdateProgress(int currentProgress, int duration, int bufferPercent); protected void startProgressLoop() { - if (progressUpdateReactor != null) progressUpdateReactor.dispose(); - progressUpdateReactor = getProgressReactor(); + progressUpdateReactor.set(getProgressReactor()); } protected void stopProgressLoop() { - if (progressUpdateReactor != null) progressUpdateReactor.dispose(); - progressUpdateReactor = null; + progressUpdateReactor.set(null); } public void triggerProgressUpdate() { @@ -482,7 +487,8 @@ public abstract class BasePlayer implements private Disposable getProgressReactor() { return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ignored -> triggerProgressUpdate()); + .subscribe(ignored -> triggerProgressUpdate(), + error -> Log.e(TAG, "Progress update failure: ", error)); } /*////////////////////////////////////////////////////////////////////////// @@ -496,12 +502,16 @@ public abstract class BasePlayer implements (manifest == null ? "no manifest" : "available manifest") + ", " + "timeline size = [" + timeline.getWindowCount() + "], " + "reason = [" + reason + "]"); + + maybeUpdateCurrentMetadata(); } @Override public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { if (DEBUG) Log.d(TAG, "ExoPlayer - onTracksChanged(), " + "track group size = " + trackGroups.length); + + maybeUpdateCurrentMetadata(); } @Override @@ -521,6 +531,8 @@ public abstract class BasePlayer implements } else if (isLoading && !isProgressLoopRunning()) { startProgressLoop(); } + + maybeUpdateCurrentMetadata(); } @Override @@ -665,24 +677,22 @@ public abstract class BasePlayer implements if (DEBUG) Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with " + "reason = [" + reason + "]"); - maybeUpdateCurrentMetadata(); - // Refresh the playback if there is a transition to the next video final int newPeriodIndex = simpleExoPlayer.getCurrentPeriodIndex(); - - /* Discontinuity reasons!! Thank you ExoPlayer lords */ switch (reason) { case DISCONTINUITY_REASON_PERIOD_TRANSITION: if (newPeriodIndex == playQueue.getIndex()) { registerView(); } else { - playQueue.offsetIndex(+1); + playQueue.setIndex(newPeriodIndex); } case DISCONTINUITY_REASON_SEEK: case DISCONTINUITY_REASON_SEEK_ADJUSTMENT: case DISCONTINUITY_REASON_INTERNAL: break; } + + maybeUpdateCurrentMetadata(); } @Override @@ -794,14 +804,6 @@ public abstract class BasePlayer implements initThumbnail(info.getThumbnailUrl()); registerView(); - - // when starting playback on the last item when not repeating, maybe auto queue - if (playQueue.getIndex() == playQueue.size() - 1 && - getRepeatMode() == Player.REPEAT_MODE_OFF && - PlayerHelper.isAutoQueueEnabled(context)) { - final PlayQueue autoQueue = PlayerHelper.autoQueueOf(info, playQueue.getStreams()); - if (autoQueue != null) playQueue.append(autoQueue.getStreams()); - } } @Override @@ -960,7 +962,7 @@ public abstract class BasePlayer implements //////////////////////////////////////////////////////////////////////////*/ private void registerView() { - if (databaseUpdateReactor == null || currentMetadata == null) return; + if (currentMetadata == null) return; final StreamInfo currentInfo = currentMetadata.getMetadata(); databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete() .subscribe( @@ -980,7 +982,7 @@ public abstract class BasePlayer implements } protected void savePlaybackState(final StreamInfo info, final long progress) { - if (info == null || databaseUpdateReactor == null) return; + if (info == null) return; final Disposable stateSaver = recordManager.saveStreamState(info, progress) .observeOn(AndroidSchedulers.mainThread()) .onErrorComplete() @@ -1012,12 +1014,23 @@ public abstract class BasePlayer implements return; } - if (metadata == null || currentMetadata == metadata) return; + if (metadata == null) return; + maybeAutoQueueNextStream(metadata); + if (currentMetadata == metadata) return; currentMetadata = metadata; onMetadataChanged(metadata); } + private void maybeAutoQueueNextStream(@NonNull final MediaSourceTag currentMetadata) { + if (playQueue == null || playQueue.getIndex() != playQueue.size() - 1 || + getRepeatMode() != Player.REPEAT_MODE_OFF || + !PlayerHelper.isAutoQueueEnabled(context)) return; + // auto queue when starting playback on the last item when not repeating + final PlayQueue autoQueue = PlayerHelper.autoQueueOf(currentMetadata.getMetadata(), + playQueue.getStreams()); + if (autoQueue != null) playQueue.append(autoQueue.getStreams()); + } /*////////////////////////////////////////////////////////////////////////// // Getters and Setters //////////////////////////////////////////////////////////////////////////*/ @@ -1135,7 +1148,7 @@ public abstract class BasePlayer implements } public boolean isProgressLoopRunning() { - return progressUpdateReactor != null && !progressUpdateReactor.isDisposed(); + return progressUpdateReactor.get() != null; } public void setRecovery() { 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 a66906c73..9130ea009 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -127,7 +127,7 @@ public final class MainVideoPlayer extends AppCompatActivity hideSystemUi(); setContentView(R.layout.activity_main_player); - playerImpl = new VideoPlayerImpl(this); + playerImpl = new VideoPlayerImpl(this); playerImpl.setup(findViewById(android.R.id.content)); if (savedInstanceState != null && savedInstanceState.get(KEY_SAVED_STATE) != null) { @@ -498,11 +498,11 @@ public final class MainVideoPlayer extends AppCompatActivity // Playback Listener //////////////////////////////////////////////////////////////////////////*/ - protected void onMetadataChanged(@Nullable final MediaSourceTag tag) { + protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { super.onMetadataChanged(tag); - titleTextView.setText(getVideoTitle()); - channelTextView.setText(getUploaderName()); + titleTextView.setText(tag.getMetadata().getName()); + channelTextView.setText(tag.getMetadata().getUploaderName()); } @Override 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 476ad73ef..0426525a4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -56,6 +56,7 @@ 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.R; @@ -428,21 +429,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(); @@ -527,6 +513,54 @@ public final class PopupVideoPlayer extends Service { }; } + /*////////////////////////////////////////////////////////////////////////// + // Thumbnail Loading + //////////////////////////////////////////////////////////////////////////*/ + + private void setDummyRemoteViewThumbnail() { + resetNotification(); + if (notRemoteView != null) { + notRemoteView.setImageViewResource(R.id.notificationCover, + R.drawable.dummy_thumbnail); + } + updateNotification(-1); + } + + @Override + public void onLoadingStarted(String imageUri, View view) { + super.onLoadingStarted(imageUri, view); + setDummyRemoteViewThumbnail(); + } + + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + super.onLoadingComplete(imageUri, view, loadedImage); + if (loadedImage == null) { + setDummyRemoteViewThumbnail(); + return; + } + + // rebuild notification here since remote view does not release bitmaps, + // causing memory leaks + resetNotification(); + if (notRemoteView != null) { + notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage); + } + updateNotification(-1); + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + super.onLoadingFailed(imageUri, view, failReason); + setDummyRemoteViewThumbnail(); + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + super.onLoadingCancelled(imageUri, view); + setDummyRemoteViewThumbnail(); + } + /*////////////////////////////////////////////////////////////////////////// // Activity Event Listener //////////////////////////////////////////////////////////////////////////*/ @@ -578,8 +612,8 @@ public final class PopupVideoPlayer extends Service { public void onRepeatModeChanged(int i) { super.onRepeatModeChanged(i); setRepeatModeRemote(notRemoteView, i); - updateNotification(-1); updatePlayback(); + updateNotification(-1); } @Override @@ -592,7 +626,7 @@ public final class PopupVideoPlayer extends Service { // Playback Listener //////////////////////////////////////////////////////////////////////////*/ - protected void onMetadataChanged(@Nullable final MediaSourceTag tag) { + protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { super.onMetadataChanged(tag); updateMetadata(); } @@ -651,12 +685,14 @@ public final class PopupVideoPlayer extends Service { public void changeState(int state) { super.changeState(state); updatePlayback(); + updateNotification(-1); } @Override public void onBlocked() { super.onBlocked(); updateNotification(R.drawable.ic_play_arrow_white); + startForeground(NOTIFICATION_ID, notBuilder.build()); } @Override @@ -664,19 +700,21 @@ public final class PopupVideoPlayer extends Service { super.onPlaying(); updateNotification(R.drawable.ic_pause_white); videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white); - lockManager.acquireWifiAndCpu(); - hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); + + startForeground(NOTIFICATION_ID, notBuilder.build()); + lockManager.acquireWifiAndCpu(); } @Override public void onBuffering() { super.onBuffering(); updateNotification(R.drawable.ic_play_arrow_white); + startForeground(NOTIFICATION_ID, notBuilder.build()); } @Override @@ -684,10 +722,13 @@ public final class PopupVideoPlayer extends Service { super.onPaused(); updateNotification(R.drawable.ic_play_arrow_white); videoPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_white); + lockManager.releaseWifiAndCpu(); windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); + + stopForeground(false); } @Override @@ -695,6 +736,7 @@ public final class PopupVideoPlayer extends Service { super.onPausedSeek(); videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white); updateNotification(R.drawable.ic_play_arrow_white); + startForeground(NOTIFICATION_ID, notBuilder.build()); } @Override @@ -702,10 +744,13 @@ public final class PopupVideoPlayer extends Service { super.onCompleted(); updateNotification(R.drawable.ic_replay_white); videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white); + lockManager.releaseWifiAndCpu(); windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); + + stopForeground(false); } @Override @@ -731,7 +776,7 @@ public final class PopupVideoPlayer extends Service { /*package-private*/ void enableVideoRenderer(final boolean enable) { final int videoRendererIndex = getRendererIndex(C.TRACK_TYPE_VIDEO); - if (trackSelector != null && videoRendererIndex != RENDERER_UNAVAILABLE) { + if (videoRendererIndex != RENDERER_UNAVAILABLE) { trackSelector.setParameters(trackSelector.buildUponParameters() .setRendererDisabled(videoRendererIndex, !enable)); } 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 eaec59f65..1ca0ff4ee 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -103,7 +103,7 @@ public abstract class VideoPlayer extends BasePlayer protected boolean wasPlaying = false; - @Nullable private VideoPlaybackResolver resolver; + @NonNull final private VideoPlaybackResolver resolver; /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ @@ -154,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) { @@ -236,8 +237,6 @@ public abstract class VideoPlayer extends BasePlayer trackSelector.setParameters(trackSelector.buildUponParameters() .setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context))); } - - resolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); } @Override @@ -292,7 +291,7 @@ 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) { + if (textRendererIndex != RENDERER_UNAVAILABLE) { trackSelector.setParameters(trackSelector.buildUponParameters() .setRendererDisabled(textRendererIndex, true)); } @@ -306,7 +305,7 @@ 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.setParameters(trackSelector.buildUponParameters() .setRendererDisabled(textRendererIndex, false)); @@ -369,7 +368,7 @@ public abstract class VideoPlayer extends BasePlayer @Override @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { - return resolver == null ? null : resolver.resolve(info); + return resolver.resolve(info); } /*////////////////////////////////////////////////////////////////////////// @@ -480,8 +479,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; } @@ -833,12 +831,12 @@ public abstract class VideoPlayer extends BasePlayer //////////////////////////////////////////////////////////////////////////*/ public void setPlaybackQuality(final String quality) { - if (resolver != null) resolver.setPlaybackQuality(quality); + this.resolver.setPlaybackQuality(quality); } @Nullable public String getPlaybackQuality() { - return resolver == null ? null : resolver.getPlaybackQuality(); + return resolver.getPlaybackQuality(); } public AspectRatioFrameLayout getAspectRatioFrameLayout() { 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 index f95f0e3bb..6bb556850 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java @@ -2,6 +2,7 @@ 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; @@ -24,6 +25,7 @@ public class AudioPlaybackResolver implements PlaybackResolver { } @Override + @Nullable public MediaSource resolve(@NonNull StreamInfo info) { final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info); if (liveSource != null) return liveSource; 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 index a7d4448e4..4bd795574 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java @@ -1,7 +1,8 @@ package org.schabi.newpipe.player.resolver; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; public interface Resolver { - Product resolve(@NonNull Source source); + @Nullable Product resolve(@NonNull Source source); } diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java index 5aa42ce3c..8f91f4886 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java @@ -47,6 +47,7 @@ public class VideoPlaybackResolver implements PlaybackResolver { } @Override + @Nullable public MediaSource resolve(@NonNull StreamInfo info) { final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info); if (liveSource != null) return liveSource; From 0ece4851d24f3962827ab33ccd359a781478add5 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Wed, 23 May 2018 19:56:43 -0700 Subject: [PATCH 12/22] -Updated ExoPlayer to 2.8.1, fixing livestream with long duration not loading. -Updated OkHttp to 3.10.0 and RxJava to 2.1.14. -Changed player recovery seek to use ExoPlayer built-in window seeking instead of seeking after stream window starts playing. -Changed playback speed changer default step size to 25%. -Changed player notification to reset on all state changes. -Fixed gradle dependency version incorrect variable names. -Fixed video player double tap not working during pause. -[#1412] Fixed NPE when sharing video to main video activity when it was playing but is out of focus: Reset main player state when new intent is received. -[#1410] Fixed fast forwarding and rewinding not working within 10 seconds from beginning or end of a stream window. --- app/build.gradle | 2 +- .../newpipe/player/BackgroundPlayer.java | 6 ++- .../org/schabi/newpipe/player/BasePlayer.java | 42 +++++-------------- .../newpipe/player/MainVideoPlayer.java | 1 - .../newpipe/player/PopupVideoPlayer.java | 2 +- .../helper/PlaybackParameterDialog.java | 36 +++++++++------- 6 files changed, 36 insertions(+), 53 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b04daebed..6410cb823 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { ext { supportLibVersion = '27.1.1' - exoPlayerLibVersion = '2.8.0' + exoPlayerLibVersion = '2.8.1' roomDbLibVersion = '1.1.1' leakCanaryLibVersion = '1.5.4' okHttpLibVersion = '3.10.0' diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index c5c9f0615..231163b7b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -544,26 +544,29 @@ public final class BackgroundPlayer extends Service { @Override public void changeState(int state) { + resetNotification(); super.changeState(state); updatePlayback(); - updateNotification(-1); } @Override public void onBlocked() { super.onBlocked(); + updateNotification(-1); startForeground(NOTIFICATION_ID, notBuilder.build()); } @Override public void onBuffering() { super.onBuffering(); + updateNotification(-1); startForeground(NOTIFICATION_ID, notBuilder.build()); } @Override public void onPausedSeek() { super.onPausedSeek(); + updateNotification(-1); startForeground(NOTIFICATION_ID, notBuilder.build()); } @@ -589,7 +592,6 @@ public final class BackgroundPlayer extends Service { public void onCompleted() { super.onCompleted(); - resetNotification(); if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false); if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false); updateNotification(R.drawable.ic_replay_white); 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 3f50cf149..113ccbf68 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -85,7 +85,6 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT; -import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; /** * Base for the players, joining the common properties @@ -157,7 +156,6 @@ public abstract class BasePlayer implements protected MediaSessionManager mediaSessionManager; private boolean isPrepared = false; - private boolean isSynchronizing = false; //////////////////////////////////////////////////////////////////////////*/ @@ -575,37 +573,17 @@ public abstract class BasePlayer implements private void maybeCorrectSeekPosition() { if (playQueue == null || simpleExoPlayer == null || currentMetadata == null) return; - final int currentSourceIndex = playQueue.getIndex(); final PlayQueueItem currentSourceItem = playQueue.getItem(); - final StreamInfo currentInfo = currentMetadata.getMetadata(); - if (currentSourceItem == null) return; - final long recoveryPositionMillis = currentSourceItem.getRecoveryPosition(); - final boolean isCurrentWindowCorrect = - simpleExoPlayer.getCurrentPeriodIndex() == currentSourceIndex; + final StreamInfo currentInfo = currentMetadata.getMetadata(); final long presetStartPositionMillis = currentInfo.getStartPosition() * 1000; - - if (recoveryPositionMillis != PlayQueueItem.RECOVERY_UNSET && isCurrentWindowCorrect) { - // Is recovering previous playback? - if (DEBUG) Log.d(TAG, "Playback - Rewinding to recovery time=" + - "[" + getTimeString((int)recoveryPositionMillis) + "]"); - seekTo(recoveryPositionMillis); - playQueue.unsetRecovery(currentSourceIndex); - - } else if (isSynchronizing && isLive()) { - // Is still synchronizing? - if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time"); - //seekToDefault(); - - } else if (isSynchronizing && presetStartPositionMillis > 0L) { + if (presetStartPositionMillis > 0L) { // Has another start position? if (DEBUG) Log.d(TAG, "Playback - Seeking to preset start " + "position=[" + presetStartPositionMillis + "]"); seekTo(presetStartPositionMillis); } - - isSynchronizing = false; } /** @@ -784,16 +762,18 @@ public abstract class BasePlayer implements "index=[" + currentPlayQueueIndex + "] with " + "playlist length=[" + currentPlaylistSize + "]"); - // If not playing correct stream, change window position and sets flag - // for synchronizing once window position is corrected - // @see maybeCorrectSeekPosition() } else if (currentPlaylistIndex != currentPlayQueueIndex || onPlaybackInitial || !isPlaying()) { if (DEBUG) Log.d(TAG, "Playback - Rewinding to correct" + " index=[" + currentPlayQueueIndex + "]," + " from=[" + currentPlaylistIndex + "], size=[" + currentPlaylistSize + "]."); - isSynchronizing = true; - simpleExoPlayer.seekToDefaultPosition(currentPlayQueueIndex); + + if (item.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) { + simpleExoPlayer.seekTo(currentPlayQueueIndex, item.getRecoveryPosition()); + playQueue.unsetRecovery(currentPlayQueueIndex); + } else { + simpleExoPlayer.seekToDefaultPosition(currentPlayQueueIndex); + } } } @@ -936,9 +916,7 @@ public abstract class BasePlayer implements public void seekTo(long positionMillis) { if (DEBUG) Log.d(TAG, "seekBy() called with: position = [" + positionMillis + "]"); - if (simpleExoPlayer == null || positionMillis < 0 || - positionMillis > simpleExoPlayer.getDuration()) return; - simpleExoPlayer.seekTo(positionMillis); + if (simpleExoPlayer != null) simpleExoPlayer.seekTo(positionMillis); } public void seekBy(long offsetMillis) { 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 9130ea009..bf728f9d5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -896,7 +896,6 @@ public final class MainVideoPlayer extends AppCompatActivity @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(); 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 0426525a4..e2929d26f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -683,9 +683,9 @@ public final class PopupVideoPlayer extends Service { @Override public void changeState(int state) { + resetNotification(); super.changeState(state); updatePlayback(); - updateNotification(-1); } @Override 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 c53680835..c184da0a5 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 @@ -27,7 +27,13 @@ public class PlaybackParameterDialog extends DialogFragment { public static final char STEP_UP_SIGN = '+'; public static final char STEP_DOWN_SIGN = '-'; - public static final double DEFAULT_PLAYBACK_STEP_VALUE = 0.05f; + + 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_PLAYBACK_STEP_VALUE = STEP_TWENTY_FIVE_PERCENT_VALUE; public static final double DEFAULT_TEMPO = 1.00f; public static final double DEFAULT_PITCH = 1.00f; @@ -244,35 +250,33 @@ public class PlaybackParameterDialog extends DialogFragment { stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent); if (stepSizeOnePercentText != null) { - final double onePercent = 0.01f; - stepSizeOnePercentText.setText(getPercentString(onePercent)); - stepSizeOnePercentText.setOnClickListener(view -> setupStepSize(onePercent)); + stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE)); + stepSizeOnePercentText.setOnClickListener(view -> + setupStepSize(STEP_ONE_PERCENT_VALUE)); } if (stepSizeFivePercentText != null) { - final double fivePercent = 0.05f; - stepSizeFivePercentText.setText(getPercentString(fivePercent)); - stepSizeFivePercentText.setOnClickListener(view -> setupStepSize(fivePercent)); + stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE)); + stepSizeFivePercentText.setOnClickListener(view -> + setupStepSize(STEP_FIVE_PERCENT_VALUE)); } if (stepSizeTenPercentText != null) { - final double tenPercent = 0.10f; - stepSizeTenPercentText.setText(getPercentString(tenPercent)); - stepSizeTenPercentText.setOnClickListener(view -> setupStepSize(tenPercent)); + stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE)); + stepSizeTenPercentText.setOnClickListener(view -> + setupStepSize(STEP_TEN_PERCENT_VALUE)); } if (stepSizeTwentyFivePercentText != null) { - final double twentyFivePercent = 0.25f; - stepSizeTwentyFivePercentText.setText(getPercentString(twentyFivePercent)); + stepSizeTwentyFivePercentText.setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE)); stepSizeTwentyFivePercentText.setOnClickListener(view -> - setupStepSize(twentyFivePercent)); + setupStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE)); } if (stepSizeOneHundredPercentText != null) { - final double oneHundredPercent = 1.00f; - stepSizeOneHundredPercentText.setText(getPercentString(oneHundredPercent)); + stepSizeOneHundredPercentText.setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE)); stepSizeOneHundredPercentText.setOnClickListener(view -> - setupStepSize(oneHundredPercent)); + setupStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE)); } } From 157b0642145623c45a545af72f74f3bab2d68620 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Wed, 23 May 2018 20:18:48 -0700 Subject: [PATCH 13/22] -Fixed player database and progress disposable disposed when destroying exoplayer. -Fixed livestream not reloading on behind live window exception. -Added nonnull annotation to player intent strings. --- .../org/schabi/newpipe/player/BasePlayer.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) 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 113ccbf68..ad62c71c3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -117,14 +117,14 @@ public abstract class BasePlayer implements // Intent //////////////////////////////////////////////////////////////////////////*/ - public static final String REPEAT_MODE = "repeat_mode"; - public static final String PLAYBACK_PITCH = "playback_pitch"; - public static final String PLAYBACK_SPEED = "playback_speed"; - public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence"; - public static final String PLAYBACK_QUALITY = "playback_quality"; - public static final String PLAY_QUEUE_KEY = "play_queue_key"; - public static final String APPEND_ONLY = "append_only"; - public static final String SELECT_ON_APPEND = "select_on_append"; + @NonNull public static final String REPEAT_MODE = "repeat_mode"; + @NonNull public static final String PLAYBACK_PITCH = "playback_pitch"; + @NonNull public static final String PLAYBACK_SPEED = "playback_speed"; + @NonNull public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence"; + @NonNull public static final String PLAYBACK_QUALITY = "playback_quality"; + @NonNull public static final String PLAY_QUEUE_KEY = "play_queue_key"; + @NonNull public static final String APPEND_ONLY = "append_only"; + @NonNull public static final String SELECT_ON_APPEND = "select_on_append"; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -276,7 +276,6 @@ public abstract class BasePlayer implements if (playQueue != null) playQueue.dispose(); if (audioReactor != null) audioReactor.dispose(); if (playbackManager != null) playbackManager.dispose(); - if (databaseUpdateReactor != null) databaseUpdateReactor.dispose(); if (mediaSessionManager != null) mediaSessionManager.dispose(); if (playQueueAdapter != null) { @@ -290,9 +289,9 @@ public abstract class BasePlayer implements destroyPlayer(); unregisterBroadcastReceiver(); - if (mediaSessionManager != null) mediaSessionManager.dispose(); + databaseUpdateReactor.clear(); + progressUpdateReactor.set(null); - mediaSessionManager = null; simpleExoPlayer = null; } @@ -635,7 +634,7 @@ public abstract class BasePlayer implements setRecovery(); final Throwable cause = error.getCause(); - if (cause instanceof BehindLiveWindowException) { + if (error instanceof BehindLiveWindowException) { reload(); } else if (cause instanceof UnknownHostException) { playQueue.error(/*isNetworkProblem=*/true); @@ -942,11 +941,12 @@ public abstract class BasePlayer implements private void registerView() { if (currentMetadata == null) return; final StreamInfo currentInfo = currentMetadata.getMetadata(); - databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete() + final Disposable viewRegister = recordManager.onViewed(currentInfo).onErrorComplete() .subscribe( ignored -> {/* successful */}, error -> Log.e(TAG, "Player onViewed() failure: ", error) - )); + ); + databaseUpdateReactor.add(viewRegister); } protected void reload() { From 2d6317bd24d76d34f561600f25fd152f6bd2cc6d Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Mon, 28 May 2018 19:51:41 -0700 Subject: [PATCH 14/22] -Fixed audio-only streams thumbnail not displaying on video players. -Fixed potential play queue desynchronization due to fast forwarding on silence. -Added current thumbnail storing in base player to allow immediate retrieval for notification building. -Removed video player buffer spinner during interim buffering but not initial buffering. -Reverted foreground notification stopping on pause and on complete. --- .../newpipe/player/BackgroundPlayer.java | 85 ++++--------------- .../org/schabi/newpipe/player/BasePlayer.java | 31 +++++-- .../newpipe/player/MainVideoPlayer.java | 1 - .../newpipe/player/PopupVideoPlayer.java | 52 ++++-------- .../schabi/newpipe/player/VideoPlayer.java | 7 +- 5 files changed, 64 insertions(+), 112 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 231163b7b..805326abd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -188,7 +188,9 @@ public final class BackgroundPlayer extends Service { .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setCustomContentView(notRemoteView) .setCustomBigContentView(bigNotRemoteView); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) builder.setPriority(NotificationCompat.PRIORITY_MAX); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { + builder.setPriority(NotificationCompat.PRIORITY_MAX); + } return builder; } @@ -197,6 +199,7 @@ public final class BackgroundPlayer extends Service { remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle()); remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName()); + remoteViews.setImageViewBitmap(R.id.notificationCover, basePlayerImpl.getThumbnail()); remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT)); @@ -244,6 +247,7 @@ public final class BackgroundPlayer extends Service { } notificationManager.notify(NOTIFICATION_ID, notBuilder.build()); } + /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -291,57 +295,26 @@ public final class BackgroundPlayer extends Service { // Thumbnail Loading //////////////////////////////////////////////////////////////////////////*/ - private void setDummyRemoteViewThumbnail() { - resetNotification(); - if (notRemoteView != null) { - notRemoteView.setImageViewResource(R.id.notificationCover, - R.drawable.dummy_thumbnail); - } - if (bigNotRemoteView != null) { - bigNotRemoteView.setImageViewResource(R.id.notificationCover, - R.drawable.dummy_thumbnail); - } - updateNotification(-1); - } - - @Override - public void onLoadingStarted(String imageUri, View view) { - super.onLoadingStarted(imageUri, view); - setDummyRemoteViewThumbnail(); - } - @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { super.onLoadingComplete(imageUri, view, loadedImage); - if (loadedImage == null) { - setDummyRemoteViewThumbnail(); - return; - } - - // rebuild notification here since remote view does not release bitmaps, - // causing memory leaks resetNotification(); - if (notRemoteView != null) { - notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage); - } - if (bigNotRemoteView != null) { - bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage); - } updateNotification(-1); } @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { super.onLoadingFailed(imageUri, view, failReason); - setDummyRemoteViewThumbnail(); + resetNotification(); + updateNotification(-1); } @Override public void onLoadingCancelled(String imageUri, View view) { super.onLoadingCancelled(imageUri, view); - setDummyRemoteViewThumbnail(); + resetNotification(); + updateNotification(-1); } - /*////////////////////////////////////////////////////////////////////////// // States Implementation //////////////////////////////////////////////////////////////////////////*/ @@ -544,60 +517,38 @@ public final class BackgroundPlayer extends Service { @Override public void changeState(int state) { - resetNotification(); super.changeState(state); updatePlayback(); } - @Override - public void onBlocked() { - super.onBlocked(); - updateNotification(-1); - startForeground(NOTIFICATION_ID, notBuilder.build()); - } - - @Override - public void onBuffering() { - super.onBuffering(); - updateNotification(-1); - startForeground(NOTIFICATION_ID, notBuilder.build()); - } - - @Override - public void onPausedSeek() { - super.onPausedSeek(); - updateNotification(-1); - startForeground(NOTIFICATION_ID, notBuilder.build()); - } - @Override public void onPlaying() { super.onPlaying(); + resetNotification(); updateNotification(R.drawable.ic_pause_white); - lockManager.acquireWifiAndCpu(); - startForeground(NOTIFICATION_ID, notBuilder.build()); } @Override public void onPaused() { super.onPaused(); + resetNotification(); updateNotification(R.drawable.ic_play_arrow_white); - lockManager.releaseWifiAndCpu(); - stopForeground(false); } @Override public void onCompleted() { super.onCompleted(); - - if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false); - if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false); + resetNotification(); + if (bigNotRemoteView != null) { + bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false); + } + if (notRemoteView != null) { + notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false); + } updateNotification(R.drawable.ic_replay_white); - lockManager.releaseWifiAndCpu(); - stopForeground(false); } } } 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 ad62c71c3..12e65cba1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.media.AudioManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -139,6 +140,7 @@ public abstract class BasePlayer implements @Nullable private PlayQueueItem currentItem; @Nullable private MediaSourceTag currentMetadata; + @Nullable private Bitmap currentThumbnail; @Nullable protected Toast errorToast; @@ -317,6 +319,7 @@ public abstract class BasePlayer implements public void onLoadingFailed(String imageUri, View view, FailReason failReason) { Log.e(TAG, "Thumbnail - onLoadingFailed() called on imageUri = [" + imageUri + "]", failReason.getCause()); + currentThumbnail = null; } @Override @@ -324,12 +327,14 @@ public abstract class BasePlayer implements if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingComplete() called with: " + "imageUri = [" + imageUri + "], view = [" + view + "], " + "loadedImage = [" + loadedImage + "]"); + currentThumbnail = loadedImage; } @Override public void onLoadingCancelled(String imageUri, View view) { if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: " + "imageUri = [" + imageUri + "], view = [" + view + "]"); + currentThumbnail = null; } /*////////////////////////////////////////////////////////////////////////// @@ -653,19 +658,25 @@ public abstract class BasePlayer implements public void onPositionDiscontinuity(@Player.DiscontinuityReason final int reason) { if (DEBUG) Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with " + "reason = [" + reason + "]"); + if (playQueue == null) return; // Refresh the playback if there is a transition to the next video - final int newPeriodIndex = simpleExoPlayer.getCurrentPeriodIndex(); + final int newWindowIndex = simpleExoPlayer.getCurrentWindowIndex(); switch (reason) { case DISCONTINUITY_REASON_PERIOD_TRANSITION: - if (newPeriodIndex == playQueue.getIndex()) { + // When player is in single repeat mode and a period transition occurs, + // we need to register a view count here since no metadata has changed + if (getRepeatMode() == Player.REPEAT_MODE_ONE && + newWindowIndex == playQueue.getIndex()) { registerView(); - } else { - playQueue.setIndex(newPeriodIndex); + break; } case DISCONTINUITY_REASON_SEEK: case DISCONTINUITY_REASON_SEEK_ADJUSTMENT: case DISCONTINUITY_REASON_INTERNAL: + if (playQueue.getIndex() != newWindowIndex) { + playQueue.setIndex(newWindowIndex); + } break; } @@ -777,9 +788,10 @@ public abstract class BasePlayer implements } protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { - Log.d(TAG, "Playback - onMetadataChanged() called, " + - "playing: " + tag.getMetadata().getName()); final StreamInfo info = tag.getMetadata(); + if (DEBUG) { + Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName()); + } initThumbnail(info.getThumbnailUrl()); registerView(); @@ -1045,6 +1057,13 @@ public abstract class BasePlayer implements return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader(); } + @Nullable + public Bitmap getThumbnail() { + return currentThumbnail == null ? + BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_thumbnail) : + currentThumbnail; + } + /** Checks if the current playback is a livestream AND is playing at or beyond the live edge */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean isLiveEdge() { 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 bf728f9d5..9f3b5d020 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -720,7 +720,6 @@ public final class MainVideoPlayer extends AppCompatActivity @Override public void onBuffering() { super.onBuffering(); - animatePlayButtons(false, 100); getRootView().setKeepScreenOn(true); } 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 e2929d26f..3bee984a8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -34,7 +34,6 @@ 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.v4.app.NotificationCompat; import android.util.DisplayMetrics; import android.util.Log; @@ -229,6 +228,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)); @@ -517,48 +517,27 @@ public final class PopupVideoPlayer extends Service { // Thumbnail Loading //////////////////////////////////////////////////////////////////////////*/ - private void setDummyRemoteViewThumbnail() { - resetNotification(); - if (notRemoteView != null) { - notRemoteView.setImageViewResource(R.id.notificationCover, - R.drawable.dummy_thumbnail); - } - updateNotification(-1); - } - - @Override - public void onLoadingStarted(String imageUri, View view) { - super.onLoadingStarted(imageUri, view); - setDummyRemoteViewThumbnail(); - } - @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { super.onLoadingComplete(imageUri, view, loadedImage); - if (loadedImage == null) { - setDummyRemoteViewThumbnail(); - return; - } - // rebuild notification here since remote view does not release bitmaps, // causing memory leaks resetNotification(); - if (notRemoteView != null) { - notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage); - } updateNotification(-1); } @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { super.onLoadingFailed(imageUri, view, failReason); - setDummyRemoteViewThumbnail(); + resetNotification(); + updateNotification(-1); } @Override public void onLoadingCancelled(String imageUri, View view) { super.onLoadingCancelled(imageUri, view); - setDummyRemoteViewThumbnail(); + resetNotification(); + updateNotification(-1); } /*////////////////////////////////////////////////////////////////////////// @@ -613,6 +592,7 @@ public final class PopupVideoPlayer extends Service { super.onRepeatModeChanged(i); setRepeatModeRemote(notRemoteView, i); updatePlayback(); + resetNotification(); updateNotification(-1); } @@ -683,7 +663,6 @@ public final class PopupVideoPlayer extends Service { @Override public void changeState(int state) { - resetNotification(); super.changeState(state); updatePlayback(); } @@ -691,14 +670,16 @@ public final class PopupVideoPlayer extends Service { @Override public void onBlocked() { super.onBlocked(); + resetNotification(); updateNotification(R.drawable.ic_play_arrow_white); - startForeground(NOTIFICATION_ID, notBuilder.build()); } @Override public void onPlaying() { super.onPlaying(); + resetNotification(); updateNotification(R.drawable.ic_pause_white); + videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white); hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); @@ -713,16 +694,17 @@ public final class PopupVideoPlayer extends Service { @Override public void onBuffering() { super.onBuffering(); + resetNotification(); updateNotification(R.drawable.ic_play_arrow_white); - startForeground(NOTIFICATION_ID, notBuilder.build()); } @Override public void onPaused() { super.onPaused(); + resetNotification(); updateNotification(R.drawable.ic_play_arrow_white); - videoPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_white); + videoPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_white); lockManager.releaseWifiAndCpu(); windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; @@ -734,17 +716,19 @@ public final class PopupVideoPlayer extends Service { @Override public void onPausedSeek() { super.onPausedSeek(); - videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white); + resetNotification(); updateNotification(R.drawable.ic_play_arrow_white); - startForeground(NOTIFICATION_ID, notBuilder.build()); + + videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white); } @Override public void onCompleted() { super.onCompleted(); + resetNotification(); updateNotification(R.drawable.ic_replay_white); - videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white); + videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white); lockManager.releaseWifiAndCpu(); windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; @@ -768,8 +752,6 @@ public final class PopupVideoPlayer extends Service { super.hideControlsAndButton(duration, delay, videoPlayPause); } - - /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ 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 1ca0ff4ee..73f5e94b1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -333,16 +333,19 @@ public abstract class VideoPlayer extends BasePlayer switch (tag.getMetadata().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; @@ -357,6 +360,7 @@ public abstract class VideoPlayer extends BasePlayer qualityTextView.setVisibility(View.VISIBLE); surfaceView.setVisibility(View.VISIBLE); default: + endScreen.setVisibility(View.GONE); playbackEndTime.setVisibility(View.VISIBLE); break; } @@ -387,7 +391,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); @@ -407,14 +410,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 From 8efcc8f80fbec871b959af4cdfd931371533dba2 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 5 Jun 2018 23:47:13 -0700 Subject: [PATCH 15/22] -Fixed main video player end screen thumbnail not fitting screen aspect ratio. --- app/src/main/res/layout/activity_main_player.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml index f2cf85802..27aa56025 100644 --- a/app/src/main/res/layout/activity_main_player.xml +++ b/app/src/main/res/layout/activity_main_player.xml @@ -41,7 +41,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" - android:scaleType="centerInside" android:visibility="gone" tools:background="@android:color/white" tools:ignore="ContentDescription" From 06374c82fd920bdc340e3e3fd66ba23b55eea887 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 5 Jun 2018 23:48:44 -0700 Subject: [PATCH 16/22] -Fixed video players end screen not cleared on restarting playback after single stream play queue is completed. --- .../schabi/newpipe/player/VideoPlayer.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) 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 73f5e94b1..679fc6645 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -315,14 +315,13 @@ public abstract class VideoPlayer extends BasePlayer } captionPopupMenu.setOnDismissListener(this); } - /*////////////////////////////////////////////////////////////////////////// - // Playback Listener - //////////////////////////////////////////////////////////////////////////*/ - protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver(); - protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { - super.onMetadataChanged(tag); + private void updateStreamRelatedViews() { + if (getCurrentMetadata() == null) return; + + final MediaSourceTag tag = getCurrentMetadata(); + final StreamInfo metadata = tag.getMetadata(); qualityTextView.setVisibility(View.GONE); playbackSpeedTextView.setVisibility(View.GONE); @@ -330,7 +329,7 @@ public abstract class VideoPlayer extends BasePlayer playbackEndTime.setVisibility(View.GONE); playbackLiveSync.setVisibility(View.GONE); - switch (tag.getMetadata().getStreamType()) { + switch (metadata.getStreamType()) { case AUDIO_STREAM: surfaceView.setVisibility(View.GONE); endScreen.setVisibility(View.VISIBLE); @@ -350,8 +349,8 @@ public abstract class VideoPlayer extends BasePlayer break; case VIDEO_STREAM: - final StreamInfo info = tag.getMetadata(); - if (info.getVideoStreams().size() + info.getVideoOnlyStreams().size() == 0) break; + if (metadata.getVideoStreams().size() + metadata.getVideoOnlyStreams().size() == 0) + break; availableStreams = tag.getSortedAvailableVideoStreams(); selectedStreamIndex = tag.getSelectedVideoStreamIndex(); @@ -368,6 +367,16 @@ 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 @@ -400,6 +409,8 @@ public abstract class VideoPlayer extends BasePlayer public void onPlaying() { super.onPlaying(); + updateStreamRelatedViews(); + showAndAnimateControl(-1, true); playbackSeekBar.setEnabled(true); From 31218c2a8c8e29a7152bf56463ba2dc1d39a304a Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 5 Jun 2018 23:50:42 -0700 Subject: [PATCH 17/22] -Fixed popup player notification metadata not updated on stream change. -Fixed popup player window not clipped to above soft input keyboard upon expansion. --- .../org/schabi/newpipe/player/PopupVideoPlayer.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 3bee984a8..c53685afa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -191,14 +191,19 @@ 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; + final int interactiveInputMethodLayout = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; windowLayoutParams = new WindowManager.LayoutParams( (int) popupWidth, (int) getMinimumVideoHeight(popupWidth), layoutParamType, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + interactiveInputMethodLayout, PixelFormat.TRANSLUCENT); windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; + windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; int centerX = (int) (screenWidth / 2f - popupWidth / 2f); int centerY = (int) (screenHeight / 2f - popupHeight / 2f); @@ -608,6 +613,8 @@ public final class PopupVideoPlayer extends Service { protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { super.onMetadataChanged(tag); + resetNotification(); + updateNotification(-1); updateMetadata(); } From e7d23176b70abe07f11e2e433237d829daabf209 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 5 Jun 2018 23:52:34 -0700 Subject: [PATCH 18/22] -Fixed database backup failing due to journal file name change after Room DB version update. --- .../org/schabi/newpipe/settings/ContentSettingsFragment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 3894c421f..8f4e6d471 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -277,6 +277,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { newpipe_db_shm.delete(); } else { + Toast.makeText(getContext(), R.string.could_not_import_all_files, Toast.LENGTH_LONG) .show(); } From aa1878c15aafad7fb6340472a098175dc102f1cf Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 26 Jun 2018 10:16:54 -0700 Subject: [PATCH 19/22] -Changed baseplayer metadata getters to use media tag as source. -Changed background player notification to no longer update bitmap on progress time change. -Changed popup player to move above soft keyboard when it is opened. --- .../newpipe/player/BackgroundPlayer.java | 25 ++++++++---- .../org/schabi/newpipe/player/BasePlayer.java | 6 +-- .../newpipe/player/PopupVideoPlayer.java | 40 ++++++++++++------- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 805326abd..dd70ea354 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -199,7 +199,6 @@ public final class BackgroundPlayer extends Service { remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle()); remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName()); - remoteViews.setImageViewBitmap(R.id.notificationCover, basePlayerImpl.getThumbnail()); remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT)); @@ -295,10 +294,22 @@ public final class BackgroundPlayer extends Service { // Thumbnail Loading //////////////////////////////////////////////////////////////////////////*/ + private void updateNotificationThumbnail() { + if (notRemoteView != null) { + notRemoteView.setImageViewBitmap(R.id.notificationCover, + basePlayerImpl.getThumbnail()); + } + if (bigNotRemoteView != null) { + bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, + basePlayerImpl.getThumbnail()); + } + } + @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { super.onLoadingComplete(imageUri, view, loadedImage); resetNotification(); + updateNotificationThumbnail(); updateNotification(-1); } @@ -306,13 +317,7 @@ public final class BackgroundPlayer extends Service { 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(); + updateNotificationThumbnail(); updateNotification(-1); } /*////////////////////////////////////////////////////////////////////////// @@ -395,6 +400,7 @@ public final class BackgroundPlayer extends Service { protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { super.onMetadataChanged(tag); resetNotification(); + updateNotificationThumbnail(); updateNotification(-1); updateMetadata(); } @@ -525,6 +531,7 @@ public final class BackgroundPlayer extends Service { public void onPlaying() { super.onPlaying(); resetNotification(); + updateNotificationThumbnail(); updateNotification(R.drawable.ic_pause_white); lockManager.acquireWifiAndCpu(); } @@ -533,6 +540,7 @@ public final class BackgroundPlayer extends Service { public void onPaused() { super.onPaused(); resetNotification(); + updateNotificationThumbnail(); updateNotification(R.drawable.ic_play_arrow_white); lockManager.releaseWifiAndCpu(); } @@ -547,6 +555,7 @@ public final class BackgroundPlayer extends Service { if (notRemoteView != null) { notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false); } + updateNotificationThumbnail(); updateNotification(R.drawable.ic_replay_white); lockManager.releaseWifiAndCpu(); } 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 12e65cba1..7339dd50f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -1044,17 +1044,17 @@ public abstract class BasePlayer implements @NonNull public String getVideoUrl() { - return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUrl(); + return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getUrl(); } @NonNull public String getVideoTitle() { - return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getTitle(); + return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getName(); } @NonNull public String getUploaderName() { - return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader(); + return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getUploaderName(); } @Nullable 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 c53685afa..86998e0ea 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -98,6 +98,11 @@ public final class PopupVideoPlayer extends Service { private static final int MINIMUM_SHOW_EXTRA_WIDTH_DP = 300; + private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS | + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + private WindowManager windowManager; private WindowManager.LayoutParams windowLayoutParams; private GestureDetector gestureDetector; @@ -194,13 +199,11 @@ public final class PopupVideoPlayer extends Service { final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_PHONE : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - final int interactiveInputMethodLayout = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | - WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; windowLayoutParams = new WindowManager.LayoutParams( (int) popupWidth, (int) getMinimumVideoHeight(popupWidth), layoutParamType, - interactiveInputMethodLayout, + IDLE_WINDOW_FLAGS, PixelFormat.TRANSLUCENT); windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; @@ -249,11 +252,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; } /** @@ -372,6 +379,12 @@ public final class PopupVideoPlayer extends Service { } } + private void updateWindowFlags(final int flags) { + if (windowLayoutParams == null || windowManager == null || playerImpl == null) return; + + windowLayoutParams.flags = flags; + windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); + } /////////////////////////////////////////////////////////////////////////// protected class VideoPlayerImpl extends VideoPlayer implements View.OnLayoutChangeListener { @@ -684,16 +697,15 @@ public final class PopupVideoPlayer extends Service { @Override public void onPlaying() { super.onPlaying(); + + 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); - windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); - startForeground(NOTIFICATION_ID, notBuilder.build()); lockManager.acquireWifiAndCpu(); } @@ -708,15 +720,15 @@ public final class PopupVideoPlayer extends Service { @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(); - windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); - stopForeground(false); } @@ -732,15 +744,15 @@ public final class PopupVideoPlayer extends Service { @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(); - windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); - stopForeground(false); } From 7f7145e8deb7cd46e1e520a7796559258557e87a Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 26 Jun 2018 10:18:01 -0700 Subject: [PATCH 20/22] -Fixed playback parameter dialog settings not persisting through rotation. -Moved playback parameter dialog step size selector to below pitch slider. --- .../helper/PlaybackParameterDialog.java | 72 +++++++++++-------- .../res/layout/dialog_playback_parameter.xml | 60 +++++++++------- 2 files changed, 75 insertions(+), 57 deletions(-) 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 c184da0a5..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 @@ -33,15 +33,19 @@ public class PlaybackParameterDialog extends DialogFragment { 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_PLAYBACK_STEP_VALUE = STEP_TWENTY_FIVE_PERCENT_VALUE; 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, final boolean playbackSkipSilence); @@ -57,6 +61,10 @@ public class PlaybackParameterDialog extends DialogFragment { 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; @Nullable private TextView tempoMaximumText; @@ -86,6 +94,10 @@ public class PlaybackParameterDialog extends DialogFragment { PlaybackParameterDialog dialog = new PlaybackParameterDialog(); dialog.initialTempo = playbackTempo; dialog.initialPitch = playbackPitch; + + dialog.tempo = playbackTempo; + dialog.pitch = playbackPitch; + dialog.initialSkipSilence = playbackSkipSilence; return dialog; } @@ -110,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); } } @@ -118,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()); } /*////////////////////////////////////////////////////////////////////////// @@ -154,8 +174,8 @@ public class PlaybackParameterDialog extends DialogFragment { setupTempoControl(rootView); setupPitchControl(rootView); - setupStepSize(DEFAULT_PLAYBACK_STEP_VALUE); + changeStepSize(stepSize); setupStepSizeSelector(rootView); } @@ -168,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(DEFAULT_PLAYBACK_STEP_VALUE)); - tempoStepUpText.setOnClickListener(view -> { - onTempoSliderUpdated(getCurrentTempo() + DEFAULT_PLAYBACK_STEP_VALUE); - setCurrentPlaybackParameters(); - }); - } - - if (tempoStepDownText != null) { - tempoStepDownText.setText(getStepDownPercentString(DEFAULT_PLAYBACK_STEP_VALUE)); - tempoStepDownText.setOnClickListener(view -> { - onTempoSliderUpdated(getCurrentTempo() - DEFAULT_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()); } } @@ -206,7 +210,7 @@ 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) @@ -214,7 +218,7 @@ public class PlaybackParameterDialog extends DialogFragment { if (pitchSlider != null) { pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); - pitchSlider.setProgress(strategy.progressOf(initialPitch)); + pitchSlider.setProgress(strategy.progressOf(pitch)); pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener()); } } @@ -222,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 @@ -252,35 +256,37 @@ public class PlaybackParameterDialog extends DialogFragment { if (stepSizeOnePercentText != null) { stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE)); stepSizeOnePercentText.setOnClickListener(view -> - setupStepSize(STEP_ONE_PERCENT_VALUE)); + changeStepSize(STEP_ONE_PERCENT_VALUE)); } if (stepSizeFivePercentText != null) { stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE)); stepSizeFivePercentText.setOnClickListener(view -> - setupStepSize(STEP_FIVE_PERCENT_VALUE)); + changeStepSize(STEP_FIVE_PERCENT_VALUE)); } if (stepSizeTenPercentText != null) { stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE)); stepSizeTenPercentText.setOnClickListener(view -> - setupStepSize(STEP_TEN_PERCENT_VALUE)); + changeStepSize(STEP_TEN_PERCENT_VALUE)); } if (stepSizeTwentyFivePercentText != null) { stepSizeTwentyFivePercentText.setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE)); stepSizeTwentyFivePercentText.setOnClickListener(view -> - setupStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE)); + changeStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE)); } if (stepSizeOneHundredPercentText != null) { stepSizeOneHundredPercentText.setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE)); stepSizeOneHundredPercentText.setOnClickListener(view -> - setupStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE)); + changeStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE)); } } - private void setupStepSize(final double stepSize) { + private void changeStepSize(final double stepSize) { + this.stepSize = stepSize; + if (tempoStepUpText != null) { tempoStepUpText.setText(getStepUpPercentString(stepSize)); tempoStepUpText.setOnClickListener(view -> { @@ -419,15 +425,19 @@ public class PlaybackParameterDialog extends DialogFragment { } 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(); } diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 47937e882..a5933397e 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -260,43 +260,19 @@ - - - - + android:layout_below="@id/separatorStepSizeSelector"> + + + + + + From ca679f5932b8ac6c118ec24108af9b013d927b60 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 28 Jun 2018 12:18:02 -0700 Subject: [PATCH 21/22] -Fixed potential NPE when updating thumbnail in background player. --- .../main/java/org/schabi/newpipe/player/BackgroundPlayer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index dd70ea354..8594ca395 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -295,6 +295,7 @@ public final class BackgroundPlayer extends Service { //////////////////////////////////////////////////////////////////////////*/ private void updateNotificationThumbnail() { + if (basePlayerImpl == null) return; if (notRemoteView != null) { notRemoteView.setImageViewBitmap(R.id.notificationCover, basePlayerImpl.getThumbnail()); From 644766b5a67873d079e25986132f944e6705030f Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 28 Jun 2018 12:21:03 -0700 Subject: [PATCH 22/22] -Updated exoplayer lib to 2.8.2. --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6410cb823..c41535163 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { ext { supportLibVersion = '27.1.1' - exoPlayerLibVersion = '2.8.1' + exoPlayerLibVersion = '2.8.2' roomDbLibVersion = '1.1.1' leakCanaryLibVersion = '1.5.4' okHttpLibVersion = '3.10.0'