Remove V1 DASH multi-period + seeking-in-window from V2.
Both of these features are being promoted to first class citizens in V2 (multi-period support will be handled via playlists, seeking-in-window will be handled by exposing the window/timeline from the player and via the normal seek API). For now, it's much easier to continue the refactoring process with the features removed. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=119518675
This commit is contained in:
parent
1c0a120aeb
commit
7170ff380c
@ -19,7 +19,6 @@ import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||
import com.google.android.exoplayer.TimeRange;
|
||||
import com.google.android.exoplayer.TrackGroup;
|
||||
import com.google.android.exoplayer.TrackGroupArray;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
@ -50,7 +49,6 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
||||
}
|
||||
|
||||
private long sessionStartTimeMs;
|
||||
private long[] availableRangeValuesUs;
|
||||
|
||||
public void startSession() {
|
||||
sessionStartTimeMs = SystemClock.elapsedRealtime();
|
||||
@ -215,13 +213,6 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
||||
Log.d(TAG, "decoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvailableRangeChanged(int sourceId, TimeRange availableRange) {
|
||||
availableRangeValuesUs = availableRange.getCurrentBoundsUs(availableRangeValuesUs);
|
||||
Log.d(TAG, "availableRange [" + availableRange.isStatic() + ", " + availableRangeValuesUs[0]
|
||||
+ ", " + availableRangeValuesUs[1] + "]");
|
||||
}
|
||||
|
||||
private void printInternalError(String type, Exception e) {
|
||||
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
|
||||
}
|
||||
|
@ -44,8 +44,6 @@ import android.os.Handler;
|
||||
// TODO[REFACTOR]: Bring back UTC timing element support.
|
||||
public class DashSourceBuilder implements SourceBuilder {
|
||||
|
||||
private static final String TAG = "DashSourceBuilder";
|
||||
|
||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||
private static final int VIDEO_BUFFER_SEGMENTS = 200;
|
||||
private static final int AUDIO_BUFFER_SEGMENTS = 54;
|
||||
@ -78,15 +76,14 @@ public class DashSourceBuilder implements SourceBuilder {
|
||||
// Build the video renderer.
|
||||
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_VIDEO,
|
||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS,
|
||||
0, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
|
||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
||||
|
||||
// Build the audio renderer.
|
||||
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_AUDIO,
|
||||
audioDataSource, null, LIVE_EDGE_LATENCY_MS, 0, mainHandler, player, DemoPlayer.TYPE_AUDIO);
|
||||
audioDataSource, null, LIVE_EDGE_LATENCY_MS);
|
||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||
DemoPlayer.TYPE_AUDIO);
|
||||
@ -94,7 +91,7 @@ public class DashSourceBuilder implements SourceBuilder {
|
||||
// Build the text renderer.
|
||||
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_TEXT,
|
||||
textDataSource, null, LIVE_EDGE_LATENCY_MS, 0, mainHandler, player, DemoPlayer.TYPE_TEXT);
|
||||
textDataSource, null, LIVE_EDGE_LATENCY_MS);
|
||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||
DemoPlayer.TYPE_TEXT);
|
||||
|
@ -27,12 +27,10 @@ import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializatio
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.SampleSource;
|
||||
import com.google.android.exoplayer.SingleSampleSource;
|
||||
import com.google.android.exoplayer.TimeRange;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||
import com.google.android.exoplayer.audio.AudioTrack;
|
||||
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener;
|
||||
import com.google.android.exoplayer.dash.DashChunkSource;
|
||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
||||
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
|
||||
import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
|
||||
@ -67,8 +65,8 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
|
||||
ChunkSampleSourceEventListener, ExtractorSampleSource.EventListener,
|
||||
SingleSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
|
||||
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
|
||||
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer,
|
||||
MetadataRenderer<List<Id3Frame>>, DebugTextViewHelper.Provider {
|
||||
StreamingDrmSessionManager.EventListener, TextRenderer, MetadataRenderer<List<Id3Frame>>,
|
||||
DebugTextViewHelper.Provider {
|
||||
|
||||
/**
|
||||
* Builds a source to play.
|
||||
@ -127,7 +125,6 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
|
||||
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs);
|
||||
void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
|
||||
long initializationDurationMs);
|
||||
void onAvailableRangeChanged(int sourceId, TimeRange availableRange);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -441,13 +438,6 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvailableRangeChanged(int sourceId, TimeRange availableRange) {
|
||||
if (infoListener != null) {
|
||||
infoListener.onAvailableRangeChanged(sourceId, availableRange);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayWhenReadyCommitted() {
|
||||
// Do nothing.
|
||||
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.TimeRange.StaticTimeRange;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit test for {@link TimeRange}.
|
||||
*/
|
||||
public class TimeRangeTest extends TestCase {
|
||||
|
||||
public void testStaticEquals() {
|
||||
TimeRange timeRange1 = new StaticTimeRange(0, 30000000);
|
||||
assertTrue(timeRange1.equals(timeRange1));
|
||||
|
||||
TimeRange timeRange2 = new StaticTimeRange(0, 30000000);
|
||||
assertTrue(timeRange1.equals(timeRange2));
|
||||
|
||||
TimeRange timeRange3 = new StaticTimeRange(0, 60000000);
|
||||
assertFalse(timeRange1.equals(timeRange3));
|
||||
}
|
||||
|
||||
}
|
@ -1,203 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.util.Clock;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
/**
|
||||
* A container to store a start and end time in microseconds.
|
||||
*/
|
||||
public interface TimeRange {
|
||||
|
||||
/**
|
||||
* Whether the range is static, meaning repeated calls to {@link #getCurrentBoundsMs(long[])}
|
||||
* or {@link #getCurrentBoundsUs(long[])} will return identical results.
|
||||
*
|
||||
* @return Whether the range is static.
|
||||
*/
|
||||
public boolean isStatic();
|
||||
|
||||
/**
|
||||
* Returns the start and end times (in milliseconds) of the TimeRange in the provided array,
|
||||
* or creates a new one.
|
||||
*
|
||||
* @param out An array to store the start and end times; can be null.
|
||||
* @return An array containing the start time (index 0) and end time (index 1) in milliseconds.
|
||||
*/
|
||||
public long[] getCurrentBoundsMs(long[] out);
|
||||
|
||||
/**
|
||||
* Returns the start and end times (in microseconds) of the TimeRange in the provided array,
|
||||
* or creates a new one.
|
||||
*
|
||||
* @param out An array to store the start and end times; can be null.
|
||||
* @return An array containing the start time (index 0) and end time (index 1) in microseconds.
|
||||
*/
|
||||
public long[] getCurrentBoundsUs(long[] out);
|
||||
|
||||
/**
|
||||
* A static {@link TimeRange}.
|
||||
*/
|
||||
public static final class StaticTimeRange implements TimeRange {
|
||||
|
||||
private final long startTimeUs;
|
||||
private final long endTimeUs;
|
||||
|
||||
/**
|
||||
* @param startTimeUs The beginning of the range.
|
||||
* @param endTimeUs The end of the range.
|
||||
*/
|
||||
public StaticTimeRange(long startTimeUs, long endTimeUs) {
|
||||
this.startTimeUs = startTimeUs;
|
||||
this.endTimeUs = endTimeUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getCurrentBoundsMs(long[] out) {
|
||||
out = getCurrentBoundsUs(out);
|
||||
out[0] /= 1000;
|
||||
out[1] /= 1000;
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getCurrentBoundsUs(long[] out) {
|
||||
if (out == null || out.length < 2) {
|
||||
out = new long[2];
|
||||
}
|
||||
out[0] = startTimeUs;
|
||||
out[1] = endTimeUs;
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + (int) startTimeUs;
|
||||
result = 31 * result + (int) endTimeUs;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
StaticTimeRange other = (StaticTimeRange) obj;
|
||||
return other.startTimeUs == startTimeUs
|
||||
&& other.endTimeUs == endTimeUs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A dynamic {@link TimeRange}.
|
||||
*/
|
||||
public static final class DynamicTimeRange implements TimeRange {
|
||||
|
||||
private final long minStartTimeUs;
|
||||
private final long maxEndTimeUs;
|
||||
private final long elapsedRealtimeAtStartUs;
|
||||
private final long bufferDepthUs;
|
||||
private final Clock systemClock;
|
||||
|
||||
/**
|
||||
* @param minStartTimeUs A lower bound on the beginning of the range.
|
||||
* @param maxEndTimeUs An upper bound on the end of the range.
|
||||
* @param elapsedRealtimeAtStartUs The value of {@link SystemClock#elapsedRealtime()},
|
||||
* multiplied by 1000, corresponding to a media time of zero.
|
||||
* @param bufferDepthUs The buffer depth of the media, or -1.
|
||||
* @param systemClock A system clock.
|
||||
*/
|
||||
public DynamicTimeRange(long minStartTimeUs, long maxEndTimeUs, long elapsedRealtimeAtStartUs,
|
||||
long bufferDepthUs, Clock systemClock) {
|
||||
this.minStartTimeUs = minStartTimeUs;
|
||||
this.maxEndTimeUs = maxEndTimeUs;
|
||||
this.elapsedRealtimeAtStartUs = elapsedRealtimeAtStartUs;
|
||||
this.bufferDepthUs = bufferDepthUs;
|
||||
this.systemClock = systemClock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getCurrentBoundsMs(long[] out) {
|
||||
out = getCurrentBoundsUs(out);
|
||||
out[0] /= 1000;
|
||||
out[1] /= 1000;
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getCurrentBoundsUs(long[] out) {
|
||||
if (out == null || out.length < 2) {
|
||||
out = new long[2];
|
||||
}
|
||||
// Don't allow the end time to be greater than the total elapsed time.
|
||||
long currentEndTimeUs = Math.min(maxEndTimeUs,
|
||||
(systemClock.elapsedRealtime() * 1000) - elapsedRealtimeAtStartUs);
|
||||
long currentStartTimeUs = minStartTimeUs;
|
||||
if (bufferDepthUs != -1) {
|
||||
// Don't allow the start time to be less than the current end time minus the buffer depth.
|
||||
currentStartTimeUs = Math.max(currentStartTimeUs,
|
||||
currentEndTimeUs - bufferDepthUs);
|
||||
}
|
||||
out[0] = currentStartTimeUs;
|
||||
out[1] = currentEndTimeUs;
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + (int) minStartTimeUs;
|
||||
result = 31 * result + (int) maxEndTimeUs;
|
||||
result = 31 * result + (int) elapsedRealtimeAtStartUs;
|
||||
result = 31 * result + (int) bufferDepthUs;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
DynamicTimeRange other = (DynamicTimeRange) obj;
|
||||
return other.minStartTimeUs == minStartTimeUs
|
||||
&& other.maxEndTimeUs == maxEndTimeUs
|
||||
&& other.elapsedRealtimeAtStartUs == elapsedRealtimeAtStartUs
|
||||
&& other.bufferDepthUs == bufferDepthUs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -50,11 +50,10 @@ public abstract class BaseMediaChunk extends MediaChunk {
|
||||
* be called at any time to obtain the sample format and drm initialization data. False if
|
||||
* these methods are only guaranteed to return correct data after the first sample data has
|
||||
* been output from the chunk.
|
||||
* @param parentId Identifier for a parent from which this chunk originates.
|
||||
*/
|
||||
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||
long startTimeUs, long endTimeUs, int chunkIndex, boolean isSampleFormatFinal, int parentId) {
|
||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, parentId);
|
||||
long startTimeUs, long endTimeUs, int chunkIndex, boolean isSampleFormatFinal) {
|
||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
|
||||
this.isSampleFormatFinal = isSampleFormatFinal;
|
||||
}
|
||||
|
||||
|
@ -76,10 +76,6 @@ public abstract class Chunk implements Loadable {
|
||||
* Implementations may define custom {@link #trigger} codes greater than or equal to this value.
|
||||
*/
|
||||
public static final int TRIGGER_CUSTOM_BASE = 10000;
|
||||
/**
|
||||
* Value of {@link #parentId} if no parent id need be specified.
|
||||
*/
|
||||
public static final int NO_PARENT_ID = -1;
|
||||
|
||||
/**
|
||||
* The type of the chunk. For reporting only.
|
||||
@ -98,10 +94,6 @@ public abstract class Chunk implements Loadable {
|
||||
* The {@link DataSpec} that defines the data to be loaded.
|
||||
*/
|
||||
public final DataSpec dataSpec;
|
||||
/**
|
||||
* Optional identifier for a parent from which this chunk originates.
|
||||
*/
|
||||
public final int parentId;
|
||||
|
||||
protected final DataSource dataSource;
|
||||
|
||||
@ -114,16 +106,13 @@ public abstract class Chunk implements Loadable {
|
||||
* @param type See {@link #type}.
|
||||
* @param trigger See {@link #trigger}.
|
||||
* @param format See {@link #format}.
|
||||
* @param parentId See {@link #parentId}.
|
||||
*/
|
||||
public Chunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format,
|
||||
int parentId) {
|
||||
public Chunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format) {
|
||||
this.dataSource = Assertions.checkNotNull(dataSource);
|
||||
this.dataSpec = Assertions.checkNotNull(dataSpec);
|
||||
this.type = type;
|
||||
this.trigger = trigger;
|
||||
this.format = format;
|
||||
this.parentId = parentId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,14 +60,13 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
|
||||
* @param isSampleFormatFinal True if {@code sampleFormat} and {@code drmInitData} are known to be
|
||||
* correct and final. False if the data may define its own sample format or initialization
|
||||
* data.
|
||||
* @param parentId Identifier for a parent from which this chunk originates.
|
||||
*/
|
||||
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
|
||||
ChunkExtractorWrapper extractorWrapper, Format sampleFormat, DrmInitData drmInitData,
|
||||
boolean isSampleFormatFinal, int parentId) {
|
||||
boolean isSampleFormatFinal) {
|
||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
|
||||
isSampleFormatFinal, parentId);
|
||||
isSampleFormatFinal);
|
||||
this.extractorWrapper = extractorWrapper;
|
||||
this.sampleOffsetUs = sampleOffsetUs;
|
||||
this.sampleFormat = getAdjustedSampleFormat(sampleFormat, sampleOffsetUs);
|
||||
|
@ -44,12 +44,11 @@ public abstract class DataChunk extends Chunk {
|
||||
* @param type See {@link #type}.
|
||||
* @param trigger See {@link #trigger}.
|
||||
* @param format See {@link #format}.
|
||||
* @param parentId Identifier for a parent from which this chunk originates.
|
||||
* @param data An optional recycled array that can be used as a holder for the data.
|
||||
*/
|
||||
public DataChunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format,
|
||||
int parentId, byte[] data) {
|
||||
super(dataSource, dataSpec, type, trigger, format, parentId);
|
||||
byte[] data) {
|
||||
super(dataSource, dataSpec, type, trigger, format);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
|
@ -46,11 +46,6 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu
|
||||
private volatile int bytesLoaded;
|
||||
private volatile boolean loadCanceled;
|
||||
|
||||
public InitializationChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||
ChunkExtractorWrapper extractorWrapper) {
|
||||
this(dataSource, dataSpec, trigger, format, extractorWrapper, Chunk.NO_PARENT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a chunk of media samples.
|
||||
*
|
||||
@ -59,11 +54,10 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu
|
||||
* @param trigger The reason for this chunk being selected.
|
||||
* @param format The format of the stream to which this chunk belongs.
|
||||
* @param extractorWrapper A wrapped extractor to use for parsing the initialization data.
|
||||
* @param parentId Identifier for a parent from which this chunk originates.
|
||||
*/
|
||||
public InitializationChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||
ChunkExtractorWrapper extractorWrapper, int parentId) {
|
||||
super(dataSource, dataSpec, Chunk.TYPE_MEDIA_INITIALIZATION, trigger, format, parentId);
|
||||
ChunkExtractorWrapper extractorWrapper) {
|
||||
super(dataSource, dataSpec, Chunk.TYPE_MEDIA_INITIALIZATION, trigger, format);
|
||||
this.extractorWrapper = extractorWrapper;
|
||||
}
|
||||
|
||||
|
@ -38,16 +38,6 @@ public abstract class MediaChunk extends Chunk {
|
||||
*/
|
||||
public final int chunkIndex;
|
||||
|
||||
public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||
long startTimeUs, long endTimeUs, int chunkIndex) {
|
||||
this(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
|
||||
Chunk.NO_PARENT_ID);
|
||||
}
|
||||
|
||||
public int getNextChunkIndex() {
|
||||
return chunkIndex + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dataSource A {@link DataSource} for loading the data.
|
||||
* @param dataSpec Defines the data to be loaded.
|
||||
@ -56,15 +46,18 @@ public abstract class MediaChunk extends Chunk {
|
||||
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
|
||||
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
|
||||
* @param chunkIndex The index of the chunk.
|
||||
* @param parentId Identifier for a parent from which this chunk originates.
|
||||
*/
|
||||
public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||
long startTimeUs, long endTimeUs, int chunkIndex, int parentId) {
|
||||
super(dataSource, dataSpec, Chunk.TYPE_MEDIA, trigger, format, parentId);
|
||||
long startTimeUs, long endTimeUs, int chunkIndex) {
|
||||
super(dataSource, dataSpec, Chunk.TYPE_MEDIA, trigger, format);
|
||||
Assertions.checkNotNull(format);
|
||||
this.startTimeUs = startTimeUs;
|
||||
this.endTimeUs = endTimeUs;
|
||||
this.chunkIndex = chunkIndex;
|
||||
}
|
||||
|
||||
public final int getNextChunkIndex() {
|
||||
return chunkIndex + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -48,13 +48,11 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
|
||||
* @param sampleFormat The format of the sample.
|
||||
* @param sampleDrmInitData The {@link DrmInitData} for the sample. Null if the sample is not drm
|
||||
* protected.
|
||||
* @param parentId Identifier for a parent from which this chunk originates.
|
||||
*/
|
||||
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
|
||||
Format format, long startTimeUs, long endTimeUs, int chunkIndex, Format sampleFormat,
|
||||
DrmInitData sampleDrmInitData, int parentId) {
|
||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true,
|
||||
parentId);
|
||||
DrmInitData sampleDrmInitData) {
|
||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true);
|
||||
this.sampleFormat = sampleFormat;
|
||||
this.sampleDrmInitData = sampleDrmInitData;
|
||||
}
|
||||
|
@ -19,9 +19,6 @@ import com.google.android.exoplayer.BehindLiveWindowException;
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.Format.DecreasingBandwidthComparator;
|
||||
import com.google.android.exoplayer.TimeRange;
|
||||
import com.google.android.exoplayer.TimeRange.DynamicTimeRange;
|
||||
import com.google.android.exoplayer.TimeRange.StaticTimeRange;
|
||||
import com.google.android.exoplayer.TrackGroup;
|
||||
import com.google.android.exoplayer.chunk.Chunk;
|
||||
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
|
||||
@ -45,13 +42,8 @@ import com.google.android.exoplayer.extractor.mkv.MatroskaExtractor;
|
||||
import com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
import com.google.android.exoplayer.util.Clock;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.SystemClock;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
@ -73,60 +65,29 @@ import java.util.List;
|
||||
// TODO[REFACTOR]: Handle multiple adaptation sets of the same type (at a higher level).
|
||||
public class DashChunkSource implements ChunkSource {
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be notified of {@link DashChunkSource} events.
|
||||
*/
|
||||
public interface EventListener {
|
||||
|
||||
/**
|
||||
* Invoked when the available seek range of the stream has changed.
|
||||
*
|
||||
* @param sourceId The id of the reporting {@link DashChunkSource}.
|
||||
* @param availableRange The range which specifies available content that can be seeked to.
|
||||
*/
|
||||
public void onAvailableRangeChanged(int sourceId, TimeRange availableRange);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when an AdaptationSet is missing from the MPD.
|
||||
*/
|
||||
public static class NoAdaptationSetException extends IOException {
|
||||
|
||||
public NoAdaptationSetException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
|
||||
private final int adaptationSetType;
|
||||
private final DataSource dataSource;
|
||||
private final FormatEvaluator adaptiveFormatEvaluator;
|
||||
private final Evaluation evaluation;
|
||||
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
||||
private final SparseArray<PeriodHolder> periodHolders;
|
||||
private final Clock systemClock;
|
||||
private final long liveEdgeLatencyUs;
|
||||
private final long elapsedRealtimeOffsetUs;
|
||||
private final long[] availableRangeValues;
|
||||
private final int eventSourceId;
|
||||
|
||||
// Properties of the initial manifest.
|
||||
private boolean live;
|
||||
private long durationUs;
|
||||
|
||||
private MediaPresentationDescription currentManifest;
|
||||
private MediaPresentationDescription processedManifest;
|
||||
private int nextPeriodHolderIndex;
|
||||
private TimeRange availableRange;
|
||||
private boolean startAtLiveEdge;
|
||||
private DrmInitData drmInitData;
|
||||
private boolean indexIsUnbounded;
|
||||
private boolean indexIsExplicit;
|
||||
|
||||
private boolean lastChunkWasInitialization;
|
||||
private IOException fatalError;
|
||||
|
||||
// Properties of exposed tracks.
|
||||
private int adaptationSetIndex;
|
||||
private TrackGroup trackGroup;
|
||||
private RepresentationHolder[] representationHolders;
|
||||
|
||||
// Properties of enabled tracks.
|
||||
private Format[] enabledFormats;
|
||||
@ -144,75 +105,16 @@ public class DashChunkSource implements ChunkSource {
|
||||
* manifest). Choosing a small value will minimize latency introduced by the player, however
|
||||
* note that the value sets an upper bound on the length of media that the player can buffer.
|
||||
* Hence a small value may increase the probability of rebuffering and playback failures.
|
||||
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
|
||||
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
|
||||
* as the server's unix time minus the local elapsed time. It unknown, set to 0.
|
||||
* @param eventHandler A handler to use when delivering events to {@code EventListener}. May be
|
||||
* null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
* @param eventSourceId An identifier that gets passed to {@code eventListener} methods.
|
||||
*/
|
||||
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
||||
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
||||
long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, Handler eventHandler,
|
||||
EventListener eventListener, int eventSourceId) {
|
||||
this(manifestFetcher, adaptationSetType, dataSource, adaptiveFormatEvaluator, new SystemClock(),
|
||||
liveEdgeLatencyMs * 1000, elapsedRealtimeOffsetMs * 1000, true, eventHandler, eventListener,
|
||||
eventSourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor to use for live DVR streaming.
|
||||
*
|
||||
* @param manifestFetcher A fetcher for the manifest.
|
||||
* @param adaptationSetType The type of the adaptation set exposed by this source. One of
|
||||
* {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and
|
||||
* {@link AdaptationSet#TYPE_TEXT}.
|
||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
||||
* @param liveEdgeLatencyMs For live streams, the number of milliseconds that the playback should
|
||||
* lag behind the "live edge" (i.e. the end of the most recently defined media in the
|
||||
* manifest). Choosing a small value will minimize latency introduced by the player, however
|
||||
* note that the value sets an upper bound on the length of media that the player can buffer.
|
||||
* Hence a small value may increase the probability of rebuffering and playback failures.
|
||||
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
|
||||
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
|
||||
* as the server's unix time minus the local elapsed time. It unknown, set to 0.
|
||||
* @param startAtLiveEdge True if the stream should start at the live edge; false if it should
|
||||
* at the beginning of the live window.
|
||||
* @param eventHandler A handler to use when delivering events to {@code EventListener}. May be
|
||||
* null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
* @param eventSourceId An identifier that gets passed to {@code eventListener} methods.
|
||||
*/
|
||||
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
||||
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
||||
long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, boolean startAtLiveEdge,
|
||||
Handler eventHandler, EventListener eventListener, int eventSourceId) {
|
||||
this(manifestFetcher, adaptationSetType, dataSource, adaptiveFormatEvaluator, new SystemClock(),
|
||||
liveEdgeLatencyMs * 1000, elapsedRealtimeOffsetMs * 1000, startAtLiveEdge, eventHandler,
|
||||
eventListener, eventSourceId);
|
||||
}
|
||||
|
||||
/* package */ DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
||||
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
|
||||
Clock systemClock, long liveEdgeLatencyUs, long elapsedRealtimeOffsetUs,
|
||||
boolean startAtLiveEdge, Handler eventHandler, EventListener eventListener,
|
||||
int eventSourceId) {
|
||||
long liveEdgeLatencyMs) {
|
||||
this.manifestFetcher = manifestFetcher;
|
||||
this.adaptationSetType = adaptationSetType;
|
||||
this.dataSource = dataSource;
|
||||
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||
this.systemClock = systemClock;
|
||||
this.liveEdgeLatencyUs = liveEdgeLatencyUs;
|
||||
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetUs;
|
||||
this.startAtLiveEdge = startAtLiveEdge;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
this.eventSourceId = eventSourceId;
|
||||
this.liveEdgeLatencyUs = liveEdgeLatencyMs * 1000;
|
||||
this.evaluation = new Evaluation();
|
||||
this.availableRangeValues = new long[2];
|
||||
periodHolders = new SparseArray<>();
|
||||
}
|
||||
|
||||
// ChunkSource implementation.
|
||||
@ -235,8 +137,6 @@ public class DashChunkSource implements ChunkSource {
|
||||
manifestFetcher.requestRefresh();
|
||||
return false;
|
||||
} else {
|
||||
live = currentManifest.dynamic;
|
||||
durationUs = live ? C.UNKNOWN_TIME_US : currentManifest.duration * 1000;
|
||||
initForManifest(currentManifest);
|
||||
}
|
||||
}
|
||||
@ -274,11 +174,8 @@ public class DashChunkSource implements ChunkSource {
|
||||
}
|
||||
|
||||
MediaPresentationDescription newManifest = manifestFetcher.getManifest();
|
||||
if (newManifest != null && newManifest != processedManifest) {
|
||||
if (newManifest != null && newManifest != currentManifest) {
|
||||
processManifest(newManifest);
|
||||
// Manifests may be rejected, so the new manifest may not become the next currentManifest.
|
||||
// Track a manifest has been processed to avoid processing twice when it was discarded.
|
||||
processedManifest = newManifest;
|
||||
}
|
||||
|
||||
// TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where
|
||||
@ -327,112 +224,76 @@ public class DashChunkSource implements ChunkSource {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean startingNewPeriod;
|
||||
PeriodHolder periodHolder;
|
||||
|
||||
availableRange.getCurrentBoundsUs(availableRangeValues);
|
||||
if (previous == null) {
|
||||
if (live) {
|
||||
if (startAtLiveEdge) {
|
||||
// We want live streams to start at the live edge instead of the beginning of the
|
||||
// manifest
|
||||
playbackPositionUs = Math.max(availableRangeValues[0],
|
||||
availableRangeValues[1] - liveEdgeLatencyUs);
|
||||
} else {
|
||||
// we subtract 1 from the upper bound because it's exclusive for that bound
|
||||
playbackPositionUs = Math.min(playbackPositionUs, availableRangeValues[1] - 1);
|
||||
playbackPositionUs = Math.max(playbackPositionUs, availableRangeValues[0]);
|
||||
}
|
||||
}
|
||||
|
||||
periodHolder = findPeriodHolder(playbackPositionUs);
|
||||
startingNewPeriod = true;
|
||||
} else {
|
||||
if (startAtLiveEdge) {
|
||||
// now that we know the player is consuming media chunks (since the queue isn't empty),
|
||||
// set startAtLiveEdge to false so that the user can perform seek operations
|
||||
startAtLiveEdge = false;
|
||||
}
|
||||
|
||||
long nextSegmentStartTimeUs = previous.endTimeUs;
|
||||
if (live && nextSegmentStartTimeUs < availableRangeValues[0]) {
|
||||
// This is before the first chunk in the current manifest.
|
||||
fatalError = new BehindLiveWindowException();
|
||||
return;
|
||||
} else if (currentManifest.dynamic && nextSegmentStartTimeUs >= availableRangeValues[1]) {
|
||||
// This chunk is beyond the last chunk in the current manifest. If the index is bounded
|
||||
// we'll need to wait until it's refreshed. If it's unbounded we just need to wait for a
|
||||
// while before attempting to load the chunk.
|
||||
return;
|
||||
} else {
|
||||
// A period's duration is the maximum of its various representation's durations, so it's
|
||||
// possible that due to the minor differences between them our available range values might
|
||||
// not sync exactly with the actual available content, so double check whether or not we've
|
||||
// really run out of content to play.
|
||||
PeriodHolder lastPeriodHolder = periodHolders.valueAt(periodHolders.size() - 1);
|
||||
if (previous.parentId == lastPeriodHolder.localIndex) {
|
||||
RepresentationHolder representationHolder =
|
||||
lastPeriodHolder.representationHolders[getTrackIndex(previous.format)];
|
||||
if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) {
|
||||
if (!currentManifest.dynamic) {
|
||||
// The current manifest isn't dynamic, so we've reached the end of the stream.
|
||||
out.endOfStream = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startingNewPeriod = false;
|
||||
periodHolder = periodHolders.get(previous.parentId);
|
||||
if (periodHolder == null) {
|
||||
// The previous chunk was from a period that's no longer on the manifest, therefore the
|
||||
// next chunk must be the first one in the first period that's still on the manifest
|
||||
// (note that we can't actually update the segmentNum yet because the new period might
|
||||
// have a different sequence and its segmentIndex might not have been loaded yet).
|
||||
periodHolder = periodHolders.valueAt(0);
|
||||
startingNewPeriod = true;
|
||||
} else if (!periodHolder.isIndexUnbounded()) {
|
||||
RepresentationHolder representationHolder =
|
||||
periodHolder.representationHolders[getTrackIndex(previous.format)];
|
||||
if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) {
|
||||
// We reached the end of a period. Start the next one.
|
||||
periodHolder = periodHolders.get(previous.parentId + 1);
|
||||
startingNewPeriod = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RepresentationHolder representationHolder =
|
||||
periodHolder.representationHolders[getTrackIndex(selectedFormat)];
|
||||
representationHolders[getTrackIndex(selectedFormat)];
|
||||
Representation selectedRepresentation = representationHolder.representation;
|
||||
DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
|
||||
|
||||
RangedUri pendingInitializationUri = null;
|
||||
RangedUri pendingIndexUri = null;
|
||||
|
||||
Format sampleFormat = representationHolder.sampleFormat;
|
||||
if (sampleFormat == null) {
|
||||
pendingInitializationUri = selectedRepresentation.getInitializationUri();
|
||||
}
|
||||
if (representationHolder.segmentIndex == null) {
|
||||
if (segmentIndex == null) {
|
||||
pendingIndexUri = selectedRepresentation.getIndexUri();
|
||||
}
|
||||
|
||||
if (pendingInitializationUri != null || pendingIndexUri != null) {
|
||||
// We have initialization and/or index requests to make.
|
||||
Chunk initializationChunk = newInitializationChunk(pendingInitializationUri, pendingIndexUri,
|
||||
selectedRepresentation, representationHolder.extractorWrapper, dataSource,
|
||||
periodHolder.localIndex, evaluation.trigger);
|
||||
evaluation.trigger);
|
||||
lastChunkWasInitialization = true;
|
||||
out.chunk = initializationChunk;
|
||||
return;
|
||||
}
|
||||
|
||||
int segmentNum = previous == null ? representationHolder.getSegmentNum(playbackPositionUs)
|
||||
: startingNewPeriod ? representationHolder.getFirstAvailableSegmentNum()
|
||||
: previous.getNextChunkIndex();
|
||||
Chunk nextMediaChunk = newMediaChunk(periodHolder, representationHolder, dataSource,
|
||||
selectedFormat, sampleFormat, segmentNum, evaluation.trigger);
|
||||
// TODO[REFACTOR]: Bring back UTC timing element support.
|
||||
long nowUs = System.currentTimeMillis() * 1000;
|
||||
|
||||
int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum();
|
||||
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
|
||||
boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
|
||||
if (indexUnbounded) {
|
||||
// The index is itself unbounded. We need to use the current time to calculate the range of
|
||||
// available segments.
|
||||
long liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
|
||||
if (currentManifest.timeShiftBufferDepth != -1) {
|
||||
long bufferDepthUs = currentManifest.timeShiftBufferDepth * 1000;
|
||||
firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum,
|
||||
representationHolder.getSegmentNum(liveEdgeTimestampUs - bufferDepthUs));
|
||||
}
|
||||
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
|
||||
// index of the last completed segment.
|
||||
lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimestampUs) - 1;
|
||||
}
|
||||
|
||||
int segmentNum;
|
||||
if (previous == null) {
|
||||
if (live) {
|
||||
playbackPositionUs = getLiveSeekPosition(nowUs);
|
||||
}
|
||||
segmentNum = representationHolder.getSegmentNum(playbackPositionUs);
|
||||
} else {
|
||||
segmentNum = previous.getNextChunkIndex();
|
||||
}
|
||||
|
||||
if (live && segmentNum < firstAvailableSegmentNum) {
|
||||
// This is before the first chunk in the current manifest.
|
||||
fatalError = new BehindLiveWindowException();
|
||||
return;
|
||||
} else if (currentManifest.dynamic) {
|
||||
if (segmentNum > lastAvailableSegmentNum) {
|
||||
// This is beyond the last chunk in the current manifest.
|
||||
return;
|
||||
}
|
||||
} else if (segmentNum > lastAvailableSegmentNum) {
|
||||
out.endOfStream = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource, selectedFormat,
|
||||
sampleFormat, segmentNum, evaluation.trigger);
|
||||
lastChunkWasInitialization = false;
|
||||
out.chunk = nextMediaChunk;
|
||||
}
|
||||
@ -441,14 +302,8 @@ public class DashChunkSource implements ChunkSource {
|
||||
public void onChunkLoadCompleted(Chunk chunk) {
|
||||
if (chunk instanceof InitializationChunk) {
|
||||
InitializationChunk initializationChunk = (InitializationChunk) chunk;
|
||||
PeriodHolder periodHolder = periodHolders.get(initializationChunk.parentId);
|
||||
if (periodHolder == null) {
|
||||
// period for this initialization chunk may no longer be on the manifest
|
||||
return;
|
||||
}
|
||||
|
||||
RepresentationHolder representationHolder =
|
||||
periodHolder.representationHolders[getTrackIndex(initializationChunk.format)];
|
||||
representationHolders[getTrackIndex(initializationChunk.format)];
|
||||
if (initializationChunk.hasSampleFormat()) {
|
||||
representationHolder.sampleFormat = initializationChunk.getSampleFormat();
|
||||
}
|
||||
@ -462,8 +317,8 @@ public class DashChunkSource implements ChunkSource {
|
||||
}
|
||||
// The null check avoids overwriting drmInitData obtained from the manifest with drmInitData
|
||||
// obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
|
||||
if (periodHolder.drmInitData == null && initializationChunk.hasDrmInitData()) {
|
||||
periodHolder.drmInitData = initializationChunk.getDrmInitData();
|
||||
if (drmInitData == null && initializationChunk.hasDrmInitData()) {
|
||||
drmInitData = initializationChunk.getDrmInitData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -479,9 +334,7 @@ public class DashChunkSource implements ChunkSource {
|
||||
if (enabledFormats.length > 1) {
|
||||
adaptiveFormatEvaluator.disable();
|
||||
}
|
||||
periodHolders.clear();
|
||||
evaluation.clear();
|
||||
availableRange = null;
|
||||
fatalError = null;
|
||||
enabledFormats = null;
|
||||
}
|
||||
@ -490,6 +343,9 @@ public class DashChunkSource implements ChunkSource {
|
||||
|
||||
private void initForManifest(MediaPresentationDescription manifest) {
|
||||
Period period = manifest.getPeriod(0);
|
||||
live = currentManifest.dynamic;
|
||||
durationUs = live ? C.UNKNOWN_TIME_US : currentManifest.duration * 1000;
|
||||
|
||||
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
||||
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
||||
if (adaptationSet.type == adaptationSetType) {
|
||||
@ -497,11 +353,18 @@ public class DashChunkSource implements ChunkSource {
|
||||
List<Representation> representations = adaptationSet.representations;
|
||||
if (!representations.isEmpty()) {
|
||||
// We've found a non-empty adaptation set of the exposed type.
|
||||
long periodDurationUs = getPeriodDurationUs(manifest, 0);
|
||||
representationHolders = new RepresentationHolder[representations.size()];
|
||||
Format[] trackFormats = new Format[representations.size()];
|
||||
for (int j = 0; j < trackFormats.length; j++) {
|
||||
trackFormats[j] = representations.get(j).format;
|
||||
Representation representation = representations.get(j);
|
||||
representationHolders[j] = new RepresentationHolder(periodDurationUs, representation);
|
||||
trackFormats[j] = representation.format;
|
||||
}
|
||||
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, trackFormats);
|
||||
drmInitData = getDrmInitData(adaptationSet);
|
||||
updateRepresentationIndependentProperties(periodDurationUs,
|
||||
representationHolders[0].representation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -509,14 +372,40 @@ public class DashChunkSource implements ChunkSource {
|
||||
trackGroup = null;
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
/* package */ TimeRange getAvailableRange() {
|
||||
return availableRange;
|
||||
private void processManifest(MediaPresentationDescription newManifest) {
|
||||
try {
|
||||
currentManifest = newManifest;
|
||||
long periodDurationUs = getPeriodDurationUs(currentManifest, 0);
|
||||
List<Representation> representations = currentManifest.getPeriod(0).adaptationSets
|
||||
.get(adaptationSetIndex).representations;
|
||||
for (int i = 0; i < representationHolders.length; i++) {
|
||||
Representation representation = representations.get(i);
|
||||
representationHolders[i].updateRepresentation(periodDurationUs, representation);
|
||||
}
|
||||
updateRepresentationIndependentProperties(periodDurationUs,
|
||||
representationHolders[0].representation);
|
||||
} catch (BehindLiveWindowException e) {
|
||||
fatalError = e;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRepresentationIndependentProperties(long periodDurationUs,
|
||||
Representation arbitaryRepresentation) {
|
||||
DashSegmentIndex segmentIndex = arbitaryRepresentation.getIndex();
|
||||
if (segmentIndex != null) {
|
||||
int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs);
|
||||
indexIsUnbounded = lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
|
||||
indexIsExplicit = segmentIndex.isExplicit();
|
||||
} else {
|
||||
indexIsUnbounded = false;
|
||||
indexIsExplicit = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
|
||||
Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource,
|
||||
int manifestIndex, int trigger) {
|
||||
int trigger) {
|
||||
RangedUri requestUri;
|
||||
if (initializationUri != null) {
|
||||
// It's common for initialization and index data to be stored adjacently. Attempt to merge
|
||||
@ -531,12 +420,11 @@ public class DashChunkSource implements ChunkSource {
|
||||
DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length,
|
||||
representation.getCacheKey());
|
||||
return new InitializationChunk(dataSource, dataSpec, trigger, representation.format,
|
||||
extractor, manifestIndex);
|
||||
extractor);
|
||||
}
|
||||
|
||||
protected Chunk newMediaChunk(PeriodHolder periodHolder,
|
||||
RepresentationHolder representationHolder, DataSource dataSource, Format trackFormat,
|
||||
Format sampleFormat, int segmentNum, int trigger) {
|
||||
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource,
|
||||
Format trackFormat, Format sampleFormat, int segmentNum, int trigger) {
|
||||
Representation representation = representationHolder.representation;
|
||||
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
|
||||
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
|
||||
@ -544,112 +432,43 @@ public class DashChunkSource implements ChunkSource {
|
||||
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
|
||||
representation.getCacheKey());
|
||||
|
||||
long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs;
|
||||
if (representationHolder.extractorWrapper == null) {
|
||||
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, trackFormat,
|
||||
startTimeUs, endTimeUs, segmentNum, trackFormat, null, periodHolder.localIndex);
|
||||
startTimeUs, endTimeUs, segmentNum, trackFormat, null);
|
||||
} else {
|
||||
boolean isSampleFormatFinal = sampleFormat != null;
|
||||
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
|
||||
return new ContainerMediaChunk(dataSource, dataSpec, trigger, trackFormat, startTimeUs,
|
||||
endTimeUs, segmentNum, sampleOffsetUs, representationHolder.extractorWrapper,
|
||||
sampleFormat, periodHolder.drmInitData, isSampleFormatFinal, periodHolder.localIndex);
|
||||
sampleFormat, drmInitData, isSampleFormatFinal);
|
||||
}
|
||||
}
|
||||
|
||||
private long getNowUnixTimeUs() {
|
||||
if (elapsedRealtimeOffsetUs != 0) {
|
||||
return (systemClock.elapsedRealtime() * 1000) + elapsedRealtimeOffsetUs;
|
||||
/**
|
||||
* For live playbacks, determines the seek position that snaps playback to be
|
||||
* {@link #liveEdgeLatencyUs} behind the live edge of the current manifest
|
||||
*
|
||||
* @return The seek position in microseconds.
|
||||
*/
|
||||
private long getLiveSeekPosition(long nowUs) {
|
||||
long elapsedTimeUs = nowUs - currentManifest.availabilityStartTime * 1000;
|
||||
long liveEdgeTimestampUs;
|
||||
if (indexIsUnbounded) {
|
||||
liveEdgeTimestampUs = elapsedTimeUs;
|
||||
} else {
|
||||
return System.currentTimeMillis() * 1000;
|
||||
liveEdgeTimestampUs = Long.MIN_VALUE;
|
||||
for (RepresentationHolder representationHolder : representationHolders) {
|
||||
int lastSegmentNum = representationHolder.getLastSegmentNum();
|
||||
long indexLiveEdgeTimestampUs = representationHolder.getSegmentEndTimeUs(lastSegmentNum);
|
||||
liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, indexLiveEdgeTimestampUs);
|
||||
}
|
||||
if (!indexIsExplicit) {
|
||||
// Some segments defined by the index may not be available yet. Bound the calculated live
|
||||
// edge based on the elapsed time since the manifest became available.
|
||||
liveEdgeTimestampUs = Math.min(liveEdgeTimestampUs, elapsedTimeUs);
|
||||
}
|
||||
}
|
||||
|
||||
private PeriodHolder findPeriodHolder(long positionUs) {
|
||||
// if positionUs is before the first period, return the first period
|
||||
if (positionUs < periodHolders.valueAt(0).getAvailableStartTimeUs()) {
|
||||
return periodHolders.valueAt(0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < periodHolders.size() - 1; i++) {
|
||||
PeriodHolder periodHolder = periodHolders.valueAt(i);
|
||||
if (positionUs < periodHolder.getAvailableEndTimeUs()) {
|
||||
return periodHolder;
|
||||
}
|
||||
}
|
||||
|
||||
// positionUs is within or after the last period
|
||||
return periodHolders.valueAt(periodHolders.size() - 1);
|
||||
}
|
||||
|
||||
private void processManifest(MediaPresentationDescription manifest) {
|
||||
// Remove old periods.
|
||||
Period firstPeriod = manifest.getPeriod(0);
|
||||
while (periodHolders.size() > 0
|
||||
&& periodHolders.valueAt(0).startTimeUs < firstPeriod.startMs * 1000) {
|
||||
PeriodHolder periodHolder = periodHolders.valueAt(0);
|
||||
// TODO: Use periodHolders.removeAt(0) if the minimum API level is ever increased to 11.
|
||||
periodHolders.remove(periodHolder.localIndex);
|
||||
}
|
||||
|
||||
// After discarding old periods, we should never have more periods than listed in the new
|
||||
// manifest. That would mean that a previously announced period is no longer advertised. If
|
||||
// this condition occurs, assume that we are hitting a manifest server that is out of sync and
|
||||
// behind, discard this manifest, and try again later.
|
||||
if (periodHolders.size() > manifest.getPeriodCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update existing periods. Only the first and last periods can change.
|
||||
try {
|
||||
int periodHolderCount = periodHolders.size();
|
||||
if (periodHolderCount > 0) {
|
||||
periodHolders.valueAt(0).updatePeriod(manifest, 0, adaptationSetIndex);
|
||||
if (periodHolderCount > 1) {
|
||||
int lastIndex = periodHolderCount - 1;
|
||||
periodHolders.valueAt(lastIndex).updatePeriod(manifest, lastIndex, adaptationSetIndex);
|
||||
}
|
||||
}
|
||||
} catch (BehindLiveWindowException e) {
|
||||
fatalError = e;
|
||||
return;
|
||||
}
|
||||
|
||||
// Add new periods.
|
||||
for (int i = periodHolders.size(); i < manifest.getPeriodCount(); i++) {
|
||||
PeriodHolder holder = new PeriodHolder(nextPeriodHolderIndex, manifest, i,
|
||||
adaptationSetIndex);
|
||||
periodHolders.put(nextPeriodHolderIndex, holder);
|
||||
nextPeriodHolderIndex++;
|
||||
}
|
||||
|
||||
// Update the available range.
|
||||
TimeRange newAvailableRange = getAvailableRange(getNowUnixTimeUs());
|
||||
if (availableRange == null || !availableRange.equals(newAvailableRange)) {
|
||||
availableRange = newAvailableRange;
|
||||
notifyAvailableRangeChanged(availableRange);
|
||||
}
|
||||
|
||||
currentManifest = manifest;
|
||||
}
|
||||
|
||||
private TimeRange getAvailableRange(long nowUnixTimeUs) {
|
||||
PeriodHolder firstPeriod = periodHolders.valueAt(0);
|
||||
PeriodHolder lastPeriod = periodHolders.valueAt(periodHolders.size() - 1);
|
||||
|
||||
if (!currentManifest.dynamic || lastPeriod.isIndexExplicit()) {
|
||||
return new StaticTimeRange(firstPeriod.getAvailableStartTimeUs(),
|
||||
lastPeriod.getAvailableEndTimeUs());
|
||||
}
|
||||
|
||||
long minStartPositionUs = firstPeriod.getAvailableStartTimeUs();
|
||||
long maxEndPositionUs = lastPeriod.isIndexUnbounded() ? Long.MAX_VALUE
|
||||
: lastPeriod.getAvailableEndTimeUs();
|
||||
long elapsedRealtimeAtZeroUs = (systemClock.elapsedRealtime() * 1000)
|
||||
- (nowUnixTimeUs - (currentManifest.availabilityStartTime * 1000));
|
||||
long timeShiftBufferDepthUs = currentManifest.timeShiftBufferDepth == -1 ? -1
|
||||
: currentManifest.timeShiftBufferDepth * 1000;
|
||||
return new DynamicTimeRange(minStartPositionUs, maxEndPositionUs, elapsedRealtimeAtZeroUs,
|
||||
timeShiftBufferDepthUs, systemClock);
|
||||
return liveEdgeTimestampUs - liveEdgeLatencyUs;
|
||||
}
|
||||
|
||||
private int getTrackIndex(Format format) {
|
||||
@ -662,14 +481,26 @@ public class DashChunkSource implements ChunkSource {
|
||||
throw new IllegalStateException("Invalid format: " + format);
|
||||
}
|
||||
|
||||
private void notifyAvailableRangeChanged(final TimeRange seekRange) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onAvailableRangeChanged(eventSourceId, seekRange);
|
||||
private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) {
|
||||
DrmInitData.Mapped drmInitData = null;
|
||||
for (int i = 0; i < adaptationSet.contentProtections.size(); i++) {
|
||||
ContentProtection contentProtection = adaptationSet.contentProtections.get(i);
|
||||
if (contentProtection.uuid != null && contentProtection.data != null) {
|
||||
if (drmInitData == null) {
|
||||
drmInitData = new DrmInitData.Mapped();
|
||||
}
|
||||
});
|
||||
drmInitData.put(contentProtection.uuid, contentProtection.data);
|
||||
}
|
||||
}
|
||||
return drmInitData;
|
||||
}
|
||||
|
||||
private static long getPeriodDurationUs(MediaPresentationDescription manifest, int index) {
|
||||
long durationMs = manifest.getPeriodDuration(index);
|
||||
if (durationMs == -1) {
|
||||
return C.UNKNOWN_TIME_US;
|
||||
} else {
|
||||
return durationMs * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
@ -683,14 +514,10 @@ public class DashChunkSource implements ChunkSource {
|
||||
public DashSegmentIndex segmentIndex;
|
||||
public Format sampleFormat;
|
||||
|
||||
private final long periodStartTimeUs;
|
||||
|
||||
private long periodDurationUs;
|
||||
private int segmentNumShift;
|
||||
|
||||
public RepresentationHolder(long periodStartTimeUs, long periodDurationUs,
|
||||
Representation representation) {
|
||||
this.periodStartTimeUs = periodStartTimeUs;
|
||||
public RepresentationHolder(long periodDurationUs, Representation representation) {
|
||||
this.periodDurationUs = periodDurationUs;
|
||||
this.representation = representation;
|
||||
String containerMimeType = representation.format.containerMimeType;
|
||||
@ -738,13 +565,20 @@ public class DashChunkSource implements ChunkSource {
|
||||
}
|
||||
}
|
||||
|
||||
public int getSegmentNum(long positionUs) {
|
||||
return segmentIndex.getSegmentNum(positionUs - periodStartTimeUs, periodDurationUs)
|
||||
+ segmentNumShift;
|
||||
public int getFirstSegmentNum() {
|
||||
return segmentIndex.getFirstSegmentNum() + segmentNumShift;
|
||||
}
|
||||
|
||||
public int getLastSegmentNum() {
|
||||
int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs);
|
||||
if (lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED) {
|
||||
return DashSegmentIndex.INDEX_UNBOUNDED;
|
||||
}
|
||||
return lastSegmentNum + segmentNumShift;
|
||||
}
|
||||
|
||||
public long getSegmentStartTimeUs(int segmentNum) {
|
||||
return segmentIndex.getTimeUs(segmentNum - segmentNumShift) + periodStartTimeUs;
|
||||
return segmentIndex.getTimeUs(segmentNum - segmentNumShift);
|
||||
}
|
||||
|
||||
public long getSegmentEndTimeUs(int segmentNum) {
|
||||
@ -752,18 +586,8 @@ public class DashChunkSource implements ChunkSource {
|
||||
+ segmentIndex.getDurationUs(segmentNum - segmentNumShift, periodDurationUs);
|
||||
}
|
||||
|
||||
public int getLastSegmentNum() {
|
||||
return segmentIndex.getLastSegmentNum(periodDurationUs);
|
||||
}
|
||||
|
||||
public boolean isBeyondLastSegment(int segmentNum) {
|
||||
int lastSegmentNum = getLastSegmentNum();
|
||||
return lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED ? false
|
||||
: segmentNum > (lastSegmentNum + segmentNumShift);
|
||||
}
|
||||
|
||||
public int getFirstAvailableSegmentNum() {
|
||||
return segmentIndex.getFirstSegmentNum() + segmentNumShift;
|
||||
public int getSegmentNum(long positionUs) {
|
||||
return segmentIndex.getSegmentNum(positionUs, periodDurationUs) + segmentNumShift;
|
||||
}
|
||||
|
||||
public RangedUri getSegmentUrl(int segmentNum) {
|
||||
@ -781,129 +605,4 @@ public class DashChunkSource implements ChunkSource {
|
||||
|
||||
}
|
||||
|
||||
protected static final class PeriodHolder {
|
||||
|
||||
public final int localIndex;
|
||||
public final long startTimeUs;
|
||||
public final RepresentationHolder[] representationHolders;
|
||||
|
||||
private DrmInitData drmInitData;
|
||||
|
||||
private boolean indexIsUnbounded;
|
||||
private boolean indexIsExplicit;
|
||||
private long availableStartTimeUs;
|
||||
private long availableEndTimeUs;
|
||||
|
||||
public PeriodHolder(int localIndex, MediaPresentationDescription manifest, int manifestIndex,
|
||||
int adaptationSetIndex) {
|
||||
this.localIndex = localIndex;
|
||||
|
||||
Period period = manifest.getPeriod(manifestIndex);
|
||||
long periodDurationUs = getPeriodDurationUs(manifest, manifestIndex);
|
||||
AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex);
|
||||
List<Representation> representations = adaptationSet.representations;
|
||||
|
||||
startTimeUs = period.startMs * 1000;
|
||||
drmInitData = getDrmInitData(adaptationSet);
|
||||
|
||||
representationHolders = new RepresentationHolder[representations.size()];
|
||||
for (int i = 0; i < representationHolders.length; i++) {
|
||||
Representation representation = representations.get(i);
|
||||
representationHolders[i] = new RepresentationHolder(startTimeUs,
|
||||
periodDurationUs, representation);
|
||||
}
|
||||
updateRepresentationIndependentProperties(periodDurationUs,
|
||||
representationHolders[0].representation);
|
||||
}
|
||||
|
||||
public void updatePeriod(MediaPresentationDescription manifest, int manifestIndex,
|
||||
int adaptationSetIndex) throws BehindLiveWindowException {
|
||||
Period period = manifest.getPeriod(manifestIndex);
|
||||
long periodDurationUs = getPeriodDurationUs(manifest, manifestIndex);
|
||||
List<Representation> representations = period.adaptationSets
|
||||
.get(adaptationSetIndex).representations;
|
||||
|
||||
for (int i = 0; i < representationHolders.length; i++) {
|
||||
Representation representation = representations.get(i);
|
||||
representationHolders[i].updateRepresentation(periodDurationUs, representation);
|
||||
}
|
||||
updateRepresentationIndependentProperties(periodDurationUs,
|
||||
representationHolders[0].representation);
|
||||
}
|
||||
|
||||
public long getAvailableStartTimeUs() {
|
||||
return availableStartTimeUs;
|
||||
}
|
||||
|
||||
public long getAvailableEndTimeUs() {
|
||||
if (isIndexUnbounded()) {
|
||||
throw new IllegalStateException("Period has unbounded index");
|
||||
}
|
||||
return availableEndTimeUs;
|
||||
}
|
||||
|
||||
public boolean isIndexUnbounded() {
|
||||
return indexIsUnbounded;
|
||||
}
|
||||
|
||||
public boolean isIndexExplicit() {
|
||||
return indexIsExplicit;
|
||||
}
|
||||
|
||||
public DrmInitData getDrmInitData() {
|
||||
return drmInitData;
|
||||
}
|
||||
|
||||
// Private methods.
|
||||
|
||||
private void updateRepresentationIndependentProperties(long periodDurationUs,
|
||||
Representation arbitaryRepresentation) {
|
||||
DashSegmentIndex segmentIndex = arbitaryRepresentation.getIndex();
|
||||
if (segmentIndex != null) {
|
||||
int firstSegmentNum = segmentIndex.getFirstSegmentNum();
|
||||
int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs);
|
||||
indexIsUnbounded = lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
|
||||
indexIsExplicit = segmentIndex.isExplicit();
|
||||
availableStartTimeUs = startTimeUs + segmentIndex.getTimeUs(firstSegmentNum);
|
||||
if (!indexIsUnbounded) {
|
||||
availableEndTimeUs = startTimeUs + segmentIndex.getTimeUs(lastSegmentNum)
|
||||
+ segmentIndex.getDurationUs(lastSegmentNum, periodDurationUs);
|
||||
}
|
||||
} else {
|
||||
indexIsUnbounded = false;
|
||||
indexIsExplicit = true;
|
||||
availableStartTimeUs = startTimeUs;
|
||||
availableEndTimeUs = startTimeUs + periodDurationUs;
|
||||
}
|
||||
}
|
||||
|
||||
private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) {
|
||||
if (adaptationSet.contentProtections.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
DrmInitData.Mapped drmInitData = null;
|
||||
for (int i = 0; i < adaptationSet.contentProtections.size(); i++) {
|
||||
ContentProtection contentProtection = adaptationSet.contentProtections.get(i);
|
||||
if (contentProtection.uuid != null && contentProtection.data != null) {
|
||||
if (drmInitData == null) {
|
||||
drmInitData = new DrmInitData.Mapped();
|
||||
}
|
||||
drmInitData.put(contentProtection.uuid, contentProtection.data);
|
||||
}
|
||||
}
|
||||
return drmInitData;
|
||||
}
|
||||
}
|
||||
|
||||
private static long getPeriodDurationUs(MediaPresentationDescription manifest, int index) {
|
||||
long durationMs = manifest.getPeriodDuration(index);
|
||||
if (durationMs == -1) {
|
||||
return C.UNKNOWN_TIME_US;
|
||||
} else {
|
||||
return durationMs * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -727,7 +727,7 @@ public class HlsChunkSource {
|
||||
byte[] scratchSpace, HlsPlaylistParser playlistParser, int variantIndex,
|
||||
Uri playlistUri) {
|
||||
super(dataSource, dataSpec, Chunk.TYPE_MANIFEST, Chunk.TRIGGER_UNSPECIFIED, format,
|
||||
Chunk.NO_PARENT_ID, scratchSpace);
|
||||
scratchSpace);
|
||||
this.variantIndex = variantIndex;
|
||||
this.playlistParser = playlistParser;
|
||||
this.playlistUri = playlistUri;
|
||||
@ -754,7 +754,7 @@ public class HlsChunkSource {
|
||||
public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, Format format,
|
||||
byte[] scratchSpace, String iv) {
|
||||
super(dataSource, dataSpec, Chunk.TYPE_DRM, Chunk.TRIGGER_UNSPECIFIED, format,
|
||||
Chunk.NO_PARENT_ID, scratchSpace);
|
||||
scratchSpace);
|
||||
this.iv = iv;
|
||||
}
|
||||
|
||||
|
@ -253,7 +253,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||
}
|
||||
chunkIndex = streamElement.getChunkIndex(playbackPositionUs);
|
||||
} else {
|
||||
chunkIndex = previous.chunkIndex + 1 - currentManifestChunkOffset;
|
||||
chunkIndex = previous.getNextChunkIndex() - currentManifestChunkOffset;
|
||||
}
|
||||
|
||||
if (live && chunkIndex < 0) {
|
||||
@ -404,7 +404,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||
long sampleOffsetUs = chunkStartTimeUs;
|
||||
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, chunkStartTimeUs,
|
||||
chunkEndTimeUs, chunkIndex, sampleOffsetUs, extractorWrapper, sampleFormat, drmInitData,
|
||||
true, Chunk.NO_PARENT_ID);
|
||||
true);
|
||||
}
|
||||
|
||||
private static byte[] getProtectionElementKeyId(byte[] initData) {
|
||||
|
@ -16,11 +16,10 @@
|
||||
package com.google.android.exoplayer.upstream;
|
||||
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.Clock;
|
||||
import com.google.android.exoplayer.util.SlidingPercentile;
|
||||
import com.google.android.exoplayer.util.SystemClock;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
|
||||
/**
|
||||
* Counts transferred bytes while transfers are open and creates a bandwidth sample and updated
|
||||
@ -35,7 +34,6 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
|
||||
|
||||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
private final Clock clock;
|
||||
private final SlidingPercentile slidingPercentile;
|
||||
|
||||
private int streamCount;
|
||||
@ -51,22 +49,12 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
|
||||
}
|
||||
|
||||
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener) {
|
||||
this(eventHandler, eventListener, new SystemClock());
|
||||
}
|
||||
|
||||
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, Clock clock) {
|
||||
this(eventHandler, eventListener, clock, DEFAULT_MAX_WEIGHT);
|
||||
this(eventHandler, eventListener, DEFAULT_MAX_WEIGHT);
|
||||
}
|
||||
|
||||
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, int maxWeight) {
|
||||
this(eventHandler, eventListener, new SystemClock(), maxWeight);
|
||||
}
|
||||
|
||||
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, Clock clock,
|
||||
int maxWeight) {
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
this.clock = clock;
|
||||
this.slidingPercentile = new SlidingPercentile(maxWeight);
|
||||
bitrateEstimate = NO_ESTIMATE;
|
||||
}
|
||||
@ -79,7 +67,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
|
||||
@Override
|
||||
public synchronized void onTransferStart() {
|
||||
if (streamCount == 0) {
|
||||
sampleStartTimeMs = clock.elapsedRealtime();
|
||||
sampleStartTimeMs = SystemClock.elapsedRealtime();
|
||||
}
|
||||
streamCount++;
|
||||
}
|
||||
@ -92,7 +80,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
|
||||
@Override
|
||||
public synchronized void onTransferEnd() {
|
||||
Assertions.checkState(streamCount > 0);
|
||||
long nowMs = clock.elapsedRealtime();
|
||||
long nowMs = SystemClock.elapsedRealtime();
|
||||
int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs);
|
||||
totalElapsedTimeMs += sampleElapsedTimeMs;
|
||||
totalBytesTransferred += sampleBytesTransferred;
|
||||
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.util;
|
||||
|
||||
/**
|
||||
* The standard implementation of {@link Clock}.
|
||||
*/
|
||||
public final class SystemClock implements Clock {
|
||||
|
||||
@Override
|
||||
public long elapsedRealtime() {
|
||||
return android.os.SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user