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

View File

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

View File

@ -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.

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

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.
*/
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;
}
/**

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
* 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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

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