mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Move SmoothStreaming manifest refresh to source level.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=128461778
This commit is contained in:
parent
1d79c26b34
commit
1722019e0c
1 changed files with 222 additions and 195 deletions
|
|
@ -16,7 +16,6 @@
|
||||||
package com.google.android.exoplayer2.source.smoothstreaming;
|
package com.google.android.exoplayer2.source.smoothstreaming;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
|
import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
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.chunk.ChunkSampleStream;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
|
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.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.source.smoothstreaming.manifest.SsManifestParser;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
|
|
@ -54,8 +52,7 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* A SmoothStreaming {@link MediaSource}.
|
* A SmoothStreaming {@link MediaSource}.
|
||||||
*/
|
*/
|
||||||
public final class SsMediaSource implements MediaPeriod, MediaSource,
|
public final class SsMediaSource implements MediaSource,
|
||||||
SequenceableLoader.Callback<ChunkSampleStream<SsChunkSource>>,
|
|
||||||
Loader.Callback<ParsingLoadable<SsManifest>> {
|
Loader.Callback<ParsingLoadable<SsManifest>> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -76,19 +73,12 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
|
||||||
private MediaSource.InvalidationListener invalidationListener;
|
private MediaSource.InvalidationListener invalidationListener;
|
||||||
private DataSource manifestDataSource;
|
private DataSource manifestDataSource;
|
||||||
private Loader manifestLoader;
|
private Loader manifestLoader;
|
||||||
private ChunkSampleStream<SsChunkSource>[] sampleStreams;
|
|
||||||
private CompositeSequenceableLoader sequenceableLoader;
|
|
||||||
|
|
||||||
private long manifestLoadStartTimestamp;
|
private long manifestLoadStartTimestamp;
|
||||||
private SsManifest manifest;
|
private SsManifest manifest;
|
||||||
|
|
||||||
private Callback callback;
|
|
||||||
private Allocator allocator;
|
|
||||||
private Handler manifestRefreshHandler;
|
private Handler manifestRefreshHandler;
|
||||||
private boolean prepared;
|
private SsMediaPeriod period;
|
||||||
private long durationUs;
|
|
||||||
private TrackEncryptionBox[] trackEncryptionBoxes;
|
|
||||||
private TrackGroupArray trackGroups;
|
|
||||||
|
|
||||||
public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory,
|
public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory,
|
||||||
SsChunkSource.Factory chunkSourceFactory, Handler eventHandler,
|
SsChunkSource.Factory chunkSourceFactory, Handler eventHandler,
|
||||||
|
|
@ -114,6 +104,10 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource(InvalidationListener listener) {
|
public void prepareSource(InvalidationListener listener) {
|
||||||
this.invalidationListener = listener;
|
this.invalidationListener = listener;
|
||||||
|
manifestDataSource = dataSourceFactory.createDataSource();
|
||||||
|
manifestLoader = new Loader("Loader:Manifest");
|
||||||
|
manifestRefreshHandler = new Handler();
|
||||||
|
startLoadingManifest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -124,142 +118,231 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index) {
|
public MediaPeriod createPeriod(int index) {
|
||||||
Assertions.checkArgument(index == 0);
|
Assertions.checkArgument(index == 0);
|
||||||
return this;
|
return period;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseSource() {
|
public void releaseSource() {
|
||||||
// do nothing
|
period = null;
|
||||||
}
|
manifest = null;
|
||||||
|
|
||||||
// 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() {
|
|
||||||
manifestDataSource = null;
|
manifestDataSource = null;
|
||||||
|
manifestLoadStartTimestamp = 0;
|
||||||
if (manifestLoader != null) {
|
if (manifestLoader != null) {
|
||||||
manifestLoader.release();
|
manifestLoader.release();
|
||||||
manifestLoader = null;
|
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) {
|
if (manifestRefreshHandler != null) {
|
||||||
manifestRefreshHandler.removeCallbacksAndMessages(null);
|
manifestRefreshHandler.removeCallbacksAndMessages(null);
|
||||||
manifestRefreshHandler = 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
|
// Loader.Callback implementation
|
||||||
|
|
@ -271,25 +354,15 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
|
||||||
loadDurationMs, loadable.bytesLoaded());
|
loadDurationMs, loadable.bytesLoaded());
|
||||||
manifest = loadable.getResult();
|
manifest = loadable.getResult();
|
||||||
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
|
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
|
||||||
if (!prepared) {
|
if (period == null) {
|
||||||
durationUs = manifest.durationUs;
|
period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount,
|
||||||
Timeline timeline = durationUs == C.UNSET_TIME_US ? new SinglePeriodTimeline(this, manifest)
|
eventDispatcher, manifestLoader);
|
||||||
: new SinglePeriodTimeline(this, manifest, durationUs / 1000);
|
Timeline timeline = manifest.durationUs == C.UNSET_TIME_US
|
||||||
|
? new SinglePeriodTimeline(this, manifest)
|
||||||
|
: new SinglePeriodTimeline(this, manifest, manifest.durationUs / 1000);
|
||||||
invalidationListener.onTimelineChanged(timeline);
|
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 {
|
} else {
|
||||||
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
|
period.updateManifest(manifest);
|
||||||
sampleStream.getChunkSource().updateManifest(manifest);
|
|
||||||
}
|
|
||||||
callback.onContinueLoadingRequested(this);
|
|
||||||
}
|
}
|
||||||
scheduleManifestRefresh();
|
scheduleManifestRefresh();
|
||||||
}
|
}
|
||||||
|
|
@ -333,50 +406,4 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
|
||||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue