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
This commit is contained in:
andrewlewis 2016-08-12 10:22:51 -07:00 committed by Oliver Woodman
parent 0ca81b1a4c
commit 6e24372414
6 changed files with 71 additions and 96 deletions

View File

@ -894,8 +894,7 @@ import java.io.IOException;
// TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed. // TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed.
if (oldTimeline != null) { if (oldTimeline != null) {
int newPlayingIndex = playingPeriod != null ? playingPeriod.index int newPlayingIndex = playingPeriod != null ? playingPeriod.index
: loadingPeriod != null ? loadingPeriod.index : loadingPeriod != null ? loadingPeriod.index : Timeline.NO_PERIOD_INDEX;
: mediaSource.getNewPlayingPeriodIndex(playbackInfo.periodIndex, oldTimeline);
if (newPlayingIndex != Timeline.NO_PERIOD_INDEX if (newPlayingIndex != Timeline.NO_PERIOD_INDEX
&& newPlayingIndex != playbackInfo.periodIndex) { && newPlayingIndex != playbackInfo.periodIndex) {
long oldPositionUs = playbackInfo.positionUs; long oldPositionUs = playbackInfo.positionUs;

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import android.util.Pair;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -64,14 +65,12 @@ public final class ConcatenatingMediaSource implements MediaSource {
} }
@Override @Override
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldConcatenatedTimeline) public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldConcatenatedTimeline) {
throws IOException {
ConcatenatedTimeline oldTimeline = (ConcatenatedTimeline) oldConcatenatedTimeline; ConcatenatedTimeline oldTimeline = (ConcatenatedTimeline) oldConcatenatedTimeline;
int sourceIndex = oldTimeline.getSourceIndexForPeriod(oldPlayingPeriodIndex); int sourceIndex = oldTimeline.getSourceIndexForPeriod(oldPlayingPeriodIndex);
int oldFirstPeriodIndex = oldTimeline.getFirstPeriodIndexInSource(sourceIndex); int oldFirstPeriodIndex = oldTimeline.getFirstPeriodIndexInSource(sourceIndex);
int firstPeriodIndex = timeline.getFirstPeriodIndexInSource(sourceIndex); int firstPeriodIndex = timeline.getFirstPeriodIndexInSource(sourceIndex);
return firstPeriodIndex == Timeline.NO_PERIOD_INDEX ? Timeline.NO_PERIOD_INDEX return firstPeriodIndex + mediaSources[sourceIndex].getNewPlayingPeriodIndex(
: firstPeriodIndex + mediaSources[sourceIndex].getNewPlayingPeriodIndex(
oldPlayingPeriodIndex - oldFirstPeriodIndex, oldTimeline.timelines[sourceIndex]); oldPlayingPeriodIndex - oldFirstPeriodIndex, oldTimeline.timelines[sourceIndex]);
} }
@ -165,17 +164,21 @@ public final class ConcatenatingMediaSource implements MediaSource {
public Object getPeriodId(int index) { public Object getPeriodId(int index) {
int sourceIndex = getSourceIndexForPeriod(index); int sourceIndex = getSourceIndexForPeriod(index);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(index); int firstPeriodIndexInSource = getFirstPeriodIndexInSource(index);
return timelines[sourceIndex].getPeriodId(index - firstPeriodIndexInSource); Object periodId = timelines[sourceIndex].getPeriodId(index - firstPeriodIndexInSource);
return Pair.create(sourceIndex, periodId);
} }
@Override @Override
public int getIndexOfPeriod(Object id) { public int getIndexOfPeriod(Object id) {
for (int sourceIndex = 0; sourceIndex < timelines.length; sourceIndex++) { // The id was returned by getPeriodId, so it is always a Pair<Integer, Object>.
int periodIndexInSource = timelines[sourceIndex].getIndexOfPeriod(id); @SuppressWarnings("unchecked")
if (periodIndexInSource != NO_PERIOD_INDEX) { Pair<Integer, Object> sourceIndexAndPeriodId = (Pair<Integer, Object>) id;
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); int sourceIndex = sourceIndexAndPeriodId.first;
return firstPeriodIndexInSource + periodIndexInSource; 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; return NO_PERIOD_INDEX;
} }
@ -195,8 +198,7 @@ public final class ConcatenatingMediaSource implements MediaSource {
} }
private int getFirstPeriodIndexInSource(int sourceIndex) { private int getFirstPeriodIndexInSource(int sourceIndex) {
return sourceIndex == 0 ? 0 : sourceIndex > sourceOffsets.length return sourceIndex == 0 ? 0 : sourceOffsets[sourceIndex - 1];
? Timeline.NO_PERIOD_INDEX : sourceOffsets[sourceIndex - 1];
} }
} }

View File

@ -78,25 +78,28 @@ public interface MediaSource {
void prepareSource(Listener listener); 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 oldPlayingPeriodIndex The period index that was being played in the old timeline.
* @param oldTimeline The old timeline. * @param oldTimeline The old timeline.
* @return The period index to play in this source's new timeline. * @return The new period index to play in this source's new timeline. Playback will resume from
* @throws IOException Thrown if the required period can't be loaded. * 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 * Returns the default {@link Position} that the player should play when when starting to play the
* {@code index}, or {@code null} if the default start period and position are not yet known. * period at {@code index}, or {@code null} if the default position is not yet known.
* <p> * <p>
* For example, sources can return a {@link Position} with the passed period {@code index} to play * 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 * 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. * @return The default start position.
*/ */
Position getDefaultStartPosition(int index); Position getDefaultStartPosition(int index);

View File

@ -63,8 +63,7 @@ public final class MergingMediaSource implements MediaSource {
} }
@Override @Override
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) {
throws IOException {
return mediaSources[0].getNewPlayingPeriodIndex(oldPlayingPeriodIndex, oldTimeline); return mediaSources[0].getNewPlayingPeriodIndex(oldPlayingPeriodIndex, oldTimeline);
} }

View File

@ -84,10 +84,6 @@ import java.util.List;
} }
} }
public long getStartUs() {
return period.startMs * 1000;
}
// MediaPeriod implementation. // MediaPeriod implementation.
@Override @Override

View File

@ -19,6 +19,7 @@ import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.util.SparseArray;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
@ -41,7 +42,6 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
@ -75,6 +75,7 @@ public final class DashMediaSource implements MediaSource {
private final DashManifestParser manifestParser; private final DashManifestParser manifestParser;
private final ManifestCallback manifestCallback; private final ManifestCallback manifestCallback;
private final Object manifestUriLock; private final Object manifestUriLock;
private final SparseArray<DashMediaPeriod> periodsById;
private final Runnable refreshSourceInfoRunnable; private final Runnable refreshSourceInfoRunnable;
private MediaSource.Listener sourceListener; private MediaSource.Listener sourceListener;
@ -86,10 +87,11 @@ public final class DashMediaSource implements MediaSource {
private long manifestLoadEndTimestamp; private long manifestLoadEndTimestamp;
private DashManifest manifest; private DashManifest manifest;
private Handler handler; private Handler handler;
private ArrayList<DashMediaPeriod> periods;
private SeekWindow seekWindow; private SeekWindow seekWindow;
private long elapsedRealtimeOffsetMs; private long elapsedRealtimeOffsetMs;
private int firstPeriodId;
public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory,
DashChunkSource.Factory chunkSourceFactory, Handler eventHandler, DashChunkSource.Factory chunkSourceFactory, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) { AdaptiveMediaSourceEventListener eventListener) {
@ -108,6 +110,7 @@ public final class DashMediaSource implements MediaSource {
manifestParser = new DashManifestParser(); manifestParser = new DashManifestParser();
manifestCallback = new ManifestCallback(); manifestCallback = new ManifestCallback();
manifestUriLock = new Object(); manifestUriLock = new Object();
periodsById = new SparseArray<>();
refreshSourceInfoRunnable = new Runnable() { refreshSourceInfoRunnable = new Runnable() {
@Override @Override
public void run() { public void run() {
@ -140,20 +143,8 @@ public final class DashMediaSource implements MediaSource {
@Override @Override
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) { public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) {
int periodIndex = oldPlayingPeriodIndex; // Seek to the default position, which is the live edge for live sources.
int oldPeriodCount = oldTimeline.getPeriodCount(); return 0;
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;
} }
@Override @Override
@ -179,11 +170,14 @@ public final class DashMediaSource implements MediaSource {
@Override @Override
public MediaPeriod createPeriod(int index) throws IOException { public MediaPeriod createPeriod(int index) throws IOException {
if (periods == null || periods.size() <= index) { if (index >= manifest.getPeriodCount()) {
loader.maybeThrowError(); loader.maybeThrowError();
return null; 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 @Override
@ -201,6 +195,7 @@ public final class DashMediaSource implements MediaSource {
handler = null; handler = null;
} }
elapsedRealtimeOffsetMs = 0; elapsedRealtimeOffsetMs = 0;
periodsById.clear();
} }
// Loadable callbacks. // Loadable callbacks.
@ -211,24 +206,22 @@ public final class DashMediaSource implements MediaSource {
loadDurationMs, loadable.bytesLoaded()); loadDurationMs, loadable.bytesLoaded());
DashManifest newManifest = loadable.getResult(); DashManifest newManifest = loadable.getResult();
int periodCount = manifest == null ? 0 : manifest.getPeriodCount();
int periodsToRemoveCount = 0; int periodsToRemoveCount = 0;
if (periods != null) { long newFirstPeriodStartTimeMs = newManifest.getPeriod(0).startMs;
int periodCount = periods.size(); while (periodsToRemoveCount < periodCount
long newFirstPeriodStartTimeUs = newManifest.getPeriod(0).startMs * 1000; && manifest.getPeriod(periodsToRemoveCount).startMs < newFirstPeriodStartTimeMs) {
while (periodsToRemoveCount < periodCount periodsToRemoveCount++;
&& periods.get(periodsToRemoveCount).getStartUs() < newFirstPeriodStartTimeUs) { }
periodsToRemoveCount++;
}
// After discarding old periods, we should never have more periods than listed in the new // 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 // 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 // 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. // behind, discard this manifest, and try again later.
if (periodCount - periodsToRemoveCount > newManifest.getPeriodCount()) { if (periodCount - periodsToRemoveCount > newManifest.getPeriodCount()) {
Log.w(TAG, "Out of sync manifest"); Log.w(TAG, "Out of sync manifest");
scheduleManifestRefresh(); scheduleManifestRefresh();
return; return;
}
} }
manifest = newManifest; manifest = newManifest;
@ -244,7 +237,7 @@ public final class DashMediaSource implements MediaSource {
} }
} }
if (periods == null) { if (periodCount == 0) {
if (manifest.utcTiming != null) { if (manifest.utcTiming != null) {
resolveUtcTimingElement(manifest.utcTiming); resolveUtcTimingElement(manifest.utcTiming);
} else { } else {
@ -253,11 +246,12 @@ public final class DashMediaSource implements MediaSource {
} else { } else {
// Remove old periods. // Remove old periods.
while (periodsToRemoveCount-- > 0) { while (periodsToRemoveCount-- > 0) {
periods.remove(0); periodsById.remove(firstPeriodId);
firstPeriodId++;
periodCount--;
} }
// Update existing periods. Only the first and the last periods can change. // Update existing periods. Only the first and the last periods can change.
int periodCount = periods.size();
if (periodCount > 0) { if (periodCount > 0) {
updatePeriod(0); updatePeriod(0);
if (periodCount > 1) { if (periodCount > 1) {
@ -270,7 +264,10 @@ public final class DashMediaSource implements MediaSource {
} }
private void updatePeriod(int index) { 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<DashManifest> loadable, /* package */ int onManifestLoadError(ParsingLoadable<DashManifest> loadable,
@ -355,15 +352,6 @@ public final class DashMediaSource implements MediaSource {
} }
private void finishManifestProcessing() { 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); handler.removeCallbacks(refreshSourceInfoRunnable);
refreshSourceInfo(); refreshSourceInfo();
scheduleManifestRefresh(); scheduleManifestRefresh();
@ -371,8 +359,7 @@ public final class DashMediaSource implements MediaSource {
private void refreshSourceInfo() { private void refreshSourceInfo() {
// Update the seek window. // Update the seek window.
int periodCount = manifest.getPeriodCount(); int lastPeriodIndex = manifest.getPeriodCount() - 1;
int lastPeriodIndex = periodCount - 1;
PeriodSeekInfo firstPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo(manifest.getPeriod(0), PeriodSeekInfo firstPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo(manifest.getPeriod(0),
manifest.getPeriodDurationUs(0)); manifest.getPeriodDurationUs(0));
PeriodSeekInfo lastPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo( PeriodSeekInfo lastPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo(
@ -397,10 +384,7 @@ public final class DashMediaSource implements MediaSource {
currentEndTimeUs = lastPeriodSeekInfo.availableEndTimeUs; currentEndTimeUs = lastPeriodSeekInfo.availableEndTimeUs;
} }
seekWindow = SeekWindow.createWindow(0, currentStartTimeUs, lastPeriodIndex, currentEndTimeUs); seekWindow = SeekWindow.createWindow(0, currentStartTimeUs, lastPeriodIndex, currentEndTimeUs);
sourceListener.onSourceInfoRefreshed(new DashTimeline(firstPeriodId, manifest, seekWindow),
DashMediaPeriod[] mediaPeriods =
periods.toArray(new DashMediaPeriod[manifest.getPeriodCount()]);
sourceListener.onSourceInfoRefreshed(new DashTimeline(manifest, mediaPeriods, seekWindow),
manifest); manifest);
} }
@ -483,13 +467,13 @@ public final class DashMediaSource implements MediaSource {
private static final class DashTimeline implements Timeline { private static final class DashTimeline implements Timeline {
private final int firstPeriodId;
private final DashManifest manifest; private final DashManifest manifest;
private final DashMediaPeriod[] periods;
private final SeekWindow seekWindow; 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.manifest = manifest;
this.periods = periods;
this.seekWindow = seekWindow; this.seekWindow = seekWindow;
} }
@ -526,20 +510,12 @@ public final class DashMediaSource implements MediaSource {
@Override @Override
public Object getPeriodId(int index) { public Object getPeriodId(int index) {
if (index < 0 || index >= manifest.getPeriodCount()) { return firstPeriodId + index;
throw new IndexOutOfBoundsException();
}
return periods[index];
} }
@Override @Override
public int getIndexOfPeriod(Object id) { public int getIndexOfPeriod(Object id) {
for (int i = 0; i < periods.length; i++) { return ((Integer) id) - firstPeriodId;
if (id == periods[i]) {
return i;
}
}
return Timeline.NO_PERIOD_INDEX;
} }
@Override @Override