From 6e24372414b6e38dec54d23644b913b3a654c694 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 12 Aug 2016 10:22:51 -0700 Subject: [PATCH] Use integers as identifiers for DASH periods. This is in preparation for making it so that periods aren't reused. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=130113382 --- .../exoplayer2/ExoPlayerImplInternal.java | 3 +- .../source/ConcatenatingMediaSource.java | 28 ++--- .../exoplayer2/source/MediaSource.java | 21 ++-- .../exoplayer2/source/MergingMediaSource.java | 3 +- .../source/dash/DashMediaPeriod.java | 4 - .../source/dash/DashMediaSource.java | 108 +++++++----------- 6 files changed, 71 insertions(+), 96 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 5a410b78ed..5a9bf0cc11 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -894,8 +894,7 @@ import java.io.IOException; // TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed. if (oldTimeline != null) { int newPlayingIndex = playingPeriod != null ? playingPeriod.index - : loadingPeriod != null ? loadingPeriod.index - : mediaSource.getNewPlayingPeriodIndex(playbackInfo.periodIndex, oldTimeline); + : loadingPeriod != null ? loadingPeriod.index : Timeline.NO_PERIOD_INDEX; if (newPlayingIndex != Timeline.NO_PERIOD_INDEX && newPlayingIndex != playbackInfo.periodIndex) { long oldPositionUs = playbackInfo.positionUs; diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 6987b18d70..e01fa17917 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source; +import android.util.Pair; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; @@ -64,14 +65,12 @@ public final class ConcatenatingMediaSource implements MediaSource { } @Override - public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldConcatenatedTimeline) - throws IOException { + public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldConcatenatedTimeline) { ConcatenatedTimeline oldTimeline = (ConcatenatedTimeline) oldConcatenatedTimeline; int sourceIndex = oldTimeline.getSourceIndexForPeriod(oldPlayingPeriodIndex); int oldFirstPeriodIndex = oldTimeline.getFirstPeriodIndexInSource(sourceIndex); int firstPeriodIndex = timeline.getFirstPeriodIndexInSource(sourceIndex); - return firstPeriodIndex == Timeline.NO_PERIOD_INDEX ? Timeline.NO_PERIOD_INDEX - : firstPeriodIndex + mediaSources[sourceIndex].getNewPlayingPeriodIndex( + return firstPeriodIndex + mediaSources[sourceIndex].getNewPlayingPeriodIndex( oldPlayingPeriodIndex - oldFirstPeriodIndex, oldTimeline.timelines[sourceIndex]); } @@ -165,17 +164,21 @@ public final class ConcatenatingMediaSource implements MediaSource { public Object getPeriodId(int index) { int sourceIndex = getSourceIndexForPeriod(index); int firstPeriodIndexInSource = getFirstPeriodIndexInSource(index); - return timelines[sourceIndex].getPeriodId(index - firstPeriodIndexInSource); + Object periodId = timelines[sourceIndex].getPeriodId(index - firstPeriodIndexInSource); + return Pair.create(sourceIndex, periodId); } @Override public int getIndexOfPeriod(Object id) { - for (int sourceIndex = 0; sourceIndex < timelines.length; sourceIndex++) { - int periodIndexInSource = timelines[sourceIndex].getIndexOfPeriod(id); - if (periodIndexInSource != NO_PERIOD_INDEX) { - int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); - return firstPeriodIndexInSource + periodIndexInSource; - } + // The id was returned by getPeriodId, so it is always a Pair. + @SuppressWarnings("unchecked") + Pair sourceIndexAndPeriodId = (Pair) id; + int sourceIndex = sourceIndexAndPeriodId.first; + Object periodId = sourceIndexAndPeriodId.second; + int periodIndexInSource = timelines[sourceIndex].getIndexOfPeriod(periodId); + if (periodIndexInSource != NO_PERIOD_INDEX) { + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); + return firstPeriodIndexInSource + periodIndexInSource; } return NO_PERIOD_INDEX; } @@ -195,8 +198,7 @@ public final class ConcatenatingMediaSource implements MediaSource { } private int getFirstPeriodIndexInSource(int sourceIndex) { - return sourceIndex == 0 ? 0 : sourceIndex > sourceOffsets.length - ? Timeline.NO_PERIOD_INDEX : sourceOffsets[sourceIndex - 1]; + return sourceIndex == 0 ? 0 : sourceOffsets[sourceIndex - 1]; } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index 2617f0488a..2ab5dd46a9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -78,25 +78,28 @@ public interface MediaSource { void prepareSource(Listener listener); /** - * Returns the period index to play in this source's new timeline. + * Returns the period index to play in this source's new timeline, or + * {@link Timeline#NO_PERIOD_INDEX} if the player should stop playback. The + * {@code oldPlayingPeriodIndex} should be an index of a period in the old timeline that is no + * longer present (based on its identifier) in the new timeline. * * @param oldPlayingPeriodIndex The period index that was being played in the old timeline. * @param oldTimeline The old timeline. - * @return The period index to play in this source's new timeline. - * @throws IOException Thrown if the required period can't be loaded. + * @return The new period index to play in this source's new timeline. Playback will resume from + * the default start position in the new period index. */ - int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) throws IOException; + int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline); /** - * Returns the default {@link Position} that the player should play when it reaches the period at - * {@code index}, or {@code null} if the default start period and position are not yet known. + * Returns the default {@link Position} that the player should play when when starting to play the + * period at {@code index}, or {@code null} if the default position is not yet known. *

* For example, sources can return a {@link Position} with the passed period {@code index} to play - * the period at {@code index} immediately after the period at {@code index - 1}. Or, sources + * the period at {@code index} immediately after the period at {@code index - 1}. Sources * providing multi-period live streams may return the index and position of the live edge when - * passed {@code index == 0} so that the playback position jumps to the live edge. + * passed {@code index == 0} to play from the live edge. * - * @param index The index of the period the player has just reached. + * @param index The index of the requested period. * @return The default start position. */ Position getDefaultStartPosition(int index); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index 3b32348445..91250cc10d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -63,8 +63,7 @@ public final class MergingMediaSource implements MediaSource { } @Override - public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) - throws IOException { + public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) { return mediaSources[0].getNewPlayingPeriodIndex(oldPlayingPeriodIndex, oldTimeline); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index 03575037eb..67dee0f98f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -84,10 +84,6 @@ import java.util.List; } } - public long getStartUs() { - return period.startMs * 1000; - } - // MediaPeriod implementation. @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 74b6b73f5e..e01fc9733b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -19,6 +19,7 @@ import android.net.Uri; import android.os.Handler; import android.os.SystemClock; import android.util.Log; +import android.util.SparseArray; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; @@ -41,7 +42,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Locale; import java.util.TimeZone; @@ -75,6 +75,7 @@ public final class DashMediaSource implements MediaSource { private final DashManifestParser manifestParser; private final ManifestCallback manifestCallback; private final Object manifestUriLock; + private final SparseArray periodsById; private final Runnable refreshSourceInfoRunnable; private MediaSource.Listener sourceListener; @@ -86,10 +87,11 @@ public final class DashMediaSource implements MediaSource { private long manifestLoadEndTimestamp; private DashManifest manifest; private Handler handler; - private ArrayList periods; private SeekWindow seekWindow; private long elapsedRealtimeOffsetMs; + private int firstPeriodId; + public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, DashChunkSource.Factory chunkSourceFactory, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { @@ -108,6 +110,7 @@ public final class DashMediaSource implements MediaSource { manifestParser = new DashManifestParser(); manifestCallback = new ManifestCallback(); manifestUriLock = new Object(); + periodsById = new SparseArray<>(); refreshSourceInfoRunnable = new Runnable() { @Override public void run() { @@ -140,20 +143,8 @@ public final class DashMediaSource implements MediaSource { @Override public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) { - int periodIndex = oldPlayingPeriodIndex; - int oldPeriodCount = oldTimeline.getPeriodCount(); - while (periodIndex < oldPeriodCount) { - Object id = oldTimeline.getPeriodId(periodIndex); - if (id == null) { - break; - } - int index = periods.indexOf(id); - if (index != -1) { - return index; - } - periodIndex++; - } - return Timeline.NO_PERIOD_INDEX; + // Seek to the default position, which is the live edge for live sources. + return 0; } @Override @@ -179,11 +170,14 @@ public final class DashMediaSource implements MediaSource { @Override public MediaPeriod createPeriod(int index) throws IOException { - if (periods == null || periods.size() <= index) { + if (index >= manifest.getPeriodCount()) { loader.maybeThrowError(); return null; } - return periods.get(index); + DashMediaPeriod mediaPeriod = new DashMediaPeriod(manifest, index, chunkSourceFactory, + minLoadableRetryCount, eventDispatcher, elapsedRealtimeOffsetMs, loader); + periodsById.put(firstPeriodId + index, mediaPeriod); + return mediaPeriod; } @Override @@ -201,6 +195,7 @@ public final class DashMediaSource implements MediaSource { handler = null; } elapsedRealtimeOffsetMs = 0; + periodsById.clear(); } // Loadable callbacks. @@ -211,24 +206,22 @@ public final class DashMediaSource implements MediaSource { loadDurationMs, loadable.bytesLoaded()); DashManifest newManifest = loadable.getResult(); + int periodCount = manifest == null ? 0 : manifest.getPeriodCount(); int periodsToRemoveCount = 0; - if (periods != null) { - int periodCount = periods.size(); - long newFirstPeriodStartTimeUs = newManifest.getPeriod(0).startMs * 1000; - while (periodsToRemoveCount < periodCount - && periods.get(periodsToRemoveCount).getStartUs() < newFirstPeriodStartTimeUs) { - periodsToRemoveCount++; - } + long newFirstPeriodStartTimeMs = newManifest.getPeriod(0).startMs; + while (periodsToRemoveCount < periodCount + && manifest.getPeriod(periodsToRemoveCount).startMs < newFirstPeriodStartTimeMs) { + periodsToRemoveCount++; + } - // 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 (periodCount - periodsToRemoveCount > newManifest.getPeriodCount()) { - Log.w(TAG, "Out of sync manifest"); - scheduleManifestRefresh(); - return; - } + // 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 (periodCount - periodsToRemoveCount > newManifest.getPeriodCount()) { + Log.w(TAG, "Out of sync manifest"); + scheduleManifestRefresh(); + return; } manifest = newManifest; @@ -244,7 +237,7 @@ public final class DashMediaSource implements MediaSource { } } - if (periods == null) { + if (periodCount == 0) { if (manifest.utcTiming != null) { resolveUtcTimingElement(manifest.utcTiming); } else { @@ -253,11 +246,12 @@ public final class DashMediaSource implements MediaSource { } else { // Remove old periods. while (periodsToRemoveCount-- > 0) { - periods.remove(0); + periodsById.remove(firstPeriodId); + firstPeriodId++; + periodCount--; } // Update existing periods. Only the first and the last periods can change. - int periodCount = periods.size(); if (periodCount > 0) { updatePeriod(0); if (periodCount > 1) { @@ -270,7 +264,10 @@ public final class DashMediaSource implements MediaSource { } private void updatePeriod(int index) { - periods.get(index).updateManifest(manifest, index); + DashMediaPeriod period = periodsById.get(firstPeriodId + index); + if (period != null) { + period.updateManifest(manifest, index); + } } /* package */ int onManifestLoadError(ParsingLoadable loadable, @@ -355,15 +352,6 @@ public final class DashMediaSource implements MediaSource { } private void finishManifestProcessing() { - if (periods == null) { - periods = new ArrayList<>(); - } - int periodCount = manifest.getPeriodCount(); - for (int i = periods.size(); i < periodCount; i++) { - periods.add(new DashMediaPeriod(manifest, i, chunkSourceFactory, minLoadableRetryCount, - eventDispatcher, elapsedRealtimeOffsetMs, loader)); - } - handler.removeCallbacks(refreshSourceInfoRunnable); refreshSourceInfo(); scheduleManifestRefresh(); @@ -371,8 +359,7 @@ public final class DashMediaSource implements MediaSource { private void refreshSourceInfo() { // Update the seek window. - int periodCount = manifest.getPeriodCount(); - int lastPeriodIndex = periodCount - 1; + int lastPeriodIndex = manifest.getPeriodCount() - 1; PeriodSeekInfo firstPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo(manifest.getPeriod(0), manifest.getPeriodDurationUs(0)); PeriodSeekInfo lastPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo( @@ -397,10 +384,7 @@ public final class DashMediaSource implements MediaSource { currentEndTimeUs = lastPeriodSeekInfo.availableEndTimeUs; } seekWindow = SeekWindow.createWindow(0, currentStartTimeUs, lastPeriodIndex, currentEndTimeUs); - - DashMediaPeriod[] mediaPeriods = - periods.toArray(new DashMediaPeriod[manifest.getPeriodCount()]); - sourceListener.onSourceInfoRefreshed(new DashTimeline(manifest, mediaPeriods, seekWindow), + sourceListener.onSourceInfoRefreshed(new DashTimeline(firstPeriodId, manifest, seekWindow), manifest); } @@ -483,13 +467,13 @@ public final class DashMediaSource implements MediaSource { private static final class DashTimeline implements Timeline { + private final int firstPeriodId; private final DashManifest manifest; - private final DashMediaPeriod[] periods; private final SeekWindow seekWindow; - public DashTimeline(DashManifest manifest, DashMediaPeriod[] periods, SeekWindow seekWindow) { + public DashTimeline(int firstPeriodId, DashManifest manifest, SeekWindow seekWindow) { + this.firstPeriodId = firstPeriodId; this.manifest = manifest; - this.periods = periods; this.seekWindow = seekWindow; } @@ -526,20 +510,12 @@ public final class DashMediaSource implements MediaSource { @Override public Object getPeriodId(int index) { - if (index < 0 || index >= manifest.getPeriodCount()) { - throw new IndexOutOfBoundsException(); - } - return periods[index]; + return firstPeriodId + index; } @Override public int getIndexOfPeriod(Object id) { - for (int i = 0; i < periods.length; i++) { - if (id == periods[i]) { - return i; - } - } - return Timeline.NO_PERIOD_INDEX; + return ((Integer) id) - firstPeriodId; } @Override