diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 39f941693..e40e0fcc7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -114,6 +114,7 @@ import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; +import org.schabi.newpipe.player.ui.BackgroundPlayerUi; import org.schabi.newpipe.player.ui.MainPlayerUi; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.player.ui.PlayerUiList; @@ -271,6 +272,7 @@ public final class Player implements PlaybackListener, Listener { @NonNull private final HistoryRecordManager recordManager; + private boolean screenOn = true; /*////////////////////////////////////////////////////////////////////////// // Constructor @@ -592,14 +594,17 @@ public final class Player implements PlaybackListener, Listener { switch (playerType) { case MAIN: UIs.destroyAll(PopupPlayerUi.class); + UIs.destroyAll(BackgroundPlayerUi.class); UIs.addAndPrepare(new MainPlayerUi(this, binding)); break; case POPUP: UIs.destroyAll(MainPlayerUi.class); + UIs.destroyAll(BackgroundPlayerUi.class); UIs.addAndPrepare(new PopupPlayerUi(this, binding)); break; case AUDIO: UIs.destroyAll(VideoPlayerUi.class); + UIs.addAndPrepare(new BackgroundPlayerUi(this)); break; } } @@ -842,6 +847,12 @@ public final class Player implements PlaybackListener, Listener { case ACTION_SHUFFLE: toggleShuffleModeEnabled(); break; + case Intent.ACTION_SCREEN_OFF: + screenOn = false; + break; + case Intent.ACTION_SCREEN_ON: + screenOn = true; + break; case Intent.ACTION_CONFIGURATION_CHANGED: if (DEBUG) { Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received"); @@ -2195,12 +2206,12 @@ public final class Player implements PlaybackListener, Listener { } } - public void useVideoSource(final boolean videoEnabled) { - if (playQueue == null || audioPlayerSelected()) { + public void useVideoAndSubtitles(final boolean videoAndSubtitlesEnabled) { + if (playQueue == null) { return; } - isAudioOnly = !videoEnabled; + isAudioOnly = !videoAndSubtitlesEnabled; getCurrentStreamInfo().ifPresentOrElse(info -> { // In case we don't know the source type, fall back to either video-with-audio, or @@ -2208,16 +2219,11 @@ public final class Player implements PlaybackListener, Listener { final SourceType sourceType = videoResolver.getStreamSourceType() .orElse(SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY); + setRecovery(); + if (playQueueManagerReloadingNeeded(sourceType, info, getVideoRendererIndex())) { reloadPlayQueueManager(); } - - setRecovery(); - - // Disable or enable video and subtitles renderers depending of the videoEnabled value - trackSelector.setParameters(trackSelector.buildUponParameters() - .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, !videoEnabled) - .setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, !videoEnabled)); }, () -> { /* The current metadata may be null sometimes (for e.g. when using an unstable connection @@ -2226,9 +2232,15 @@ public final class Player implements PlaybackListener, Listener { Reload the play queue manager in this case, which is the behavior when we don't know the index of the video renderer or playQueueManagerReloadingNeeded returns true */ - reloadPlayQueueManager(); setRecovery(); + reloadPlayQueueManager(); }); + + // Disable or enable video and subtitles renderers depending of the + // videoAndSubtitlesEnabled value + trackSelector.setParameters(trackSelector.buildUponParameters() + .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, !videoAndSubtitlesEnabled) + .setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, !videoAndSubtitlesEnabled)); } /** @@ -2461,4 +2473,11 @@ public final class Player implements PlaybackListener, Listener { .orElse(RENDERER_UNAVAILABLE); } //endregion + + /** + * @return whether the device screen is turned on. + */ + public boolean isScreenOn() { + return screenOn; + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java index 0530d56e9..506b643fe 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java @@ -129,6 +129,13 @@ public class PlayerDataSource { getDefaultDashChunkSourceFactory(cachelessDataSourceFactory), cachelessDataSourceFactory); } + + public DashMediaSource.Factory getLiveYoutubeDashMediaSourceFactory() { + return new DashMediaSource.Factory( + getDefaultDashChunkSourceFactory(cachelessDataSourceFactory), + cachelessDataSourceFactory) + .setManifestParser(new YoutubeDashLiveManifestParser()); + } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/YoutubeDashLiveManifestParser.java b/app/src/main/java/org/schabi/newpipe/player/helper/YoutubeDashLiveManifestParser.java new file mode 100644 index 000000000..00f5de071 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/helper/YoutubeDashLiveManifestParser.java @@ -0,0 +1,68 @@ +package org.schabi.newpipe.player.helper; + +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.source.dash.manifest.DashManifest; +import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; +import com.google.android.exoplayer2.source.dash.manifest.Period; +import com.google.android.exoplayer2.source.dash.manifest.ProgramInformation; +import com.google.android.exoplayer2.source.dash.manifest.ServiceDescriptionElement; +import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement; + +import java.util.List; + +/** + * A {@link DashManifestParser} fixing YouTube DASH manifests to allow starting playback from the + * newest period available instead of the earliest one in some cases. + * + *
+ * It changes the {@code availabilityStartTime} passed to a custom value doing the workaround. + * A better approach to fix the issue should be investigated and used in the future. + *
+ */ +public class YoutubeDashLiveManifestParser extends DashManifestParser { + + // Result of Util.parseXsDateTime("1970-01-01T00:00:00Z") + private static final long AVAILABILITY_START_TIME_TO_USE = 0; + + // There is no computation made with the availabilityStartTime value in the + // parseMediaPresentationDescription method itself, so we can just override methods called in + // this method using the workaround value + // Overriding parsePeriod does not seem to be needed + + @SuppressWarnings("checkstyle:ParameterNumber") + @NonNull + @Override + protected DashManifest buildMediaPresentationDescription( + final long availabilityStartTime, + final long durationMs, + final long minBufferTimeMs, + final boolean dynamic, + final long minUpdateTimeMs, + final long timeShiftBufferDepthMs, + final long suggestedPresentationDelayMs, + final long publishTimeMs, + @Nullable final ProgramInformation programInformation, + @Nullable final UtcTimingElement utcTiming, + @Nullable final ServiceDescriptionElement serviceDescription, + @Nullable final Uri location, + @NonNull final List+ * This allows reducing data usage for manifest sources with demuxed audio and video, + * such as livestreams. + *
+ */ +public class BackgroundPlayerUi extends PlayerUi { + + public BackgroundPlayerUi(@NonNull final Player player) { + super(player); + } + + @Override + public void initPlayback() { + super.initPlayback(); + + // Make sure to disable video and subtitles track types + player.useVideoAndSubtitles(false); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index d8efb30df..de4e9211a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -216,6 +216,10 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh playQueueAdapter = new PlayQueueAdapter(context, Objects.requireNonNull(player.getPlayQueue())); segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener()); + + // Make sure video and text tracks are enabled if the user is in the app, in the case user + // switched from background player to main player + player.useVideoAndSubtitles(fragmentIsVisible); } @Override @@ -329,7 +333,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } else if (VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED.equals(intent.getAction())) { // Restore video source when user returns to the fragment fragmentIsVisible = true; - player.useVideoSource(true); + player.useVideoAndSubtitles(true); // When a user returns from background, the system UI will always be shown even if // controls are invisible: hide it in that case @@ -368,7 +372,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh if (player.isPlaying() || player.isLoading()) { switch (getMinimizeOnExitAction(context)) { case MINIMIZE_ON_EXIT_MODE_BACKGROUND: - player.useVideoSource(false); + player.useVideoAndSubtitles(false); break; case MINIMIZE_ON_EXIT_MODE_POPUP: getParentActivity().ifPresent(activity -> { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index 6c98ab0fa..e967594fe 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -151,6 +151,14 @@ public final class PopupPlayerUi extends VideoPlayerUi { windowManager.addView(closeOverlayBinding.getRoot(), closeOverlayLayoutParams); } + @Override + public void initPlayback() { + super.initPlayback(); + // Make sure video and text tracks are enabled if the screen is turned on (which should + // always be the case), in the case user switched from background player to popup player + player.useVideoAndSubtitles(player.isScreenOn()); + } + @Override protected void setupElementsVisibility() { binding.fullScreenButton.setVisibility(View.VISIBLE); @@ -216,10 +224,10 @@ public final class PopupPlayerUi extends VideoPlayerUi { } else if (player.isPlaying() || player.isLoading()) { if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { // Use only audio source when screen turns off while popup player is playing - player.useVideoSource(false); + player.useVideoAndSubtitles(false); } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { // Restore video source when screen turns on and user was watching video in popup - player.useVideoSource(true); + player.useVideoAndSubtitles(true); } } }