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:
olly 2016-04-11 04:18:20 -07:00 committed by Oliver Woodman
parent 1c0a120aeb
commit 7170ff380c
17 changed files with 195 additions and 828 deletions

View File

@ -19,7 +19,6 @@ import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
import com.google.android.exoplayer.ExoPlayer; import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.TrackGroup; import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
@ -50,7 +49,6 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
} }
private long sessionStartTimeMs; private long sessionStartTimeMs;
private long[] availableRangeValuesUs;
public void startSession() { public void startSession() {
sessionStartTimeMs = SystemClock.elapsedRealtime(); sessionStartTimeMs = SystemClock.elapsedRealtime();
@ -215,13 +213,6 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
Log.d(TAG, "decoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]"); 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) { private void printInternalError(String type, Exception e) {
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e); Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
} }

View File

@ -44,8 +44,6 @@ import android.os.Handler;
// TODO[REFACTOR]: Bring back UTC timing element support. // TODO[REFACTOR]: Bring back UTC timing element support.
public class DashSourceBuilder implements SourceBuilder { public class DashSourceBuilder implements SourceBuilder {
private static final String TAG = "DashSourceBuilder";
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024; private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200; private static final int VIDEO_BUFFER_SEGMENTS = 200;
private static final int AUDIO_BUFFER_SEGMENTS = 54; private static final int AUDIO_BUFFER_SEGMENTS = 54;
@ -78,15 +76,14 @@ public class DashSourceBuilder implements SourceBuilder {
// Build the video renderer. // Build the video renderer.
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter); DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_VIDEO, ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_VIDEO,
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
0, mainHandler, player, DemoPlayer.TYPE_VIDEO);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO); VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
// Build the audio renderer. // Build the audio renderer.
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter); DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_AUDIO, 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, ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
DemoPlayer.TYPE_AUDIO); DemoPlayer.TYPE_AUDIO);
@ -94,7 +91,7 @@ public class DashSourceBuilder implements SourceBuilder {
// Build the text renderer. // Build the text renderer.
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter); DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, AdaptationSet.TYPE_TEXT, 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, ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
DemoPlayer.TYPE_TEXT); DemoPlayer.TYPE_TEXT);

View File

@ -27,12 +27,10 @@ import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializatio
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SingleSampleSource; import com.google.android.exoplayer.SingleSampleSource;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioTrack; import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener; 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.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.extractor.ExtractorSampleSource;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer; import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
@ -67,8 +65,8 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
ChunkSampleSourceEventListener, ExtractorSampleSource.EventListener, ChunkSampleSourceEventListener, ExtractorSampleSource.EventListener,
SingleSampleSource.EventListener, DefaultBandwidthMeter.EventListener, SingleSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener, MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer, StreamingDrmSessionManager.EventListener, TextRenderer, MetadataRenderer<List<Id3Frame>>,
MetadataRenderer<List<Id3Frame>>, DebugTextViewHelper.Provider { DebugTextViewHelper.Provider {
/** /**
* Builds a source to play. * 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); long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs);
void onDecoderInitialized(String decoderName, long elapsedRealtimeMs, void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
long initializationDurationMs); 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 @Override
public void onPlayWhenReadyCommitted() { public void onPlayWhenReadyCommitted() {
// Do nothing. // Do nothing.

View File

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

View File

@ -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;
}
}
}

View File

@ -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 * 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 * these methods are only guaranteed to return correct data after the first sample data has
* been output from the chunk. * 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, public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isSampleFormatFinal, int parentId) { long startTimeUs, long endTimeUs, int chunkIndex, boolean isSampleFormatFinal) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, parentId); super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
this.isSampleFormatFinal = isSampleFormatFinal; this.isSampleFormatFinal = isSampleFormatFinal;
} }

View File

@ -76,10 +76,6 @@ public abstract class Chunk implements Loadable {
* Implementations may define custom {@link #trigger} codes greater than or equal to this value. * Implementations may define custom {@link #trigger} codes greater than or equal to this value.
*/ */
public static final int TRIGGER_CUSTOM_BASE = 10000; 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. * 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. * The {@link DataSpec} that defines the data to be loaded.
*/ */
public final DataSpec dataSpec; public final DataSpec dataSpec;
/**
* Optional identifier for a parent from which this chunk originates.
*/
public final int parentId;
protected final DataSource dataSource; protected final DataSource dataSource;
@ -114,16 +106,13 @@ public abstract class Chunk implements Loadable {
* @param type See {@link #type}. * @param type See {@link #type}.
* @param trigger See {@link #trigger}. * @param trigger See {@link #trigger}.
* @param format See {@link #format}. * @param format See {@link #format}.
* @param parentId See {@link #parentId}.
*/ */
public Chunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format, public Chunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format) {
int parentId) {
this.dataSource = Assertions.checkNotNull(dataSource); this.dataSource = Assertions.checkNotNull(dataSource);
this.dataSpec = Assertions.checkNotNull(dataSpec); this.dataSpec = Assertions.checkNotNull(dataSpec);
this.type = type; this.type = type;
this.trigger = trigger; this.trigger = trigger;
this.format = format; this.format = format;
this.parentId = parentId;
} }
/** /**

View File

@ -60,14 +60,13 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
* @param isSampleFormatFinal True if {@code sampleFormat} and {@code drmInitData} are known to be * @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 * correct and final. False if the data may define its own sample format or initialization
* data. * data.
* @param parentId Identifier for a parent from which this chunk originates.
*/ */
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs, long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
ChunkExtractorWrapper extractorWrapper, Format sampleFormat, DrmInitData drmInitData, ChunkExtractorWrapper extractorWrapper, Format sampleFormat, DrmInitData drmInitData,
boolean isSampleFormatFinal, int parentId) { boolean isSampleFormatFinal) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
isSampleFormatFinal, parentId); isSampleFormatFinal);
this.extractorWrapper = extractorWrapper; this.extractorWrapper = extractorWrapper;
this.sampleOffsetUs = sampleOffsetUs; this.sampleOffsetUs = sampleOffsetUs;
this.sampleFormat = getAdjustedSampleFormat(sampleFormat, sampleOffsetUs); this.sampleFormat = getAdjustedSampleFormat(sampleFormat, sampleOffsetUs);

View File

@ -44,12 +44,11 @@ public abstract class DataChunk extends Chunk {
* @param type See {@link #type}. * @param type See {@link #type}.
* @param trigger See {@link #trigger}. * @param trigger See {@link #trigger}.
* @param format See {@link #format}. * @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. * @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, public DataChunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format,
int parentId, byte[] data) { byte[] data) {
super(dataSource, dataSpec, type, trigger, format, parentId); super(dataSource, dataSpec, type, trigger, format);
this.data = data; this.data = data;
} }

View File

@ -46,11 +46,6 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu
private volatile int bytesLoaded; private volatile int bytesLoaded;
private volatile boolean loadCanceled; 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. * 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 trigger The reason for this chunk being selected.
* @param format The format of the stream to which this chunk belongs. * @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 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, public InitializationChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
ChunkExtractorWrapper extractorWrapper, int parentId) { ChunkExtractorWrapper extractorWrapper) {
super(dataSource, dataSpec, Chunk.TYPE_MEDIA_INITIALIZATION, trigger, format, parentId); super(dataSource, dataSpec, Chunk.TYPE_MEDIA_INITIALIZATION, trigger, format);
this.extractorWrapper = extractorWrapper; this.extractorWrapper = extractorWrapper;
} }

View File

@ -38,16 +38,6 @@ public abstract class MediaChunk extends Chunk {
*/ */
public final int chunkIndex; 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 dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded. * @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 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 endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk. * @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, public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, int parentId) { long startTimeUs, long endTimeUs, int chunkIndex) {
super(dataSource, dataSpec, Chunk.TYPE_MEDIA, trigger, format, parentId); super(dataSource, dataSpec, Chunk.TYPE_MEDIA, trigger, format);
Assertions.checkNotNull(format); Assertions.checkNotNull(format);
this.startTimeUs = startTimeUs; this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs; this.endTimeUs = endTimeUs;
this.chunkIndex = chunkIndex; this.chunkIndex = chunkIndex;
} }
public final int getNextChunkIndex() {
return chunkIndex + 1;
}
} }

View File

@ -48,13 +48,11 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
* @param sampleFormat The format of the sample. * @param sampleFormat The format of the sample.
* @param sampleDrmInitData The {@link DrmInitData} for the sample. Null if the sample is not drm * @param sampleDrmInitData The {@link DrmInitData} for the sample. Null if the sample is not drm
* protected. * protected.
* @param parentId Identifier for a parent from which this chunk originates.
*/ */
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
Format format, long startTimeUs, long endTimeUs, int chunkIndex, Format sampleFormat, Format format, long startTimeUs, long endTimeUs, int chunkIndex, Format sampleFormat,
DrmInitData sampleDrmInitData, int parentId) { DrmInitData sampleDrmInitData) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true, super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true);
parentId);
this.sampleFormat = sampleFormat; this.sampleFormat = sampleFormat;
this.sampleDrmInitData = sampleDrmInitData; this.sampleDrmInitData = sampleDrmInitData;
} }

View File

@ -19,9 +19,6 @@ import com.google.android.exoplayer.BehindLiveWindowException;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.Format.DecreasingBandwidthComparator; 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.TrackGroup;
import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper; 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.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec; 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.ManifestFetcher;
import com.google.android.exoplayer.util.MimeTypes; 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.io.IOException;
import java.util.Arrays; 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). // TODO[REFACTOR]: Handle multiple adaptation sets of the same type (at a higher level).
public class DashChunkSource implements ChunkSource { 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 int adaptationSetType;
private final DataSource dataSource; private final DataSource dataSource;
private final FormatEvaluator adaptiveFormatEvaluator; private final FormatEvaluator adaptiveFormatEvaluator;
private final Evaluation evaluation; private final Evaluation evaluation;
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher; private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
private final SparseArray<PeriodHolder> periodHolders;
private final Clock systemClock;
private final long liveEdgeLatencyUs; 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 boolean live;
private long durationUs; private long durationUs;
private MediaPresentationDescription currentManifest; private MediaPresentationDescription currentManifest;
private MediaPresentationDescription processedManifest; private DrmInitData drmInitData;
private int nextPeriodHolderIndex; private boolean indexIsUnbounded;
private TimeRange availableRange; private boolean indexIsExplicit;
private boolean startAtLiveEdge;
private boolean lastChunkWasInitialization; private boolean lastChunkWasInitialization;
private IOException fatalError; private IOException fatalError;
// Properties of exposed tracks. // Properties of exposed tracks.
private int adaptationSetIndex; private int adaptationSetIndex;
private TrackGroup trackGroup; private TrackGroup trackGroup;
private RepresentationHolder[] representationHolders;
// Properties of enabled tracks. // Properties of enabled tracks.
private Format[] enabledFormats; 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 * 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. * 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. * 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, public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, Handler eventHandler, long liveEdgeLatencyMs) {
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) {
this.manifestFetcher = manifestFetcher; this.manifestFetcher = manifestFetcher;
this.adaptationSetType = adaptationSetType; this.adaptationSetType = adaptationSetType;
this.dataSource = dataSource; this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.systemClock = systemClock; this.liveEdgeLatencyUs = liveEdgeLatencyMs * 1000;
this.liveEdgeLatencyUs = liveEdgeLatencyUs;
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetUs;
this.startAtLiveEdge = startAtLiveEdge;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.eventSourceId = eventSourceId;
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
this.availableRangeValues = new long[2];
periodHolders = new SparseArray<>();
} }
// ChunkSource implementation. // ChunkSource implementation.
@ -235,8 +137,6 @@ public class DashChunkSource implements ChunkSource {
manifestFetcher.requestRefresh(); manifestFetcher.requestRefresh();
return false; return false;
} else { } else {
live = currentManifest.dynamic;
durationUs = live ? C.UNKNOWN_TIME_US : currentManifest.duration * 1000;
initForManifest(currentManifest); initForManifest(currentManifest);
} }
} }
@ -274,11 +174,8 @@ public class DashChunkSource implements ChunkSource {
} }
MediaPresentationDescription newManifest = manifestFetcher.getManifest(); MediaPresentationDescription newManifest = manifestFetcher.getManifest();
if (newManifest != null && newManifest != processedManifest) { if (newManifest != null && newManifest != currentManifest) {
processManifest(newManifest); 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 // 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; 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 = RepresentationHolder representationHolder =
periodHolder.representationHolders[getTrackIndex(selectedFormat)]; representationHolders[getTrackIndex(selectedFormat)];
Representation selectedRepresentation = representationHolder.representation; Representation selectedRepresentation = representationHolder.representation;
DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
RangedUri pendingInitializationUri = null; RangedUri pendingInitializationUri = null;
RangedUri pendingIndexUri = null; RangedUri pendingIndexUri = null;
Format sampleFormat = representationHolder.sampleFormat; Format sampleFormat = representationHolder.sampleFormat;
if (sampleFormat == null) { if (sampleFormat == null) {
pendingInitializationUri = selectedRepresentation.getInitializationUri(); pendingInitializationUri = selectedRepresentation.getInitializationUri();
} }
if (representationHolder.segmentIndex == null) { if (segmentIndex == null) {
pendingIndexUri = selectedRepresentation.getIndexUri(); pendingIndexUri = selectedRepresentation.getIndexUri();
} }
if (pendingInitializationUri != null || pendingIndexUri != null) { if (pendingInitializationUri != null || pendingIndexUri != null) {
// We have initialization and/or index requests to make. // We have initialization and/or index requests to make.
Chunk initializationChunk = newInitializationChunk(pendingInitializationUri, pendingIndexUri, Chunk initializationChunk = newInitializationChunk(pendingInitializationUri, pendingIndexUri,
selectedRepresentation, representationHolder.extractorWrapper, dataSource, selectedRepresentation, representationHolder.extractorWrapper, dataSource,
periodHolder.localIndex, evaluation.trigger); evaluation.trigger);
lastChunkWasInitialization = true; lastChunkWasInitialization = true;
out.chunk = initializationChunk; out.chunk = initializationChunk;
return; return;
} }
int segmentNum = previous == null ? representationHolder.getSegmentNum(playbackPositionUs) // TODO[REFACTOR]: Bring back UTC timing element support.
: startingNewPeriod ? representationHolder.getFirstAvailableSegmentNum() long nowUs = System.currentTimeMillis() * 1000;
: previous.getNextChunkIndex();
Chunk nextMediaChunk = newMediaChunk(periodHolder, representationHolder, dataSource, int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum();
selectedFormat, sampleFormat, segmentNum, evaluation.trigger); 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; lastChunkWasInitialization = false;
out.chunk = nextMediaChunk; out.chunk = nextMediaChunk;
} }
@ -441,14 +302,8 @@ public class DashChunkSource implements ChunkSource {
public void onChunkLoadCompleted(Chunk chunk) { public void onChunkLoadCompleted(Chunk chunk) {
if (chunk instanceof InitializationChunk) { if (chunk instanceof InitializationChunk) {
InitializationChunk initializationChunk = (InitializationChunk) chunk; 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 = RepresentationHolder representationHolder =
periodHolder.representationHolders[getTrackIndex(initializationChunk.format)]; representationHolders[getTrackIndex(initializationChunk.format)];
if (initializationChunk.hasSampleFormat()) { if (initializationChunk.hasSampleFormat()) {
representationHolder.sampleFormat = initializationChunk.getSampleFormat(); 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 // 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. // obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
if (periodHolder.drmInitData == null && initializationChunk.hasDrmInitData()) { if (drmInitData == null && initializationChunk.hasDrmInitData()) {
periodHolder.drmInitData = initializationChunk.getDrmInitData(); drmInitData = initializationChunk.getDrmInitData();
} }
} }
} }
@ -479,9 +334,7 @@ public class DashChunkSource implements ChunkSource {
if (enabledFormats.length > 1) { if (enabledFormats.length > 1) {
adaptiveFormatEvaluator.disable(); adaptiveFormatEvaluator.disable();
} }
periodHolders.clear();
evaluation.clear(); evaluation.clear();
availableRange = null;
fatalError = null; fatalError = null;
enabledFormats = null; enabledFormats = null;
} }
@ -490,6 +343,9 @@ public class DashChunkSource implements ChunkSource {
private void initForManifest(MediaPresentationDescription manifest) { private void initForManifest(MediaPresentationDescription manifest) {
Period period = manifest.getPeriod(0); 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++) { for (int i = 0; i < period.adaptationSets.size(); i++) {
AdaptationSet adaptationSet = period.adaptationSets.get(i); AdaptationSet adaptationSet = period.adaptationSets.get(i);
if (adaptationSet.type == adaptationSetType) { if (adaptationSet.type == adaptationSetType) {
@ -497,11 +353,18 @@ public class DashChunkSource implements ChunkSource {
List<Representation> representations = adaptationSet.representations; List<Representation> representations = adaptationSet.representations;
if (!representations.isEmpty()) { if (!representations.isEmpty()) {
// We've found a non-empty adaptation set of the exposed type. // 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()]; Format[] trackFormats = new Format[representations.size()];
for (int j = 0; j < trackFormats.length; j++) { 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); trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, trackFormats);
drmInitData = getDrmInitData(adaptationSet);
updateRepresentationIndependentProperties(periodDurationUs,
representationHolders[0].representation);
return; return;
} }
} }
@ -509,14 +372,40 @@ public class DashChunkSource implements ChunkSource {
trackGroup = null; trackGroup = null;
} }
// Visible for testing. private void processManifest(MediaPresentationDescription newManifest) {
/* package */ TimeRange getAvailableRange() { try {
return availableRange; 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, private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource, Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource,
int manifestIndex, int trigger) { int trigger) {
RangedUri requestUri; RangedUri requestUri;
if (initializationUri != null) { if (initializationUri != null) {
// It's common for initialization and index data to be stored adjacently. Attempt to merge // 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, DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length,
representation.getCacheKey()); representation.getCacheKey());
return new InitializationChunk(dataSource, dataSpec, trigger, representation.format, return new InitializationChunk(dataSource, dataSpec, trigger, representation.format,
extractor, manifestIndex); extractor);
} }
protected Chunk newMediaChunk(PeriodHolder periodHolder, private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource,
RepresentationHolder representationHolder, DataSource dataSource, Format trackFormat, Format trackFormat, Format sampleFormat, int segmentNum, int trigger) {
Format sampleFormat, int segmentNum, int trigger) {
Representation representation = representationHolder.representation; Representation representation = representationHolder.representation;
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum); long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
long endTimeUs = representationHolder.getSegmentEndTimeUs(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, DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
representation.getCacheKey()); representation.getCacheKey());
long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs;
if (representationHolder.extractorWrapper == null) { if (representationHolder.extractorWrapper == null) {
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, trackFormat, return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, trackFormat,
startTimeUs, endTimeUs, segmentNum, trackFormat, null, periodHolder.localIndex); startTimeUs, endTimeUs, segmentNum, trackFormat, null);
} else { } else {
boolean isSampleFormatFinal = sampleFormat != null; boolean isSampleFormatFinal = sampleFormat != null;
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
return new ContainerMediaChunk(dataSource, dataSpec, trigger, trackFormat, startTimeUs, return new ContainerMediaChunk(dataSource, dataSpec, trigger, trackFormat, startTimeUs,
endTimeUs, segmentNum, sampleOffsetUs, representationHolder.extractorWrapper, endTimeUs, segmentNum, sampleOffsetUs, representationHolder.extractorWrapper,
sampleFormat, periodHolder.drmInitData, isSampleFormatFinal, periodHolder.localIndex); sampleFormat, drmInitData, isSampleFormatFinal);
} }
} }
private long getNowUnixTimeUs() { /**
if (elapsedRealtimeOffsetUs != 0) { * For live playbacks, determines the seek position that snaps playback to be
return (systemClock.elapsedRealtime() * 1000) + elapsedRealtimeOffsetUs; * {@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 { } else {
return System.currentTimeMillis() * 1000; liveEdgeTimestampUs = Long.MIN_VALUE;
} for (RepresentationHolder representationHolder : representationHolders) {
} int lastSegmentNum = representationHolder.getLastSegmentNum();
long indexLiveEdgeTimestampUs = representationHolder.getSegmentEndTimeUs(lastSegmentNum);
private PeriodHolder findPeriodHolder(long positionUs) { liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, indexLiveEdgeTimestampUs);
// if positionUs is before the first period, return the first period }
if (positionUs < periodHolders.valueAt(0).getAvailableStartTimeUs()) { if (!indexIsExplicit) {
return periodHolders.valueAt(0); // 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);
for (int i = 0; i < periodHolders.size() - 1; i++) {
PeriodHolder periodHolder = periodHolders.valueAt(i);
if (positionUs < periodHolder.getAvailableEndTimeUs()) {
return periodHolder;
} }
} }
return liveEdgeTimestampUs - liveEdgeLatencyUs;
// 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);
} }
private int getTrackIndex(Format format) { private int getTrackIndex(Format format) {
@ -662,14 +481,26 @@ public class DashChunkSource implements ChunkSource {
throw new IllegalStateException("Invalid format: " + format); throw new IllegalStateException("Invalid format: " + format);
} }
private void notifyAvailableRangeChanged(final TimeRange seekRange) { private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) {
if (eventHandler != null && eventListener != null) { DrmInitData.Mapped drmInitData = null;
eventHandler.post(new Runnable() { for (int i = 0; i < adaptationSet.contentProtections.size(); i++) {
@Override ContentProtection contentProtection = adaptationSet.contentProtections.get(i);
public void run() { if (contentProtection.uuid != null && contentProtection.data != null) {
eventListener.onAvailableRangeChanged(eventSourceId, seekRange); 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 DashSegmentIndex segmentIndex;
public Format sampleFormat; public Format sampleFormat;
private final long periodStartTimeUs;
private long periodDurationUs; private long periodDurationUs;
private int segmentNumShift; private int segmentNumShift;
public RepresentationHolder(long periodStartTimeUs, long periodDurationUs, public RepresentationHolder(long periodDurationUs, Representation representation) {
Representation representation) {
this.periodStartTimeUs = periodStartTimeUs;
this.periodDurationUs = periodDurationUs; this.periodDurationUs = periodDurationUs;
this.representation = representation; this.representation = representation;
String containerMimeType = representation.format.containerMimeType; String containerMimeType = representation.format.containerMimeType;
@ -738,13 +565,20 @@ public class DashChunkSource implements ChunkSource {
} }
} }
public int getSegmentNum(long positionUs) { public int getFirstSegmentNum() {
return segmentIndex.getSegmentNum(positionUs - periodStartTimeUs, periodDurationUs) return segmentIndex.getFirstSegmentNum() + segmentNumShift;
+ 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) { public long getSegmentStartTimeUs(int segmentNum) {
return segmentIndex.getTimeUs(segmentNum - segmentNumShift) + periodStartTimeUs; return segmentIndex.getTimeUs(segmentNum - segmentNumShift);
} }
public long getSegmentEndTimeUs(int segmentNum) { public long getSegmentEndTimeUs(int segmentNum) {
@ -752,18 +586,8 @@ public class DashChunkSource implements ChunkSource {
+ segmentIndex.getDurationUs(segmentNum - segmentNumShift, periodDurationUs); + segmentIndex.getDurationUs(segmentNum - segmentNumShift, periodDurationUs);
} }
public int getLastSegmentNum() { public int getSegmentNum(long positionUs) {
return segmentIndex.getLastSegmentNum(periodDurationUs); return segmentIndex.getSegmentNum(positionUs, periodDurationUs) + segmentNumShift;
}
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 RangedUri getSegmentUrl(int segmentNum) { 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;
}
}
}
} }

View File

@ -727,7 +727,7 @@ public class HlsChunkSource {
byte[] scratchSpace, HlsPlaylistParser playlistParser, int variantIndex, byte[] scratchSpace, HlsPlaylistParser playlistParser, int variantIndex,
Uri playlistUri) { Uri playlistUri) {
super(dataSource, dataSpec, Chunk.TYPE_MANIFEST, Chunk.TRIGGER_UNSPECIFIED, format, super(dataSource, dataSpec, Chunk.TYPE_MANIFEST, Chunk.TRIGGER_UNSPECIFIED, format,
Chunk.NO_PARENT_ID, scratchSpace); scratchSpace);
this.variantIndex = variantIndex; this.variantIndex = variantIndex;
this.playlistParser = playlistParser; this.playlistParser = playlistParser;
this.playlistUri = playlistUri; this.playlistUri = playlistUri;
@ -754,7 +754,7 @@ public class HlsChunkSource {
public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, Format format, public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, Format format,
byte[] scratchSpace, String iv) { byte[] scratchSpace, String iv) {
super(dataSource, dataSpec, Chunk.TYPE_DRM, Chunk.TRIGGER_UNSPECIFIED, format, super(dataSource, dataSpec, Chunk.TYPE_DRM, Chunk.TRIGGER_UNSPECIFIED, format,
Chunk.NO_PARENT_ID, scratchSpace); scratchSpace);
this.iv = iv; this.iv = iv;
} }

View File

@ -253,7 +253,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
} }
chunkIndex = streamElement.getChunkIndex(playbackPositionUs); chunkIndex = streamElement.getChunkIndex(playbackPositionUs);
} else { } else {
chunkIndex = previous.chunkIndex + 1 - currentManifestChunkOffset; chunkIndex = previous.getNextChunkIndex() - currentManifestChunkOffset;
} }
if (live && chunkIndex < 0) { if (live && chunkIndex < 0) {
@ -404,7 +404,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
long sampleOffsetUs = chunkStartTimeUs; long sampleOffsetUs = chunkStartTimeUs;
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, chunkStartTimeUs, return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, chunkStartTimeUs,
chunkEndTimeUs, chunkIndex, sampleOffsetUs, extractorWrapper, sampleFormat, drmInitData, chunkEndTimeUs, chunkIndex, sampleOffsetUs, extractorWrapper, sampleFormat, drmInitData,
true, Chunk.NO_PARENT_ID); true);
} }
private static byte[] getProtectionElementKeyId(byte[] initData) { private static byte[] getProtectionElementKeyId(byte[] initData) {

View File

@ -16,11 +16,10 @@
package com.google.android.exoplayer.upstream; package com.google.android.exoplayer.upstream;
import com.google.android.exoplayer.util.Assertions; 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.SlidingPercentile;
import com.google.android.exoplayer.util.SystemClock;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock;
/** /**
* Counts transferred bytes while transfers are open and creates a bandwidth sample and updated * 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 Handler eventHandler;
private final EventListener eventListener; private final EventListener eventListener;
private final Clock clock;
private final SlidingPercentile slidingPercentile; private final SlidingPercentile slidingPercentile;
private int streamCount; private int streamCount;
@ -51,22 +49,12 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
} }
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener) { public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener) {
this(eventHandler, eventListener, new SystemClock()); this(eventHandler, eventListener, DEFAULT_MAX_WEIGHT);
}
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, Clock clock) {
this(eventHandler, eventListener, clock, DEFAULT_MAX_WEIGHT);
} }
public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, int maxWeight) { 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.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
this.clock = clock;
this.slidingPercentile = new SlidingPercentile(maxWeight); this.slidingPercentile = new SlidingPercentile(maxWeight);
bitrateEstimate = NO_ESTIMATE; bitrateEstimate = NO_ESTIMATE;
} }
@ -79,7 +67,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
@Override @Override
public synchronized void onTransferStart() { public synchronized void onTransferStart() {
if (streamCount == 0) { if (streamCount == 0) {
sampleStartTimeMs = clock.elapsedRealtime(); sampleStartTimeMs = SystemClock.elapsedRealtime();
} }
streamCount++; streamCount++;
} }
@ -92,7 +80,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter {
@Override @Override
public synchronized void onTransferEnd() { public synchronized void onTransferEnd() {
Assertions.checkState(streamCount > 0); Assertions.checkState(streamCount > 0);
long nowMs = clock.elapsedRealtime(); long nowMs = SystemClock.elapsedRealtime();
int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs); int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs);
totalElapsedTimeMs += sampleElapsedTimeMs; totalElapsedTimeMs += sampleElapsedTimeMs;
totalBytesTransferred += sampleBytesTransferred; totalBytesTransferred += sampleBytesTransferred;

View File

@ -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();
}
}