Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 30 additions & 11 deletions app/src/main/java/org/schabi/newpipe/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
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;
Expand Down Expand Up @@ -265,6 +266,7 @@ public final class Player implements PlaybackListener, Listener {
@NonNull
private final HistoryRecordManager recordManager;

private boolean screenOn = true;

/*//////////////////////////////////////////////////////////////////////////
// Constructor
Expand Down Expand Up @@ -500,14 +502,17 @@ private void initUIsForCurrentPlayerType() {
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;
}
}
Expand Down Expand Up @@ -752,6 +757,12 @@ private void onBroadcastReceived(final Intent intent) {
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");
Expand Down Expand Up @@ -2095,29 +2106,24 @@ private void notifyAudioTrackUpdateToListeners() {
}
}

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
// audio-only source type
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
Expand All @@ -2126,9 +2132,15 @@ The current metadata may be null sometimes (for e.g. when using an unstable conn
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();
Comment on lines 2135 to +2136
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason behind swapping setRecovery() and reloadPlayQueueManager() here and above?

});

// 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));
}

/**
Expand Down Expand Up @@ -2361,4 +2373,11 @@ private int getVideoRendererIndex() {
.orElse(RENDERER_UNAVAILABLE);
}
//endregion

/**
* @return whether the device screen is turned on.
*/
public boolean isScreenOn() {
return screenOn;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
cachelessDataSourceFactory);
}

public DashMediaSource.Factory getLiveYoutubeDashMediaSourceFactory() {
return new DashMediaSource.Factory(
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
cachelessDataSourceFactory)
.setManifestParser(new YoutubeDashLiveManifestParser());
}
//endregion


Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>
* 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.
* </p>
*/
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<Period> periods) {
return super.buildMediaPresentationDescription(
AVAILABILITY_START_TIME_TO_USE,
durationMs,
minBufferTimeMs,
dynamic,
minUpdateTimeMs,
timeShiftBufferDepthMs,
suggestedPresentationDelayMs,
publishTimeMs,
programInformation,
utcTiming,
serviceDescription,
location,
periods);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,13 @@ static MediaSource maybeBuildLiveMediaSource(final PlayerDataSource dataSource,

try {
final StreamInfoTag tag = StreamInfoTag.of(info);
if (!info.getHlsUrl().isEmpty()) {
return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.CONTENT_TYPE_HLS, tag);
} else if (!info.getDashMpdUrl().isEmpty()) {
if (!info.getDashMpdUrl().isEmpty()) {
return buildLiveMediaSource(
dataSource, info.getDashMpdUrl(), C.CONTENT_TYPE_DASH, tag);
}
if (!info.getHlsUrl().isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment explaining that we prefer Dash over HLS because of the exoplayer bug (which I guess is the reason why you reordered here).

return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.CONTENT_TYPE_HLS, tag);
}
} catch (final Exception e) {
Log.w(TAG, "Error when generating live media source, falling back to standard sources",
e);
Expand All @@ -225,7 +226,11 @@ static MediaSource buildLiveMediaSource(final PlayerDataSource dataSource,
factory = dataSource.getLiveSsMediaSourceFactory();
break;
case C.CONTENT_TYPE_DASH:
factory = dataSource.getLiveDashMediaSourceFactory();
if (metadata.getServiceId() == ServiceList.YouTube.getServiceId()) {
factory = dataSource.getLiveYoutubeDashMediaSourceFactory();
} else {
factory = dataSource.getLiveDashMediaSourceFactory();
}
break;
case C.CONTENT_TYPE_HLS:
factory = dataSource.getLiveHlsMediaSourceFactory();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.schabi.newpipe.player.ui;

import androidx.annotation.NonNull;

import org.schabi.newpipe.player.Player;

/**
* This is not a real UI for the background player, it used to disable fetching video and text
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* This is not a real UI for the background player, it used to disable fetching video and text
* This is not a "graphical" UI for the background player, but it is used to disable fetching video and text

* tracks with it.
*
* <p>
* This allows reducing data usage for manifest sources with demuxed audio and video,
* such as livestreams.
* </p>
*/
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ public void initPlayback() {
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
Expand Down Expand Up @@ -329,7 +333,7 @@ public void onBroadcastReceived(final Intent intent) {
} 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
Expand Down Expand Up @@ -368,7 +372,7 @@ private void onFragmentStopped() {
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 -> {
Expand Down
12 changes: 10 additions & 2 deletions app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ private void initPopupCloseOverlay() {
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);
Expand Down Expand Up @@ -216,10 +224,10 @@ public void onBroadcastReceived(final Intent intent) {
} 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);
}
}
}
Expand Down