mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2025-10-03 17:59:41 +02:00
Merge 268ae39e2b
into 965eea2124
This commit is contained in:
commit
4e9aeb4ced
7 changed files with 159 additions and 19 deletions
|
@ -114,6 +114,7 @@ import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
||||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType;
|
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.MainPlayerUi;
|
||||||
import org.schabi.newpipe.player.ui.PlayerUi;
|
import org.schabi.newpipe.player.ui.PlayerUi;
|
||||||
import org.schabi.newpipe.player.ui.PlayerUiList;
|
import org.schabi.newpipe.player.ui.PlayerUiList;
|
||||||
|
@ -271,6 +272,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
@NonNull
|
@NonNull
|
||||||
private final HistoryRecordManager recordManager;
|
private final HistoryRecordManager recordManager;
|
||||||
|
|
||||||
|
private boolean screenOn = true;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
|
@ -592,14 +594,17 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
switch (playerType) {
|
switch (playerType) {
|
||||||
case MAIN:
|
case MAIN:
|
||||||
UIs.destroyAll(PopupPlayerUi.class);
|
UIs.destroyAll(PopupPlayerUi.class);
|
||||||
|
UIs.destroyAll(BackgroundPlayerUi.class);
|
||||||
UIs.addAndPrepare(new MainPlayerUi(this, binding));
|
UIs.addAndPrepare(new MainPlayerUi(this, binding));
|
||||||
break;
|
break;
|
||||||
case POPUP:
|
case POPUP:
|
||||||
UIs.destroyAll(MainPlayerUi.class);
|
UIs.destroyAll(MainPlayerUi.class);
|
||||||
|
UIs.destroyAll(BackgroundPlayerUi.class);
|
||||||
UIs.addAndPrepare(new PopupPlayerUi(this, binding));
|
UIs.addAndPrepare(new PopupPlayerUi(this, binding));
|
||||||
break;
|
break;
|
||||||
case AUDIO:
|
case AUDIO:
|
||||||
UIs.destroyAll(VideoPlayerUi.class);
|
UIs.destroyAll(VideoPlayerUi.class);
|
||||||
|
UIs.addAndPrepare(new BackgroundPlayerUi(this));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -842,6 +847,12 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
case ACTION_SHUFFLE:
|
case ACTION_SHUFFLE:
|
||||||
toggleShuffleModeEnabled();
|
toggleShuffleModeEnabled();
|
||||||
break;
|
break;
|
||||||
|
case Intent.ACTION_SCREEN_OFF:
|
||||||
|
screenOn = false;
|
||||||
|
break;
|
||||||
|
case Intent.ACTION_SCREEN_ON:
|
||||||
|
screenOn = true;
|
||||||
|
break;
|
||||||
case Intent.ACTION_CONFIGURATION_CHANGED:
|
case Intent.ACTION_CONFIGURATION_CHANGED:
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received");
|
Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received");
|
||||||
|
@ -2195,12 +2206,12 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useVideoSource(final boolean videoEnabled) {
|
public void useVideoAndSubtitles(final boolean videoAndSubtitlesEnabled) {
|
||||||
if (playQueue == null || audioPlayerSelected()) {
|
if (playQueue == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isAudioOnly = !videoEnabled;
|
isAudioOnly = !videoAndSubtitlesEnabled;
|
||||||
|
|
||||||
getCurrentStreamInfo().ifPresentOrElse(info -> {
|
getCurrentStreamInfo().ifPresentOrElse(info -> {
|
||||||
// In case we don't know the source type, fall back to either video-with-audio, or
|
// 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()
|
final SourceType sourceType = videoResolver.getStreamSourceType()
|
||||||
.orElse(SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY);
|
.orElse(SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY);
|
||||||
|
|
||||||
|
setRecovery();
|
||||||
|
|
||||||
if (playQueueManagerReloadingNeeded(sourceType, info, getVideoRendererIndex())) {
|
if (playQueueManagerReloadingNeeded(sourceType, info, getVideoRendererIndex())) {
|
||||||
reloadPlayQueueManager();
|
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
|
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
|
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
|
index of the video renderer or playQueueManagerReloadingNeeded returns true
|
||||||
*/
|
*/
|
||||||
reloadPlayQueueManager();
|
|
||||||
setRecovery();
|
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);
|
.orElse(RENDERER_UNAVAILABLE);
|
||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the device screen is turned on.
|
||||||
|
*/
|
||||||
|
public boolean isScreenOn() {
|
||||||
|
return screenOn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,6 +129,13 @@ public class PlayerDataSource {
|
||||||
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
|
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
|
||||||
cachelessDataSourceFactory);
|
cachelessDataSourceFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DashMediaSource.Factory getLiveYoutubeDashMediaSourceFactory() {
|
||||||
|
return new DashMediaSource.Factory(
|
||||||
|
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
|
||||||
|
cachelessDataSourceFactory)
|
||||||
|
.setManifestParser(new YoutubeDashLiveManifestParser());
|
||||||
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -201,12 +201,13 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final StreamInfoTag tag = StreamInfoTag.of(info);
|
final StreamInfoTag tag = StreamInfoTag.of(info);
|
||||||
if (!info.getHlsUrl().isEmpty()) {
|
if (!info.getDashMpdUrl().isEmpty()) {
|
||||||
return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.CONTENT_TYPE_HLS, tag);
|
|
||||||
} else if (!info.getDashMpdUrl().isEmpty()) {
|
|
||||||
return buildLiveMediaSource(
|
return buildLiveMediaSource(
|
||||||
dataSource, info.getDashMpdUrl(), C.CONTENT_TYPE_DASH, tag);
|
dataSource, info.getDashMpdUrl(), C.CONTENT_TYPE_DASH, tag);
|
||||||
}
|
}
|
||||||
|
if (!info.getHlsUrl().isEmpty()) {
|
||||||
|
return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.CONTENT_TYPE_HLS, tag);
|
||||||
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
Log.w(TAG, "Error when generating live media source, falling back to standard sources",
|
Log.w(TAG, "Error when generating live media source, falling back to standard sources",
|
||||||
e);
|
e);
|
||||||
|
@ -225,7 +226,11 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
|
||||||
factory = dataSource.getLiveSsMediaSourceFactory();
|
factory = dataSource.getLiveSsMediaSourceFactory();
|
||||||
break;
|
break;
|
||||||
case C.CONTENT_TYPE_DASH:
|
case C.CONTENT_TYPE_DASH:
|
||||||
|
if (metadata.getServiceId() == ServiceList.YouTube.getServiceId()) {
|
||||||
|
factory = dataSource.getLiveYoutubeDashMediaSourceFactory();
|
||||||
|
} else {
|
||||||
factory = dataSource.getLiveDashMediaSourceFactory();
|
factory = dataSource.getLiveDashMediaSourceFactory();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case C.CONTENT_TYPE_HLS:
|
case C.CONTENT_TYPE_HLS:
|
||||||
factory = dataSource.getLiveHlsMediaSourceFactory();
|
factory = dataSource.getLiveHlsMediaSourceFactory();
|
||||||
|
|
|
@ -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
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -216,6 +216,10 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
|
||||||
playQueueAdapter = new PlayQueueAdapter(context,
|
playQueueAdapter = new PlayQueueAdapter(context,
|
||||||
Objects.requireNonNull(player.getPlayQueue()));
|
Objects.requireNonNull(player.getPlayQueue()));
|
||||||
segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener());
|
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
|
@Override
|
||||||
|
@ -329,7 +333,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
|
||||||
} else if (VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED.equals(intent.getAction())) {
|
} else if (VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED.equals(intent.getAction())) {
|
||||||
// Restore video source when user returns to the fragment
|
// Restore video source when user returns to the fragment
|
||||||
fragmentIsVisible = true;
|
fragmentIsVisible = true;
|
||||||
player.useVideoSource(true);
|
player.useVideoAndSubtitles(true);
|
||||||
|
|
||||||
// When a user returns from background, the system UI will always be shown even if
|
// When a user returns from background, the system UI will always be shown even if
|
||||||
// controls are invisible: hide it in that case
|
// 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()) {
|
if (player.isPlaying() || player.isLoading()) {
|
||||||
switch (getMinimizeOnExitAction(context)) {
|
switch (getMinimizeOnExitAction(context)) {
|
||||||
case MINIMIZE_ON_EXIT_MODE_BACKGROUND:
|
case MINIMIZE_ON_EXIT_MODE_BACKGROUND:
|
||||||
player.useVideoSource(false);
|
player.useVideoAndSubtitles(false);
|
||||||
break;
|
break;
|
||||||
case MINIMIZE_ON_EXIT_MODE_POPUP:
|
case MINIMIZE_ON_EXIT_MODE_POPUP:
|
||||||
getParentActivity().ifPresent(activity -> {
|
getParentActivity().ifPresent(activity -> {
|
||||||
|
|
|
@ -151,6 +151,14 @@ public final class PopupPlayerUi extends VideoPlayerUi {
|
||||||
windowManager.addView(closeOverlayBinding.getRoot(), closeOverlayLayoutParams);
|
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
|
@Override
|
||||||
protected void setupElementsVisibility() {
|
protected void setupElementsVisibility() {
|
||||||
binding.fullScreenButton.setVisibility(View.VISIBLE);
|
binding.fullScreenButton.setVisibility(View.VISIBLE);
|
||||||
|
@ -216,10 +224,10 @@ public final class PopupPlayerUi extends VideoPlayerUi {
|
||||||
} else if (player.isPlaying() || player.isLoading()) {
|
} else if (player.isPlaying() || player.isLoading()) {
|
||||||
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
|
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
|
||||||
// Use only audio source when screen turns off while popup player is playing
|
// 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())) {
|
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
|
||||||
// Restore video source when screen turns on and user was watching video in popup
|
// Restore video source when screen turns on and user was watching video in popup
|
||||||
player.useVideoSource(true);
|
player.useVideoAndSubtitles(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue