Skip to content

Commit 864725b

Browse files
authored
Merge pull request #12601 from AudricV/live-prefer-dash-and-fetch-bg-audio-only
2 parents eb7351c + c272309 commit 864725b

7 files changed

Lines changed: 164 additions & 21 deletions

File tree

app/src/main/java/org/schabi/newpipe/player/Player.java

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
115115
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
116116
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType;
117+
import org.schabi.newpipe.player.ui.BackgroundPlayerUi;
117118
import org.schabi.newpipe.player.ui.MainPlayerUi;
118119
import org.schabi.newpipe.player.ui.PlayerUi;
119120
import org.schabi.newpipe.player.ui.PlayerUiList;
@@ -271,6 +272,7 @@ public final class Player implements PlaybackListener, Listener {
271272
@NonNull
272273
private final HistoryRecordManager recordManager;
273274

275+
private boolean screenOn = true;
274276

275277
/*//////////////////////////////////////////////////////////////////////////
276278
// Constructor
@@ -574,6 +576,7 @@ private static PlayQueue getPlayQueueFromCache(@NonNull final Intent intent) {
574576

575577
private void initUIsForCurrentPlayerType() {
576578
if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN)
579+
|| (UIs.get(BackgroundPlayerUi.class).isPresent() && playerType == PlayerType.AUDIO)
577580
|| (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) {
578581
// correct UI already in place
579582
return;
@@ -592,14 +595,17 @@ private void initUIsForCurrentPlayerType() {
592595
switch (playerType) {
593596
case MAIN:
594597
UIs.destroyAll(PopupPlayerUi.class);
598+
UIs.destroyAll(BackgroundPlayerUi.class);
595599
UIs.addAndPrepare(new MainPlayerUi(this, binding));
596600
break;
597601
case POPUP:
598602
UIs.destroyAll(MainPlayerUi.class);
603+
UIs.destroyAll(BackgroundPlayerUi.class);
599604
UIs.addAndPrepare(new PopupPlayerUi(this, binding));
600605
break;
601606
case AUDIO:
602-
UIs.destroyAll(VideoPlayerUi.class);
607+
UIs.destroyAll(VideoPlayerUi.class); // destroys both MainPlayerUi and PopupPlayerUi
608+
UIs.addAndPrepare(new BackgroundPlayerUi(this));
603609
break;
604610
}
605611
}
@@ -842,6 +848,12 @@ private void onBroadcastReceived(final Intent intent) {
842848
case ACTION_SHUFFLE:
843849
toggleShuffleModeEnabled();
844850
break;
851+
case Intent.ACTION_SCREEN_OFF:
852+
screenOn = false;
853+
break;
854+
case Intent.ACTION_SCREEN_ON:
855+
screenOn = true;
856+
break;
845857
case Intent.ACTION_CONFIGURATION_CHANGED:
846858
if (DEBUG) {
847859
Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received");
@@ -2195,40 +2207,41 @@ private void notifyAudioTrackUpdateToListeners() {
21952207
}
21962208
}
21972209

2198-
public void useVideoSource(final boolean videoEnabled) {
2199-
if (playQueue == null || audioPlayerSelected()) {
2210+
public void useVideoAndSubtitles(final boolean videoAndSubtitlesEnabled) {
2211+
if (playQueue == null) {
22002212
return;
22012213
}
22022214

2203-
isAudioOnly = !videoEnabled;
2215+
isAudioOnly = !videoAndSubtitlesEnabled;
22042216

22052217
getCurrentStreamInfo().ifPresentOrElse(info -> {
22062218
// In case we don't know the source type, fall back to either video-with-audio, or
22072219
// audio-only source type
22082220
final SourceType sourceType = videoResolver.getStreamSourceType()
22092221
.orElse(SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY);
22102222

2223+
setRecovery(); // making sure to save playback position before reloadPlayQueueManager()
2224+
22112225
if (playQueueManagerReloadingNeeded(sourceType, info, getVideoRendererIndex())) {
22122226
reloadPlayQueueManager();
22132227
}
2214-
2215-
setRecovery();
2216-
2217-
// Disable or enable video and subtitles renderers depending of the videoEnabled value
2218-
trackSelector.setParameters(trackSelector.buildUponParameters()
2219-
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, !videoEnabled)
2220-
.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, !videoEnabled));
22212228
}, () -> {
22222229
/*
22232230
The current metadata may be null sometimes (for e.g. when using an unstable connection
2224-
in livestreams) so we will be not able to execute the block below
2231+
in livestreams) so we will be not able to execute the block above
22252232
22262233
Reload the play queue manager in this case, which is the behavior when we don't know the
22272234
index of the video renderer or playQueueManagerReloadingNeeded returns true
22282235
*/
2236+
setRecovery(); // making sure to save playback position before reloadPlayQueueManager()
22292237
reloadPlayQueueManager();
2230-
setRecovery();
22312238
});
2239+
2240+
// Disable or enable video and subtitles renderers depending of the
2241+
// videoAndSubtitlesEnabled value
2242+
trackSelector.setParameters(trackSelector.buildUponParameters()
2243+
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, !videoAndSubtitlesEnabled)
2244+
.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, !videoAndSubtitlesEnabled));
22322245
}
22332246

22342247
/**
@@ -2461,4 +2474,11 @@ private int getVideoRendererIndex() {
24612474
.orElse(RENDERER_UNAVAILABLE);
24622475
}
24632476
//endregion
2477+
2478+
/**
2479+
* @return whether the device screen is turned on.
2480+
*/
2481+
public boolean isScreenOn() {
2482+
return screenOn;
2483+
}
24642484
}

app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
129129
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
130130
cachelessDataSourceFactory);
131131
}
132+
133+
public DashMediaSource.Factory getLiveYoutubeDashMediaSourceFactory() {
134+
return new DashMediaSource.Factory(
135+
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
136+
cachelessDataSourceFactory)
137+
.setManifestParser(new YoutubeDashLiveManifestParser());
138+
}
132139
//endregion
133140

134141

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package org.schabi.newpipe.player.helper;
2+
3+
import android.net.Uri;
4+
5+
import androidx.annotation.NonNull;
6+
import androidx.annotation.Nullable;
7+
8+
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
9+
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
10+
import com.google.android.exoplayer2.source.dash.manifest.Period;
11+
import com.google.android.exoplayer2.source.dash.manifest.ProgramInformation;
12+
import com.google.android.exoplayer2.source.dash.manifest.ServiceDescriptionElement;
13+
import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
14+
15+
import java.util.List;
16+
17+
/**
18+
* A {@link DashManifestParser} fixing YouTube DASH manifests to allow starting playback from the
19+
* newest period available instead of the earliest one in some cases.
20+
*
21+
* <p>
22+
* It changes the {@code availabilityStartTime} passed to a custom value doing the workaround.
23+
* A better approach to fix the issue should be investigated and used in the future.
24+
* </p>
25+
*/
26+
public class YoutubeDashLiveManifestParser extends DashManifestParser {
27+
28+
// Result of Util.parseXsDateTime("1970-01-01T00:00:00Z")
29+
private static final long AVAILABILITY_START_TIME_TO_USE = 0;
30+
31+
// There is no computation made with the availabilityStartTime value in the
32+
// parseMediaPresentationDescription method itself, so we can just override methods called in
33+
// this method using the workaround value
34+
// Overriding parsePeriod does not seem to be needed
35+
36+
@SuppressWarnings("checkstyle:ParameterNumber")
37+
@NonNull
38+
@Override
39+
protected DashManifest buildMediaPresentationDescription(
40+
final long availabilityStartTime,
41+
final long durationMs,
42+
final long minBufferTimeMs,
43+
final boolean dynamic,
44+
final long minUpdateTimeMs,
45+
final long timeShiftBufferDepthMs,
46+
final long suggestedPresentationDelayMs,
47+
final long publishTimeMs,
48+
@Nullable final ProgramInformation programInformation,
49+
@Nullable final UtcTimingElement utcTiming,
50+
@Nullable final ServiceDescriptionElement serviceDescription,
51+
@Nullable final Uri location,
52+
@NonNull final List<Period> periods) {
53+
return super.buildMediaPresentationDescription(
54+
AVAILABILITY_START_TIME_TO_USE,
55+
durationMs,
56+
minBufferTimeMs,
57+
dynamic,
58+
minUpdateTimeMs,
59+
timeShiftBufferDepthMs,
60+
suggestedPresentationDelayMs,
61+
publishTimeMs,
62+
programInformation,
63+
utcTiming,
64+
serviceDescription,
65+
location,
66+
periods);
67+
}
68+
}

app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,15 @@ static MediaSource maybeBuildLiveMediaSource(final PlayerDataSource dataSource,
201201

202202
try {
203203
final StreamInfoTag tag = StreamInfoTag.of(info);
204-
if (!info.getHlsUrl().isEmpty()) {
205-
return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.CONTENT_TYPE_HLS, tag);
206-
} else if (!info.getDashMpdUrl().isEmpty()) {
204+
// Prefer DASH over HLS because of an exoPlayer bug that causes the background player to
205+
// also fetch the video stream even if it is supposed to just fetch the audio stream.
206+
if (!info.getDashMpdUrl().isEmpty()) {
207207
return buildLiveMediaSource(
208208
dataSource, info.getDashMpdUrl(), C.CONTENT_TYPE_DASH, tag);
209209
}
210+
if (!info.getHlsUrl().isEmpty()) {
211+
return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.CONTENT_TYPE_HLS, tag);
212+
}
210213
} catch (final Exception e) {
211214
Log.w(TAG, "Error when generating live media source, falling back to standard sources",
212215
e);
@@ -225,7 +228,11 @@ static MediaSource buildLiveMediaSource(final PlayerDataSource dataSource,
225228
factory = dataSource.getLiveSsMediaSourceFactory();
226229
break;
227230
case C.CONTENT_TYPE_DASH:
228-
factory = dataSource.getLiveDashMediaSourceFactory();
231+
if (metadata.getServiceId() == ServiceList.YouTube.getServiceId()) {
232+
factory = dataSource.getLiveYoutubeDashMediaSourceFactory();
233+
} else {
234+
factory = dataSource.getLiveDashMediaSourceFactory();
235+
}
229236
break;
230237
case C.CONTENT_TYPE_HLS:
231238
factory = dataSource.getLiveHlsMediaSourceFactory();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.schabi.newpipe.player.ui;
2+
3+
import androidx.annotation.NonNull;
4+
5+
import org.schabi.newpipe.player.Player;
6+
7+
/**
8+
* This is not a "graphical" UI for the background player, but it is used to disable fetching video
9+
* and text tracks with it.
10+
*
11+
* <p>
12+
* This allows reducing data usage for manifest sources with demuxed audio and video,
13+
* such as livestreams.
14+
* </p>
15+
*/
16+
public class BackgroundPlayerUi extends PlayerUi {
17+
18+
public BackgroundPlayerUi(@NonNull final Player player) {
19+
super(player);
20+
}
21+
22+
@Override
23+
public void initPlayback() {
24+
super.initPlayback();
25+
26+
// Make sure to disable video and subtitles track types
27+
player.useVideoAndSubtitles(false);
28+
}
29+
}

app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ public void initPlayback() {
216216
playQueueAdapter = new PlayQueueAdapter(context,
217217
Objects.requireNonNull(player.getPlayQueue()));
218218
segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener());
219+
220+
// Make sure video and text tracks are enabled if the user is in the app, in the case user
221+
// switched from background player to main player
222+
player.useVideoAndSubtitles(fragmentIsVisible);
219223
}
220224

221225
@Override
@@ -331,7 +335,7 @@ public void onBroadcastReceived(final Intent intent) {
331335
} else if (VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED.equals(intent.getAction())) {
332336
// Restore video source when user returns to the fragment
333337
fragmentIsVisible = true;
334-
player.useVideoSource(true);
338+
player.useVideoAndSubtitles(true);
335339

336340
// When a user returns from background, the system UI will always be shown even if
337341
// controls are invisible: hide it in that case
@@ -370,7 +374,7 @@ private void onFragmentStopped() {
370374
if (player.isPlaying() || player.isLoading()) {
371375
switch (getMinimizeOnExitAction(context)) {
372376
case MINIMIZE_ON_EXIT_MODE_BACKGROUND:
373-
player.useVideoSource(false);
377+
player.useVideoAndSubtitles(false);
374378
break;
375379
case MINIMIZE_ON_EXIT_MODE_POPUP:
376380
getParentActivity().ifPresent(activity -> {

app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,14 @@ private void initPopupCloseOverlay() {
152152
windowManager.addView(closeOverlayBinding.getRoot(), closeOverlayLayoutParams);
153153
}
154154

155+
@Override
156+
public void initPlayback() {
157+
super.initPlayback();
158+
// Make sure video and text tracks are enabled if the screen is turned on (which should
159+
// always be the case), in the case user switched from background player to popup player
160+
player.useVideoAndSubtitles(player.isScreenOn());
161+
}
162+
155163
@Override
156164
protected void setupElementsVisibility() {
157165
binding.fullScreenButton.setVisibility(View.VISIBLE);
@@ -219,10 +227,10 @@ public void onBroadcastReceived(final Intent intent) {
219227
} else if (player.isPlaying() || player.isLoading()) {
220228
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
221229
// Use only audio source when screen turns off while popup player is playing
222-
player.useVideoSource(false);
230+
player.useVideoAndSubtitles(false);
223231
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
224232
// Restore video source when screen turns on and user was watching video in popup
225-
player.useVideoSource(true);
233+
player.useVideoAndSubtitles(true);
226234
}
227235
}
228236
}

0 commit comments

Comments
 (0)