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 1675ac066b..1000a38820 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 @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTrack import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser; +import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParserFactory; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; @@ -171,7 +172,11 @@ public final class HlsMediaSource extends BaseMediaSource * @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists. * @return This factory, for convenience. * @throws IllegalStateException If one of the {@code create} methods has already been called. + * @deprecated Use {@link #setPlaylistTracker(HlsPlaylistTracker)} instead. Using this method + * prevents support for attributes that are carried over from the master playlist to the + * media playlists. */ + @Deprecated public Factory setPlaylistParser(ParsingLoadable.Parser playlistParser) { Assertions.checkState(!isCreateCalled); Assertions.checkState(playlistTracker == null, "A playlist tracker has already been set."); @@ -239,11 +244,15 @@ public final class HlsMediaSource extends BaseMediaSource public HlsMediaSource createMediaSource(Uri playlistUri) { isCreateCalled = true; if (playlistTracker == null) { - playlistTracker = - new DefaultHlsPlaylistTracker( - hlsDataSourceFactory, - loadErrorHandlingPolicy, - playlistParser != null ? playlistParser : new HlsPlaylistParser()); + if (playlistParser == null) { + playlistTracker = + new DefaultHlsPlaylistTracker( + hlsDataSourceFactory, loadErrorHandlingPolicy, HlsPlaylistParserFactory.DEFAULT); + } else { + playlistTracker = + new DefaultHlsPlaylistTracker( + hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParser); + } } return new HlsMediaSource( playlistUri, diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java index 896950e6e5..a61c8116ac 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.hls.playlist; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; @@ -47,18 +48,19 @@ public final class DefaultHlsPlaylistTracker private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 3.5; private final HlsDataSourceFactory dataSourceFactory; - private final ParsingLoadable.Parser playlistParser; + private final HlsPlaylistParserFactory playlistParserFactory; private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final IdentityHashMap playlistBundles; private final List listeners; - private EventDispatcher eventDispatcher; - private Loader initialPlaylistLoader; - private Handler playlistRefreshHandler; - private PrimaryPlaylistListener primaryPlaylistListener; - private HlsMasterPlaylist masterPlaylist; - private HlsUrl primaryHlsUrl; - private HlsMediaPlaylist primaryUrlSnapshot; + private @Nullable ParsingLoadable.Parser mediaPlaylistParser; + private @Nullable EventDispatcher eventDispatcher; + private @Nullable Loader initialPlaylistLoader; + private @Nullable Handler playlistRefreshHandler; + private @Nullable PrimaryPlaylistListener primaryPlaylistListener; + private @Nullable HlsMasterPlaylist masterPlaylist; + private @Nullable HlsUrl primaryHlsUrl; + private @Nullable HlsMediaPlaylist primaryUrlSnapshot; private boolean isLive; private long initialStartTimeUs; @@ -66,13 +68,30 @@ public final class DefaultHlsPlaylistTracker * @param dataSourceFactory A factory for {@link DataSource} instances. * @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}. * @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists. + * @deprecated Use {@link #DefaultHlsPlaylistTracker(HlsDataSourceFactory, + * LoadErrorHandlingPolicy, HlsPlaylistParserFactory)} instead. Using this constructor + * prevents support for attributes that are carried over from the master playlist to the media + * playlists. */ + @Deprecated public DefaultHlsPlaylistTracker( HlsDataSourceFactory dataSourceFactory, LoadErrorHandlingPolicy loadErrorHandlingPolicy, ParsingLoadable.Parser playlistParser) { + this(dataSourceFactory, loadErrorHandlingPolicy, createFixedFactory(playlistParser)); + } + + /** + * @param dataSourceFactory A factory for {@link DataSource} instances. + * @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}. + * @param playlistParserFactory An {@link HlsPlaylistParserFactory}. + */ + public DefaultHlsPlaylistTracker( + HlsDataSourceFactory dataSourceFactory, + LoadErrorHandlingPolicy loadErrorHandlingPolicy, + HlsPlaylistParserFactory playlistParserFactory) { this.dataSourceFactory = dataSourceFactory; - this.playlistParser = playlistParser; + this.playlistParserFactory = playlistParserFactory; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; listeners = new ArrayList<>(); playlistBundles = new IdentityHashMap<>(); @@ -94,7 +113,7 @@ public final class DefaultHlsPlaylistTracker dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST), initialPlaylistUri, C.DATA_TYPE_MANIFEST, - playlistParser); + playlistParserFactory.createPlaylistParser()); Assertions.checkState(initialPlaylistLoader == null); initialPlaylistLoader = new Loader("DefaultHlsPlaylistTracker:MasterPlaylist"); long elapsedRealtime = @@ -198,6 +217,7 @@ public final class DefaultHlsPlaylistTracker masterPlaylist = (HlsMasterPlaylist) result; } this.masterPlaylist = masterPlaylist; + mediaPlaylistParser = playlistParserFactory.createPlaylistParser(masterPlaylist); primaryHlsUrl = masterPlaylist.variants.get(0); ArrayList urls = new ArrayList<>(); urls.addAll(masterPlaylist.variants); @@ -420,7 +440,7 @@ public final class DefaultHlsPlaylistTracker dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST), UriUtil.resolveToUri(masterPlaylist.baseUri, playlistUrl.url), C.DATA_TYPE_MANIFEST, - playlistParser); + mediaPlaylistParser); } public HlsMediaPlaylist getPlaylistSnapshot() { @@ -569,9 +589,6 @@ public final class DefaultHlsPlaylistTracker } private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist, long loadDurationMs) { - // Update the loaded playlist with any inheritable information from the master playlist. - loadedPlaylist = loadedPlaylist.copyWithMasterPlaylistInfo(masterPlaylist); - HlsMediaPlaylist oldPlaylist = playlistSnapshot; long currentTimeMs = SystemClock.elapsedRealtime(); lastSnapshotLoadMs = currentTimeMs; @@ -630,4 +647,26 @@ public final class DefaultHlsPlaylistTracker return primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl(); } } + + /** + * Creates a factory which always returns the given playlist parser. + * + * @param playlistParser The parser to return. + * @return A factory which always returns the given playlist parser. + */ + private static HlsPlaylistParserFactory createFixedFactory( + ParsingLoadable.Parser playlistParser) { + return new HlsPlaylistParserFactory() { + @Override + public ParsingLoadable.Parser createPlaylistParser() { + return playlistParser; + } + + @Override + public ParsingLoadable.Parser createPlaylistParser( + HlsMasterPlaylist masterPlaylist) { + return playlistParser; + } + }; + } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java index 6b73ad4195..c45c2dd547 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -25,6 +25,18 @@ import java.util.List; /** Represents an HLS master playlist. */ public final class HlsMasterPlaylist extends HlsPlaylist { + /** Represents an empty master playlist, from which no attributes can be inherited. */ + public static final HlsMasterPlaylist EMPTY = + new HlsMasterPlaylist( + /* baseUri= */ "", + /* tags= */ Collections.emptyList(), + /* variants= */ Collections.emptyList(), + /* audios= */ Collections.emptyList(), + /* subtitles= */ Collections.emptyList(), + /* muxedAudioFormat= */ null, + /* muxedCaptionFormats= */ Collections.emptyList(), + /* hasIndependentSegments= */ false); + public static final int GROUP_INDEX_VARIANT = 0; public static final int GROUP_INDEX_AUDIO = 1; public static final int GROUP_INDEX_SUBTITLE = 2; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index 4278ae6825..841c13f953 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -336,40 +336,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist { segments); } - /** - * Returns a playlist identical to this one, except for adding any inheritable attributes from the - * provided {@link HlsMasterPlaylist}. - * - *

The inheritable attributes are: - * - *

    - *
  • {@link #hasIndependentSegments}. - *
- * - * @return An identical playlist including the inheritable attributes from {@code masterPlaylist}. - */ - public HlsMediaPlaylist copyWithMasterPlaylistInfo(HlsMasterPlaylist masterPlaylist) { - if (hasIndependentSegments || !masterPlaylist.hasIndependentSegments) { - return this; - } - return new HlsMediaPlaylist( - playlistType, - baseUri, - tags, - startOffsetUs, - startTimeUs, - hasDiscontinuitySequence, - discontinuitySequence, - mediaSequence, - version, - targetDurationUs, - hasIndependentSegments || masterPlaylist.hasIndependentSegments, - hasEndTag, - hasProgramDateTime, - protectionSchemes, - segments); - } - /** * Returns a playlist identical to this one except that an end tag is added. If an end tag is * already present then the playlist will return itself. diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 8473fd1f17..e287b5220e 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -148,6 +148,26 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser segments = new ArrayList<>(); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParserFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParserFactory.java new file mode 100644 index 0000000000..717825c168 --- /dev/null +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParserFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 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.hls.playlist; + +import com.google.android.exoplayer2.upstream.ParsingLoadable; + +/** Factory for {@link HlsPlaylist} parsers. */ +public interface HlsPlaylistParserFactory { + + HlsPlaylistParserFactory DEFAULT = + new HlsPlaylistParserFactory() { + @Override + public ParsingLoadable.Parser createPlaylistParser() { + return new HlsPlaylistParser(); + } + + @Override + public ParsingLoadable.Parser createPlaylistParser( + HlsMasterPlaylist masterPlaylist) { + return new HlsPlaylistParser(masterPlaylist); + } + }; + + /** + * Returns a stand-alone playlist parser. Playlists parsed by the returned parser do not inherit + * any attributes from other playlists. + */ + ParsingLoadable.Parser createPlaylistParser(); + + /** + * Returns a playlist parser for playlists that were referenced by the given {@link + * HlsMasterPlaylist}. Returned {@link HlsMediaPlaylist} instances may inherit attributes from + * {@code masterPlaylist}. + * + * @param masterPlaylist The master playlist that referenced any parsed media playlists. + * @return A parser for HLS playlists. + */ + ParsingLoadable.Parser createPlaylistParser(HlsMasterPlaylist masterPlaylist); +} diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java index fd4180d439..6e71aebb74 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java @@ -383,10 +383,11 @@ public class HlsMediaPlaylistParserTest { + "#EXTINF:5.005,\n" + "02/00/47.ts\n"; InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); - HlsMediaPlaylist playlist = + HlsMediaPlaylist standalonePlaylist = (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); - assertThat(playlist.hasIndependentSegments).isFalse(); + assertThat(standalonePlaylist.hasIndependentSegments).isFalse(); + inputStream.reset(); HlsMasterPlaylist masterPlaylist = new HlsMasterPlaylist( /* baseUri= */ "https://example.com/", @@ -397,7 +398,8 @@ public class HlsMediaPlaylistParserTest { /* muxedAudioFormat= */ null, /* muxedCaptionFormats= */ null, /* hasIndependentSegments= */ true); - - assertThat(playlist.copyWithMasterPlaylistInfo(masterPlaylist).hasIndependentSegments).isTrue(); + HlsMediaPlaylist playlistWithInheritance = + (HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream); + assertThat(playlistWithInheritance.hasIndependentSegments).isTrue(); } }