mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Split DashMediaSource MediaPeriod implementation into DashMediaPeriod and support multiple periods.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=127315877
This commit is contained in:
parent
a0c1595725
commit
4a5bb55920
3 changed files with 305 additions and 215 deletions
|
|
@ -34,9 +34,7 @@ import com.google.android.exoplayer2.source.chunk.FormatEvaluator.Evaluation;
|
||||||
import com.google.android.exoplayer2.source.chunk.InitializationChunk;
|
import com.google.android.exoplayer2.source.chunk.InitializationChunk;
|
||||||
import com.google.android.exoplayer2.source.chunk.MediaChunk;
|
import com.google.android.exoplayer2.source.chunk.MediaChunk;
|
||||||
import com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk;
|
import com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk;
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.AdaptationSet;
|
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.MediaPresentationDescription;
|
import com.google.android.exoplayer2.source.dash.mpd.MediaPresentationDescription;
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.Period;
|
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.RangedUri;
|
import com.google.android.exoplayer2.source.dash.mpd.RangedUri;
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.Representation;
|
import com.google.android.exoplayer2.source.dash.mpd.Representation;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
|
@ -84,11 +82,11 @@ public class DashChunkSource implements ChunkSource {
|
||||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
||||||
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
|
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
|
||||||
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
|
* 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.
|
* as the server's unix time minus the local elapsed time. If unknown, set to 0.
|
||||||
*/
|
*/
|
||||||
public DashChunkSource(Loader manifestLoader, MediaPresentationDescription manifest,
|
public DashChunkSource(Loader manifestLoader, MediaPresentationDescription manifest,
|
||||||
int adaptationSetIndex, TrackGroup trackGroup, int[] tracks, DataSource dataSource,
|
int adaptationSetIndex, TrackGroup trackGroup, int[] tracks, DataSource dataSource,
|
||||||
FormatEvaluator adaptiveFormatEvaluator, long elapsedRealtimeOffsetMs) {
|
FormatEvaluator adaptiveFormatEvaluator, long elapsedRealtimeOffsetMs, int index) {
|
||||||
this.manifestLoader = manifestLoader;
|
this.manifestLoader = manifestLoader;
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
this.adaptationSetIndex = adaptationSetIndex;
|
this.adaptationSetIndex = adaptationSetIndex;
|
||||||
|
|
@ -98,12 +96,10 @@ public class DashChunkSource implements ChunkSource {
|
||||||
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetMs * 1000;
|
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetMs * 1000;
|
||||||
this.evaluation = new Evaluation();
|
this.evaluation = new Evaluation();
|
||||||
|
|
||||||
Period period = manifest.getPeriod(0);
|
long periodDurationUs = getPeriodDurationUs(index);
|
||||||
long periodDurationUs = getPeriodDurationUs(manifest, 0);
|
List<Representation> representations = getRepresentations(index);
|
||||||
AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex);
|
|
||||||
|
|
||||||
List<Representation> representations = adaptationSet.representations;
|
|
||||||
representationHolders = new RepresentationHolder[representations.size()];
|
representationHolders = new RepresentationHolder[representations.size()];
|
||||||
|
|
||||||
for (int i = 0; i < representations.size(); i++) {
|
for (int i = 0; i < representations.size(); i++) {
|
||||||
Representation representation = representations.get(i);
|
Representation representation = representations.get(i);
|
||||||
representationHolders[i] = new RepresentationHolder(periodDurationUs, representation);
|
representationHolders[i] = new RepresentationHolder(periodDurationUs, representation);
|
||||||
|
|
@ -121,12 +117,11 @@ public class DashChunkSource implements ChunkSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateManifest(MediaPresentationDescription newManifest) {
|
public void updateManifest(MediaPresentationDescription newManifest, int index) {
|
||||||
try {
|
try {
|
||||||
manifest = newManifest;
|
manifest = newManifest;
|
||||||
long periodDurationUs = getPeriodDurationUs(manifest, 0);
|
long periodDurationUs = getPeriodDurationUs(index);
|
||||||
List<Representation> representations = manifest.getPeriod(0).adaptationSets
|
List<Representation> representations = getRepresentations(index);
|
||||||
.get(adaptationSetIndex).representations;
|
|
||||||
for (int i = 0; i < representationHolders.length; i++) {
|
for (int i = 0; i < representationHolders.length; i++) {
|
||||||
Representation representation = representations.get(i);
|
Representation representation = representations.get(i);
|
||||||
representationHolders[i].updateRepresentation(periodDurationUs, representation);
|
representationHolders[i].updateRepresentation(periodDurationUs, representation);
|
||||||
|
|
@ -136,6 +131,10 @@ public class DashChunkSource implements ChunkSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Representation> getRepresentations(int index) {
|
||||||
|
return manifest.getPeriod(index).adaptationSets.get(adaptationSetIndex).representations;
|
||||||
|
}
|
||||||
|
|
||||||
// ChunkSource implementation.
|
// ChunkSource implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -356,7 +355,7 @@ public class DashChunkSource implements ChunkSource {
|
||||||
throw new IllegalStateException("Invalid format: " + format);
|
throw new IllegalStateException("Invalid format: " + format);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long getPeriodDurationUs(MediaPresentationDescription manifest, int index) {
|
private long getPeriodDurationUs(int index) {
|
||||||
long durationMs = manifest.getPeriodDuration(index);
|
long durationMs = manifest.getPeriodDuration(index);
|
||||||
if (durationMs == -1) {
|
if (durationMs == -1) {
|
||||||
return C.UNSET_TIME_US;
|
return C.UNSET_TIME_US;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,265 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.exoplayer2.source.dash;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||||
|
import com.google.android.exoplayer2.source.CompositeSequenceableLoader;
|
||||||
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
|
import com.google.android.exoplayer2.source.SampleStream;
|
||||||
|
import com.google.android.exoplayer2.source.SequenceableLoader;
|
||||||
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
|
||||||
|
import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
|
||||||
|
import com.google.android.exoplayer2.source.dash.mpd.AdaptationSet;
|
||||||
|
import com.google.android.exoplayer2.source.dash.mpd.MediaPresentationDescription;
|
||||||
|
import com.google.android.exoplayer2.source.dash.mpd.Period;
|
||||||
|
import com.google.android.exoplayer2.source.dash.mpd.Representation;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
|
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.upstream.Loader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DASH {@link MediaPeriod}.
|
||||||
|
*/
|
||||||
|
/* package */ final class DashMediaPeriod implements MediaPeriod,
|
||||||
|
SequenceableLoader.Callback<ChunkSampleStream<DashChunkSource>> {
|
||||||
|
|
||||||
|
private final Loader loader;
|
||||||
|
private final DataSourceFactory dataSourceFactory;
|
||||||
|
private final BandwidthMeter bandwidthMeter;
|
||||||
|
private final EventDispatcher eventDispatcher;
|
||||||
|
|
||||||
|
private ChunkSampleStream<DashChunkSource>[] sampleStreams;
|
||||||
|
private CompositeSequenceableLoader sequenceableLoader;
|
||||||
|
private Callback callback;
|
||||||
|
private Allocator allocator;
|
||||||
|
private long durationUs;
|
||||||
|
private TrackGroupArray trackGroups;
|
||||||
|
private int[] trackGroupAdaptationSetIndices;
|
||||||
|
private MediaPresentationDescription manifest;
|
||||||
|
private int index;
|
||||||
|
private int minLoadableRetryCount;
|
||||||
|
private long elapsedRealtimeOffset;
|
||||||
|
private Period period;
|
||||||
|
|
||||||
|
public DashMediaPeriod(MediaPresentationDescription manifest, int index,
|
||||||
|
DataSourceFactory dataSourceFactory, BandwidthMeter bandwidthMeter,
|
||||||
|
int minLoadableRetryCount, EventDispatcher eventDispatcher, long elapsedRealtimeOffset,
|
||||||
|
Loader loader) {
|
||||||
|
this.manifest = manifest;
|
||||||
|
this.index = index;
|
||||||
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
|
this.bandwidthMeter = bandwidthMeter;
|
||||||
|
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||||
|
this.eventDispatcher = eventDispatcher;
|
||||||
|
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
|
||||||
|
this.loader = loader;
|
||||||
|
period = manifest.getPeriod(index);
|
||||||
|
durationUs = manifest.dynamic ? C.UNSET_TIME_US : manifest.getPeriodDuration(index) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateManifest(MediaPresentationDescription manifest, int index) {
|
||||||
|
this.manifest = manifest;
|
||||||
|
this.index = index;
|
||||||
|
period = manifest.getPeriod(index);
|
||||||
|
if (sampleStreams != null) {
|
||||||
|
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
|
||||||
|
sampleStream.getChunkSource().updateManifest(manifest, index);
|
||||||
|
}
|
||||||
|
callback.onContinueLoadingRequested(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MediaPeriod implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare(Callback callback, Allocator allocator, long positionUs) {
|
||||||
|
this.callback = callback;
|
||||||
|
this.allocator = allocator;
|
||||||
|
sampleStreams = newSampleStreamArray(0);
|
||||||
|
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
||||||
|
buildTrackGroups();
|
||||||
|
callback.onPeriodPrepared(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
|
loader.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<DashChunkSource>[] newSampleStreams =
|
||||||
|
newSampleStreamArray(newEnabledSourceCount);
|
||||||
|
int newEnabledSourceIndex = 0;
|
||||||
|
|
||||||
|
// Iterate over currently enabled streams, either releasing them or adding them to the new
|
||||||
|
// list.
|
||||||
|
for (ChunkSampleStream<DashChunkSource> 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<DashChunkSource> 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<DashChunkSource> sampleStream : sampleStreams) {
|
||||||
|
sampleStream.seekToUs(positionUs);
|
||||||
|
}
|
||||||
|
return positionUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
if (sampleStreams != null) {
|
||||||
|
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
|
||||||
|
sampleStream.release();
|
||||||
|
}
|
||||||
|
sampleStreams = null;
|
||||||
|
}
|
||||||
|
sequenceableLoader = null;
|
||||||
|
callback = null;
|
||||||
|
allocator = null;
|
||||||
|
durationUs = 0;
|
||||||
|
trackGroups = null;
|
||||||
|
trackGroupAdaptationSetIndices = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SequenceableLoader.Callback implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContinueLoadingRequested(ChunkSampleStream<DashChunkSource> sampleStream) {
|
||||||
|
callback.onContinueLoadingRequested(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal methods.
|
||||||
|
|
||||||
|
private void buildTrackGroups() {
|
||||||
|
int trackGroupCount = 0;
|
||||||
|
trackGroupAdaptationSetIndices = new int[period.adaptationSets.size()];
|
||||||
|
TrackGroup[] trackGroupArray = new TrackGroup[period.adaptationSets.size()];
|
||||||
|
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
||||||
|
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
||||||
|
int adaptationSetType = adaptationSet.type;
|
||||||
|
List<Representation> representations = adaptationSet.representations;
|
||||||
|
if (!representations.isEmpty() && (adaptationSetType == C.TRACK_TYPE_AUDIO
|
||||||
|
|| adaptationSetType == C.TRACK_TYPE_VIDEO || adaptationSetType == C.TRACK_TYPE_TEXT)) {
|
||||||
|
Format[] formats = new Format[representations.size()];
|
||||||
|
for (int j = 0; j < formats.length; j++) {
|
||||||
|
formats[j] = representations.get(j).format;
|
||||||
|
}
|
||||||
|
trackGroupAdaptationSetIndices[trackGroupCount] = i;
|
||||||
|
boolean adaptive = adaptationSetType == C.TRACK_TYPE_VIDEO;
|
||||||
|
trackGroupArray[trackGroupCount++] = new TrackGroup(adaptive, formats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (trackGroupCount < trackGroupArray.length) {
|
||||||
|
trackGroupAdaptationSetIndices = Arrays.copyOf(trackGroupAdaptationSetIndices,
|
||||||
|
trackGroupCount);
|
||||||
|
trackGroupArray = Arrays.copyOf(trackGroupArray, trackGroupCount);
|
||||||
|
}
|
||||||
|
trackGroups = new TrackGroupArray(trackGroupArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackSelection selection,
|
||||||
|
long positionUs) {
|
||||||
|
int[] selectedTracks = selection.getTracks();
|
||||||
|
FormatEvaluator adaptiveEvaluator = selectedTracks.length > 1
|
||||||
|
? new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter) : null;
|
||||||
|
int adaptationSetIndex = trackGroupAdaptationSetIndices[selection.group];
|
||||||
|
AdaptationSet adaptationSet = period.adaptationSets.get(
|
||||||
|
adaptationSetIndex);
|
||||||
|
int adaptationSetType = adaptationSet.type;
|
||||||
|
DataSource dataSource =
|
||||||
|
dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
|
DashChunkSource chunkSource = new DashChunkSource(loader, manifest, adaptationSetIndex,
|
||||||
|
trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator,
|
||||||
|
elapsedRealtimeOffset, index);
|
||||||
|
return new ChunkSampleStream<>(adaptationSetType, chunkSource, this, allocator, positionUs,
|
||||||
|
minLoadableRetryCount, eventDispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static ChunkSampleStream<DashChunkSource>[] newSampleStreamArray(int length) {
|
||||||
|
return new ChunkSampleStream[length];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,34 +16,19 @@
|
||||||
package com.google.android.exoplayer2.source.dash;
|
package com.google.android.exoplayer2.source.dash;
|
||||||
|
|
||||||
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.source.AdaptiveMediaSourceEventListener;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||||
import com.google.android.exoplayer2.source.CompositeSequenceableLoader;
|
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.SampleStream;
|
|
||||||
import com.google.android.exoplayer2.source.SequenceableLoader;
|
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
|
||||||
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
|
|
||||||
import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
|
|
||||||
import com.google.android.exoplayer2.source.chunk.FormatEvaluator.AdaptiveEvaluator;
|
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.AdaptationSet;
|
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.MediaPresentationDescription;
|
import com.google.android.exoplayer2.source.dash.mpd.MediaPresentationDescription;
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.MediaPresentationDescriptionParser;
|
import com.google.android.exoplayer2.source.dash.mpd.MediaPresentationDescriptionParser;
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.Period;
|
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.Representation;
|
|
||||||
import com.google.android.exoplayer2.source.dash.mpd.UtcTimingElement;
|
import com.google.android.exoplayer2.source.dash.mpd.UtcTimingElement;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
|
||||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DataSourceFactory;
|
||||||
import com.google.android.exoplayer2.upstream.Loader;
|
import com.google.android.exoplayer2.upstream.Loader;
|
||||||
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
@ -57,16 +42,13 @@ 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.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DASH {@link MediaSource}.
|
* A DASH {@link MediaSource}.
|
||||||
*/
|
*/
|
||||||
public final class DashMediaSource implements MediaPeriod, MediaSource,
|
public final class DashMediaSource implements MediaSource {
|
||||||
SequenceableLoader.Callback<ChunkSampleStream<DashChunkSource>> {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default minimum number of times to retry loading data prior to failing.
|
* The default minimum number of times to retry loading data prior to failing.
|
||||||
|
|
@ -84,22 +66,14 @@ public final class DashMediaSource implements MediaPeriod, MediaSource,
|
||||||
|
|
||||||
private DataSource dataSource;
|
private DataSource dataSource;
|
||||||
private Loader loader;
|
private Loader loader;
|
||||||
private ChunkSampleStream<DashChunkSource>[] sampleStreams;
|
|
||||||
private CompositeSequenceableLoader sequenceableLoader;
|
|
||||||
|
|
||||||
private Uri manifestUri;
|
private Uri manifestUri;
|
||||||
private long manifestLoadStartTimestamp;
|
private long manifestLoadStartTimestamp;
|
||||||
private long manifestLoadEndTimestamp;
|
private long manifestLoadEndTimestamp;
|
||||||
private MediaPresentationDescription manifest;
|
private MediaPresentationDescription manifest;
|
||||||
|
|
||||||
private Callback callback;
|
|
||||||
private Allocator allocator;
|
|
||||||
private Handler manifestRefreshHandler;
|
private Handler manifestRefreshHandler;
|
||||||
private boolean prepared;
|
private DashMediaPeriod[] periods;
|
||||||
private long durationUs;
|
|
||||||
private long elapsedRealtimeOffset;
|
private long elapsedRealtimeOffset;
|
||||||
private TrackGroupArray trackGroups;
|
|
||||||
private int[] trackGroupAdaptationSetIndices;
|
|
||||||
|
|
||||||
public DashMediaSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
|
public DashMediaSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
|
||||||
BandwidthMeter bandwidthMeter, Handler eventHandler,
|
BandwidthMeter bandwidthMeter, Handler eventHandler,
|
||||||
|
|
@ -118,29 +92,14 @@ public final class DashMediaSource implements MediaPeriod, MediaSource,
|
||||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||||
manifestParser = new MediaPresentationDescriptionParser();
|
manifestParser = new MediaPresentationDescriptionParser();
|
||||||
manifestCallback = new ManifestCallback();
|
manifestCallback = new ManifestCallback();
|
||||||
|
// TODO: Remove this call when prepare() is a part of MediaSource.
|
||||||
|
prepare(dataSourceFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MediaSource implementation.
|
// MediaSource implementation.
|
||||||
|
|
||||||
@Override
|
// TODO @Override
|
||||||
public int getPeriodCount() {
|
public void prepare(DataSourceFactory dataSourceFactory) {
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaPeriod createPeriod(int index) {
|
|
||||||
Assertions.checkArgument(index == 0);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// MediaPeriod implementation.
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void prepare(Callback callback, Allocator allocator, long positionUs) {
|
|
||||||
this.callback = callback;
|
|
||||||
this.allocator = allocator;
|
|
||||||
sampleStreams = newSampleStreamArray(0);
|
|
||||||
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
|
||||||
dataSource = dataSourceFactory.createDataSource();
|
dataSource = dataSourceFactory.createDataSource();
|
||||||
loader = new Loader("Loader:DashMediaSource");
|
loader = new Loader("Loader:DashMediaSource");
|
||||||
manifestRefreshHandler = new Handler();
|
manifestRefreshHandler = new Handler();
|
||||||
|
|
@ -148,120 +107,36 @@ public final class DashMediaSource implements MediaPeriod, MediaSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowPrepareError() throws IOException {
|
public int getPeriodCount() {
|
||||||
loader.maybeThrowError();
|
if (manifest == null) {
|
||||||
}
|
return UNKNOWN_PERIOD_COUNT;
|
||||||
|
|
||||||
@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<DashChunkSource>[] newSampleStreams =
|
|
||||||
newSampleStreamArray(newEnabledSourceCount);
|
|
||||||
int newEnabledSourceIndex = 0;
|
|
||||||
|
|
||||||
// Iterate over currently enabled streams, either releasing them or adding them to the new list.
|
|
||||||
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
|
|
||||||
if (oldStreams.contains(sampleStream)) {
|
|
||||||
sampleStream.release();
|
|
||||||
} else {
|
|
||||||
newSampleStreams[newEnabledSourceIndex++] = sampleStream;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return manifest.getPeriodCount();
|
||||||
|
}
|
||||||
|
|
||||||
// Instantiate and return new streams.
|
@Override
|
||||||
SampleStream[] streamsToReturn = new SampleStream[newSelections.size()];
|
public MediaPeriod createPeriod(int index) {
|
||||||
for (int i = 0; i < newSelections.size(); i++) {
|
if (periods == null) {
|
||||||
newSampleStreams[newEnabledSourceIndex] = buildSampleStream(newSelections.get(i), positionUs);
|
return null;
|
||||||
streamsToReturn[i] = newSampleStreams[newEnabledSourceIndex];
|
|
||||||
newEnabledSourceIndex++;
|
|
||||||
}
|
}
|
||||||
|
return periods[index];
|
||||||
sampleStreams = newSampleStreams;
|
|
||||||
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
|
||||||
return streamsToReturn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// TODO @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<DashChunkSource> 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<DashChunkSource> sampleStream : sampleStreams) {
|
|
||||||
sampleStream.seekToUs(positionUs);
|
|
||||||
}
|
|
||||||
return positionUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void release() {
|
public void release() {
|
||||||
dataSource = null;
|
dataSource = null;
|
||||||
if (loader != null) {
|
if (loader != null) {
|
||||||
loader.release();
|
loader.release();
|
||||||
loader = null;
|
loader = null;
|
||||||
}
|
}
|
||||||
if (sampleStreams != null) {
|
|
||||||
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
|
|
||||||
sampleStream.release();
|
|
||||||
}
|
|
||||||
sampleStreams = null;
|
|
||||||
}
|
|
||||||
sequenceableLoader = null;
|
|
||||||
manifestLoadStartTimestamp = 0;
|
manifestLoadStartTimestamp = 0;
|
||||||
manifestLoadEndTimestamp = 0;
|
manifestLoadEndTimestamp = 0;
|
||||||
manifest = null;
|
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;
|
|
||||||
elapsedRealtimeOffset = 0;
|
elapsedRealtimeOffset = 0;
|
||||||
trackGroups = null;
|
|
||||||
trackGroupAdaptationSetIndices = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SequenceableLoader.Callback implementation.
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onContinueLoadingRequested(ChunkSampleStream<DashChunkSource> sampleStream) {
|
|
||||||
callback.onContinueLoadingRequested(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loadable callbacks.
|
// Loadable callbacks.
|
||||||
|
|
@ -276,20 +151,17 @@ public final class DashMediaSource implements MediaPeriod, MediaSource,
|
||||||
if (manifest.location != null) {
|
if (manifest.location != null) {
|
||||||
manifestUri = manifest.location;
|
manifestUri = manifest.location;
|
||||||
}
|
}
|
||||||
if (!prepared) {
|
|
||||||
durationUs = manifest.dynamic ? C.UNSET_TIME_US : manifest.getPeriodDuration(0) * 1000;
|
if (periods == null) {
|
||||||
buildTrackGroups(manifest);
|
|
||||||
if (manifest.utcTiming != null) {
|
if (manifest.utcTiming != null) {
|
||||||
resolveUtcTimingElement(manifest.utcTiming);
|
resolveUtcTimingElement(manifest.utcTiming);
|
||||||
} else {
|
} else {
|
||||||
finishPrepare();
|
finishPrepare();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
|
for (int i = 0; i < periods.length; i++) {
|
||||||
sampleStream.getChunkSource().updateManifest(manifest);
|
periods[i].updateManifest(manifest, i);
|
||||||
}
|
}
|
||||||
callback.onContinueLoadingRequested(this);
|
|
||||||
scheduleManifestRefresh();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,8 +243,12 @@ public final class DashMediaSource implements MediaPeriod, MediaSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
private void finishPrepare() {
|
private void finishPrepare() {
|
||||||
prepared = true;
|
int periodCount = manifest.getPeriodCount();
|
||||||
callback.onPeriodPrepared(this);
|
periods = new DashMediaPeriod[periodCount];
|
||||||
|
for (int i = 0; i < periodCount; i++) {
|
||||||
|
periods[i] = new DashMediaPeriod(manifest, i, dataSourceFactory, bandwidthMeter,
|
||||||
|
minLoadableRetryCount, eventDispatcher, elapsedRealtimeOffset, loader);
|
||||||
|
}
|
||||||
scheduleManifestRefresh();
|
scheduleManifestRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -404,56 +280,6 @@ public final class DashMediaSource implements MediaPeriod, MediaSource,
|
||||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildTrackGroups(MediaPresentationDescription manifest) {
|
|
||||||
Period period = manifest.getPeriod(0);
|
|
||||||
int trackGroupCount = 0;
|
|
||||||
trackGroupAdaptationSetIndices = new int[period.adaptationSets.size()];
|
|
||||||
TrackGroup[] trackGroupArray = new TrackGroup[period.adaptationSets.size()];
|
|
||||||
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
|
||||||
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
|
||||||
int adaptationSetType = adaptationSet.type;
|
|
||||||
List<Representation> representations = adaptationSet.representations;
|
|
||||||
if (!representations.isEmpty() && (adaptationSetType == C.TRACK_TYPE_AUDIO
|
|
||||||
|| adaptationSetType == C.TRACK_TYPE_VIDEO || adaptationSetType == C.TRACK_TYPE_TEXT)) {
|
|
||||||
Format[] formats = new Format[representations.size()];
|
|
||||||
for (int j = 0; j < formats.length; j++) {
|
|
||||||
formats[j] = representations.get(j).format;
|
|
||||||
}
|
|
||||||
trackGroupAdaptationSetIndices[trackGroupCount] = i;
|
|
||||||
boolean adaptive = adaptationSetType == C.TRACK_TYPE_VIDEO;
|
|
||||||
trackGroupArray[trackGroupCount++] = new TrackGroup(adaptive, formats);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (trackGroupCount < trackGroupArray.length) {
|
|
||||||
trackGroupAdaptationSetIndices = Arrays.copyOf(trackGroupAdaptationSetIndices,
|
|
||||||
trackGroupCount);
|
|
||||||
trackGroupArray = Arrays.copyOf(trackGroupArray, trackGroupCount);
|
|
||||||
}
|
|
||||||
trackGroups = new TrackGroupArray(trackGroupArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackSelection selection,
|
|
||||||
long positionUs) {
|
|
||||||
int[] selectedTracks = selection.getTracks();
|
|
||||||
FormatEvaluator adaptiveEvaluator = selectedTracks.length > 1
|
|
||||||
? new AdaptiveEvaluator(bandwidthMeter) : null;
|
|
||||||
int adaptationSetIndex = trackGroupAdaptationSetIndices[selection.group];
|
|
||||||
AdaptationSet adaptationSet = manifest.getPeriod(0).adaptationSets.get(
|
|
||||||
adaptationSetIndex);
|
|
||||||
int adaptationSetType = adaptationSet.type;
|
|
||||||
DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
|
||||||
DashChunkSource chunkSource = new DashChunkSource(loader, manifest, adaptationSetIndex,
|
|
||||||
trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator,
|
|
||||||
elapsedRealtimeOffset);
|
|
||||||
return new ChunkSampleStream<>(adaptationSetType, chunkSource, this, allocator, positionUs,
|
|
||||||
minLoadableRetryCount, eventDispatcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static ChunkSampleStream<DashChunkSource>[] newSampleStreamArray(int length) {
|
|
||||||
return new ChunkSampleStream[length];
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class ManifestCallback implements
|
private final class ManifestCallback implements
|
||||||
Loader.Callback<ParsingLoadable<MediaPresentationDescription>> {
|
Loader.Callback<ParsingLoadable<MediaPresentationDescription>> {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue