Commit 4530944e by aquilescanta Committed by Oliver Woodman

Rework HlsPlaylist attribute inheritance

The reason for the change is that variable substititution requires
master playlist variable definitions at the moment of parsing.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=208997963
parent 7a34869f
...@@ -36,6 +36,7 @@ import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTrack ...@@ -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.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; 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.HlsPlaylistParser;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParserFactory;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
...@@ -171,7 +172,11 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -171,7 +172,11 @@ public final class HlsMediaSource extends BaseMediaSource
* @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists. * @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists.
* @return This factory, for convenience. * @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called. * @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<HlsPlaylist> playlistParser) { public Factory setPlaylistParser(ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
Assertions.checkState(!isCreateCalled); Assertions.checkState(!isCreateCalled);
Assertions.checkState(playlistTracker == null, "A playlist tracker has already been set."); Assertions.checkState(playlistTracker == null, "A playlist tracker has already been set.");
...@@ -239,11 +244,15 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -239,11 +244,15 @@ public final class HlsMediaSource extends BaseMediaSource
public HlsMediaSource createMediaSource(Uri playlistUri) { public HlsMediaSource createMediaSource(Uri playlistUri) {
isCreateCalled = true; isCreateCalled = true;
if (playlistTracker == null) { if (playlistTracker == null) {
if (playlistParser == null) {
playlistTracker = playlistTracker =
new DefaultHlsPlaylistTracker( new DefaultHlsPlaylistTracker(
hlsDataSourceFactory, hlsDataSourceFactory, loadErrorHandlingPolicy, HlsPlaylistParserFactory.DEFAULT);
loadErrorHandlingPolicy, } else {
playlistParser != null ? playlistParser : new HlsPlaylistParser()); playlistTracker =
new DefaultHlsPlaylistTracker(
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParser);
}
} }
return new HlsMediaSource( return new HlsMediaSource(
playlistUri, playlistUri,
......
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.hls.playlist; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.hls.playlist;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
...@@ -47,18 +48,19 @@ public final class DefaultHlsPlaylistTracker ...@@ -47,18 +48,19 @@ public final class DefaultHlsPlaylistTracker
private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 3.5; private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 3.5;
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final ParsingLoadable.Parser<HlsPlaylist> playlistParser; private final HlsPlaylistParserFactory playlistParserFactory;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles; private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles;
private final List<PlaylistEventListener> listeners; private final List<PlaylistEventListener> listeners;
private EventDispatcher eventDispatcher; private @Nullable ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser;
private Loader initialPlaylistLoader; private @Nullable EventDispatcher eventDispatcher;
private Handler playlistRefreshHandler; private @Nullable Loader initialPlaylistLoader;
private PrimaryPlaylistListener primaryPlaylistListener; private @Nullable Handler playlistRefreshHandler;
private HlsMasterPlaylist masterPlaylist; private @Nullable PrimaryPlaylistListener primaryPlaylistListener;
private HlsUrl primaryHlsUrl; private @Nullable HlsMasterPlaylist masterPlaylist;
private HlsMediaPlaylist primaryUrlSnapshot; private @Nullable HlsUrl primaryHlsUrl;
private @Nullable HlsMediaPlaylist primaryUrlSnapshot;
private boolean isLive; private boolean isLive;
private long initialStartTimeUs; private long initialStartTimeUs;
...@@ -66,13 +68,30 @@ public final class DefaultHlsPlaylistTracker ...@@ -66,13 +68,30 @@ public final class DefaultHlsPlaylistTracker
* @param dataSourceFactory A factory for {@link DataSource} instances. * @param dataSourceFactory A factory for {@link DataSource} instances.
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}. * @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}.
* @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists. * @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( public DefaultHlsPlaylistTracker(
HlsDataSourceFactory dataSourceFactory, HlsDataSourceFactory dataSourceFactory,
LoadErrorHandlingPolicy loadErrorHandlingPolicy, LoadErrorHandlingPolicy loadErrorHandlingPolicy,
ParsingLoadable.Parser<HlsPlaylist> playlistParser) { ParsingLoadable.Parser<HlsPlaylist> 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.dataSourceFactory = dataSourceFactory;
this.playlistParser = playlistParser; this.playlistParserFactory = playlistParserFactory;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
listeners = new ArrayList<>(); listeners = new ArrayList<>();
playlistBundles = new IdentityHashMap<>(); playlistBundles = new IdentityHashMap<>();
...@@ -94,7 +113,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -94,7 +113,7 @@ public final class DefaultHlsPlaylistTracker
dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST), dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST),
initialPlaylistUri, initialPlaylistUri,
C.DATA_TYPE_MANIFEST, C.DATA_TYPE_MANIFEST,
playlistParser); playlistParserFactory.createPlaylistParser());
Assertions.checkState(initialPlaylistLoader == null); Assertions.checkState(initialPlaylistLoader == null);
initialPlaylistLoader = new Loader("DefaultHlsPlaylistTracker:MasterPlaylist"); initialPlaylistLoader = new Loader("DefaultHlsPlaylistTracker:MasterPlaylist");
long elapsedRealtime = long elapsedRealtime =
...@@ -198,6 +217,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -198,6 +217,7 @@ public final class DefaultHlsPlaylistTracker
masterPlaylist = (HlsMasterPlaylist) result; masterPlaylist = (HlsMasterPlaylist) result;
} }
this.masterPlaylist = masterPlaylist; this.masterPlaylist = masterPlaylist;
mediaPlaylistParser = playlistParserFactory.createPlaylistParser(masterPlaylist);
primaryHlsUrl = masterPlaylist.variants.get(0); primaryHlsUrl = masterPlaylist.variants.get(0);
ArrayList<HlsUrl> urls = new ArrayList<>(); ArrayList<HlsUrl> urls = new ArrayList<>();
urls.addAll(masterPlaylist.variants); urls.addAll(masterPlaylist.variants);
...@@ -420,7 +440,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -420,7 +440,7 @@ public final class DefaultHlsPlaylistTracker
dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST), dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST),
UriUtil.resolveToUri(masterPlaylist.baseUri, playlistUrl.url), UriUtil.resolveToUri(masterPlaylist.baseUri, playlistUrl.url),
C.DATA_TYPE_MANIFEST, C.DATA_TYPE_MANIFEST,
playlistParser); mediaPlaylistParser);
} }
public HlsMediaPlaylist getPlaylistSnapshot() { public HlsMediaPlaylist getPlaylistSnapshot() {
...@@ -569,9 +589,6 @@ public final class DefaultHlsPlaylistTracker ...@@ -569,9 +589,6 @@ public final class DefaultHlsPlaylistTracker
} }
private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist, long loadDurationMs) { 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; HlsMediaPlaylist oldPlaylist = playlistSnapshot;
long currentTimeMs = SystemClock.elapsedRealtime(); long currentTimeMs = SystemClock.elapsedRealtime();
lastSnapshotLoadMs = currentTimeMs; lastSnapshotLoadMs = currentTimeMs;
...@@ -630,4 +647,26 @@ public final class DefaultHlsPlaylistTracker ...@@ -630,4 +647,26 @@ public final class DefaultHlsPlaylistTracker
return primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl(); 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<HlsPlaylist> playlistParser) {
return new HlsPlaylistParserFactory() {
@Override
public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser() {
return playlistParser;
}
@Override
public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(
HlsMasterPlaylist masterPlaylist) {
return playlistParser;
}
};
}
} }
...@@ -25,6 +25,18 @@ import java.util.List; ...@@ -25,6 +25,18 @@ import java.util.List;
/** Represents an HLS master playlist. */ /** Represents an HLS master playlist. */
public final class HlsMasterPlaylist extends HlsPlaylist { 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_VARIANT = 0;
public static final int GROUP_INDEX_AUDIO = 1; public static final int GROUP_INDEX_AUDIO = 1;
public static final int GROUP_INDEX_SUBTITLE = 2; public static final int GROUP_INDEX_SUBTITLE = 2;
......
...@@ -337,40 +337,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -337,40 +337,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
} }
/** /**
* Returns a playlist identical to this one, except for adding any inheritable attributes from the
* provided {@link HlsMasterPlaylist}.
*
* <p>The inheritable attributes are:
*
* <ul>
* <li>{@link #hasIndependentSegments}.
* </ul>
*
* @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 * 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. * already present then the playlist will return itself.
*/ */
......
...@@ -148,6 +148,26 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -148,6 +148,26 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final Pattern REGEX_DEFAULT = compileBooleanAttrPattern("DEFAULT"); private static final Pattern REGEX_DEFAULT = compileBooleanAttrPattern("DEFAULT");
private static final Pattern REGEX_FORCED = compileBooleanAttrPattern("FORCED"); private static final Pattern REGEX_FORCED = compileBooleanAttrPattern("FORCED");
private final HlsMasterPlaylist masterPlaylist;
/**
* Creates an instance where media playlists are parsed without inheriting attributes from a
* master playlist.
*/
public HlsPlaylistParser() {
this(HlsMasterPlaylist.EMPTY);
}
/**
* Creates an instance where parsed media playlists inherit attributes from the given master
* playlist.
*
* @param masterPlaylist The master playlist from which media playlists will inherit attributes.
*/
public HlsPlaylistParser(HlsMasterPlaylist masterPlaylist) {
this.masterPlaylist = masterPlaylist;
}
@Override @Override
public HlsPlaylist parse(Uri uri, InputStream inputStream) throws IOException { public HlsPlaylist parse(Uri uri, InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
...@@ -174,7 +194,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -174,7 +194,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|| line.equals(TAG_DISCONTINUITY_SEQUENCE) || line.equals(TAG_DISCONTINUITY_SEQUENCE)
|| line.equals(TAG_ENDLIST)) { || line.equals(TAG_ENDLIST)) {
extraLines.add(line); extraLines.add(line);
return parseMediaPlaylist(new LineIterator(extraLines, reader), uri.toString()); return parseMediaPlaylist(
masterPlaylist, new LineIterator(extraLines, reader), uri.toString());
} else { } else {
extraLines.add(line); extraLines.add(line);
} }
...@@ -402,14 +423,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -402,14 +423,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
return flags; return flags;
} }
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) private static HlsMediaPlaylist parseMediaPlaylist(
throws IOException { HlsMasterPlaylist masterPlaylist, LineIterator iterator, String baseUri) throws IOException {
@HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN; @HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
long startOffsetUs = C.TIME_UNSET; long startOffsetUs = C.TIME_UNSET;
long mediaSequence = 0; long mediaSequence = 0;
int version = 1; // Default version == 1. int version = 1; // Default version == 1.
long targetDurationUs = C.TIME_UNSET; long targetDurationUs = C.TIME_UNSET;
boolean hasIndependentSegmentsTag = false; boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments;
boolean hasEndTag = false; boolean hasEndTag = false;
Segment initializationSegment = null; Segment initializationSegment = null;
List<Segment> segments = new ArrayList<>(); List<Segment> segments = new ArrayList<>();
......
/*
* 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<HlsPlaylist> createPlaylistParser() {
return new HlsPlaylistParser();
}
@Override
public ParsingLoadable.Parser<HlsPlaylist> 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<HlsPlaylist> 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<HlsPlaylist> createPlaylistParser(HlsMasterPlaylist masterPlaylist);
}
...@@ -383,10 +383,11 @@ public class HlsMediaPlaylistParserTest { ...@@ -383,10 +383,11 @@ public class HlsMediaPlaylistParserTest {
+ "#EXTINF:5.005,\n" + "#EXTINF:5.005,\n"
+ "02/00/47.ts\n"; + "02/00/47.ts\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsMediaPlaylist playlist = HlsMediaPlaylist standalonePlaylist =
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
assertThat(playlist.hasIndependentSegments).isFalse(); assertThat(standalonePlaylist.hasIndependentSegments).isFalse();
inputStream.reset();
HlsMasterPlaylist masterPlaylist = HlsMasterPlaylist masterPlaylist =
new HlsMasterPlaylist( new HlsMasterPlaylist(
/* baseUri= */ "https://example.com/", /* baseUri= */ "https://example.com/",
...@@ -397,7 +398,8 @@ public class HlsMediaPlaylistParserTest { ...@@ -397,7 +398,8 @@ public class HlsMediaPlaylistParserTest {
/* muxedAudioFormat= */ null, /* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ null, /* muxedCaptionFormats= */ null,
/* hasIndependentSegments= */ true); /* hasIndependentSegments= */ true);
HlsMediaPlaylist playlistWithInheritance =
assertThat(playlist.copyWithMasterPlaylistInfo(masterPlaylist).hasIndependentSegments).isTrue(); (HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
assertThat(playlistWithInheritance.hasIndependentSegments).isTrue();
} }
} }
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