fetch and merge
This commit is contained in:
commit
47798febed
208 changed files with 5335 additions and 1780 deletions
|
|
@ -51,9 +51,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
|
|
@ -154,6 +151,7 @@ import org.schabi.newpipe.info_list.StreamSegmentAdapter;
|
|||
import org.schabi.newpipe.ktx.AnimationType;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.player.MainPlayer.PlayerType;
|
||||
import org.schabi.newpipe.player.event.DisplayPortion;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.event.PlayerGestureListener;
|
||||
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
|
||||
|
|
@ -188,6 +186,7 @@ import org.schabi.newpipe.util.StreamTypeUtil;
|
|||
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.views.ExpandableSurfaceView;
|
||||
import org.schabi.newpipe.views.player.PlayerFastSeekOverlay;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -247,6 +246,7 @@ public final class Player implements
|
|||
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
|
||||
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
|
||||
public static final int DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds
|
||||
public static final int SEEK_OVERLAY_DURATION = 450; // 450 millis
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Other constants
|
||||
|
|
@ -313,7 +313,6 @@ public final class Player implements
|
|||
|
||||
private PlayerBinding binding;
|
||||
|
||||
private ValueAnimator controlViewAnimator;
|
||||
private final Handler controlsVisibilityHandler = new Handler();
|
||||
|
||||
// fullscreen player
|
||||
|
|
@ -365,6 +364,7 @@ public final class Player implements
|
|||
|
||||
private int maxGestureLength; // scaled
|
||||
private GestureDetectorCompat gestureDetector;
|
||||
private PlayerGestureListener playerGestureListener;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Listeners and disposables
|
||||
|
|
@ -449,6 +449,8 @@ public final class Player implements
|
|||
initPlayer(true);
|
||||
}
|
||||
initListeners();
|
||||
|
||||
setupPlayerSeekOverlay();
|
||||
}
|
||||
|
||||
private void initViews(@NonNull final PlayerBinding playerBinding) {
|
||||
|
|
@ -525,9 +527,9 @@ public final class Player implements
|
|||
binding.resizeTextView.setOnClickListener(this);
|
||||
binding.playbackLiveSync.setOnClickListener(this);
|
||||
|
||||
final PlayerGestureListener listener = new PlayerGestureListener(this, service);
|
||||
gestureDetector = new GestureDetectorCompat(context, listener);
|
||||
binding.getRoot().setOnTouchListener(listener);
|
||||
playerGestureListener = new PlayerGestureListener(this, service);
|
||||
gestureDetector = new GestureDetectorCompat(context, playerGestureListener);
|
||||
binding.getRoot().setOnTouchListener(playerGestureListener);
|
||||
|
||||
binding.queueButton.setOnClickListener(this);
|
||||
binding.segmentsButton.setOnClickListener(this);
|
||||
|
|
@ -578,6 +580,68 @@ public final class Player implements
|
|||
v.getPaddingRight(),
|
||||
v.getPaddingBottom()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Fast-For/Backward overlay.
|
||||
*/
|
||||
private void setupPlayerSeekOverlay() {
|
||||
binding.fastSeekOverlay
|
||||
.seekSecondsSupplier(
|
||||
() -> (int) (retrieveSeekDurationFromPreferences(this) / 1000.0f))
|
||||
.performListener(new PlayerFastSeekOverlay.PerformListener() {
|
||||
|
||||
@Override
|
||||
public void onDoubleTap() {
|
||||
animate(binding.fastSeekOverlay, true, SEEK_OVERLAY_DURATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDoubleTapEnd() {
|
||||
animate(binding.fastSeekOverlay, false, SEEK_OVERLAY_DURATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastSeekDirection getFastSeekDirection(
|
||||
@NonNull final DisplayPortion portion
|
||||
) {
|
||||
if (exoPlayerIsNull()) {
|
||||
// Abort seeking
|
||||
playerGestureListener.endMultiDoubleTap();
|
||||
return FastSeekDirection.NONE;
|
||||
}
|
||||
if (portion == DisplayPortion.LEFT) {
|
||||
// Check if it's possible to rewind
|
||||
// Small puffer to eliminate infinite rewind seeking
|
||||
if (simpleExoPlayer.getCurrentPosition() < 500L) {
|
||||
return FastSeekDirection.NONE;
|
||||
}
|
||||
return FastSeekDirection.BACKWARD;
|
||||
} else if (portion == DisplayPortion.RIGHT) {
|
||||
// Check if it's possible to fast-forward
|
||||
if (currentState == STATE_COMPLETED
|
||||
|| simpleExoPlayer.getCurrentPosition()
|
||||
>= simpleExoPlayer.getDuration()) {
|
||||
return FastSeekDirection.NONE;
|
||||
}
|
||||
return FastSeekDirection.FORWARD;
|
||||
}
|
||||
/* portion == DisplayPortion.MIDDLE */
|
||||
return FastSeekDirection.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(final boolean forward) {
|
||||
playerGestureListener.keepInDoubleTapMode();
|
||||
if (forward) {
|
||||
fastForward();
|
||||
} else {
|
||||
fastRewind();
|
||||
}
|
||||
}
|
||||
});
|
||||
playerGestureListener.doubleTapControls(binding.fastSeekOverlay);
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
|
@ -635,6 +699,7 @@ public final class Player implements
|
|||
final boolean isMuted = intent.getBooleanExtra(IS_MUTED, isMuted());
|
||||
|
||||
/*
|
||||
* TODO As seen in #7427 this does not work:
|
||||
* There are 3 situations when playback shouldn't be started from scratch (zero timestamp):
|
||||
* 1. User pressed on a timestamp link and the same video should be rewound to the timestamp
|
||||
* 2. User changed a player from, for example. main to popup, or from audio to main, etc
|
||||
|
|
@ -1795,71 +1860,6 @@ public final class Player implements
|
|||
return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a animation, and depending on goneOnEnd, will stay on the screen or be gone.
|
||||
*
|
||||
* @param drawableId the drawable that will be used to animate,
|
||||
* pass -1 to clear any animation that is visible
|
||||
* @param goneOnEnd will set the animation view to GONE on the end of the animation
|
||||
*/
|
||||
public void showAndAnimateControl(final int drawableId, final boolean goneOnEnd) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "showAndAnimateControl() called with: "
|
||||
+ "drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]");
|
||||
}
|
||||
if (controlViewAnimator != null && controlViewAnimator.isRunning()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning");
|
||||
}
|
||||
controlViewAnimator.end();
|
||||
}
|
||||
|
||||
if (drawableId == -1) {
|
||||
if (binding.controlAnimationView.getVisibility() == View.VISIBLE) {
|
||||
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(
|
||||
binding.controlAnimationView,
|
||||
PropertyValuesHolder.ofFloat(View.ALPHA, 1.0f, 0.0f),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1.0f),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1.0f)
|
||||
).setDuration(DEFAULT_CONTROLS_DURATION);
|
||||
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
binding.controlAnimationView.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
controlViewAnimator.start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final float scaleFrom = goneOnEnd ? 1f : 1f;
|
||||
final float scaleTo = goneOnEnd ? 1.8f : 1.4f;
|
||||
final float alphaFrom = goneOnEnd ? 1f : 0f;
|
||||
final float alphaTo = goneOnEnd ? 0f : 1f;
|
||||
|
||||
|
||||
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(
|
||||
binding.controlAnimationView,
|
||||
PropertyValuesHolder.ofFloat(View.ALPHA, alphaFrom, alphaTo),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_X, scaleFrom, scaleTo),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleFrom, scaleTo)
|
||||
);
|
||||
controlViewAnimator.setDuration(goneOnEnd ? 1000 : 500);
|
||||
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
binding.controlAnimationView.setVisibility(goneOnEnd ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
binding.controlAnimationView.setVisibility(View.VISIBLE);
|
||||
binding.controlAnimationView.setImageDrawable(
|
||||
AppCompatResources.getDrawable(context, drawableId));
|
||||
controlViewAnimator.start();
|
||||
}
|
||||
|
||||
public void showControlsThenHide() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "showControlsThenHide() called");
|
||||
|
|
@ -1904,6 +1904,7 @@ public final class Player implements
|
|||
}
|
||||
|
||||
private void showHideShadow(final boolean show, final long duration) {
|
||||
animate(binding.playbackControlsShadow, show, duration, AnimationType.ALPHA, 0, null);
|
||||
animate(binding.playerTopShadow, show, duration, AnimationType.ALPHA, 0, null);
|
||||
animate(binding.playerBottomShadow, show, duration, AnimationType.ALPHA, 0, null);
|
||||
}
|
||||
|
|
@ -2047,7 +2048,7 @@ public final class Player implements
|
|||
if (currentState == STATE_BLOCKED) {
|
||||
changeState(STATE_BUFFERING);
|
||||
}
|
||||
simpleExoPlayer.setMediaSource(mediaSource);
|
||||
simpleExoPlayer.setMediaSource(mediaSource, false);
|
||||
simpleExoPlayer.prepare();
|
||||
}
|
||||
|
||||
|
|
@ -2101,8 +2102,8 @@ public final class Player implements
|
|||
startProgressLoop();
|
||||
}
|
||||
|
||||
controlsVisibilityHandler.removeCallbacksAndMessages(null);
|
||||
animate(binding.playbackControlRoot, false, DEFAULT_CONTROLS_DURATION);
|
||||
// if we are e.g. switching players, hide controls
|
||||
hideControls(DEFAULT_CONTROLS_DURATION, 0);
|
||||
|
||||
binding.playbackSeekBar.setEnabled(false);
|
||||
binding.playbackSeekBar.getThumb()
|
||||
|
|
@ -2129,8 +2130,6 @@ public final class Player implements
|
|||
|
||||
updateStreamRelatedViews();
|
||||
|
||||
showAndAnimateControl(-1, true);
|
||||
|
||||
binding.playbackSeekBar.setEnabled(true);
|
||||
binding.playbackSeekBar.getThumb()
|
||||
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
|
||||
|
|
@ -2178,18 +2177,21 @@ public final class Player implements
|
|||
stopProgressLoop();
|
||||
}
|
||||
|
||||
showControls(400);
|
||||
binding.loadingPanel.setVisibility(View.GONE);
|
||||
|
||||
animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0,
|
||||
() -> {
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow);
|
||||
animatePlayButtons(true, 200);
|
||||
if (!isQueueVisible) {
|
||||
binding.playPauseButton.requestFocus();
|
||||
}
|
||||
});
|
||||
// Don't let UI elements popup during double tap seeking. This state is entered sometimes
|
||||
// during seeking/loading. This if-else check ensures that the controls aren't popping up.
|
||||
if (!playerGestureListener.isDoubleTapping()) {
|
||||
showControls(400);
|
||||
binding.loadingPanel.setVisibility(View.GONE);
|
||||
|
||||
animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0,
|
||||
() -> {
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow);
|
||||
animatePlayButtons(true, 200);
|
||||
if (!isQueueVisible) {
|
||||
binding.playPauseButton.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
changePopupWindowFlags(IDLE_WINDOW_FLAGS);
|
||||
|
||||
// Remove running notification when user does not want minimization to background or popup
|
||||
|
|
@ -2207,7 +2209,6 @@ public final class Player implements
|
|||
if (DEBUG) {
|
||||
Log.d(TAG, "onPausedSeek() called");
|
||||
}
|
||||
showAndAnimateControl(-1, true);
|
||||
|
||||
animatePlayButtons(false, 100);
|
||||
binding.getRoot().setKeepScreenOn(true);
|
||||
|
|
@ -2516,23 +2517,11 @@ public final class Player implements
|
|||
Log.e(TAG, "ExoPlayer - onPlayerError() called with:", error);
|
||||
|
||||
saveStreamProgressState();
|
||||
|
||||
// create error notification
|
||||
final ErrorInfo errorInfo;
|
||||
if (currentMetadata == null) {
|
||||
errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
|
||||
"Player error[type=" + error.type + "] occurred, currentMetadata is null");
|
||||
} else {
|
||||
errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
|
||||
"Player error[type=" + error.type + "] occurred while playing "
|
||||
+ currentMetadata.getMetadata().getUrl(),
|
||||
currentMetadata.getMetadata());
|
||||
}
|
||||
ErrorUtil.createNotification(context, errorInfo);
|
||||
boolean isCatchableException = false;
|
||||
|
||||
switch (error.type) {
|
||||
case ExoPlaybackException.TYPE_SOURCE:
|
||||
processSourceError(error.getSourceException());
|
||||
isCatchableException = processSourceError(error.getSourceException());
|
||||
break;
|
||||
case ExoPlaybackException.TYPE_UNEXPECTED:
|
||||
setRecovery();
|
||||
|
|
@ -2545,22 +2534,60 @@ public final class Player implements
|
|||
break;
|
||||
}
|
||||
|
||||
if (isCatchableException) {
|
||||
return;
|
||||
}
|
||||
|
||||
createErrorNotification(error);
|
||||
|
||||
if (fragmentListener != null) {
|
||||
fragmentListener.onPlayerError(error);
|
||||
}
|
||||
}
|
||||
|
||||
private void processSourceError(final IOException error) {
|
||||
if (exoPlayerIsNull() || playQueue == null) {
|
||||
return;
|
||||
private void createErrorNotification(@NonNull final ExoPlaybackException error) {
|
||||
final ErrorInfo errorInfo;
|
||||
if (currentMetadata == null) {
|
||||
errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
|
||||
"Player error[type=" + error.type + "] occurred, currentMetadata is null");
|
||||
} else {
|
||||
errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
|
||||
"Player error[type=" + error.type + "] occurred while playing "
|
||||
+ currentMetadata.getMetadata().getUrl(),
|
||||
currentMetadata.getMetadata());
|
||||
}
|
||||
ErrorUtil.createNotification(context, errorInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an {@link IOException} returned by {@link ExoPlaybackException#getSourceException()}
|
||||
* for {@link ExoPlaybackException#TYPE_SOURCE} exceptions.
|
||||
*
|
||||
* <p>
|
||||
* This method sets the recovery position and sends an error message to the play queue if the
|
||||
* exception is not a {@link BehindLiveWindowException}.
|
||||
* </p>
|
||||
* @param error the source error which was thrown by ExoPlayer
|
||||
* @return whether the exception thrown is a {@link BehindLiveWindowException} ({@code false}
|
||||
* is always returned if ExoPlayer or the play queue is null)
|
||||
*/
|
||||
private boolean processSourceError(final IOException error) {
|
||||
if (exoPlayerIsNull() || playQueue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setRecovery();
|
||||
|
||||
if (error instanceof BehindLiveWindowException) {
|
||||
reloadPlayQueueManager();
|
||||
} else {
|
||||
playQueue.error();
|
||||
simpleExoPlayer.seekToDefaultPosition();
|
||||
simpleExoPlayer.prepare();
|
||||
// Inform the user that we are reloading the stream by switching to the buffering state
|
||||
onBuffering();
|
||||
return true;
|
||||
}
|
||||
|
||||
playQueue.error();
|
||||
return false;
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
|
@ -2837,7 +2864,6 @@ public final class Player implements
|
|||
}
|
||||
seekBy(retrieveSeekDurationFromPreferences(this));
|
||||
triggerProgressUpdate();
|
||||
showAndAnimateControl(R.drawable.ic_fast_forward, true);
|
||||
}
|
||||
|
||||
public void fastRewind() {
|
||||
|
|
@ -2846,7 +2872,6 @@ public final class Player implements
|
|||
}
|
||||
seekBy(-retrieveSeekDurationFromPreferences(this));
|
||||
triggerProgressUpdate();
|
||||
showAndAnimateControl(R.drawable.ic_fast_rewind, true);
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
|
@ -4278,6 +4303,10 @@ public final class Player implements
|
|||
return binding.currentDisplaySeek;
|
||||
}
|
||||
|
||||
public PlayerFastSeekOverlay getFastSeekOverlay() {
|
||||
return binding.fastSeekOverlay;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public WindowManager.LayoutParams getPopupLayoutParams() {
|
||||
return popupLayoutParams;
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ abstract class BasePlayerGestureListener(
|
|||
var doubleTapControls: DoubleTapListener? = null
|
||||
private set
|
||||
|
||||
val isDoubleTapEnabled: Boolean
|
||||
private val isDoubleTapEnabled: Boolean
|
||||
get() = doubleTapDelay > 0
|
||||
|
||||
var isDoubleTapping = false
|
||||
|
|
@ -459,10 +459,6 @@ abstract class BasePlayerGestureListener(
|
|||
doubleTapControls?.onDoubleTapFinished()
|
||||
}
|
||||
|
||||
fun enableMultiDoubleTap(enable: Boolean) = apply {
|
||||
doubleTapDelay = if (enable) DOUBLE_TAP_DELAY else 0
|
||||
}
|
||||
|
||||
// ///////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
// ///////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -55,12 +55,10 @@ public class PlayerGestureListener
|
|||
player.hideControls(0, 0);
|
||||
}
|
||||
|
||||
if (portion == DisplayPortion.LEFT) {
|
||||
player.fastRewind();
|
||||
if (portion == DisplayPortion.LEFT || portion == DisplayPortion.RIGHT) {
|
||||
startMultiDoubleTap(event);
|
||||
} else if (portion == DisplayPortion.MIDDLE) {
|
||||
player.playPause();
|
||||
} else if (portion == DisplayPortion.RIGHT) {
|
||||
player.fastForward();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,10 +230,10 @@ public class PlayerGestureListener
|
|||
if (DEBUG) {
|
||||
Log.d(TAG, "onPopupResizingStart called");
|
||||
}
|
||||
player.showAndAnimateControl(-1, true);
|
||||
player.getLoadingPanel().setVisibility(View.GONE);
|
||||
|
||||
player.hideControls(0, 0);
|
||||
animate(player.getFastSeekOverlay(), false, 0);
|
||||
animate(player.getCurrentDisplaySeek(), false, 0, ALPHA, 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.google.android.exoplayer2.source.SingleSampleMediaSource;
|
|||
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
|
||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTracker;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
|
|
@ -17,9 +18,18 @@ import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
|||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
public class PlayerDataSource {
|
||||
|
||||
public static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000;
|
||||
|
||||
/**
|
||||
* An approximately 4.3 times greater value than the
|
||||
* {@link DefaultHlsPlaylistTracker#DEFAULT_PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT default}
|
||||
* to ensure that (very) low latency livestreams which got stuck for a moment don't crash too
|
||||
* early.
|
||||
*/
|
||||
private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 15;
|
||||
private static final int MANIFEST_MINIMUM_RETRY = 5;
|
||||
private static final int EXTRACTOR_MINIMUM_RETRY = Integer.MAX_VALUE;
|
||||
public static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000;
|
||||
|
||||
private final DataSource.Factory cacheDataSourceFactory;
|
||||
private final DataSource.Factory cachelessDataSourceFactory;
|
||||
|
|
@ -44,8 +54,13 @@ public class PlayerDataSource {
|
|||
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
|
||||
return new HlsMediaSource.Factory(cachelessDataSourceFactory)
|
||||
.setAllowChunklessPreparation(true)
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
|
||||
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(
|
||||
MANIFEST_MINIMUM_RETRY))
|
||||
.setPlaylistTrackerFactory((dataSourceFactory, loadErrorHandlingPolicy,
|
||||
playlistParserFactory) ->
|
||||
new DefaultHlsPlaylistTracker(dataSourceFactory, loadErrorHandlingPolicy,
|
||||
playlistParserFactory, PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT)
|
||||
);
|
||||
}
|
||||
|
||||
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue