Commit 15a890c3 by olly Committed by Oliver Woodman

Refactor #6.0: Add SampleSources for DASH and SS.

This is a mechanical change. The two new SampleSource classes
are forked from MultiSampleSource, with the logic that previously
was in the demo app's SourceBuilder methods copied into the
corresponding constructors. Subsequent steps will:

1. Pull the initial manifest processing from DashChunkSource and
SmoothStreamingChunkSource into the new SampleSource classes.
2. Remove the construction of ChunkSampleSource instances from
the constructors, instead instantiating children only as needed
when tracks are enabled.
3. Simplify ChunkSampleSource down into a ChunkTrackStream object.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=121001021
parent 3d14c724
...@@ -20,13 +20,8 @@ import com.google.android.exoplayer.DefaultLoadControl; ...@@ -20,13 +20,8 @@ import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MultiSampleSource; import com.google.android.exoplayer.MultiSampleSource;
import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.FormatEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; import com.google.android.exoplayer.dash.DashSampleSource;
import com.google.android.exoplayer.dash.DashChunkSource;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
import com.google.android.exoplayer.drm.MediaDrmCallback; import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.extractor.ExtractorSampleSource;
import com.google.android.exoplayer.hls.HlsChunkSource; import com.google.android.exoplayer.hls.HlsChunkSource;
...@@ -34,16 +29,13 @@ import com.google.android.exoplayer.hls.HlsSampleSource; ...@@ -34,16 +29,13 @@ import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider; import com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider;
import com.google.android.exoplayer.hls.playlist.HlsPlaylist; import com.google.android.exoplayer.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsPlaylistParser; import com.google.android.exoplayer.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingSampleSource;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser;
import com.google.android.exoplayer.upstream.Allocator; import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory; import com.google.android.exoplayer.upstream.DataSourceFactory;
import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.Util;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
...@@ -53,90 +45,21 @@ import android.os.Handler; ...@@ -53,90 +45,21 @@ import android.os.Handler;
*/ */
public class SourceBuilder { public class SourceBuilder {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int MUXED_BUFFER_SEGMENTS = 256;
private static final int VIDEO_BUFFER_SEGMENTS = 200;
private static final int AUDIO_BUFFER_SEGMENTS = 54;
private static final int TEXT_BUFFER_SEGMENTS = 2;
private SourceBuilder() {} private SourceBuilder() {}
// TODO[REFACTOR]: Bring back DASH DRM support. // TODO[REFACTOR]: Bring back DASH DRM support.
// TODO[REFACTOR]: Bring back DASH UTC timing element support. // TODO[REFACTOR]: Bring back DASH UTC timing element support.
public static SampleSource buildDashSource(DemoPlayer player, DataSourceFactory dataSourceFactory, public static SampleSource buildDashSource(DemoPlayer player, DataSourceFactory dataSourceFactory,
Uri uri, MediaDrmCallback drmCallback) { Uri uri, MediaDrmCallback drmCallback) {
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); return new DashSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(),
DataSource manifestDataSource = dataSourceFactory.createDataSource(); player.getMainHandler(), player);
// TODO[REFACTOR]: This needs releasing.
ManifestFetcher<MediaPresentationDescription> manifestFetcher = new ManifestFetcher<>(uri,
manifestDataSource, parser);
Handler mainHandler = player.getMainHandler();
BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
// Build the video renderer.
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_VIDEO,
videoDataSource, new AdaptiveEvaluator(bandwidthMeter));
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, C.TRACK_TYPE_VIDEO);
// Build the audio renderer.
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO,
audioDataSource, null);
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, C.TRACK_TYPE_AUDIO);
// Build the text renderer.
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT,
textDataSource, null);
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, C.TRACK_TYPE_TEXT);
return new MultiSampleSource(videoSampleSource, audioSampleSource, textSampleSource);
} }
// TODO[REFACTOR]: Bring back DRM support. // TODO[REFACTOR]: Bring back DRM support.
public static SampleSource buildSmoothStreamingSource(DemoPlayer player, public static SampleSource buildSmoothStreamingSource(DemoPlayer player,
DataSourceFactory dataSourceFactory, Uri uri, MediaDrmCallback drmCallback) { DataSourceFactory dataSourceFactory, Uri uri, MediaDrmCallback drmCallback) {
if (!Util.toLowerInvariant(uri.getLastPathSegment()).equals("manifest")) { return new SmoothStreamingSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(),
uri = Uri.withAppendedPath(uri, "Manifest"); player.getMainHandler(), player);
}
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
// TODO[REFACTOR]: This needs releasing.
DataSource manifestDataSource = dataSourceFactory.createDataSource();
ManifestFetcher<SmoothStreamingManifest> manifestFetcher = new ManifestFetcher<>(uri,
manifestDataSource, parser);
Handler mainHandler = player.getMainHandler();
BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
// Build the video renderer.
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
C.TRACK_TYPE_VIDEO, videoDataSource, new AdaptiveEvaluator(bandwidthMeter));
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, C.TRACK_TYPE_VIDEO);
// Build the audio renderer.
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
C.TRACK_TYPE_AUDIO, audioDataSource, null);
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, C.TRACK_TYPE_AUDIO);
// Build the text renderer.
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT,
textDataSource, null);
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, C.TRACK_TYPE_TEXT);
return new MultiSampleSource(videoSampleSource, audioSampleSource, textSampleSource);
} }
public static SampleSource buildHlsSource(DemoPlayer player, DataSourceFactory dataSourceFactory, public static SampleSource buildHlsSource(DemoPlayer player, DataSourceFactory dataSourceFactory,
...@@ -149,7 +72,8 @@ public class SourceBuilder { ...@@ -149,7 +72,8 @@ public class SourceBuilder {
Handler mainHandler = player.getMainHandler(); Handler mainHandler = player.getMainHandler();
BandwidthMeter bandwidthMeter = player.getBandwidthMeter(); BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); LoadControl loadControl = new DefaultLoadControl(
new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider(); PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
DataSource defaultDataSource = dataSourceFactory.createDataSource(bandwidthMeter); DataSource defaultDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
...@@ -157,29 +81,29 @@ public class SourceBuilder { ...@@ -157,29 +81,29 @@ public class SourceBuilder {
defaultDataSource, timestampAdjusterProvider, defaultDataSource, timestampAdjusterProvider,
new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter)); new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter));
HlsSampleSource defaultSampleSource = new HlsSampleSource(defaultChunkSource, loadControl, HlsSampleSource defaultSampleSource = new HlsSampleSource(defaultChunkSource, loadControl,
MUXED_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, C.TRACK_TYPE_VIDEO); C.DEFAULT_MUXED_BUFFER_SIZE, mainHandler, player, C.TRACK_TYPE_VIDEO);
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter); DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
HlsChunkSource audioChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO, HlsChunkSource audioChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO,
audioDataSource, timestampAdjusterProvider, null); audioDataSource, timestampAdjusterProvider, null);
HlsSampleSource audioSampleSource = new HlsSampleSource(audioChunkSource, loadControl, HlsSampleSource audioSampleSource = new HlsSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, C.TRACK_TYPE_AUDIO); C.DEFAULT_AUDIO_BUFFER_SIZE, mainHandler, player, C.TRACK_TYPE_AUDIO);
DataSource subtitleDataSource = dataSourceFactory.createDataSource(bandwidthMeter); DataSource subtitleDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
HlsChunkSource subtitleChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT, HlsChunkSource subtitleChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT,
subtitleDataSource, timestampAdjusterProvider, null); subtitleDataSource, timestampAdjusterProvider, null);
HlsSampleSource subtitleSampleSource = new HlsSampleSource(subtitleChunkSource, loadControl, HlsSampleSource subtitleSampleSource = new HlsSampleSource(subtitleChunkSource, loadControl,
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, C.TRACK_TYPE_TEXT); C.DEFAULT_TEXT_BUFFER_SIZE, mainHandler, player, C.TRACK_TYPE_TEXT);
return new MultiSampleSource(defaultSampleSource, audioSampleSource, subtitleSampleSource); return new MultiSampleSource(defaultSampleSource, audioSampleSource, subtitleSampleSource);
} }
public static SampleSource buildExtractorSource(DemoPlayer player, public static SampleSource buildExtractorSource(DemoPlayer player,
DataSourceFactory dataSourceFactory, Uri uri) { DataSourceFactory dataSourceFactory, Uri uri) {
Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE); Allocator allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE);
DataSource dataSource = dataSourceFactory.createDataSource(player.getBandwidthMeter()); DataSource dataSource = dataSourceFactory.createDataSource(player.getBandwidthMeter());
return new ExtractorSampleSource(uri, dataSource, allocator, return new ExtractorSampleSource(uri, dataSource, allocator,
MUXED_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, player.getMainHandler(), player, 0); C.DEFAULT_MUXED_BUFFER_SIZE, player.getMainHandler(), player, 0);
} }
} }
...@@ -144,6 +144,32 @@ public final class C { ...@@ -144,6 +144,32 @@ public final class C {
*/ */
public static final int TRACK_TYPE_TEXT = 3; public static final int TRACK_TYPE_TEXT = 3;
/**
* A default size in bytes for an individual allocation that forms part of a larger buffer.
*/
public static final int DEFAULT_BUFFER_SEGMENT_SIZE = 64 * 1024;
/**
* A default size in bytes for a video buffer.
*/
public static final int DEFAULT_VIDEO_BUFFER_SIZE = 200 * DEFAULT_BUFFER_SEGMENT_SIZE;
/**
* A default size in bytes for an audio buffer.
*/
public static final int DEFAULT_AUDIO_BUFFER_SIZE = 54 * DEFAULT_BUFFER_SEGMENT_SIZE;
/**
* A default size in bytes for a text buffer.
*/
public static final int DEFAULT_TEXT_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE;
/**
* A default size in bytes for a muxed buffer (e.g. containing video, audio and text).
*/
public static final int DEFAULT_MUXED_BUFFER_SIZE = DEFAULT_VIDEO_BUFFER_SIZE
+ DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE;
private C() {} private C() {}
} }
/*
* Copyright (C) 2014 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.exoplayer.dash;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener;
import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ManifestFetcher;
import android.net.Uri;
import android.os.Handler;
import android.util.Pair;
import java.io.IOException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
/**
* Combines multiple {@link SampleSource} instances.
*/
public final class DashSampleSource implements SampleSource {
private final SampleSource[] sources;
private final IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
private final int[] selectedTrackCounts;
private boolean prepared;
private boolean seenFirstTrackSelection;
private long durationUs;
private TrackGroupArray trackGroups;
private SampleSource[] enabledSources;
public DashSampleSource(Uri uri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Handler eventHandler,
ChunkSampleSourceEventListener eventListener) {
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
DataSource manifestDataSource = dataSourceFactory.createDataSource();
// TODO[REFACTOR]: This needs releasing.
ManifestFetcher<MediaPresentationDescription> manifestFetcher = new ManifestFetcher<>(uri,
manifestDataSource, parser);
LoadControl loadControl = new DefaultLoadControl(
new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
// Build the video renderer.
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_VIDEO,
videoDataSource, new AdaptiveEvaluator(bandwidthMeter));
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
C.DEFAULT_VIDEO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO);
// Build the audio renderer.
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO,
audioDataSource, null);
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO);
// Build the text renderer.
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT,
textDataSource, null);
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT);
sources = new SampleSource[] {videoSampleSource, audioSampleSource, textSampleSource};
trackStreamSources = new IdentityHashMap<>();
selectedTrackCounts = new int[sources.length];
}
@Override
public boolean prepare(long positionUs) throws IOException {
if (prepared) {
return true;
}
boolean sourcesPrepared = true;
for (SampleSource source : sources) {
sourcesPrepared &= source.prepare(positionUs);
}
if (!sourcesPrepared) {
return false;
}
durationUs = 0;
int totalTrackGroupCount = 0;
for (SampleSource source : sources) {
totalTrackGroupCount += source.getTrackGroups().length;
if (durationUs != C.UNSET_TIME_US) {
long sourceDurationUs = source.getDurationUs();
durationUs = sourceDurationUs == C.UNSET_TIME_US
? C.UNSET_TIME_US : Math.max(durationUs, sourceDurationUs);
}
}
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
int trackGroupIndex = 0;
for (SampleSource source : sources) {
int sourceTrackGroupCount = source.getTrackGroups().length;
for (int j = 0; j < sourceTrackGroupCount; j++) {
trackGroupArray[trackGroupIndex++] = source.getTrackGroups().get(j);
}
}
trackGroups = new TrackGroupArray(trackGroupArray);
prepared = true;
return true;
}
@Override
public long getDurationUs() {
return durationUs;
}
@Override
public TrackGroupArray getTrackGroups() {
return trackGroups;
}
@Override
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
List<TrackSelection> newSelections, long positionUs) {
Assertions.checkState(prepared);
TrackStream[] newStreams = new TrackStream[newSelections.size()];
// Select tracks for each source.
int enabledSourceCount = 0;
for (int i = 0; i < sources.length; i++) {
selectedTrackCounts[i] += selectTracks(sources[i], oldStreams, newSelections, positionUs,
newStreams);
if (selectedTrackCounts[i] > 0) {
enabledSourceCount++;
}
}
// Update the enabled sources.
enabledSources = new SampleSource[enabledSourceCount];
enabledSourceCount = 0;
for (int i = 0; i < sources.length; i++) {
if (selectedTrackCounts[i] > 0) {
enabledSources[enabledSourceCount++] = sources[i];
}
}
seenFirstTrackSelection = true;
return newStreams;
}
@Override
public void continueBuffering(long positionUs) {
for (SampleSource source : enabledSources) {
source.continueBuffering(positionUs);
}
}
@Override
public long readReset() {
long resetPositionUs = C.UNSET_TIME_US;
for (SampleSource source : enabledSources) {
long childResetPositionUs = source.readReset();
if (resetPositionUs == C.UNSET_TIME_US) {
resetPositionUs = childResetPositionUs;
} else if (childResetPositionUs != C.UNSET_TIME_US) {
resetPositionUs = Math.min(resetPositionUs, childResetPositionUs);
}
}
return resetPositionUs;
}
@Override
public long getBufferedPositionUs() {
long bufferedPositionUs = durationUs != C.UNSET_TIME_US ? durationUs : Long.MAX_VALUE;
for (SampleSource source : enabledSources) {
long rendererBufferedPositionUs = source.getBufferedPositionUs();
if (rendererBufferedPositionUs == C.UNSET_TIME_US) {
return C.UNSET_TIME_US;
} else if (rendererBufferedPositionUs == C.END_OF_SOURCE_US) {
// This source is fully buffered.
} else {
bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs);
}
}
return bufferedPositionUs == Long.MAX_VALUE ? C.UNSET_TIME_US : bufferedPositionUs;
}
@Override
public void seekToUs(long positionUs) {
for (SampleSource source : enabledSources) {
source.seekToUs(positionUs);
}
}
@Override
public void release() {
for (SampleSource source : sources) {
source.release();
}
}
// Internal methods.
private int selectTracks(SampleSource source, List<TrackStream> allOldStreams,
List<TrackSelection> allNewSelections, long positionUs, TrackStream[] allNewStreams) {
// Get the subset of the old streams for the source.
ArrayList<TrackStream> oldStreams = new ArrayList<>();
for (int i = 0; i < allOldStreams.size(); i++) {
TrackStream stream = allOldStreams.get(i);
if (trackStreamSources.get(stream) == source) {
trackStreamSources.remove(stream);
oldStreams.add(stream);
}
}
// Get the subset of the new selections for the source.
ArrayList<TrackSelection> newSelections = new ArrayList<>();
int[] newSelectionOriginalIndices = new int[allNewSelections.size()];
for (int i = 0; i < allNewSelections.size(); i++) {
TrackSelection selection = allNewSelections.get(i);
Pair<SampleSource, Integer> sourceAndGroup = getSourceAndGroup(selection.group);
if (sourceAndGroup.first == source) {
newSelectionOriginalIndices[newSelections.size()] = i;
newSelections.add(new TrackSelection(sourceAndGroup.second, selection.getTracks()));
}
}
// Do nothing if nothing has changed, except during the first selection.
if (seenFirstTrackSelection && oldStreams.isEmpty() && newSelections.isEmpty()) {
return 0;
}
// Perform the selection.
TrackStream[] newStreams = source.selectTracks(oldStreams, newSelections, positionUs);
for (int j = 0; j < newStreams.length; j++) {
allNewStreams[newSelectionOriginalIndices[j]] = newStreams[j];
trackStreamSources.put(newStreams[j], source);
}
return newSelections.size() - oldStreams.size();
}
private Pair<SampleSource, Integer> getSourceAndGroup(int group) {
int totalTrackGroupCount = 0;
for (SampleSource source : sources) {
int sourceTrackGroupCount = source.getTrackGroups().length;
if (group < totalTrackGroupCount + sourceTrackGroupCount) {
return Pair.create(source, group - totalTrackGroupCount);
}
totalTrackGroupCount += sourceTrackGroupCount;
}
throw new IndexOutOfBoundsException();
}
}
/*
* Copyright (C) 2014 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.exoplayer.smoothstreaming;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener;
import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.Util;
import android.net.Uri;
import android.os.Handler;
import android.util.Pair;
import java.io.IOException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
/**
* Combines multiple {@link SampleSource} instances.
*/
public final class SmoothStreamingSampleSource implements SampleSource {
private final SampleSource[] sources;
private final IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
private final int[] selectedTrackCounts;
private boolean prepared;
private boolean seenFirstTrackSelection;
private long durationUs;
private TrackGroupArray trackGroups;
private SampleSource[] enabledSources;
public SmoothStreamingSampleSource(Uri uri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Handler eventHandler,
ChunkSampleSourceEventListener eventListener) {
if (!Util.toLowerInvariant(uri.getLastPathSegment()).equals("manifest")) {
uri = Uri.withAppendedPath(uri, "Manifest");
}
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
DataSource manifestDataSource = dataSourceFactory.createDataSource();
// TODO[REFACTOR]: This needs releasing.
ManifestFetcher<SmoothStreamingManifest> manifestFetcher = new ManifestFetcher<>(uri,
manifestDataSource, parser);
LoadControl loadControl = new DefaultLoadControl(
new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
// Build the video renderer.
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
C.TRACK_TYPE_VIDEO, videoDataSource, new AdaptiveEvaluator(bandwidthMeter));
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
C.DEFAULT_VIDEO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO);
// Build the audio renderer.
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
C.TRACK_TYPE_AUDIO, audioDataSource, null);
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO);
// Build the text renderer.
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT,
textDataSource, null);
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT);
sources = new SampleSource[] {videoSampleSource, audioSampleSource, textSampleSource};
trackStreamSources = new IdentityHashMap<>();
selectedTrackCounts = new int[sources.length];
}
@Override
public boolean prepare(long positionUs) throws IOException {
if (prepared) {
return true;
}
boolean sourcesPrepared = true;
for (SampleSource source : sources) {
sourcesPrepared &= source.prepare(positionUs);
}
if (!sourcesPrepared) {
return false;
}
durationUs = 0;
int totalTrackGroupCount = 0;
for (SampleSource source : sources) {
totalTrackGroupCount += source.getTrackGroups().length;
if (durationUs != C.UNSET_TIME_US) {
long sourceDurationUs = source.getDurationUs();
durationUs = sourceDurationUs == C.UNSET_TIME_US
? C.UNSET_TIME_US : Math.max(durationUs, sourceDurationUs);
}
}
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
int trackGroupIndex = 0;
for (SampleSource source : sources) {
int sourceTrackGroupCount = source.getTrackGroups().length;
for (int j = 0; j < sourceTrackGroupCount; j++) {
trackGroupArray[trackGroupIndex++] = source.getTrackGroups().get(j);
}
}
trackGroups = new TrackGroupArray(trackGroupArray);
prepared = true;
return true;
}
@Override
public long getDurationUs() {
return durationUs;
}
@Override
public TrackGroupArray getTrackGroups() {
return trackGroups;
}
@Override
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
List<TrackSelection> newSelections, long positionUs) {
Assertions.checkState(prepared);
TrackStream[] newStreams = new TrackStream[newSelections.size()];
// Select tracks for each source.
int enabledSourceCount = 0;
for (int i = 0; i < sources.length; i++) {
selectedTrackCounts[i] += selectTracks(sources[i], oldStreams, newSelections, positionUs,
newStreams);
if (selectedTrackCounts[i] > 0) {
enabledSourceCount++;
}
}
// Update the enabled sources.
enabledSources = new SampleSource[enabledSourceCount];
enabledSourceCount = 0;
for (int i = 0; i < sources.length; i++) {
if (selectedTrackCounts[i] > 0) {
enabledSources[enabledSourceCount++] = sources[i];
}
}
seenFirstTrackSelection = true;
return newStreams;
}
@Override
public void continueBuffering(long positionUs) {
for (SampleSource source : enabledSources) {
source.continueBuffering(positionUs);
}
}
@Override
public long readReset() {
long resetPositionUs = C.UNSET_TIME_US;
for (SampleSource source : enabledSources) {
long childResetPositionUs = source.readReset();
if (resetPositionUs == C.UNSET_TIME_US) {
resetPositionUs = childResetPositionUs;
} else if (childResetPositionUs != C.UNSET_TIME_US) {
resetPositionUs = Math.min(resetPositionUs, childResetPositionUs);
}
}
return resetPositionUs;
}
@Override
public long getBufferedPositionUs() {
long bufferedPositionUs = durationUs != C.UNSET_TIME_US ? durationUs : Long.MAX_VALUE;
for (SampleSource source : enabledSources) {
long rendererBufferedPositionUs = source.getBufferedPositionUs();
if (rendererBufferedPositionUs == C.UNSET_TIME_US) {
return C.UNSET_TIME_US;
} else if (rendererBufferedPositionUs == C.END_OF_SOURCE_US) {
// This source is fully buffered.
} else {
bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs);
}
}
return bufferedPositionUs == Long.MAX_VALUE ? C.UNSET_TIME_US : bufferedPositionUs;
}
@Override
public void seekToUs(long positionUs) {
for (SampleSource source : enabledSources) {
source.seekToUs(positionUs);
}
}
@Override
public void release() {
for (SampleSource source : sources) {
source.release();
}
}
// Internal methods.
private int selectTracks(SampleSource source, List<TrackStream> allOldStreams,
List<TrackSelection> allNewSelections, long positionUs, TrackStream[] allNewStreams) {
// Get the subset of the old streams for the source.
ArrayList<TrackStream> oldStreams = new ArrayList<>();
for (int i = 0; i < allOldStreams.size(); i++) {
TrackStream stream = allOldStreams.get(i);
if (trackStreamSources.get(stream) == source) {
trackStreamSources.remove(stream);
oldStreams.add(stream);
}
}
// Get the subset of the new selections for the source.
ArrayList<TrackSelection> newSelections = new ArrayList<>();
int[] newSelectionOriginalIndices = new int[allNewSelections.size()];
for (int i = 0; i < allNewSelections.size(); i++) {
TrackSelection selection = allNewSelections.get(i);
Pair<SampleSource, Integer> sourceAndGroup = getSourceAndGroup(selection.group);
if (sourceAndGroup.first == source) {
newSelectionOriginalIndices[newSelections.size()] = i;
newSelections.add(new TrackSelection(sourceAndGroup.second, selection.getTracks()));
}
}
// Do nothing if nothing has changed, except during the first selection.
if (seenFirstTrackSelection && oldStreams.isEmpty() && newSelections.isEmpty()) {
return 0;
}
// Perform the selection.
TrackStream[] newStreams = source.selectTracks(oldStreams, newSelections, positionUs);
for (int j = 0; j < newStreams.length; j++) {
allNewStreams[newSelectionOriginalIndices[j]] = newStreams[j];
trackStreamSources.put(newStreams[j], source);
}
return newSelections.size() - oldStreams.size();
}
private Pair<SampleSource, Integer> getSourceAndGroup(int group) {
int totalTrackGroupCount = 0;
for (SampleSource source : sources) {
int sourceTrackGroupCount = source.getTrackGroups().length;
if (group < totalTrackGroupCount + sourceTrackGroupCount) {
return Pair.create(source, group - totalTrackGroupCount);
}
totalTrackGroupCount += sourceTrackGroupCount;
}
throw new IndexOutOfBoundsException();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment