From 0de57cbfae7165dd3bb829e323d089cd312b4b1b Mon Sep 17 00:00:00 2001 From: hoangtc Date: Mon, 20 Nov 2017 08:22:19 -0800 Subject: [PATCH] Allow more flexible loading strategy when loading multiple sub streams. Currently for a DASH ChunkSource that consists of multiple sub-streams, we always use a CompositeSequenceableLoader, which only allows the furthest behind loader or any loader that are behind current playback position to continue loading. This changes allow clients to have more flexibility when deciding the loading strategy: - They can construct a different kind of composite SequenceableLoader from the sub-loaders, and use it by injecting a different CompositeSequeableLoaderFactory accordingly. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=176363870 --- RELEASENOTES.md | 8 +++- .../source/CompositeSequenceableLoader.java | 6 +-- .../CompositeSequenceableLoaderFactory.java | 31 +++++++++++++ ...ultCompositeSequenceableLoaderFactory.java | 29 ++++++++++++ .../exoplayer2/source/MergingMediaPeriod.java | 16 ++++--- .../exoplayer2/source/MergingMediaSource.java | 15 ++++++- .../source/dash/DashMediaPeriod.java | 23 ++++++---- .../source/dash/DashMediaSource.java | 42 ++++++++++++++--- .../exoplayer2/source/hls/HlsMediaPeriod.java | 20 ++++++--- .../exoplayer2/source/hls/HlsMediaSource.java | 42 ++++++++++++++++- .../source/smoothstreaming/SsMediaPeriod.java | 19 +++++--- .../source/smoothstreaming/SsMediaSource.java | 45 +++++++++++++++---- 12 files changed, 244 insertions(+), 52 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderFactory.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/source/DefaultCompositeSequenceableLoaderFactory.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d9ed3e5d2aa..41748fa10d2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,8 +2,12 @@ ### dev-v2 (not yet released) ### -* Add Builder to ExtractorMediaSource, HlsMediaSource, SsMediaSource, - DashMediaSource, SingleSampleMediaSource. +* Allow more flexible loading strategy when playing media containing multiple + sub-streams, by allowing injection of custom `CompositeSequenceableLoader` + factories through `DashMediaSource.Builder`, `HlsMediaSource.Builder`, + `SsMediaSource.Builder`, and `MergingMediaSource`. +* Add Builder to `ExtractorMediaSource`, `HlsMediaSource`, `SsMediaSource`, + `DashMediaSource`, `SingleSampleMediaSource`. * DASH: * Support in-MPD EventStream. * Allow a back-buffer of media to be retained behind the current playback diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java index a85d5897623..e9a187a747a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java @@ -20,9 +20,9 @@ /** * A {@link SequenceableLoader} that encapsulates multiple other {@link SequenceableLoader}s. */ -public final class CompositeSequenceableLoader implements SequenceableLoader { +public class CompositeSequenceableLoader implements SequenceableLoader { - private final SequenceableLoader[] loaders; + protected final SequenceableLoader[] loaders; public CompositeSequenceableLoader(SequenceableLoader[] loaders) { this.loaders = loaders; @@ -53,7 +53,7 @@ public final long getNextLoadPositionUs() { } @Override - public final boolean continueLoading(long positionUs) { + public boolean continueLoading(long positionUs) { boolean madeProgress = false; boolean madeProgressThisIteration; do { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderFactory.java new file mode 100644 index 00000000000..b4a266feef1 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 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; + +/** + * A factory to create composite {@link SequenceableLoader}s. + */ +public interface CompositeSequenceableLoaderFactory { + + /** + * Creates a composite {@link SequenceableLoader}. + * + * @param loaders The sub-loaders that make up the {@link SequenceableLoader} to be built. + * @return A composite {@link SequenceableLoader} that comprises the given loaders. + */ + SequenceableLoader createCompositeSequenceableLoader(SequenceableLoader... loaders); + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultCompositeSequenceableLoaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultCompositeSequenceableLoaderFactory.java new file mode 100644 index 00000000000..759b0824afc --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultCompositeSequenceableLoaderFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017 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; + +/** + * Default implementation of {@link CompositeSequenceableLoaderFactory}. + */ +public final class DefaultCompositeSequenceableLoaderFactory + implements CompositeSequenceableLoaderFactory { + + @Override + public SequenceableLoader createCompositeSequenceableLoader(SequenceableLoader... loaders) { + return new CompositeSequenceableLoader(loaders); + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java index 786a4693d0e..bd37b5efec5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java @@ -30,15 +30,18 @@ public final MediaPeriod[] periods; private final IdentityHashMap streamPeriodIndices; + private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private Callback callback; private int pendingChildPrepareCount; private TrackGroupArray trackGroups; private MediaPeriod[] enabledPeriods; - private SequenceableLoader sequenceableLoader; + private SequenceableLoader compositeSequenceableLoader; - public MergingMediaPeriod(MediaPeriod... periods) { + public MergingMediaPeriod(CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, + MediaPeriod... periods) { + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; this.periods = periods; streamPeriodIndices = new IdentityHashMap<>(); } @@ -124,7 +127,8 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF // Update the local state. enabledPeriods = new MediaPeriod[enabledPeriodsList.size()]; enabledPeriodsList.toArray(enabledPeriods); - sequenceableLoader = new CompositeSequenceableLoader(enabledPeriods); + compositeSequenceableLoader = + compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(enabledPeriods); return positionUs; } @@ -137,12 +141,12 @@ public void discardBuffer(long positionUs, boolean toKeyframe) { @Override public boolean continueLoading(long positionUs) { - return sequenceableLoader.continueLoading(positionUs); + return compositeSequenceableLoader.continueLoading(positionUs); } @Override public long getNextLoadPositionUs() { - return sequenceableLoader.getNextLoadPositionUs(); + return compositeSequenceableLoader.getNextLoadPositionUs(); } @Override @@ -168,7 +172,7 @@ public long readDiscontinuity() { @Override public long getBufferedPositionUs() { - return sequenceableLoader.getBufferedPositionUs(); + return compositeSequenceableLoader.getBufferedPositionUs(); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index 1550970e473..ea0274796fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -74,6 +74,7 @@ public IllegalMergeException(@Reason int reason) { private final MediaSource[] mediaSources; private final ArrayList pendingTimelineSources; private final Timeline.Window window; + private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private Listener listener; private Timeline primaryTimeline; @@ -85,7 +86,19 @@ public IllegalMergeException(@Reason int reason) { * @param mediaSources The {@link MediaSource}s to merge. */ public MergingMediaSource(MediaSource... mediaSources) { + this(new DefaultCompositeSequenceableLoaderFactory(), mediaSources); + } + + /** + * @param compositeSequenceableLoaderFactory A factory to create composite + * {@link SequenceableLoader}s for when this media source loads data from multiple streams + * (video, audio etc...). + * @param mediaSources The {@link MediaSource}s to merge. + */ + public MergingMediaSource(CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, + MediaSource... mediaSources) { this.mediaSources = mediaSources; + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; pendingTimelineSources = new ArrayList<>(Arrays.asList(mediaSources)); window = new Timeline.Window(); periodCount = PERIOD_COUNT_UNSET; @@ -121,7 +134,7 @@ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { for (int i = 0; i < periods.length; i++) { periods[i] = mediaSources[i].createPeriod(id, allocator); } - return new MergingMediaPeriod(periods); + return new MergingMediaPeriod(compositeSequenceableLoaderFactory, periods); } @Override diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index 5a60ee46ae2..70fba4dd003 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -21,7 +21,7 @@ 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.CompositeSequenceableLoaderFactory; import com.google.android.exoplayer2.source.EmptySampleStream; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.SampleStream; @@ -64,19 +64,21 @@ private final Allocator allocator; private final TrackGroupArray trackGroups; private final TrackGroupInfo[] trackGroupInfos; + private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private Callback callback; private ChunkSampleStream[] sampleStreams; private EventSampleStream[] eventSampleStreams; - private CompositeSequenceableLoader sequenceableLoader; + private SequenceableLoader compositeSequenceableLoader; private DashManifest manifest; private int periodIndex; private List eventStreams; public DashMediaPeriod(int id, DashManifest manifest, int periodIndex, - DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, EventDispatcher eventDispatcher, long elapsedRealtimeOffset, - LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) { + LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator, + CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory) { this.id = id; this.manifest = manifest; this.periodIndex = periodIndex; @@ -86,9 +88,11 @@ public DashMediaPeriod(int id, DashManifest manifest, int periodIndex, this.elapsedRealtimeOffset = elapsedRealtimeOffset; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.allocator = allocator; + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; sampleStreams = newSampleStreamArray(0); eventSampleStreams = new EventSampleStream[0]; - sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + compositeSequenceableLoader = + compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); Period period = manifest.getPeriod(periodIndex); eventStreams = period.eventStreams; Pair result = buildTrackGroups(period.adaptationSets, @@ -163,7 +167,8 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF primarySampleStreams.values().toArray(sampleStreams); eventSampleStreams = new EventSampleStream[eventSampleStreamList.size()]; eventSampleStreamList.toArray(eventSampleStreams); - sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + compositeSequenceableLoader = + compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); return positionUs; } @@ -267,12 +272,12 @@ public void discardBuffer(long positionUs, boolean toKeyframe) { @Override public boolean continueLoading(long positionUs) { - return sequenceableLoader.continueLoading(positionUs); + return compositeSequenceableLoader.continueLoading(positionUs); } @Override public long getNextLoadPositionUs() { - return sequenceableLoader.getNextLoadPositionUs(); + return compositeSequenceableLoader.getNextLoadPositionUs(); } @Override @@ -282,7 +287,7 @@ public long readDiscontinuity() { @Override public long getBufferedPositionUs() { - return sequenceableLoader.getBufferedPositionUs(); + return compositeSequenceableLoader.getBufferedPositionUs(); } @Override diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index a82b5af5831..68d39b5a185 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -28,8 +28,11 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; +import com.google.android.exoplayer2.source.DefaultCompositeSequenceableLoaderFactory; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement; @@ -71,6 +74,7 @@ public static final class Builder { private ParsingLoadable.Parser manifestParser; private AdaptiveMediaSourceEventListener eventListener; private Handler eventHandler; + private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private int minLoadableRetryCount; private long livePresentationDelayMs; @@ -171,6 +175,22 @@ public Builder setManifestParser( return this; } + /** + * Sets the factory to create composite {@link SequenceableLoader}s for when this media source + * loads data from multiple streams (video, audio etc...). The default is an instance of + * {@link DefaultCompositeSequenceableLoaderFactory}. + * + * @param compositeSequenceableLoaderFactory A factory to create composite + * {@link SequenceableLoader}s for when this media source loads data from multiple streams + * (video, audio etc...). + * @return This builder. + */ + public Builder setCompositeSequenceableLoaderFactory( + CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory) { + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; + return this; + } + /** * Builds a new {@link DashMediaSource} using the current parameters. *

@@ -186,9 +206,12 @@ public DashMediaSource build() { if (loadableManifestUri && manifestParser == null) { manifestParser = new DashManifestParser(); } + if (compositeSequenceableLoaderFactory == null) { + compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); + } return new DashMediaSource(manifest, manifestUri, manifestDataSourceFactory, manifestParser, - chunkSourceFactory, minLoadableRetryCount, livePresentationDelayMs, eventHandler, - eventListener); + chunkSourceFactory, compositeSequenceableLoaderFactory, minLoadableRetryCount, + livePresentationDelayMs, eventHandler, eventListener); } } @@ -226,6 +249,7 @@ public DashMediaSource build() { private final boolean sideloadedManifest; private final DataSource.Factory manifestDataSourceFactory; private final DashChunkSource.Factory chunkSourceFactory; + private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private final int minLoadableRetryCount; private final long livePresentationDelayMs; private final EventDispatcher eventDispatcher; @@ -280,7 +304,8 @@ public DashMediaSource(DashManifest manifest, DashChunkSource.Factory chunkSourc public DashMediaSource(DashManifest manifest, DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { - this(manifest, null, null, null, chunkSourceFactory, minLoadableRetryCount, + this(manifest, null, null, null, chunkSourceFactory, + new DefaultCompositeSequenceableLoaderFactory(), minLoadableRetryCount, DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS, eventHandler, eventListener); } @@ -356,14 +381,16 @@ public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFac long livePresentationDelayMs, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { this(null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, - minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); + new DefaultCompositeSequenceableLoaderFactory(), minLoadableRetryCount, + livePresentationDelayMs, eventHandler, eventListener); } private DashMediaSource(DashManifest manifest, Uri manifestUri, DataSource.Factory manifestDataSourceFactory, ParsingLoadable.Parser manifestParser, - DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, - long livePresentationDelayMs, Handler eventHandler, + DashChunkSource.Factory chunkSourceFactory, + CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, + int minLoadableRetryCount, long livePresentationDelayMs, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { this.manifest = manifest; this.manifestUri = manifestUri; @@ -372,6 +399,7 @@ private DashMediaSource(DashManifest manifest, Uri manifestUri, this.chunkSourceFactory = chunkSourceFactory; this.minLoadableRetryCount = minLoadableRetryCount; this.livePresentationDelayMs = livePresentationDelayMs; + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; sideloadedManifest = manifest != null; eventDispatcher = new EventDispatcher(eventHandler, eventListener); manifestUriLock = new Object(); @@ -438,7 +466,7 @@ public MediaPeriod createPeriod(MediaPeriodId periodId, Allocator allocator) { manifest.getPeriod(periodIndex).startMs); DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + periodIndex, manifest, periodIndex, chunkSourceFactory, minLoadableRetryCount, periodEventDispatcher, - elapsedRealtimeOffsetMs, loaderErrorThrower, allocator); + elapsedRealtimeOffsetMs, loaderErrorThrower, allocator, compositeSequenceableLoaderFactory); periodsById.put(mediaPeriod.id, mediaPeriod); return mediaPeriod; } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index bc2b92cfe81..b6c74d61bb5 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -20,9 +20,10 @@ 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.CompositeSequenceableLoaderFactory; 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.hls.playlist.HlsMasterPlaylist; @@ -53,23 +54,26 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper private final IdentityHashMap streamWrapperIndices; private final TimestampAdjusterProvider timestampAdjusterProvider; private final Handler continueLoadingHandler; + private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private Callback callback; private int pendingPrepareCount; private TrackGroupArray trackGroups; private HlsSampleStreamWrapper[] sampleStreamWrappers; private HlsSampleStreamWrapper[] enabledSampleStreamWrappers; - private CompositeSequenceableLoader sequenceableLoader; + private SequenceableLoader compositeSequenceableLoader; public HlsMediaPeriod(HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker, HlsDataSourceFactory dataSourceFactory, int minLoadableRetryCount, - EventDispatcher eventDispatcher, Allocator allocator) { + EventDispatcher eventDispatcher, Allocator allocator, + CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory) { this.extractorFactory = extractorFactory; this.playlistTracker = playlistTracker; this.dataSourceFactory = dataSourceFactory; this.minLoadableRetryCount = minLoadableRetryCount; this.eventDispatcher = eventDispatcher; this.allocator = allocator; + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; streamWrapperIndices = new IdentityHashMap<>(); timestampAdjusterProvider = new TimestampAdjusterProvider(); continueLoadingHandler = new Handler(); @@ -178,7 +182,9 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF // Update the local state. enabledSampleStreamWrappers = Arrays.copyOf(newEnabledSampleStreamWrappers, newEnabledSampleStreamWrapperCount); - sequenceableLoader = new CompositeSequenceableLoader(enabledSampleStreamWrappers); + compositeSequenceableLoader = + compositeSequenceableLoaderFactory.createCompositeSequenceableLoader( + enabledSampleStreamWrappers); return positionUs; } @@ -191,12 +197,12 @@ public void discardBuffer(long positionUs, boolean toKeyframe) { @Override public boolean continueLoading(long positionUs) { - return sequenceableLoader.continueLoading(positionUs); + return compositeSequenceableLoader.continueLoading(positionUs); } @Override public long getNextLoadPositionUs() { - return sequenceableLoader.getNextLoadPositionUs(); + return compositeSequenceableLoader.getNextLoadPositionUs(); } @Override @@ -206,7 +212,7 @@ public long readDiscontinuity() { @Override public long getBufferedPositionUs() { - return sequenceableLoader.getBufferedPositionUs(); + return compositeSequenceableLoader.getBufferedPositionUs(); } @Override diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 3f28981f0e5..a412b8c3e9c 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -23,8 +23,11 @@ import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; +import com.google.android.exoplayer2.source.DefaultCompositeSequenceableLoaderFactory; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; @@ -59,6 +62,8 @@ public static final class Builder { private ParsingLoadable.Parser playlistParser; private AdaptiveMediaSourceEventListener eventListener; private Handler eventHandler; + private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; + private int minLoadableRetryCount; private boolean isBuildCalled; @@ -150,6 +155,22 @@ public Builder setPlaylistParser(ParsingLoadable.Parser playlistPar return this; } + /** + * Sets the factory to create composite {@link SequenceableLoader}s for when this media source + * loads data from multiple streams (video, audio etc...). The default is an instance of + * {@link DefaultCompositeSequenceableLoaderFactory}. + * + * @param compositeSequenceableLoaderFactory A factory to create composite + * {@link SequenceableLoader}s for when this media source loads data from multiple streams + * (video, audio etc...). + * @return This builder. + */ + public Builder setCompositeSequenceableLoaderFactory( + CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory) { + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; + return this; + } + /** * Builds a new {@link HlsMediaSource} using the current parameters. *

@@ -167,8 +188,12 @@ public HlsMediaSource build() { if (playlistParser == null) { playlistParser = new HlsPlaylistParser(); } + if (compositeSequenceableLoaderFactory == null) { + compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); + } return new HlsMediaSource(manifestUri, hlsDataSourceFactory, extractorFactory, - minLoadableRetryCount, eventHandler, eventListener, playlistParser); + compositeSequenceableLoaderFactory, minLoadableRetryCount, eventHandler, eventListener, + playlistParser); } } @@ -181,6 +206,7 @@ public HlsMediaSource build() { private final HlsExtractorFactory extractorFactory; private final Uri manifestUri; private final HlsDataSourceFactory dataSourceFactory; + private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private final int minLoadableRetryCount; private final EventDispatcher eventDispatcher; private final ParsingLoadable.Parser playlistParser; @@ -242,11 +268,23 @@ public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory, HlsExtractorFactory extractorFactory, int minLoadableRetryCount, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener, ParsingLoadable.Parser playlistParser) { + this(manifestUri, dataSourceFactory, extractorFactory, + new DefaultCompositeSequenceableLoaderFactory(), minLoadableRetryCount, eventHandler, + eventListener, playlistParser); + } + + private HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory, + HlsExtractorFactory extractorFactory, + CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, + int minLoadableRetryCount, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener, + ParsingLoadable.Parser playlistParser) { this.manifestUri = manifestUri; this.dataSourceFactory = dataSourceFactory; this.extractorFactory = extractorFactory; this.minLoadableRetryCount = minLoadableRetryCount; this.playlistParser = playlistParser; + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; eventDispatcher = new EventDispatcher(eventHandler, eventListener); } @@ -268,7 +306,7 @@ public void maybeThrowSourceInfoRefreshError() throws IOException { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { Assertions.checkArgument(id.periodIndex == 0); return new HlsMediaPeriod(extractorFactory, playlistTracker, dataSourceFactory, - minLoadableRetryCount, eventDispatcher, allocator); + minLoadableRetryCount, eventDispatcher, allocator, compositeSequenceableLoaderFactory); } @Override diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java index 3c51abcd497..c079a36d625 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java @@ -19,7 +19,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; -import com.google.android.exoplayer2.source.CompositeSequenceableLoader; +import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SequenceableLoader; @@ -49,13 +49,15 @@ private final Allocator allocator; private final TrackGroupArray trackGroups; private final TrackEncryptionBox[] trackEncryptionBoxes; + private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private Callback callback; private SsManifest manifest; private ChunkSampleStream[] sampleStreams; - private CompositeSequenceableLoader sequenceableLoader; + private SequenceableLoader compositeSequenceableLoader; public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, + CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, int minLoadableRetryCount, EventDispatcher eventDispatcher, LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) { this.chunkSourceFactory = chunkSourceFactory; @@ -63,6 +65,7 @@ public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFacto this.minLoadableRetryCount = minLoadableRetryCount; this.eventDispatcher = eventDispatcher; this.allocator = allocator; + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; trackGroups = buildTrackGroups(manifest); ProtectionElement protectionElement = manifest.protectionElement; @@ -76,7 +79,8 @@ public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFacto } this.manifest = manifest; sampleStreams = newSampleStreamArray(0); - sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + compositeSequenceableLoader = + compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); } public void updateManifest(SsManifest manifest) { @@ -133,7 +137,8 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF } sampleStreams = newSampleStreamArray(sampleStreamsList.size()); sampleStreamsList.toArray(sampleStreams); - sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + compositeSequenceableLoader = + compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); return positionUs; } @@ -146,12 +151,12 @@ public void discardBuffer(long positionUs, boolean toKeyframe) { @Override public boolean continueLoading(long positionUs) { - return sequenceableLoader.continueLoading(positionUs); + return compositeSequenceableLoader.continueLoading(positionUs); } @Override public long getNextLoadPositionUs() { - return sequenceableLoader.getNextLoadPositionUs(); + return compositeSequenceableLoader.getNextLoadPositionUs(); } @Override @@ -161,7 +166,7 @@ public long readDiscontinuity() { @Override public long getBufferedPositionUs() { - return sequenceableLoader.getBufferedPositionUs(); + return compositeSequenceableLoader.getBufferedPositionUs(); } @Override diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 5a938474285..a4b601aafe9 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -26,8 +26,11 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; +import com.google.android.exoplayer2.source.DefaultCompositeSequenceableLoaderFactory; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; @@ -65,6 +68,7 @@ public static final class Builder { private ParsingLoadable.Parser manifestParser; private AdaptiveMediaSourceEventListener eventListener; private Handler eventHandler; + private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private int minLoadableRetryCount; private long livePresentationDelayMs; @@ -162,6 +166,22 @@ public Builder setManifestParser(ParsingLoadable.Parser ma return this; } + /** + * Sets the factory to create composite {@link SequenceableLoader}s for when this media source + * loads data from multiple streams (video, audio etc...). The default is an instance of + * {@link DefaultCompositeSequenceableLoaderFactory}. + * + * @param compositeSequenceableLoaderFactory A factory to create composite + * {@link SequenceableLoader}s for when this media source loads data from multiple streams + * (video, audio etc...). + * @return This builder. + */ + public Builder setCompositeSequenceableLoaderFactory( + CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory) { + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; + return this; + } + /** * Builds a new {@link SsMediaSource} using the current parameters. *

@@ -177,9 +197,12 @@ public SsMediaSource build() { if (loadableManifestUri && manifestParser == null) { manifestParser = new SsManifestParser(); } + if (compositeSequenceableLoaderFactory == null) { + compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); + } return new SsMediaSource(manifest, manifestUri, manifestDataSourceFactory, manifestParser, - chunkSourceFactory, minLoadableRetryCount, livePresentationDelayMs, eventHandler, - eventListener); + chunkSourceFactory, compositeSequenceableLoaderFactory, minLoadableRetryCount, + livePresentationDelayMs, eventHandler, eventListener); } } @@ -206,6 +229,7 @@ public SsMediaSource build() { private final Uri manifestUri; private final DataSource.Factory manifestDataSourceFactory; private final SsChunkSource.Factory chunkSourceFactory; + private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private final int minLoadableRetryCount; private final long livePresentationDelayMs; private final EventDispatcher eventDispatcher; @@ -252,7 +276,8 @@ public SsMediaSource(SsManifest manifest, SsChunkSource.Factory chunkSourceFacto public SsMediaSource(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { - this(manifest, null, null, null, chunkSourceFactory, minLoadableRetryCount, + this(manifest, null, null, null, chunkSourceFactory, + new DefaultCompositeSequenceableLoaderFactory(), minLoadableRetryCount, DEFAULT_LIVE_PRESENTATION_DELAY_MS, eventHandler, eventListener); } @@ -324,14 +349,16 @@ public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFacto long livePresentationDelayMs, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { this(null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, - minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); + new DefaultCompositeSequenceableLoaderFactory(), minLoadableRetryCount, + livePresentationDelayMs, eventHandler, eventListener); } private SsMediaSource(SsManifest manifest, Uri manifestUri, DataSource.Factory manifestDataSourceFactory, ParsingLoadable.Parser manifestParser, - SsChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, - long livePresentationDelayMs, Handler eventHandler, + SsChunkSource.Factory chunkSourceFactory, + CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, + int minLoadableRetryCount, long livePresentationDelayMs, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { Assertions.checkState(manifest == null || !manifest.isLive); this.manifest = manifest; @@ -341,6 +368,7 @@ private SsMediaSource(SsManifest manifest, Uri manifestUri, this.manifestDataSourceFactory = manifestDataSourceFactory; this.manifestParser = manifestParser; this.chunkSourceFactory = chunkSourceFactory; + this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; this.minLoadableRetryCount = minLoadableRetryCount; this.livePresentationDelayMs = livePresentationDelayMs; this.eventDispatcher = new EventDispatcher(eventHandler, eventListener); @@ -372,8 +400,9 @@ public void maybeThrowSourceInfoRefreshError() throws IOException { @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { Assertions.checkArgument(id.periodIndex == 0); - SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount, - eventDispatcher, manifestLoaderErrorThrower, allocator); + SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, + compositeSequenceableLoaderFactory, minLoadableRetryCount, eventDispatcher, + manifestLoaderErrorThrower, allocator); mediaPeriods.add(period); return period; }