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
This commit is contained in:
aquilescanta 2018-08-16 09:42:45 -07:00 committed by Oliver Woodman
parent 7a34869f9a
commit 4530944ed7
7 changed files with 162 additions and 61 deletions

View file

@ -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<HlsPlaylist> 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,

View file

@ -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<HlsPlaylist> playlistParser;
private final HlsPlaylistParserFactory playlistParserFactory;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles;
private final List<PlaylistEventListener> 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<HlsPlaylist> 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<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.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<HlsUrl> 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<HlsPlaylist> playlistParser) {
return new HlsPlaylistParserFactory() {
@Override
public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser() {
return playlistParser;
}
@Override
public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(
HlsMasterPlaylist masterPlaylist) {
return playlistParser;
}
};
}
}

View file

@ -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;

View file

@ -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}.
*
* <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
* already present then the playlist will return itself.

View file

@ -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_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
public HlsPlaylist parse(Uri uri, InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
@ -174,7 +194,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|| line.equals(TAG_DISCONTINUITY_SEQUENCE)
|| line.equals(TAG_ENDLIST)) {
extraLines.add(line);
return parseMediaPlaylist(new LineIterator(extraLines, reader), uri.toString());
return parseMediaPlaylist(
masterPlaylist, new LineIterator(extraLines, reader), uri.toString());
} else {
extraLines.add(line);
}
@ -402,14 +423,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
return flags;
}
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
throws IOException {
private static HlsMediaPlaylist parseMediaPlaylist(
HlsMasterPlaylist masterPlaylist, LineIterator iterator, String baseUri) throws IOException {
@HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
long startOffsetUs = C.TIME_UNSET;
long mediaSequence = 0;
int version = 1; // Default version == 1.
long targetDurationUs = C.TIME_UNSET;
boolean hasIndependentSegmentsTag = false;
boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments;
boolean hasEndTag = false;
Segment initializationSegment = null;
List<Segment> segments = new ArrayList<>();

View file

@ -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<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);
}

View file

@ -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();
}
}