Commit 99f89132 by olly Committed by Toni

Remove HlsUrl and introduce HlsMasterPlaylist.mediaPlaylistUrls

- This removes the need for Variant and Rendition to have a common
  base class, allowing the url field to be marked as @Nullable in
  Rendition but not in Variant.
- The addition of mediaPlaylistUrls is needed for the new StreamKey
  indexing for HLS. It's also convenient in a couple of places (e.g.
  HlsDownloader), where a list of all media playlist URLs is needed.
- Lots of places where HlsUrl was passed only needed the actual
  URL (not the Format, which is the other piece of HlsUrl). Passing
  just the URL is a little simpler, and resolves some of the naming
  confusion.

Issue: #5596
Issue: #2600
PiperOrigin-RevId: 240970466
parent 2623b4b3
......@@ -27,7 +27,6 @@ import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.metadata.id3.PrivFrame;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
......@@ -52,9 +51,10 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param extractorFactory A {@link HlsExtractorFactory} from which the HLS media chunk extractor
* is obtained.
* @param dataSource The source from which the data should be loaded.
* @param format The chunk format.
* @param startOfPlaylistInPeriodUs The position of the playlist in the period in microseconds.
* @param mediaPlaylist The media playlist from which this chunk was obtained.
* @param hlsUrl The url of the playlist from which this chunk was obtained.
* @param playlistUrl The url of the playlist from which this chunk was obtained.
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist.
* @param trackSelectionReason See {@link #trackSelectionReason}.
......@@ -70,10 +70,11 @@ import java.util.concurrent.atomic.AtomicInteger;
public static HlsMediaChunk createInstance(
HlsExtractorFactory extractorFactory,
DataSource dataSource,
Format format,
long startOfPlaylistInPeriodUs,
HlsMediaPlaylist mediaPlaylist,
int segmentIndexInPlaylist,
HlsUrl hlsUrl,
Uri playlistUrl,
@Nullable List<Format> muxedCaptionFormats,
int trackSelectionReason,
@Nullable Object trackSelectionData,
......@@ -126,7 +127,8 @@ import java.util.concurrent.atomic.AtomicInteger;
if (previousChunk != null) {
id3Decoder = previousChunk.id3Decoder;
scratchId3Data = previousChunk.scratchId3Data;
shouldSpliceIn = previousChunk.hlsUrl != hlsUrl || !previousChunk.loadCompleted;
shouldSpliceIn =
!playlistUrl.equals(previousChunk.playlistUrl) || !previousChunk.loadCompleted;
previousExtractor =
previousChunk.isExtractorReusable
&& previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber
......@@ -143,11 +145,12 @@ import java.util.concurrent.atomic.AtomicInteger;
extractorFactory,
mediaDataSource,
dataSpec,
format,
mediaSegmentEncrypted,
initDataSource,
initDataSpec,
initSegmentEncrypted,
hlsUrl,
playlistUrl,
muxedCaptionFormats,
trackSelectionReason,
trackSelectionData,
......@@ -180,10 +183,8 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public final int discontinuitySequenceNumber;
/**
* The url of the playlist from which this chunk was obtained.
*/
public final HlsUrl hlsUrl;
/** The url of the playlist from which this chunk was obtained. */
public final Uri playlistUrl;
@Nullable private final DataSource initDataSource;
@Nullable private final DataSpec initDataSpec;
......@@ -214,11 +215,12 @@ import java.util.concurrent.atomic.AtomicInteger;
HlsExtractorFactory extractorFactory,
DataSource mediaDataSource,
DataSpec dataSpec,
Format format,
boolean mediaSegmentEncrypted,
DataSource initDataSource,
@Nullable DataSpec initDataSpec,
boolean initSegmentEncrypted,
HlsUrl hlsUrl,
Uri playlistUrl,
@Nullable List<Format> muxedCaptionFormats,
int trackSelectionReason,
Object trackSelectionData,
......@@ -237,7 +239,7 @@ import java.util.concurrent.atomic.AtomicInteger;
super(
mediaDataSource,
dataSpec,
hlsUrl.format,
format,
trackSelectionReason,
trackSelectionData,
startTimeUs,
......@@ -248,7 +250,7 @@ import java.util.concurrent.atomic.AtomicInteger;
this.initDataSource = initDataSource;
this.initDataSpec = initDataSpec;
this.initSegmentEncrypted = initSegmentEncrypted;
this.hlsUrl = hlsUrl;
this.playlistUrl = playlistUrl;
this.isMasterTimestampSource = isMasterTimestampSource;
this.timestampAdjuster = timestampAdjuster;
this.hasGapTag = hasGapTag;
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.hls;
import android.net.Uri;
import android.os.Handler;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
......@@ -37,8 +38,6 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
......@@ -79,8 +78,7 @@ import java.util.Map;
* Called to schedule a {@link #continueLoading(long)} call when the playlist referred by the
* given url changes.
*/
void onPlaylistRefreshRequired(HlsMasterPlaylist.HlsUrl playlistUrl);
void onPlaylistRefreshRequired(Uri playlistUrl);
}
private static final String TAG = "HlsSampleStreamWrapper";
......@@ -451,8 +449,8 @@ import java.util.Map;
chunkSource.setIsTimestampMaster(isTimestampMaster);
}
public boolean onPlaylistError(HlsUrl url, long blacklistDurationMs) {
return chunkSource.onPlaylistError(url, blacklistDurationMs);
public boolean onPlaylistError(Uri playlistUrl, long blacklistDurationMs) {
return chunkSource.onPlaylistError(playlistUrl, blacklistDurationMs);
}
// SampleStream implementation.
......@@ -590,7 +588,7 @@ import java.util.Map;
chunkSource.getNextChunk(positionUs, loadPositionUs, chunkQueue, nextChunkHolder);
boolean endOfStream = nextChunkHolder.endOfStream;
Chunk loadable = nextChunkHolder.chunk;
HlsMasterPlaylist.HlsUrl playlistToLoad = nextChunkHolder.playlist;
Uri playlistUrlToLoad = nextChunkHolder.playlistUrl;
nextChunkHolder.clear();
if (endOfStream) {
......@@ -600,8 +598,8 @@ import java.util.Map;
}
if (loadable == null) {
if (playlistToLoad != null) {
callback.onPlaylistRefreshRequired(playlistToLoad);
if (playlistUrlToLoad != null) {
callback.onPlaylistRefreshRequired(playlistUrlToLoad);
}
return false;
}
......
......@@ -21,7 +21,6 @@ import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.offline.SegmentDownloader;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
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;
......@@ -81,9 +80,7 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
ArrayList<DataSpec> mediaPlaylistDataSpecs = new ArrayList<>();
if (playlist instanceof HlsMasterPlaylist) {
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
addMediaPlaylistDataSpecs(masterPlaylist.variants, mediaPlaylistDataSpecs);
addMediaPlaylistDataSpecs(masterPlaylist.audios, mediaPlaylistDataSpecs);
addMediaPlaylistDataSpecs(masterPlaylist.subtitles, mediaPlaylistDataSpecs);
addMediaPlaylistDataSpecs(masterPlaylist.mediaPlaylistUrls, mediaPlaylistDataSpecs);
} else {
mediaPlaylistDataSpecs.add(
SegmentDownloader.getCompressibleDataSpec(Uri.parse(playlist.baseUri)));
......@@ -118,9 +115,9 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
return segments;
}
private void addMediaPlaylistDataSpecs(List<? extends HlsUrl> urls, List<DataSpec> out) {
for (int i = 0; i < urls.size(); i++) {
out.add(SegmentDownloader.getCompressibleDataSpec(urls.get(i).url));
private void addMediaPlaylistDataSpecs(List<Uri> mediaPlaylistUrls, List<DataSpec> out) {
for (int i = 0; i < mediaPlaylistUrls.size(); i++) {
out.add(SegmentDownloader.getCompressibleDataSpec(mediaPlaylistUrls.get(i)));
}
}
......
......@@ -50,28 +50,14 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
public static final int GROUP_INDEX_AUDIO = 1;
public static final int GROUP_INDEX_SUBTITLE = 2;
/** Represents a url in an HLS master playlist. */
public abstract static class HlsUrl {
/** A variant (i.e. an #EXT-X-STREAM-INF tag) in a master playlist. */
public static final class Variant {
/** The http url from which the media playlist can be obtained. */
/** The variant's url. */
public final Uri url;
/**
* Format information associated with the HLS url.
*/
public final Format format;
/**
* @param url See {@link #url}.
* @param format See {@link #format}.
*/
public HlsUrl(Uri url, Format format) {
this.url = url;
this.format = format;
}
}
/** A variant in a master playlist. */
public static final class Variant extends HlsUrl {
/** Format information associated with this variant. */
public final Format format;
/** The video rendition group referenced by this variant, or {@code null}. */
@Nullable public final String videoGroupId;
......@@ -100,7 +86,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
@Nullable String audioGroupId,
@Nullable String subtitleGroupId,
@Nullable String captionGroupId) {
super(url, format);
this.url = url;
this.format = format;
this.videoGroupId = videoGroupId;
this.audioGroupId = audioGroupId;
this.subtitleGroupId = subtitleGroupId;
......@@ -135,8 +122,14 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
}
}
/** A rendition in a master playlist. */
public static final class Rendition extends HlsUrl {
/** A rendition (i.e. an #EXT-X-MEDIA tag) in a master playlist. */
public static final class Rendition {
/** The rendition's url, or null if the tag does not have a URI attribute. */
@Nullable public final Uri url;
/** Format information associated with this rendition. */
public final Format format;
/** The group to which this rendition belongs. */
public final String groupId;
......@@ -150,14 +143,17 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
* @param groupId See {@link #groupId}.
* @param name See {@link #name}.
*/
public Rendition(Uri url, Format format, String groupId, String name) {
super(url, format);
public Rendition(@Nullable Uri url, Format format, String groupId, String name) {
this.url = url;
this.format = format;
this.groupId = groupId;
this.name = name;
}
}
/** All of the media playlist URLs referenced by the playlist. */
public final List<Uri> mediaPlaylistUrls;
/** The variants declared by the playlist. */
public final List<Variant> variants;
/** The video renditions declared by the playlist. */
......@@ -213,6 +209,9 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
Map<String, String> variableDefinitions,
List<DrmInitData> sessionKeyDrmInitData) {
super(baseUri, tags, hasIndependentSegments);
this.mediaPlaylistUrls =
Collections.unmodifiableList(
getMediaPlaylistUrls(variants, videos, audios, subtitles, closedCaptions));
this.variants = Collections.unmodifiableList(variants);
this.videos = Collections.unmodifiableList(videos);
this.audios = Collections.unmodifiableList(audios);
......@@ -268,7 +267,36 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
/* sessionKeyDrmInitData= */ Collections.emptyList());
}
private static <T extends HlsUrl> List<T> copyStreams(
private static List<Uri> getMediaPlaylistUrls(
List<Variant> variants,
List<Rendition> videos,
List<Rendition> audios,
List<Rendition> subtitles,
List<Rendition> closedCaptions) {
ArrayList<Uri> mediaPlaylistUrls = new ArrayList<>();
for (int i = 0; i < variants.size(); i++) {
Uri uri = variants.get(i).url;
if (!mediaPlaylistUrls.contains(uri)) {
mediaPlaylistUrls.add(uri);
}
}
addMediaPlaylistUrls(videos, mediaPlaylistUrls);
addMediaPlaylistUrls(audios, mediaPlaylistUrls);
addMediaPlaylistUrls(subtitles, mediaPlaylistUrls);
addMediaPlaylistUrls(closedCaptions, mediaPlaylistUrls);
return mediaPlaylistUrls;
}
private static void addMediaPlaylistUrls(List<Rendition> renditions, List<Uri> out) {
for (int i = 0; i < renditions.size(); i++) {
Uri uri = renditions.get(i).url;
if (uri != null && !out.contains(uri)) {
out.add(uri);
}
}
}
private static <T> List<T> copyStreams(
List<T> streams, int groupIndex, List<StreamKey> streamKeys) {
List<T> copiedStreams = new ArrayList<>(streamKeys.size());
// TODO:
......
......@@ -20,7 +20,6 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import java.io.IOException;
......@@ -81,7 +80,7 @@ public interface HlsPlaylistTracker {
* {@link C#TIME_UNSET} if the playlist should not be blacklisted.
* @return True if blacklisting did not encounter errors. False otherwise.
*/
boolean onPlaylistError(HlsUrl url, long blacklistDurationMs);
boolean onPlaylistError(Uri url, long blacklistDurationMs);
}
/** Thrown when a playlist is considered to be stuck due to a server side error. */
......@@ -164,16 +163,16 @@ public interface HlsPlaylistTracker {
/**
* Returns the most recent snapshot available of the playlist referenced by the provided {@link
* HlsUrl}.
* Uri}.
*
* @param url The {@link HlsUrl} corresponding to the requested media playlist.
* @param url The {@link Uri} corresponding to the requested media playlist.
* @param isForPlayback Whether the caller might use the snapshot to request media segments for
* playback. If true, the primary playlist may be updated to the one requested.
* @return The most recent snapshot of the playlist referenced by the provided {@link HlsUrl}. May
* be null if no snapshot has been loaded yet.
* @return The most recent snapshot of the playlist referenced by the provided {@link Uri}. May be
* null if no snapshot has been loaded yet.
*/
@Nullable
HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url, boolean isForPlayback);
HlsMediaPlaylist getPlaylistSnapshot(Uri url, boolean isForPlayback);
/**
* Returns the start time of the first loaded primary playlist, or {@link C#TIME_UNSET} if no
......@@ -182,15 +181,14 @@ public interface HlsPlaylistTracker {
long getInitialStartTimeUs();
/**
* Returns whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is
* valid, meaning all the segments referenced by the playlist are expected to be available. If the
* Returns whether the snapshot of the playlist referenced by the provided {@link Uri} is valid,
* meaning all the segments referenced by the playlist are expected to be available. If the
* playlist is not valid then some of the segments may no longer be available.
*
* @param url The {@link HlsUrl}.
* @return Whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is
* valid.
* @param url The {@link Uri}.
* @return Whether the snapshot of the playlist referenced by the provided {@link Uri} is valid.
*/
boolean isSnapshotValid(HlsUrl url);
boolean isSnapshotValid(Uri url);
/**
* If the tracker is having trouble refreshing the master playlist or the primary playlist, this
......@@ -201,13 +199,13 @@ public interface HlsPlaylistTracker {
void maybeThrowPrimaryPlaylistRefreshError() throws IOException;
/**
* If the playlist is having trouble refreshing the playlist referenced by the given {@link
* HlsUrl}, this method throws the underlying error.
* If the playlist is having trouble refreshing the playlist referenced by the given {@link Uri},
* this method throws the underlying error.
*
* @param url The {@link HlsUrl}.
* @param url The {@link Uri}.
* @throws IOException The underyling error.
*/
void maybeThrowPlaylistRefreshError(HlsUrl url) throws IOException;
void maybeThrowPlaylistRefreshError(Uri url) throws IOException;
/**
* Requests a playlist refresh and whitelists it.
......@@ -215,9 +213,9 @@ public interface HlsPlaylistTracker {
* <p>The playlist tracker may choose the delay the playlist refresh. The request is discarded if
* a refresh was already pending.
*
* @param url The {@link HlsUrl} of the playlist to be refreshed.
* @param url The {@link Uri} of the playlist to be refreshed.
*/
void refreshPlaylist(HlsUrl url);
void refreshPlaylist(Uri url);
/**
* Returns whether the tracked playlists describe a live stream.
......
......@@ -290,7 +290,7 @@ public class HlsMasterPlaylistParserTest {
public void testVariableSubstitution() throws IOException {
HlsMasterPlaylist playlistWithSubstitutions =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_VARIABLE_SUBSTITUTION);
HlsMasterPlaylist.HlsUrl variant = playlistWithSubstitutions.variants.get(0);
HlsMasterPlaylist.Variant variant = playlistWithSubstitutions.variants.get(0);
assertThat(variant.format.codecs).isEqualTo("mp4a.40.5");
assertThat(variant.url)
.isEqualTo(Uri.parse("http://example.com/This/{$nested}/reference/shouldnt/work"));
......
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