diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index 1e7bf0f368..c9dd91cd65 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -24,18 +24,24 @@ import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializatio import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroupArray; +import com.google.android.exoplayer.dash.DashSampleSource; import com.google.android.exoplayer.demo.player.DemoPlayer; -import com.google.android.exoplayer.demo.player.SourceBuilder; import com.google.android.exoplayer.demo.ui.TrackSelectionHelper; import com.google.android.exoplayer.drm.UnsupportedDrmException; +import com.google.android.exoplayer.extractor.ExtractorSampleSource; +import com.google.android.exoplayer.hls.HlsSource; import com.google.android.exoplayer.metadata.id3.GeobFrame; import com.google.android.exoplayer.metadata.id3.Id3Frame; import com.google.android.exoplayer.metadata.id3.PrivFrame; import com.google.android.exoplayer.metadata.id3.TxxxFrame; +import com.google.android.exoplayer.smoothstreaming.SmoothStreamingSampleSource; import com.google.android.exoplayer.text.CaptionStyleCompat; import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.SubtitleLayout; +import com.google.android.exoplayer.upstream.Allocator; +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.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer.util.DebugTextViewHelper; import com.google.android.exoplayer.util.Util; @@ -260,18 +266,25 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, // Internal methods + @SuppressWarnings("unused") private SampleSource buildSource(Uri uri, String id, String provider, int type) { switch (type) { case Util.TYPE_SS: - return SourceBuilder.buildSmoothStreamingSource(player, dataSourceFactory, uri, - new SmoothStreamingTestMediaDrmCallback()); + // TODO: Bring back DRM support. new SmoothStreamingTestMediaDrmCallback() + return new SmoothStreamingSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), + player.getMainHandler(), player); case Util.TYPE_DASH: - return SourceBuilder.buildDashSource(player, dataSourceFactory, uri, - new WidevineTestMediaDrmCallback(id, provider)); + // TODO: Bring back DRM support. new WidevineTestMediaDrmCallback(id, provider) + return new DashSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), + player.getMainHandler(), player); case Util.TYPE_HLS: - return SourceBuilder.buildHlsSource(player, dataSourceFactory, uri); + return new HlsSource(uri, dataSourceFactory, player.getBandwidthMeter(), + player.getMainHandler(), player); case Util.TYPE_OTHER: - return SourceBuilder.buildExtractorSource(player, dataSourceFactory, uri); + Allocator allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE); + DataSource dataSource = dataSourceFactory.createDataSource(player.getBandwidthMeter()); + return new ExtractorSampleSource(uri, dataSource, allocator, + C.DEFAULT_MUXED_BUFFER_SIZE, player.getMainHandler(), player, 0); default: throw new IllegalStateException("Unsupported type: " + type); } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java index e7468dce5a..f72c8d44f1 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java @@ -279,7 +279,7 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even return player.getPlayWhenReady(); } - /* package */ Handler getMainHandler() { + public Handler getMainHandler() { return mainHandler; } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/SourceBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/SourceBuilder.java deleted file mode 100644 index abbb715ba2..0000000000 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/SourceBuilder.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.demo.player; - -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.DefaultLoadControl; -import com.google.android.exoplayer.LoadControl; -import com.google.android.exoplayer.MultiSampleSource; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.chunk.FormatEvaluator; -import com.google.android.exoplayer.dash.DashSampleSource; -import com.google.android.exoplayer.drm.MediaDrmCallback; -import com.google.android.exoplayer.extractor.ExtractorSampleSource; -import com.google.android.exoplayer.hls.HlsChunkSource; -import com.google.android.exoplayer.hls.HlsSampleSource; -import com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider; -import com.google.android.exoplayer.hls.playlist.HlsPlaylist; -import com.google.android.exoplayer.hls.playlist.HlsPlaylistParser; -import com.google.android.exoplayer.smoothstreaming.SmoothStreamingSampleSource; -import com.google.android.exoplayer.upstream.Allocator; -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.ManifestFetcher; - -import android.net.Uri; -import android.os.Handler; - -/** - * A utility class for building {@link SampleSource} instances. - */ -public class SourceBuilder { - - private SourceBuilder() {} - - // TODO[REFACTOR]: Bring back DASH DRM support. - // TODO[REFACTOR]: Bring back DASH UTC timing element support. - public static SampleSource buildDashSource(DemoPlayer player, DataSourceFactory dataSourceFactory, - Uri uri, MediaDrmCallback drmCallback) { - return new DashSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), - player.getMainHandler(), player); - } - - // TODO[REFACTOR]: Bring back DRM support. - public static SampleSource buildSmoothStreamingSource(DemoPlayer player, - DataSourceFactory dataSourceFactory, Uri uri, MediaDrmCallback drmCallback) { - return new SmoothStreamingSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), - player.getMainHandler(), player); - } - - public static SampleSource buildHlsSource(DemoPlayer player, DataSourceFactory dataSourceFactory, - Uri uri) { - HlsPlaylistParser parser = new HlsPlaylistParser(); - DataSource manifestDataSource = dataSourceFactory.createDataSource(); - // TODO[REFACTOR]: This needs releasing. - ManifestFetcher manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, - parser); - - Handler mainHandler = player.getMainHandler(); - BandwidthMeter bandwidthMeter = player.getBandwidthMeter(); - LoadControl loadControl = new DefaultLoadControl( - new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); - PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider(); - - DataSource defaultDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - HlsChunkSource defaultChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_DEFAULT, - defaultDataSource, timestampAdjusterProvider, - new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter)); - HlsSampleSource defaultSampleSource = new HlsSampleSource(defaultChunkSource, loadControl, - C.DEFAULT_MUXED_BUFFER_SIZE, mainHandler, player, C.TRACK_TYPE_VIDEO); - - DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - HlsChunkSource audioChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO, - audioDataSource, timestampAdjusterProvider, null); - HlsSampleSource audioSampleSource = new HlsSampleSource(audioChunkSource, loadControl, - C.DEFAULT_AUDIO_BUFFER_SIZE, mainHandler, player, C.TRACK_TYPE_AUDIO); - - DataSource subtitleDataSource = dataSourceFactory.createDataSource(bandwidthMeter); - HlsChunkSource subtitleChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT, - subtitleDataSource, timestampAdjusterProvider, null); - HlsSampleSource subtitleSampleSource = new HlsSampleSource(subtitleChunkSource, loadControl, - C.DEFAULT_TEXT_BUFFER_SIZE, mainHandler, player, C.TRACK_TYPE_TEXT); - - return new MultiSampleSource(defaultSampleSource, audioSampleSource, subtitleSampleSource); - } - - public static SampleSource buildExtractorSource(DemoPlayer player, - DataSourceFactory dataSourceFactory, Uri uri) { - Allocator allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE); - DataSource dataSource = dataSourceFactory.createDataSource(player.getBandwidthMeter()); - return new ExtractorSampleSource(uri, dataSource, allocator, - C.DEFAULT_MUXED_BUFFER_SIZE, player.getMainHandler(), player, 0); - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index d311a46c92..daf84d567f 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -45,7 +45,7 @@ import java.util.List; /** * A {@link SampleSource} for HLS streams. */ -public final class HlsSampleSource implements SampleSource, Loader.Callback { +public final class HlsSampleSource implements Loader.Callback { /** * The default minimum number of times to retry loading data prior to failing. @@ -139,9 +139,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { pendingResetPositionUs = C.UNSET_TIME_US; } - // SampleSource implementation. - - @Override public boolean prepare(long positionUs) throws IOException { if (prepared) { return true; @@ -173,17 +170,14 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { return false; } - @Override public long getDurationUs() { return chunkSource.getDurationUs(); } - @Override public TrackGroupArray getTrackGroups() { return trackGroups; } - @Override public TrackStream[] selectTracks(List oldStreams, List newSelections, long positionUs) { Assertions.checkState(prepared); @@ -233,7 +227,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { return newStreams; } - @Override public void continueBuffering(long playbackPositionUs) { downstreamPositionUs = playbackPositionUs; discardSamplesForDisabledTracks(); @@ -242,7 +235,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { } } - @Override public long readReset() { if (notifyReset) { notifyReset = false; @@ -251,7 +243,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { return C.UNSET_TIME_US; } - @Override public long getBufferedPositionUs() { if (loadingFinished) { return C.END_OF_SOURCE_US; @@ -273,12 +264,10 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { } } - @Override public void seekToUs(long positionUs) { seekToInternal(positionUs); } - @Override public void release() { if (enabledTrackCount > 0) { loadControl.unregister(this); diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSource.java new file mode 100644 index 0000000000..0e4eb8ff2b --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSource.java @@ -0,0 +1,268 @@ +/* + * 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.hls; + +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.ChunkTrackStreamEventListener; +import com.google.android.exoplayer.chunk.FormatEvaluator; +import com.google.android.exoplayer.hls.playlist.HlsPlaylist; +import com.google.android.exoplayer.hls.playlist.HlsPlaylistParser; +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; + +/** + * A {@link SampleSource} for HLS streams. + */ +public final class HlsSource implements SampleSource { + + private final ManifestFetcher manifestFetcher; + private final HlsSampleSource[] sources; + private final IdentityHashMap trackStreamSources; + private final int[] selectedTrackCounts; + + private boolean prepared; + private boolean seenFirstTrackSelection; + private long durationUs; + private TrackGroupArray trackGroups; + private HlsSampleSource[] enabledSources; + + public HlsSource(Uri uri, DataSourceFactory dataSourceFactory, BandwidthMeter bandwidthMeter, + Handler eventHandler, ChunkTrackStreamEventListener eventListener) { + HlsPlaylistParser parser = new HlsPlaylistParser(); + DataSource manifestDataSource = dataSourceFactory.createDataSource(); + manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, parser); + + LoadControl loadControl = new DefaultLoadControl( + new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); + PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider(); + + DataSource defaultDataSource = dataSourceFactory.createDataSource(bandwidthMeter); + HlsChunkSource defaultChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_DEFAULT, + defaultDataSource, timestampAdjusterProvider, + new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter)); + HlsSampleSource defaultSampleSource = new HlsSampleSource(defaultChunkSource, loadControl, + C.DEFAULT_MUXED_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO); + + DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter); + HlsChunkSource audioChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO, + audioDataSource, timestampAdjusterProvider, null); + HlsSampleSource audioSampleSource = new HlsSampleSource(audioChunkSource, loadControl, + C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO); + + DataSource subtitleDataSource = dataSourceFactory.createDataSource(bandwidthMeter); + HlsChunkSource subtitleChunkSource = new HlsChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT, + subtitleDataSource, timestampAdjusterProvider, null); + HlsSampleSource subtitleSampleSource = new HlsSampleSource(subtitleChunkSource, loadControl, + C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT); + + sources = new HlsSampleSource[] {defaultSampleSource, audioSampleSource, subtitleSampleSource}; + selectedTrackCounts = new int[sources.length]; + trackStreamSources = new IdentityHashMap<>(); + } + + @Override + public boolean prepare(long positionUs) throws IOException { + if (prepared) { + return true; + } + boolean sourcesPrepared = true; + for (HlsSampleSource source : sources) { + sourcesPrepared &= source.prepare(positionUs); + } + if (!sourcesPrepared) { + return false; + } + durationUs = 0; + int totalTrackGroupCount = 0; + for (HlsSampleSource 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 (HlsSampleSource 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 oldStreams, + List 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 HlsSampleSource[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 (HlsSampleSource source : enabledSources) { + source.continueBuffering(positionUs); + } + } + + @Override + public long readReset() { + long resetPositionUs = C.UNSET_TIME_US; + for (HlsSampleSource 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 (HlsSampleSource 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 (HlsSampleSource source : enabledSources) { + source.seekToUs(positionUs); + } + } + + @Override + public void release() { + manifestFetcher.release(); + for (HlsSampleSource source : sources) { + source.release(); + } + } + + // Internal methods. + + private int selectTracks(HlsSampleSource source, List allOldStreams, + List allNewSelections, long positionUs, TrackStream[] allNewStreams) { + // Get the subset of the old streams for the source. + ArrayList 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 newSelections = new ArrayList<>(); + int[] newSelectionOriginalIndices = new int[allNewSelections.size()]; + for (int i = 0; i < allNewSelections.size(); i++) { + TrackSelection selection = allNewSelections.get(i); + Pair 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 getSourceAndGroup(int group) { + int totalTrackGroupCount = 0; + for (HlsSampleSource source : sources) { + int sourceTrackGroupCount = source.getTrackGroups().length; + if (group < totalTrackGroupCount + sourceTrackGroupCount) { + return Pair.create(source, group - totalTrackGroupCount); + } + totalTrackGroupCount += sourceTrackGroupCount; + } + throw new IndexOutOfBoundsException(); + } + +}