Move SmoothStreaming manifest refresh to source level.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=128461778
This commit is contained in:
olly 2016-07-26 05:30:23 -07:00 committed by Oliver Woodman
parent 1d79c26b34
commit 1722019e0c

View File

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.smoothstreaming;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
@ -33,7 +32,6 @@ import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
@ -54,8 +52,7 @@ import java.util.List;
/**
* A SmoothStreaming {@link MediaSource}.
*/
public final class SsMediaSource implements MediaPeriod, MediaSource,
SequenceableLoader.Callback<ChunkSampleStream<SsChunkSource>>,
public final class SsMediaSource implements MediaSource,
Loader.Callback<ParsingLoadable<SsManifest>> {
/**
@ -76,19 +73,12 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
private MediaSource.InvalidationListener invalidationListener;
private DataSource manifestDataSource;
private Loader manifestLoader;
private ChunkSampleStream<SsChunkSource>[] sampleStreams;
private CompositeSequenceableLoader sequenceableLoader;
private long manifestLoadStartTimestamp;
private SsManifest manifest;
private Callback callback;
private Allocator allocator;
private Handler manifestRefreshHandler;
private boolean prepared;
private long durationUs;
private TrackEncryptionBox[] trackEncryptionBoxes;
private TrackGroupArray trackGroups;
private SsMediaPeriod period;
public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory,
SsChunkSource.Factory chunkSourceFactory, Handler eventHandler,
@ -114,6 +104,10 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
@Override
public void prepareSource(InvalidationListener listener) {
this.invalidationListener = listener;
manifestDataSource = dataSourceFactory.createDataSource();
manifestLoader = new Loader("Loader:Manifest");
manifestRefreshHandler = new Handler();
startLoadingManifest();
}
@Override
@ -124,142 +118,231 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
@Override
public MediaPeriod createPeriod(int index) {
Assertions.checkArgument(index == 0);
return this;
return period;
}
@Override
public void releaseSource() {
// do nothing
}
// MediaPeriod implementation.
@Override
public void preparePeriod(Callback callback, Allocator allocator, long positionUs) {
this.callback = callback;
this.allocator = allocator;
sampleStreams = newSampleStreamArray(0);
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
manifestDataSource = dataSourceFactory.createDataSource();
manifestLoader = new Loader("Loader:Manifest");
manifestRefreshHandler = new Handler();
startLoadingManifest();
}
@Override
public void maybeThrowPrepareError() throws IOException {
manifestLoader.maybeThrowError();
}
@Override
public long getDurationUs() {
return durationUs;
}
@Override
public TrackGroupArray getTrackGroups() {
return trackGroups;
}
@Override
public SampleStream[] selectTracks(List<SampleStream> oldStreams,
List<TrackSelection> newSelections, long positionUs) {
int newEnabledSourceCount = sampleStreams.length + newSelections.size() - oldStreams.size();
ChunkSampleStream<SsChunkSource>[] newSampleStreams =
newSampleStreamArray(newEnabledSourceCount);
int newEnabledSourceIndex = 0;
// Iterate over currently enabled streams, either releasing them or adding them to the new list.
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
if (oldStreams.contains(sampleStream)) {
sampleStream.release();
} else {
newSampleStreams[newEnabledSourceIndex++] = sampleStream;
}
}
// Instantiate and return new streams.
SampleStream[] streamsToReturn = new SampleStream[newSelections.size()];
for (int i = 0; i < newSelections.size(); i++) {
newSampleStreams[newEnabledSourceIndex] = buildSampleStream(newSelections.get(i), positionUs);
streamsToReturn[i] = newSampleStreams[newEnabledSourceIndex];
newEnabledSourceIndex++;
}
sampleStreams = newSampleStreams;
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
return streamsToReturn;
}
@Override
public boolean continueLoading(long positionUs) {
return sequenceableLoader.continueLoading(positionUs);
}
@Override
public long getNextLoadPositionUs() {
return sequenceableLoader.getNextLoadPositionUs();
}
@Override
public long readDiscontinuity() {
return C.UNSET_TIME_US;
}
@Override
public long getBufferedPositionUs() {
long bufferedPositionUs = Long.MAX_VALUE;
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
long rendererBufferedPositionUs = sampleStream.getBufferedPositionUs();
if (rendererBufferedPositionUs != C.END_OF_SOURCE_US) {
bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs);
}
}
return bufferedPositionUs == Long.MAX_VALUE ? C.END_OF_SOURCE_US : bufferedPositionUs;
}
@Override
public long seekToUs(long positionUs) {
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
sampleStream.seekToUs(positionUs);
}
return positionUs;
}
@Override
public void releasePeriod() {
period = null;
manifest = null;
manifestDataSource = null;
manifestLoadStartTimestamp = 0;
if (manifestLoader != null) {
manifestLoader.release();
manifestLoader = null;
}
if (sampleStreams != null) {
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
sampleStream.release();
}
sampleStreams = null;
}
sequenceableLoader = null;
manifestLoadStartTimestamp = 0;
manifest = null;
callback = null;
allocator = null;
if (manifestRefreshHandler != null) {
manifestRefreshHandler.removeCallbacksAndMessages(null);
manifestRefreshHandler = null;
}
prepared = false;
durationUs = 0;
trackEncryptionBoxes = null;
trackGroups = null;
}
// SequenceableLoader.Callback implementation
// MediaPeriod implementation.
// TODO: Move into separate file.
private static class SsMediaPeriod implements MediaPeriod,
SequenceableLoader.Callback<ChunkSampleStream<SsChunkSource>> {
private final SsChunkSource.Factory chunkSourceFactory;
private final Loader manifestLoader;
private final int minLoadableRetryCount;
private final EventDispatcher eventDispatcher;
private final long durationUs;
private final TrackGroupArray trackGroups;
private final TrackEncryptionBox[] trackEncryptionBoxes;
private SsManifest manifest;
private ChunkSampleStream<SsChunkSource>[] sampleStreams;
private CompositeSequenceableLoader sequenceableLoader;
private Callback callback;
private Allocator allocator;
public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory,
int minLoadableRetryCount, EventDispatcher eventDispatcher, Loader manifestLoader) {
this.manifest = manifest;
this.chunkSourceFactory = chunkSourceFactory;
this.manifestLoader = manifestLoader;
this.minLoadableRetryCount = minLoadableRetryCount;
this.eventDispatcher = eventDispatcher;
durationUs = manifest.durationUs;
trackGroups = buildTrackGroups(manifest);
ProtectionElement protectionElement = manifest.protectionElement;
if (protectionElement != null) {
byte[] keyId = getProtectionElementKeyId(protectionElement.data);
trackEncryptionBoxes = new TrackEncryptionBox[] {
new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId)};
} else {
trackEncryptionBoxes = null;
}
}
public void updateManifest(SsManifest manifest) {
this.manifest = manifest;
if (sampleStreams != null) {
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
sampleStream.getChunkSource().updateManifest(manifest);
}
callback.onContinueLoadingRequested(this);
}
}
@Override
public void preparePeriod(Callback callback, Allocator allocator, long positionUs) {
this.callback = callback;
this.allocator = allocator;
sampleStreams = newSampleStreamArray(0);
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
callback.onPeriodPrepared(this);
}
@Override
public void maybeThrowPrepareError() throws IOException {
manifestLoader.maybeThrowError();
}
@Override
public long getDurationUs() {
return durationUs;
}
@Override
public TrackGroupArray getTrackGroups() {
return trackGroups;
}
@Override
public SampleStream[] selectTracks(List<SampleStream> oldStreams,
List<TrackSelection> newSelections, long positionUs) {
int newEnabledSourceCount = sampleStreams.length + newSelections.size() - oldStreams.size();
ChunkSampleStream<SsChunkSource>[] newSampleStreams =
newSampleStreamArray(newEnabledSourceCount);
int newEnabledSourceIndex = 0;
// Iterate over currently enabled streams, either releasing them or adding them to the new
// list.
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
if (oldStreams.contains(sampleStream)) {
sampleStream.release();
} else {
newSampleStreams[newEnabledSourceIndex++] = sampleStream;
}
}
// Instantiate and return new streams.
SampleStream[] streamsToReturn = new SampleStream[newSelections.size()];
for (int i = 0; i < newSelections.size(); i++) {
newSampleStreams[newEnabledSourceIndex] =
buildSampleStream(newSelections.get(i), positionUs);
streamsToReturn[i] = newSampleStreams[newEnabledSourceIndex];
newEnabledSourceIndex++;
}
sampleStreams = newSampleStreams;
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
return streamsToReturn;
}
@Override
public boolean continueLoading(long positionUs) {
return sequenceableLoader.continueLoading(positionUs);
}
@Override
public long getNextLoadPositionUs() {
return sequenceableLoader.getNextLoadPositionUs();
}
@Override
public long readDiscontinuity() {
return C.UNSET_TIME_US;
}
@Override
public long getBufferedPositionUs() {
long bufferedPositionUs = Long.MAX_VALUE;
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
long rendererBufferedPositionUs = sampleStream.getBufferedPositionUs();
if (rendererBufferedPositionUs != C.END_OF_SOURCE_US) {
bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs);
}
}
return bufferedPositionUs == Long.MAX_VALUE ? C.END_OF_SOURCE_US : bufferedPositionUs;
}
@Override
public long seekToUs(long positionUs) {
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
sampleStream.seekToUs(positionUs);
}
return positionUs;
}
@Override
public void releasePeriod() {
if (sampleStreams != null) {
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
sampleStream.release();
}
sampleStreams = null;
}
sequenceableLoader = null;
callback = null;
allocator = null;
}
// SequenceableLoader.Callback implementation
@Override
public void onContinueLoadingRequested(ChunkSampleStream<SsChunkSource> sampleStream) {
callback.onContinueLoadingRequested(this);
}
// Private methods.
private ChunkSampleStream<SsChunkSource> buildSampleStream(TrackSelection selection,
long positionUs) {
int streamElementIndex = trackGroups.indexOf(selection.group);
SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoader, manifest,
streamElementIndex, selection, trackEncryptionBoxes);
return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, chunkSource,
this, allocator, positionUs, minLoadableRetryCount, eventDispatcher);
}
private static TrackGroupArray buildTrackGroups(SsManifest manifest) {
TrackGroup[] trackGroupArray = new TrackGroup[manifest.streamElements.length];
for (int i = 0; i < manifest.streamElements.length; i++) {
trackGroupArray[i++] = new TrackGroup(manifest.streamElements[i].formats);
}
return new TrackGroupArray(trackGroupArray);
}
@SuppressWarnings("unchecked")
private static ChunkSampleStream<SsChunkSource>[] newSampleStreamArray(int length) {
return new ChunkSampleStream[length];
}
private static byte[] getProtectionElementKeyId(byte[] initData) {
StringBuilder initDataStringBuilder = new StringBuilder();
for (int i = 0; i < initData.length; i += 2) {
initDataStringBuilder.append((char) initData[i]);
}
String initDataString = initDataStringBuilder.toString();
String keyIdString = initDataString.substring(
initDataString.indexOf("<KID>") + 5, initDataString.indexOf("</KID>"));
byte[] keyId = Base64.decode(keyIdString, Base64.DEFAULT);
swap(keyId, 0, 3);
swap(keyId, 1, 2);
swap(keyId, 4, 5);
swap(keyId, 6, 7);
return keyId;
}
private static void swap(byte[] data, int firstPosition, int secondPosition) {
byte temp = data[firstPosition];
data[firstPosition] = data[secondPosition];
data[secondPosition] = temp;
}
@Override
public void onContinueLoadingRequested(
ChunkSampleStream<SsChunkSource> sampleStream) {
callback.onContinueLoadingRequested(this);
}
// Loader.Callback implementation
@ -271,25 +354,15 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
loadDurationMs, loadable.bytesLoaded());
manifest = loadable.getResult();
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
if (!prepared) {
durationUs = manifest.durationUs;
Timeline timeline = durationUs == C.UNSET_TIME_US ? new SinglePeriodTimeline(this, manifest)
: new SinglePeriodTimeline(this, manifest, durationUs / 1000);
if (period == null) {
period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount,
eventDispatcher, manifestLoader);
Timeline timeline = manifest.durationUs == C.UNSET_TIME_US
? new SinglePeriodTimeline(this, manifest)
: new SinglePeriodTimeline(this, manifest, manifest.durationUs / 1000);
invalidationListener.onTimelineChanged(timeline);
buildTrackGroups(manifest);
ProtectionElement protectionElement = manifest.protectionElement;
if (protectionElement != null) {
byte[] keyId = getProtectionElementKeyId(protectionElement.data);
trackEncryptionBoxes = new TrackEncryptionBox[] {
new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId)};
}
prepared = true;
callback.onPeriodPrepared(this);
} else {
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
sampleStream.getChunkSource().updateManifest(manifest);
}
callback.onContinueLoadingRequested(this);
period.updateManifest(manifest);
}
scheduleManifestRefresh();
}
@ -333,50 +406,4 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
}
private void buildTrackGroups(SsManifest manifest) {
TrackGroup[] trackGroupArray = new TrackGroup[manifest.streamElements.length];
for (int i = 0; i < manifest.streamElements.length; i++) {
StreamElement streamElement = manifest.streamElements[i];
Format[] formats = streamElement.formats;
trackGroupArray[i] = new TrackGroup(formats);
}
trackGroups = new TrackGroupArray(trackGroupArray);
}
private ChunkSampleStream<SsChunkSource> buildSampleStream(TrackSelection selection,
long positionUs) {
int streamElementIndex = trackGroups.indexOf(selection.group);
SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoader, manifest,
streamElementIndex, selection, trackEncryptionBoxes);
return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, chunkSource,
this, allocator, positionUs, minLoadableRetryCount, eventDispatcher);
}
@SuppressWarnings("unchecked")
private static ChunkSampleStream<SsChunkSource>[] newSampleStreamArray(int length) {
return new ChunkSampleStream[length];
}
private static byte[] getProtectionElementKeyId(byte[] initData) {
StringBuilder initDataStringBuilder = new StringBuilder();
for (int i = 0; i < initData.length; i += 2) {
initDataStringBuilder.append((char) initData[i]);
}
String initDataString = initDataStringBuilder.toString();
String keyIdString = initDataString.substring(
initDataString.indexOf("<KID>") + 5, initDataString.indexOf("</KID>"));
byte[] keyId = Base64.decode(keyIdString, Base64.DEFAULT);
swap(keyId, 0, 3);
swap(keyId, 1, 2);
swap(keyId, 4, 5);
swap(keyId, 6, 7);
return keyId;
}
private static void swap(byte[] data, int firstPosition, int secondPosition) {
byte temp = data[firstPosition];
data[firstPosition] = data[secondPosition];
data[secondPosition] = temp;
}
}