mirror of
https://github.com/androidx/media.git
synced 2025-05-10 00:59:51 +08:00
HLS: More control over buffering + tweak caption impl.
- Move all three buffering constants to a single class (the chunk source). - Increase the target buffer to 40s for increased robustness against temporary network blips. - Make values configurable via the chunk source constructor. - Treat captions as a text track for HLS. This allows them to be enabled/disabled through the demo app UI. Issue: #165
This commit is contained in:
parent
8e2801ce9b
commit
40f3172237
@ -25,7 +25,6 @@ import com.google.android.exoplayer.demo.full.player.DemoPlayer;
|
|||||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
|
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
|
||||||
import com.google.android.exoplayer.demo.full.player.HlsRendererBuilder;
|
import com.google.android.exoplayer.demo.full.player.HlsRendererBuilder;
|
||||||
import com.google.android.exoplayer.demo.full.player.SmoothStreamingRendererBuilder;
|
import com.google.android.exoplayer.demo.full.player.SmoothStreamingRendererBuilder;
|
||||||
import com.google.android.exoplayer.metadata.ClosedCaption;
|
|
||||||
import com.google.android.exoplayer.metadata.TxxxMetadata;
|
import com.google.android.exoplayer.metadata.TxxxMetadata;
|
||||||
import com.google.android.exoplayer.text.CaptionStyleCompat;
|
import com.google.android.exoplayer.text.CaptionStyleCompat;
|
||||||
import com.google.android.exoplayer.text.SubtitleView;
|
import com.google.android.exoplayer.text.SubtitleView;
|
||||||
@ -57,15 +56,13 @@ import android.widget.PopupMenu;
|
|||||||
import android.widget.PopupMenu.OnMenuItemClickListener;
|
import android.widget.PopupMenu.OnMenuItemClickListener;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity that plays media using {@link DemoPlayer}.
|
* An activity that plays media using {@link DemoPlayer}.
|
||||||
*/
|
*/
|
||||||
public class FullPlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
|
public class FullPlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
|
||||||
DemoPlayer.Listener, DemoPlayer.TextListener, DemoPlayer.Id3MetadataListener,
|
DemoPlayer.Listener, DemoPlayer.TextListener, DemoPlayer.Id3MetadataListener {
|
||||||
DemoPlayer.ClosedCaptionListener {
|
|
||||||
|
|
||||||
private static final String TAG = "FullPlayerActivity";
|
private static final String TAG = "FullPlayerActivity";
|
||||||
|
|
||||||
@ -198,7 +195,6 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
|
|||||||
player.addListener(this);
|
player.addListener(this);
|
||||||
player.setTextListener(this);
|
player.setTextListener(this);
|
||||||
player.setMetadataListener(this);
|
player.setMetadataListener(this);
|
||||||
player.setClosedCaptionListener(this);
|
|
||||||
player.seekTo(playerPosition);
|
player.seekTo(playerPosition);
|
||||||
playerNeedsPrepare = true;
|
playerNeedsPrepare = true;
|
||||||
mediaController.setMediaPlayer(player.getPlayerControl());
|
mediaController.setMediaPlayer(player.getPlayerControl());
|
||||||
@ -428,31 +424,6 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DemoPlayer.ClosedCaptioListener implementation
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClosedCaption(List<ClosedCaption> closedCaptions) {
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
|
||||||
for (ClosedCaption caption : closedCaptions) {
|
|
||||||
// Ignore control characters and just insert a new line in between words.
|
|
||||||
if (caption.type == ClosedCaption.TYPE_CTRL) {
|
|
||||||
if (stringBuilder.length() > 0
|
|
||||||
&& stringBuilder.charAt(stringBuilder.length() - 1) != '\n') {
|
|
||||||
stringBuilder.append('\n');
|
|
||||||
}
|
|
||||||
} else if (caption.type == ClosedCaption.TYPE_TEXT) {
|
|
||||||
stringBuilder.append(caption.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stringBuilder.length() > 0 && stringBuilder.charAt(stringBuilder.length() - 1) == '\n') {
|
|
||||||
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
|
|
||||||
}
|
|
||||||
if (stringBuilder.length() > 0) {
|
|
||||||
subtitleView.setVisibility(View.VISIBLE);
|
|
||||||
subtitleView.setText(stringBuilder.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SurfaceHolder.Callback implementation
|
// SurfaceHolder.Callback implementation
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -145,13 +145,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
void onId3Metadata(Map<String, Object> metadata);
|
void onId3Metadata(Map<String, Object> metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener for receiving closed captions parsed from the media stream.
|
|
||||||
*/
|
|
||||||
public interface ClosedCaptionListener {
|
|
||||||
void onClosedCaption(List<ClosedCaption> closedCaptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constants pulled into this class for convenience.
|
// Constants pulled into this class for convenience.
|
||||||
public static final int STATE_IDLE = ExoPlayer.STATE_IDLE;
|
public static final int STATE_IDLE = ExoPlayer.STATE_IDLE;
|
||||||
public static final int STATE_PREPARING = ExoPlayer.STATE_PREPARING;
|
public static final int STATE_PREPARING = ExoPlayer.STATE_PREPARING;
|
||||||
@ -162,13 +155,12 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
public static final int DISABLED_TRACK = -1;
|
public static final int DISABLED_TRACK = -1;
|
||||||
public static final int PRIMARY_TRACK = 0;
|
public static final int PRIMARY_TRACK = 0;
|
||||||
|
|
||||||
public static final int RENDERER_COUNT = 6;
|
public static final int RENDERER_COUNT = 5;
|
||||||
public static final int TYPE_VIDEO = 0;
|
public static final int TYPE_VIDEO = 0;
|
||||||
public static final int TYPE_AUDIO = 1;
|
public static final int TYPE_AUDIO = 1;
|
||||||
public static final int TYPE_TEXT = 2;
|
public static final int TYPE_TEXT = 2;
|
||||||
public static final int TYPE_TIMED_METADATA = 3;
|
public static final int TYPE_TIMED_METADATA = 3;
|
||||||
public static final int TYPE_CLOSED_CAPTIONS = 4;
|
public static final int TYPE_DEBUG = 4;
|
||||||
public static final int TYPE_DEBUG = 5;
|
|
||||||
|
|
||||||
private static final int RENDERER_BUILDING_STATE_IDLE = 1;
|
private static final int RENDERER_BUILDING_STATE_IDLE = 1;
|
||||||
private static final int RENDERER_BUILDING_STATE_BUILDING = 2;
|
private static final int RENDERER_BUILDING_STATE_BUILDING = 2;
|
||||||
@ -179,6 +171,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
private final PlayerControl playerControl;
|
private final PlayerControl playerControl;
|
||||||
private final Handler mainHandler;
|
private final Handler mainHandler;
|
||||||
private final CopyOnWriteArrayList<Listener> listeners;
|
private final CopyOnWriteArrayList<Listener> listeners;
|
||||||
|
private final StringBuilder closedCaptionStringBuilder;
|
||||||
|
|
||||||
private int rendererBuildingState;
|
private int rendererBuildingState;
|
||||||
private int lastReportedPlaybackState;
|
private int lastReportedPlaybackState;
|
||||||
@ -194,7 +187,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
|
|
||||||
private TextListener textListener;
|
private TextListener textListener;
|
||||||
private Id3MetadataListener id3MetadataListener;
|
private Id3MetadataListener id3MetadataListener;
|
||||||
private ClosedCaptionListener closedCaptionListener;
|
|
||||||
private InternalErrorListener internalErrorListener;
|
private InternalErrorListener internalErrorListener;
|
||||||
private InfoListener infoListener;
|
private InfoListener infoListener;
|
||||||
|
|
||||||
@ -210,6 +202,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
selectedTracks = new int[RENDERER_COUNT];
|
selectedTracks = new int[RENDERER_COUNT];
|
||||||
// Disable text initially.
|
// Disable text initially.
|
||||||
selectedTracks[TYPE_TEXT] = DISABLED_TRACK;
|
selectedTracks[TYPE_TEXT] = DISABLED_TRACK;
|
||||||
|
closedCaptionStringBuilder = new StringBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerControl getPlayerControl() {
|
public PlayerControl getPlayerControl() {
|
||||||
@ -240,10 +233,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
id3MetadataListener = listener;
|
id3MetadataListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setClosedCaptionListener(ClosedCaptionListener listener) {
|
|
||||||
closedCaptionListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSurface(Surface surface) {
|
public void setSurface(Surface surface) {
|
||||||
this.surface = surface;
|
this.surface = surface;
|
||||||
pushSurfaceAndVideoTrack(false);
|
pushSurfaceAndVideoTrack(false);
|
||||||
@ -275,6 +264,9 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
pushSurfaceAndVideoTrack(false);
|
pushSurfaceAndVideoTrack(false);
|
||||||
} else {
|
} else {
|
||||||
pushTrackSelection(type, true);
|
pushTrackSelection(type, true);
|
||||||
|
if (type == TYPE_TEXT && index == DISABLED_TRACK && textListener != null) {
|
||||||
|
textListener.onText(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,36 +475,28 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onText(String text) {
|
public void onText(String text) {
|
||||||
if (textListener != null) {
|
processText(text);
|
||||||
textListener.onText(text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ MetadataTrackRenderer.MetadataRenderer<Map<String, Object>>
|
/* package */ MetadataTrackRenderer.MetadataRenderer<Map<String, Object>>
|
||||||
getId3MetadataRenderer() {
|
getId3MetadataRenderer() {
|
||||||
return new MetadataTrackRenderer.MetadataRenderer<Map<String, Object>>() {
|
return new MetadataTrackRenderer.MetadataRenderer<Map<String, Object>>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMetadata(Map<String, Object> metadata) {
|
public void onMetadata(Map<String, Object> metadata) {
|
||||||
if (id3MetadataListener != null) {
|
if (id3MetadataListener != null) {
|
||||||
id3MetadataListener.onId3Metadata(metadata);
|
id3MetadataListener.onId3Metadata(metadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ MetadataTrackRenderer.MetadataRenderer<List<ClosedCaption>>
|
/* package */ MetadataTrackRenderer.MetadataRenderer<List<ClosedCaption>>
|
||||||
getClosedCaptionMetadataRenderer() {
|
getClosedCaptionMetadataRenderer() {
|
||||||
return new MetadataTrackRenderer.MetadataRenderer<List<ClosedCaption>>() {
|
return new MetadataTrackRenderer.MetadataRenderer<List<ClosedCaption>>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMetadata(List<ClosedCaption> metadata) {
|
public void onMetadata(List<ClosedCaption> metadata) {
|
||||||
if (closedCaptionListener != null) {
|
processClosedCaption(metadata);
|
||||||
closedCaptionListener.onClosedCaption(metadata);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,6 +591,36 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* package */ void processText(String text) {
|
||||||
|
if (textListener == null || selectedTracks[TYPE_TEXT] == DISABLED_TRACK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
textListener.onText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void processClosedCaption(List<ClosedCaption> metadata) {
|
||||||
|
if (textListener == null || selectedTracks[TYPE_TEXT] == DISABLED_TRACK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closedCaptionStringBuilder.setLength(0);
|
||||||
|
for (ClosedCaption caption : metadata) {
|
||||||
|
// Ignore control characters and just insert a new line in between words.
|
||||||
|
if (caption.type == ClosedCaption.TYPE_CTRL) {
|
||||||
|
if (closedCaptionStringBuilder.length() > 0
|
||||||
|
&& closedCaptionStringBuilder.charAt(closedCaptionStringBuilder.length() - 1) != '\n') {
|
||||||
|
closedCaptionStringBuilder.append('\n');
|
||||||
|
}
|
||||||
|
} else if (caption.type == ClosedCaption.TYPE_TEXT) {
|
||||||
|
closedCaptionStringBuilder.append(caption.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (closedCaptionStringBuilder.length() > 0
|
||||||
|
&& closedCaptionStringBuilder.charAt(closedCaptionStringBuilder.length() - 1) == '\n') {
|
||||||
|
closedCaptionStringBuilder.deleteCharAt(closedCaptionStringBuilder.length() - 1);
|
||||||
|
}
|
||||||
|
textListener.onText(closedCaptionStringBuilder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
private class InternalRendererBuilderCallback implements RendererBuilderCallback {
|
private class InternalRendererBuilderCallback implements RendererBuilderCallback {
|
||||||
|
|
||||||
private boolean canceled;
|
private boolean canceled;
|
||||||
|
@ -100,7 +100,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
|
|||||||
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
|
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
|
||||||
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
|
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
|
||||||
renderers[DemoPlayer.TYPE_TIMED_METADATA] = id3Renderer;
|
renderers[DemoPlayer.TYPE_TIMED_METADATA] = id3Renderer;
|
||||||
renderers[DemoPlayer.TYPE_CLOSED_CAPTIONS] = closedCaptionRenderer;
|
renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer;
|
||||||
callback.onRenderers(null, null, renderers);
|
callback.onRenderers(null, null, renderers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,9 +80,24 @@ public class HlsChunkSource {
|
|||||||
*/
|
*/
|
||||||
public static final int ADAPTIVE_MODE_ABRUPT = 3;
|
public static final int ADAPTIVE_MODE_ABRUPT = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default target buffer duration in milliseconds.
|
||||||
|
*/
|
||||||
|
public static final long DEFAULT_TARGET_BUFFER_DURATION_MS = 40000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default minimum duration of media that needs to be buffered for a switch to a higher
|
||||||
|
* quality variant to be considered.
|
||||||
|
*/
|
||||||
|
public static final long DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS = 5000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default maximum duration of media that needs to be buffered for a switch to a lower
|
||||||
|
* quality variant to be considered.
|
||||||
|
*/
|
||||||
|
public static final long DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS = 20000;
|
||||||
|
|
||||||
private static final float BANDWIDTH_FRACTION = 0.8f;
|
private static final float BANDWIDTH_FRACTION = 0.8f;
|
||||||
private static final long MIN_BUFFER_TO_SWITCH_UP_US = 5000000;
|
|
||||||
private static final long MAX_BUFFER_TO_SWITCH_DOWN_US = 15000000;
|
|
||||||
|
|
||||||
private final SamplePool samplePool = new TsExtractor.SamplePool();
|
private final SamplePool samplePool = new TsExtractor.SamplePool();
|
||||||
private final DataSource upstreamDataSource;
|
private final DataSource upstreamDataSource;
|
||||||
@ -94,6 +109,9 @@ public class HlsChunkSource {
|
|||||||
private final Uri baseUri;
|
private final Uri baseUri;
|
||||||
private final int maxWidth;
|
private final int maxWidth;
|
||||||
private final int maxHeight;
|
private final int maxHeight;
|
||||||
|
private final long targetBufferDurationUs;
|
||||||
|
private final long minBufferDurationToSwitchUpUs;
|
||||||
|
private final long maxBufferDurationToSwitchDownUs;
|
||||||
|
|
||||||
/* package */ final HlsMediaPlaylist[] mediaPlaylists;
|
/* package */ final HlsMediaPlaylist[] mediaPlaylists;
|
||||||
/* package */ final long[] lastMediaPlaylistLoadTimesMs;
|
/* package */ final long[] lastMediaPlaylistLoadTimesMs;
|
||||||
@ -106,6 +124,13 @@ public class HlsChunkSource {
|
|||||||
private String encryptedDataSourceIv;
|
private String encryptedDataSourceIv;
|
||||||
private byte[] encryptedDataSourceSecretKey;
|
private byte[] encryptedDataSourceSecretKey;
|
||||||
|
|
||||||
|
public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
|
||||||
|
BandwidthMeter bandwidthMeter, int[] variantIndices, int adaptiveMode) {
|
||||||
|
this(dataSource, playlistUrl, playlist, bandwidthMeter, variantIndices, adaptiveMode,
|
||||||
|
DEFAULT_TARGET_BUFFER_DURATION_MS, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS,
|
||||||
|
DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||||
* @param playlistUrl The playlist URL.
|
* @param playlistUrl The playlist URL.
|
||||||
@ -116,12 +141,24 @@ public class HlsChunkSource {
|
|||||||
* @param adaptiveMode The mode for switching from one variant to another. One of
|
* @param adaptiveMode The mode for switching from one variant to another. One of
|
||||||
* {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
|
* {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
|
||||||
* {@link #ADAPTIVE_MODE_SPLICE}.
|
* {@link #ADAPTIVE_MODE_SPLICE}.
|
||||||
|
* @param targetBufferDurationMs The targeted duration of media to buffer ahead of the current
|
||||||
|
* playback position. Note that the greater this value, the greater the amount of memory
|
||||||
|
* that will be consumed.
|
||||||
|
* @param minBufferDurationToSwitchUpMs The minimum duration of media that needs to be buffered
|
||||||
|
* for a switch to a higher quality variant to be considered.
|
||||||
|
* @param maxBufferDurationToSwitchDownMs The maximum duration of media that needs to be buffered
|
||||||
|
* for a switch to a lower quality variant to be considered.
|
||||||
*/
|
*/
|
||||||
public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
|
public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
|
||||||
BandwidthMeter bandwidthMeter, int[] variantIndices, int adaptiveMode) {
|
BandwidthMeter bandwidthMeter, int[] variantIndices, int adaptiveMode,
|
||||||
|
long targetBufferDurationMs, long minBufferDurationToSwitchUpMs,
|
||||||
|
long maxBufferDurationToSwitchDownMs) {
|
||||||
this.upstreamDataSource = dataSource;
|
this.upstreamDataSource = dataSource;
|
||||||
this.bandwidthMeter = bandwidthMeter;
|
this.bandwidthMeter = bandwidthMeter;
|
||||||
this.adaptiveMode = adaptiveMode;
|
this.adaptiveMode = adaptiveMode;
|
||||||
|
targetBufferDurationUs = targetBufferDurationMs * 1000;
|
||||||
|
minBufferDurationToSwitchUpUs = minBufferDurationToSwitchUpMs * 1000;
|
||||||
|
maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000;
|
||||||
baseUri = playlist.baseUri;
|
baseUri = playlist.baseUri;
|
||||||
bitArray = new BitArray();
|
bitArray = new BitArray();
|
||||||
playlistParser = new HlsPlaylistParser();
|
playlistParser = new HlsPlaylistParser();
|
||||||
@ -182,8 +219,9 @@ public class HlsChunkSource {
|
|||||||
*/
|
*/
|
||||||
public HlsChunk getChunkOperation(TsChunk previousTsChunk, long seekPositionUs,
|
public HlsChunk getChunkOperation(TsChunk previousTsChunk, long seekPositionUs,
|
||||||
long playbackPositionUs) {
|
long playbackPositionUs) {
|
||||||
if (previousTsChunk != null && previousTsChunk.isLastChunk) {
|
if (previousTsChunk != null && (previousTsChunk.isLastChunk
|
||||||
// We're already finished.
|
|| previousTsChunk.endTimeUs - playbackPositionUs >= targetBufferDurationUs)) {
|
||||||
|
// We're either finished, or we have the target amount of data buffered.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,8 +341,8 @@ public class HlsChunkSource {
|
|||||||
: adaptiveMode == ADAPTIVE_MODE_SPLICE ? previousTsChunk.startTimeUs
|
: adaptiveMode == ADAPTIVE_MODE_SPLICE ? previousTsChunk.startTimeUs
|
||||||
: previousTsChunk.endTimeUs;
|
: previousTsChunk.endTimeUs;
|
||||||
long bufferedUs = bufferedPositionUs - playbackPositionUs;
|
long bufferedUs = bufferedPositionUs - playbackPositionUs;
|
||||||
if ((idealVariantIndex > variantIndex && bufferedUs < MAX_BUFFER_TO_SWITCH_DOWN_US)
|
if ((idealVariantIndex > variantIndex && bufferedUs < maxBufferDurationToSwitchDownUs)
|
||||||
|| (idealVariantIndex < variantIndex && bufferedUs > MIN_BUFFER_TO_SWITCH_UP_US)) {
|
|| (idealVariantIndex < variantIndex && bufferedUs > minBufferDurationToSwitchUpUs)) {
|
||||||
// Switch variant.
|
// Switch variant.
|
||||||
return idealVariantIndex;
|
return idealVariantIndex;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
*/
|
*/
|
||||||
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 1;
|
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 1;
|
||||||
|
|
||||||
private static final long BUFFER_DURATION_US = 20000000;
|
|
||||||
private static final int NO_RESET_PENDING = -1;
|
private static final int NO_RESET_PENDING = -1;
|
||||||
|
|
||||||
private final HlsChunkSource chunkSource;
|
private final HlsChunkSource chunkSource;
|
||||||
@ -350,7 +349,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void maybeStartLoading() {
|
private void maybeStartLoading() {
|
||||||
if (currentLoadableExceptionFatal || loadingFinished) {
|
if (currentLoadableExceptionFatal || loadingFinished || loader.isLoading()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,17 +363,6 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean bufferFull = false;
|
|
||||||
if (!extractors.isEmpty()) {
|
|
||||||
long largestSampleTimestamp = extractors.getLast().getLargestSampleTimestamp();
|
|
||||||
bufferFull = largestSampleTimestamp != Long.MIN_VALUE
|
|
||||||
&& (largestSampleTimestamp - downstreamPositionUs) >= BUFFER_DURATION_US;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loader.isLoading() || bufferFull) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HlsChunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable,
|
HlsChunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable,
|
||||||
pendingResetPositionUs, downstreamPositionUs);
|
pendingResetPositionUs, downstreamPositionUs);
|
||||||
if (nextLoadable == null) {
|
if (nextLoadable == null) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user