From 7170ff380c4570c9a3fc6f65e522b2ae182f28e6 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 11 Apr 2016 04:18:20 -0700 Subject: [PATCH] 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 --- .../android/exoplayer/demo/EventLogger.java | 9 - .../demo/player/DashSourceBuilder.java | 9 +- .../exoplayer/demo/player/DemoPlayer.java | 14 +- .../android/exoplayer/TimeRangeTest.java | 38 -- .../google/android/exoplayer/TimeRange.java | 203 ------ .../exoplayer/chunk/BaseMediaChunk.java | 5 +- .../google/android/exoplayer/chunk/Chunk.java | 13 +- .../exoplayer/chunk/ContainerMediaChunk.java | 5 +- .../android/exoplayer/chunk/DataChunk.java | 5 +- .../exoplayer/chunk/InitializationChunk.java | 10 +- .../android/exoplayer/chunk/MediaChunk.java | 19 +- .../chunk/SingleSampleMediaChunk.java | 6 +- .../exoplayer/dash/DashChunkSource.java | 631 +++++------------- .../android/exoplayer/hls/HlsChunkSource.java | 4 +- .../SmoothStreamingChunkSource.java | 4 +- .../upstream/DefaultBandwidthMeter.java | 20 +- .../android/exoplayer/util/SystemClock.java | 28 - 17 files changed, 195 insertions(+), 828 deletions(-) delete mode 100644 library/src/androidTest/java/com/google/android/exoplayer/TimeRangeTest.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/TimeRange.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/util/SystemClock.java diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java index 0195cb12cb..515cf2121d 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java @@ -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); } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashSourceBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashSourceBuilder.java index c6a4a70ea6..532e4f0b10 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashSourceBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashSourceBuilder.java @@ -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); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java index e99e110e0f..a6db2919fc 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java @@ -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>, DebugTextViewHelper.Provider { + StreamingDrmSessionManager.EventListener, TextRenderer, MetadataRenderer>, + 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. diff --git a/library/src/androidTest/java/com/google/android/exoplayer/TimeRangeTest.java b/library/src/androidTest/java/com/google/android/exoplayer/TimeRangeTest.java deleted file mode 100644 index dee65dd768..0000000000 --- a/library/src/androidTest/java/com/google/android/exoplayer/TimeRangeTest.java +++ /dev/null @@ -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)); - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/TimeRange.java b/library/src/main/java/com/google/android/exoplayer/TimeRange.java deleted file mode 100644 index 46db5aa059..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/TimeRange.java +++ /dev/null @@ -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; - } - - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java index 813f1f63d4..16568652f7 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java index 232fd92ab9..aa53a0ee26 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java @@ -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; } /** diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java index da055e5866..3e98191fe1 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java @@ -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); diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java index 6effc285d0..264893aa51 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java index ea0c22e7fe..cc468cd084 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java index 5ac7845203..b69e9dc154 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java @@ -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; + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java index 4b348abd6d..1520442dbe 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index 603bddec68..29d1c1e6cd 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -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 manifestFetcher; - private final SparseArray 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 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 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 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 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 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 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 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; - } - } - - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index e506c2c16a..a174f69329 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java index a6fcc53552..b8abd8a6ea 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java @@ -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) { diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java index 6511f2d240..7b932780cf 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer/util/SystemClock.java b/library/src/main/java/com/google/android/exoplayer/util/SystemClock.java deleted file mode 100644 index 6da67eae11..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/util/SystemClock.java +++ /dev/null @@ -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(); - } - -}