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.source.chunk.Chunk; ...@@ -27,7 +27,6 @@ import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.DataChunk; import com.google.android.exoplayer2.source.chunk.DataChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
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.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
...@@ -70,10 +69,8 @@ import java.util.Map; ...@@ -70,10 +69,8 @@ import java.util.Map;
*/ */
public boolean endOfStream; public boolean endOfStream;
/** /** Indicates that the chunk source is waiting for the referred playlist to be refreshed. */
* Indicates that the chunk source is waiting for the referred playlist to be refreshed. public Uri playlistUrl;
*/
public HlsUrl playlist;
/** /**
* Clears the holder. * Clears the holder.
...@@ -81,7 +78,7 @@ import java.util.Map; ...@@ -81,7 +78,7 @@ import java.util.Map;
public void clear() { public void clear() {
chunk = null; chunk = null;
endOfStream = false; endOfStream = false;
playlist = null; playlistUrl = null;
} }
} }
...@@ -96,7 +93,8 @@ import java.util.Map; ...@@ -96,7 +93,8 @@ import java.util.Map;
private final DataSource mediaDataSource; private final DataSource mediaDataSource;
private final DataSource encryptionDataSource; private final DataSource encryptionDataSource;
private final TimestampAdjusterProvider timestampAdjusterProvider; private final TimestampAdjusterProvider timestampAdjusterProvider;
private final HlsUrl[] hlsUrls; private final Uri[] playlistUrls;
private final Format[] playlistFormats;
private final HlsPlaylistTracker playlistTracker; private final HlsPlaylistTracker playlistTracker;
private final TrackGroup trackGroup; private final TrackGroup trackGroup;
private final List<Format> muxedCaptionFormats; private final List<Format> muxedCaptionFormats;
...@@ -105,7 +103,7 @@ import java.util.Map; ...@@ -105,7 +103,7 @@ import java.util.Map;
private boolean isTimestampMaster; private boolean isTimestampMaster;
private byte[] scratchSpace; private byte[] scratchSpace;
private IOException fatalError; private IOException fatalError;
private HlsUrl expectedPlaylistUrl; private Uri expectedPlaylistUrl;
private boolean independentSegments; private boolean independentSegments;
// Note: The track group in the selection is typically *not* equal to trackGroup. This is due to // Note: The track group in the selection is typically *not* equal to trackGroup. This is due to
...@@ -119,8 +117,9 @@ import java.util.Map; ...@@ -119,8 +117,9 @@ import java.util.Map;
* @param extractorFactory An {@link HlsExtractorFactory} from which to obtain the extractors for * @param extractorFactory An {@link HlsExtractorFactory} from which to obtain the extractors for
* media chunks. * media chunks.
* @param playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists. * @param playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists.
* @param hlsUrls The {@link HlsUrl} instances corresponding to media playlists from which this * @param playlistUrls The {@link Uri}s of the media playlists that can be adapted between by this
* chunk source can obtain media. * chunk source.
* @param playlistFormats The {@link Format Formats} corresponding to the media playlists.
* @param dataSourceFactory An {@link HlsDataSourceFactory} to create {@link DataSource}s for the * @param dataSourceFactory An {@link HlsDataSourceFactory} to create {@link DataSource}s for the
* chunks. * chunks.
* @param mediaTransferListener The transfer listener which should be informed of any media data * @param mediaTransferListener The transfer listener which should be informed of any media data
...@@ -134,30 +133,30 @@ import java.util.Map; ...@@ -134,30 +133,30 @@ import java.util.Map;
public HlsChunkSource( public HlsChunkSource(
HlsExtractorFactory extractorFactory, HlsExtractorFactory extractorFactory,
HlsPlaylistTracker playlistTracker, HlsPlaylistTracker playlistTracker,
HlsUrl[] hlsUrls, Uri[] playlistUrls,
Format[] playlistFormats,
HlsDataSourceFactory dataSourceFactory, HlsDataSourceFactory dataSourceFactory,
@Nullable TransferListener mediaTransferListener, @Nullable TransferListener mediaTransferListener,
TimestampAdjusterProvider timestampAdjusterProvider, TimestampAdjusterProvider timestampAdjusterProvider,
List<Format> muxedCaptionFormats) { List<Format> muxedCaptionFormats) {
this.extractorFactory = extractorFactory; this.extractorFactory = extractorFactory;
this.playlistTracker = playlistTracker; this.playlistTracker = playlistTracker;
this.hlsUrls = hlsUrls; this.playlistUrls = playlistUrls;
this.playlistFormats = playlistFormats;
this.timestampAdjusterProvider = timestampAdjusterProvider; this.timestampAdjusterProvider = timestampAdjusterProvider;
this.muxedCaptionFormats = muxedCaptionFormats; this.muxedCaptionFormats = muxedCaptionFormats;
keyCache = new FullSegmentEncryptionKeyCache(); keyCache = new FullSegmentEncryptionKeyCache();
liveEdgeInPeriodTimeUs = C.TIME_UNSET; liveEdgeInPeriodTimeUs = C.TIME_UNSET;
Format[] trackFormats = new Format[hlsUrls.length];
int[] initialTrackSelection = new int[hlsUrls.length];
for (int i = 0; i < hlsUrls.length; i++) {
trackFormats[i] = hlsUrls[i].format;
initialTrackSelection[i] = i;
}
mediaDataSource = dataSourceFactory.createDataSource(C.DATA_TYPE_MEDIA); mediaDataSource = dataSourceFactory.createDataSource(C.DATA_TYPE_MEDIA);
if (mediaTransferListener != null) { if (mediaTransferListener != null) {
mediaDataSource.addTransferListener(mediaTransferListener); mediaDataSource.addTransferListener(mediaTransferListener);
} }
encryptionDataSource = dataSourceFactory.createDataSource(C.DATA_TYPE_DRM); encryptionDataSource = dataSourceFactory.createDataSource(C.DATA_TYPE_DRM);
trackGroup = new TrackGroup(trackFormats); trackGroup = new TrackGroup(playlistFormats);
int[] initialTrackSelection = new int[playlistUrls.length];
for (int i = 0; i < playlistUrls.length; i++) {
initialTrackSelection[i] = i;
}
trackSelection = new InitializationTrackSelection(trackGroup, initialTrackSelection); trackSelection = new InitializationTrackSelection(trackGroup, initialTrackSelection);
} }
...@@ -221,8 +220,8 @@ import java.util.Map; ...@@ -221,8 +220,8 @@ import java.util.Map;
* *
* <p>If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream * <p>If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream
* has been reached then {@link HlsChunkHolder#endOfStream} is set. If a chunk is not available * has been reached then {@link HlsChunkHolder#endOfStream} is set. If a chunk is not available
* but the end of the stream has not been reached, {@link HlsChunkHolder#playlist} is set to * but the end of the stream has not been reached, {@link HlsChunkHolder#playlistUrl} is set to
* contain the {@link HlsUrl} that refers to the playlist that needs refreshing. * contain the {@link Uri} that refers to the playlist that needs refreshing.
* *
* @param playbackPositionUs The current playback position relative to the period start in * @param playbackPositionUs The current playback position relative to the period start in
* microseconds. If playback of the period to which this chunk source belongs has not yet * microseconds. If playback of the period to which this chunk source belongs has not yet
...@@ -259,16 +258,16 @@ import java.util.Map; ...@@ -259,16 +258,16 @@ import java.util.Map;
int selectedTrackIndex = trackSelection.getSelectedIndexInTrackGroup(); int selectedTrackIndex = trackSelection.getSelectedIndexInTrackGroup();
boolean switchingTrack = oldTrackIndex != selectedTrackIndex; boolean switchingTrack = oldTrackIndex != selectedTrackIndex;
HlsUrl selectedHlsUrl = hlsUrls[selectedTrackIndex]; Uri selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
if (!playlistTracker.isSnapshotValid(selectedHlsUrl)) { if (!playlistTracker.isSnapshotValid(selectedPlaylistUrl)) {
out.playlist = selectedHlsUrl; out.playlistUrl = selectedPlaylistUrl;
seenExpectedPlaylistError &= expectedPlaylistUrl == selectedHlsUrl; seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl);
expectedPlaylistUrl = selectedHlsUrl; expectedPlaylistUrl = selectedPlaylistUrl;
// Retry when playlist is refreshed. // Retry when playlist is refreshed.
return; return;
} }
HlsMediaPlaylist mediaPlaylist = HlsMediaPlaylist mediaPlaylist =
playlistTracker.getPlaylistSnapshot(selectedHlsUrl, /* isForPlayback= */ true); playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */ true);
independentSegments = mediaPlaylist.hasIndependentSegments; independentSegments = mediaPlaylist.hasIndependentSegments;
updateLiveEdgeTimeUs(mediaPlaylist); updateLiveEdgeTimeUs(mediaPlaylist);
...@@ -284,9 +283,9 @@ import java.util.Map; ...@@ -284,9 +283,9 @@ import java.util.Map;
// We try getting the next chunk without adapting in case that's the reason for falling // We try getting the next chunk without adapting in case that's the reason for falling
// behind the live window. // behind the live window.
selectedTrackIndex = oldTrackIndex; selectedTrackIndex = oldTrackIndex;
selectedHlsUrl = hlsUrls[selectedTrackIndex]; selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
mediaPlaylist = mediaPlaylist =
playlistTracker.getPlaylistSnapshot(selectedHlsUrl, /* isForPlayback= */ true); playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */ true);
startOfPlaylistInPeriodUs = startOfPlaylistInPeriodUs =
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs(); mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
chunkMediaSequence = previous.getNextChunkIndex(); chunkMediaSequence = previous.getNextChunkIndex();
...@@ -301,9 +300,9 @@ import java.util.Map; ...@@ -301,9 +300,9 @@ import java.util.Map;
if (mediaPlaylist.hasEndTag) { if (mediaPlaylist.hasEndTag) {
out.endOfStream = true; out.endOfStream = true;
} else /* Live */ { } else /* Live */ {
out.playlist = selectedHlsUrl; out.playlistUrl = selectedPlaylistUrl;
seenExpectedPlaylistError &= expectedPlaylistUrl == selectedHlsUrl; seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl);
expectedPlaylistUrl = selectedHlsUrl; expectedPlaylistUrl = selectedPlaylistUrl;
} }
return; return;
} }
...@@ -330,10 +329,11 @@ import java.util.Map; ...@@ -330,10 +329,11 @@ import java.util.Map;
HlsMediaChunk.createInstance( HlsMediaChunk.createInstance(
extractorFactory, extractorFactory,
mediaDataSource, mediaDataSource,
playlistFormats[selectedTrackIndex],
startOfPlaylistInPeriodUs, startOfPlaylistInPeriodUs,
mediaPlaylist, mediaPlaylist,
segmentIndexInPlaylist, segmentIndexInPlaylist,
selectedHlsUrl, selectedPlaylistUrl,
muxedCaptionFormats, muxedCaptionFormats,
trackSelection.getSelectionReason(), trackSelection.getSelectionReason(),
trackSelection.getSelectionData(), trackSelection.getSelectionData(),
...@@ -375,13 +375,19 @@ import java.util.Map; ...@@ -375,13 +375,19 @@ import java.util.Map;
/** /**
* Called when a playlist load encounters an error. * Called when a playlist load encounters an error.
* *
* @param hlsUrl The {@link HlsUrl} of the playlist whose load encountered an error. * @param playlistUrl The {@link Uri} of the playlist whose load encountered an error.
* @param blacklistDurationMs The duration for which the playlist should be blacklisted. Or {@link * @param blacklistDurationMs The duration for which the playlist should be blacklisted. Or {@link
* C#TIME_UNSET} if the playlist should not be blacklisted. * C#TIME_UNSET} if the playlist should not be blacklisted.
* @return True if blacklisting did not encounter errors. False otherwise. * @return True if blacklisting did not encounter errors. False otherwise.
*/ */
public boolean onPlaylistError(HlsUrl hlsUrl, long blacklistDurationMs) { public boolean onPlaylistError(Uri playlistUrl, long blacklistDurationMs) {
int trackGroupIndex = trackGroup.indexOf(hlsUrl.format); int trackGroupIndex = C.INDEX_UNSET;
for (int i = 0; i < playlistUrls.length; i++) {
if (playlistUrls[i].equals(playlistUrl)) {
trackGroupIndex = i;
break;
}
}
if (trackGroupIndex == C.INDEX_UNSET) { if (trackGroupIndex == C.INDEX_UNSET) {
return true; return true;
} }
...@@ -389,7 +395,7 @@ import java.util.Map; ...@@ -389,7 +395,7 @@ import java.util.Map;
if (trackSelectionIndex == C.INDEX_UNSET) { if (trackSelectionIndex == C.INDEX_UNSET) {
return true; return true;
} }
seenExpectedPlaylistError |= expectedPlaylistUrl == hlsUrl; seenExpectedPlaylistError |= playlistUrl.equals(expectedPlaylistUrl);
return blacklistDurationMs == C.TIME_UNSET return blacklistDurationMs == C.TIME_UNSET
|| trackSelection.blacklist(trackSelectionIndex, blacklistDurationMs); || trackSelection.blacklist(trackSelectionIndex, blacklistDurationMs);
} }
...@@ -407,13 +413,13 @@ import java.util.Map; ...@@ -407,13 +413,13 @@ import java.util.Map;
MediaChunkIterator[] chunkIterators = new MediaChunkIterator[trackSelection.length()]; MediaChunkIterator[] chunkIterators = new MediaChunkIterator[trackSelection.length()];
for (int i = 0; i < chunkIterators.length; i++) { for (int i = 0; i < chunkIterators.length; i++) {
int trackIndex = trackSelection.getIndexInTrackGroup(i); int trackIndex = trackSelection.getIndexInTrackGroup(i);
HlsUrl hlsUrl = hlsUrls[trackIndex]; Uri playlistUrl = playlistUrls[trackIndex];
if (!playlistTracker.isSnapshotValid(hlsUrl)) { if (!playlistTracker.isSnapshotValid(playlistUrl)) {
chunkIterators[i] = MediaChunkIterator.EMPTY; chunkIterators[i] = MediaChunkIterator.EMPTY;
continue; continue;
} }
HlsMediaPlaylist playlist = HlsMediaPlaylist playlist =
playlistTracker.getPlaylistSnapshot(hlsUrl, /* isForPlayback= */ false); playlistTracker.getPlaylistSnapshot(playlistUrl, /* isForPlayback= */ false);
long startOfPlaylistInPeriodUs = long startOfPlaylistInPeriodUs =
playlist.startTimeUs - playlistTracker.getInitialStartTimeUs(); playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
boolean switchingTrack = trackIndex != oldTrackIndex; boolean switchingTrack = trackIndex != oldTrackIndex;
...@@ -502,7 +508,7 @@ import java.util.Map; ...@@ -502,7 +508,7 @@ import java.util.Map;
return new EncryptionKeyChunk( return new EncryptionKeyChunk(
encryptionDataSource, encryptionDataSource,
dataSpec, dataSpec,
hlsUrls[selectedTrackIndex].format, playlistFormats[selectedTrackIndex],
trackSelection.getSelectionReason(), trackSelection.getSelectionReason(),
trackSelection.getSelectionData(), trackSelection.getSelectionData(),
scratchSpace); scratchSpace);
......
...@@ -27,7 +27,6 @@ import com.google.android.exoplayer2.metadata.Metadata; ...@@ -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.Id3Decoder;
import com.google.android.exoplayer2.metadata.id3.PrivFrame; import com.google.android.exoplayer2.metadata.id3.PrivFrame;
import com.google.android.exoplayer2.source.chunk.MediaChunk; 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.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
...@@ -52,9 +51,10 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -52,9 +51,10 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param extractorFactory A {@link HlsExtractorFactory} from which the HLS media chunk extractor * @param extractorFactory A {@link HlsExtractorFactory} from which the HLS media chunk extractor
* is obtained. * is obtained.
* @param dataSource The source from which the data should be loaded. * @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 startOfPlaylistInPeriodUs The position of the playlist in the period in microseconds.
* @param mediaPlaylist The media playlist from which this chunk was obtained. * @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 * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist. * information is available in the master playlist.
* @param trackSelectionReason See {@link #trackSelectionReason}. * @param trackSelectionReason See {@link #trackSelectionReason}.
...@@ -70,10 +70,11 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -70,10 +70,11 @@ import java.util.concurrent.atomic.AtomicInteger;
public static HlsMediaChunk createInstance( public static HlsMediaChunk createInstance(
HlsExtractorFactory extractorFactory, HlsExtractorFactory extractorFactory,
DataSource dataSource, DataSource dataSource,
Format format,
long startOfPlaylistInPeriodUs, long startOfPlaylistInPeriodUs,
HlsMediaPlaylist mediaPlaylist, HlsMediaPlaylist mediaPlaylist,
int segmentIndexInPlaylist, int segmentIndexInPlaylist,
HlsUrl hlsUrl, Uri playlistUrl,
@Nullable List<Format> muxedCaptionFormats, @Nullable List<Format> muxedCaptionFormats,
int trackSelectionReason, int trackSelectionReason,
@Nullable Object trackSelectionData, @Nullable Object trackSelectionData,
...@@ -126,7 +127,8 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -126,7 +127,8 @@ import java.util.concurrent.atomic.AtomicInteger;
if (previousChunk != null) { if (previousChunk != null) {
id3Decoder = previousChunk.id3Decoder; id3Decoder = previousChunk.id3Decoder;
scratchId3Data = previousChunk.scratchId3Data; scratchId3Data = previousChunk.scratchId3Data;
shouldSpliceIn = previousChunk.hlsUrl != hlsUrl || !previousChunk.loadCompleted; shouldSpliceIn =
!playlistUrl.equals(previousChunk.playlistUrl) || !previousChunk.loadCompleted;
previousExtractor = previousExtractor =
previousChunk.isExtractorReusable previousChunk.isExtractorReusable
&& previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber && previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber
...@@ -143,11 +145,12 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -143,11 +145,12 @@ import java.util.concurrent.atomic.AtomicInteger;
extractorFactory, extractorFactory,
mediaDataSource, mediaDataSource,
dataSpec, dataSpec,
format,
mediaSegmentEncrypted, mediaSegmentEncrypted,
initDataSource, initDataSource,
initDataSpec, initDataSpec,
initSegmentEncrypted, initSegmentEncrypted,
hlsUrl, playlistUrl,
muxedCaptionFormats, muxedCaptionFormats,
trackSelectionReason, trackSelectionReason,
trackSelectionData, trackSelectionData,
...@@ -180,10 +183,8 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -180,10 +183,8 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
public final int discontinuitySequenceNumber; public final int discontinuitySequenceNumber;
/** /** The url of the playlist from which this chunk was obtained. */
* The url of the playlist from which this chunk was obtained. public final Uri playlistUrl;
*/
public final HlsUrl hlsUrl;
@Nullable private final DataSource initDataSource; @Nullable private final DataSource initDataSource;
@Nullable private final DataSpec initDataSpec; @Nullable private final DataSpec initDataSpec;
...@@ -214,11 +215,12 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -214,11 +215,12 @@ import java.util.concurrent.atomic.AtomicInteger;
HlsExtractorFactory extractorFactory, HlsExtractorFactory extractorFactory,
DataSource mediaDataSource, DataSource mediaDataSource,
DataSpec dataSpec, DataSpec dataSpec,
Format format,
boolean mediaSegmentEncrypted, boolean mediaSegmentEncrypted,
DataSource initDataSource, DataSource initDataSource,
@Nullable DataSpec initDataSpec, @Nullable DataSpec initDataSpec,
boolean initSegmentEncrypted, boolean initSegmentEncrypted,
HlsUrl hlsUrl, Uri playlistUrl,
@Nullable List<Format> muxedCaptionFormats, @Nullable List<Format> muxedCaptionFormats,
int trackSelectionReason, int trackSelectionReason,
Object trackSelectionData, Object trackSelectionData,
...@@ -237,7 +239,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -237,7 +239,7 @@ import java.util.concurrent.atomic.AtomicInteger;
super( super(
mediaDataSource, mediaDataSource,
dataSpec, dataSpec,
hlsUrl.format, format,
trackSelectionReason, trackSelectionReason,
trackSelectionData, trackSelectionData,
startTimeUs, startTimeUs,
...@@ -248,7 +250,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -248,7 +250,7 @@ import java.util.concurrent.atomic.AtomicInteger;
this.initDataSource = initDataSource; this.initDataSource = initDataSource;
this.initDataSpec = initDataSpec; this.initDataSpec = initDataSpec;
this.initSegmentEncrypted = initSegmentEncrypted; this.initSegmentEncrypted = initSegmentEncrypted;
this.hlsUrl = hlsUrl; this.playlistUrl = playlistUrl;
this.isMasterTimestampSource = isMasterTimestampSource; this.isMasterTimestampSource = isMasterTimestampSource;
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
this.hasGapTag = hasGapTag; this.hasGapTag = hasGapTag;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.hls; package com.google.android.exoplayer2.source.hls;
import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -31,8 +32,8 @@ import com.google.android.exoplayer2.source.SequenceableLoader; ...@@ -31,8 +32,8 @@ import com.google.android.exoplayer2.source.SequenceableLoader;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; 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.HlsMasterPlaylist.Rendition; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Rendition;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
...@@ -410,7 +411,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -410,7 +411,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
} }
@Override @Override
public void onPlaylistRefreshRequired(HlsUrl url) { public void onPlaylistRefreshRequired(Uri url) {
playlistTracker.refreshPlaylist(url); playlistTracker.refreshPlaylist(url);
} }
...@@ -427,7 +428,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -427,7 +428,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
} }
@Override @Override
public boolean onPlaylistError(HlsUrl url, long blacklistDurationMs) { public boolean onPlaylistError(Uri url, long blacklistDurationMs) {
boolean noBlacklistingFailure = true; boolean noBlacklistingFailure = true;
for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) { for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) {
noBlacklistingFailure &= streamWrapper.onPlaylistError(url, blacklistDurationMs); noBlacklistingFailure &= streamWrapper.onPlaylistError(url, blacklistDurationMs);
...@@ -473,11 +474,12 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -473,11 +474,12 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
// Subtitle stream wrappers. We can always use master playlist information to prepare these. // Subtitle stream wrappers. We can always use master playlist information to prepare these.
for (int i = 0; i < subtitleRenditions.size(); i++) { for (int i = 0; i < subtitleRenditions.size(); i++) {
HlsUrl url = subtitleRenditions.get(i); Rendition subtitleRendition = subtitleRenditions.get(i);
HlsSampleStreamWrapper sampleStreamWrapper = HlsSampleStreamWrapper sampleStreamWrapper =
buildSampleStreamWrapper( buildSampleStreamWrapper(
C.TRACK_TYPE_TEXT, C.TRACK_TYPE_TEXT,
new HlsUrl[] {url}, new Uri[] {subtitleRendition.url},
new Format[] {subtitleRendition.format},
null, null,
Collections.emptyList(), Collections.emptyList(),
overridingDrmInitData, overridingDrmInitData,
...@@ -485,7 +487,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -485,7 +487,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
manifestUrlIndicesPerWrapper.add(new int[] {i}); manifestUrlIndicesPerWrapper.add(new int[] {i});
sampleStreamWrappers.add(sampleStreamWrapper); sampleStreamWrappers.add(sampleStreamWrapper);
sampleStreamWrapper.prepareWithMasterPlaylistInfo( sampleStreamWrapper.prepareWithMasterPlaylistInfo(
new TrackGroupArray(new TrackGroup(url.format)), 0, TrackGroupArray.EMPTY); new TrackGroupArray(new TrackGroup(subtitleRendition.format)), 0, TrackGroupArray.EMPTY);
} }
this.sampleStreamWrappers = sampleStreamWrappers.toArray(new HlsSampleStreamWrapper[0]); this.sampleStreamWrappers = sampleStreamWrappers.toArray(new HlsSampleStreamWrapper[0]);
...@@ -538,7 +540,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -538,7 +540,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
int videoVariantCount = 0; int videoVariantCount = 0;
int audioVariantCount = 0; int audioVariantCount = 0;
for (int i = 0; i < masterPlaylist.variants.size(); i++) { for (int i = 0; i < masterPlaylist.variants.size(); i++) {
HlsUrl variant = masterPlaylist.variants.get(i); Variant variant = masterPlaylist.variants.get(i);
Format format = variant.format; Format format = variant.format;
if (format.height > 0 || Util.getCodecsOfType(format.codecs, C.TRACK_TYPE_VIDEO) != null) { if (format.height > 0 || Util.getCodecsOfType(format.codecs, C.TRACK_TYPE_VIDEO) != null) {
variantTypes[i] = C.TRACK_TYPE_VIDEO; variantTypes[i] = C.TRACK_TYPE_VIDEO;
...@@ -565,21 +567,25 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -565,21 +567,25 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
useNonAudioVariantsOnly = true; useNonAudioVariantsOnly = true;
selectedVariantsCount = variantTypes.length - audioVariantCount; selectedVariantsCount = variantTypes.length - audioVariantCount;
} }
HlsUrl[] selectedVariants = new HlsUrl[selectedVariantsCount]; Uri[] selectedPlaylistUrls = new Uri[selectedVariantsCount];
Format[] selectedPlaylistFormats = new Format[selectedVariantsCount];
int[] selectedVariantIndices = new int[selectedVariantsCount]; int[] selectedVariantIndices = new int[selectedVariantsCount];
int outIndex = 0; int outIndex = 0;
for (int i = 0; i < masterPlaylist.variants.size(); i++) { for (int i = 0; i < masterPlaylist.variants.size(); i++) {
if ((!useVideoVariantsOnly || variantTypes[i] == C.TRACK_TYPE_VIDEO) if ((!useVideoVariantsOnly || variantTypes[i] == C.TRACK_TYPE_VIDEO)
&& (!useNonAudioVariantsOnly || variantTypes[i] != C.TRACK_TYPE_AUDIO)) { && (!useNonAudioVariantsOnly || variantTypes[i] != C.TRACK_TYPE_AUDIO)) {
selectedVariants[outIndex] = masterPlaylist.variants.get(i); Variant variant = masterPlaylist.variants.get(i);
selectedPlaylistUrls[outIndex] = variant.url;
selectedPlaylistFormats[outIndex] = variant.format;
selectedVariantIndices[outIndex++] = i; selectedVariantIndices[outIndex++] = i;
} }
} }
String codecs = selectedVariants[0].format.codecs; String codecs = selectedPlaylistFormats[0].codecs;
HlsSampleStreamWrapper sampleStreamWrapper = HlsSampleStreamWrapper sampleStreamWrapper =
buildSampleStreamWrapper( buildSampleStreamWrapper(
C.TRACK_TYPE_DEFAULT, C.TRACK_TYPE_DEFAULT,
selectedVariants, selectedPlaylistUrls,
selectedPlaylistFormats,
masterPlaylist.muxedAudioFormat, masterPlaylist.muxedAudioFormat,
masterPlaylist.muxedCaptionFormats, masterPlaylist.muxedCaptionFormats,
overridingDrmInitData, overridingDrmInitData,
...@@ -593,7 +599,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -593,7 +599,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
if (variantsContainVideoCodecs) { if (variantsContainVideoCodecs) {
Format[] videoFormats = new Format[selectedVariantsCount]; Format[] videoFormats = new Format[selectedVariantsCount];
for (int i = 0; i < videoFormats.length; i++) { for (int i = 0; i < videoFormats.length; i++) {
videoFormats[i] = deriveVideoFormat(selectedVariants[i].format); videoFormats[i] = deriveVideoFormat(selectedPlaylistFormats[i]);
} }
muxedTrackGroups.add(new TrackGroup(videoFormats)); muxedTrackGroups.add(new TrackGroup(videoFormats));
...@@ -602,7 +608,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -602,7 +608,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
muxedTrackGroups.add( muxedTrackGroups.add(
new TrackGroup( new TrackGroup(
deriveAudioFormat( deriveAudioFormat(
selectedVariants[0].format, selectedPlaylistFormats[0],
masterPlaylist.muxedAudioFormat, masterPlaylist.muxedAudioFormat,
/* isPrimaryTrackInVariant= */ false))); /* isPrimaryTrackInVariant= */ false)));
} }
...@@ -616,10 +622,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -616,10 +622,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
// Variants only contain audio. // Variants only contain audio.
Format[] audioFormats = new Format[selectedVariantsCount]; Format[] audioFormats = new Format[selectedVariantsCount];
for (int i = 0; i < audioFormats.length; i++) { for (int i = 0; i < audioFormats.length; i++) {
Format variantFormat = selectedVariants[i].format;
audioFormats[i] = audioFormats[i] =
deriveAudioFormat( deriveAudioFormat(
variantFormat, /* variantFormat= */ selectedPlaylistFormats[i],
masterPlaylist.muxedAudioFormat, masterPlaylist.muxedAudioFormat,
/* isPrimaryTrackInVariant= */ true); /* isPrimaryTrackInVariant= */ true);
} }
...@@ -652,7 +657,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -652,7 +657,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
List<HlsSampleStreamWrapper> sampleStreamWrappers, List<HlsSampleStreamWrapper> sampleStreamWrappers,
List<int[]> manifestUrlsIndicesPerWrapper, List<int[]> manifestUrlsIndicesPerWrapper,
Map<String, DrmInitData> overridingDrmInitData) { Map<String, DrmInitData> overridingDrmInitData) {
ArrayList<HlsUrl> scratchRenditionList = ArrayList<Uri> scratchPlaylistUrls =
new ArrayList<>(/* initialCapacity= */ audioRenditions.size());
ArrayList<Format> scratchPlaylistFormats =
new ArrayList<>(/* initialCapacity= */ audioRenditions.size()); new ArrayList<>(/* initialCapacity= */ audioRenditions.size());
ArrayList<Integer> scratchIndicesList = ArrayList<Integer> scratchIndicesList =
new ArrayList<>(/* initialCapacity= */ audioRenditions.size()); new ArrayList<>(/* initialCapacity= */ audioRenditions.size());
...@@ -667,14 +674,16 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -667,14 +674,16 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
} }
boolean renditionsHaveCodecs = true; boolean renditionsHaveCodecs = true;
scratchRenditionList.clear(); scratchPlaylistUrls.clear();
scratchPlaylistFormats.clear();
scratchIndicesList.clear(); scratchIndicesList.clear();
// Group all renditions with matching name. // Group all renditions with matching name.
for (int renditionIndex = 0; renditionIndex < audioRenditions.size(); renditionIndex++) { for (int renditionIndex = 0; renditionIndex < audioRenditions.size(); renditionIndex++) {
if (Util.areEqual(name, audioRenditions.get(renditionIndex).name)) { if (Util.areEqual(name, audioRenditions.get(renditionIndex).name)) {
HlsUrl rendition = audioRenditions.get(renditionIndex); Rendition rendition = audioRenditions.get(renditionIndex);
scratchIndicesList.add(renditionIndex); scratchIndicesList.add(renditionIndex);
scratchRenditionList.add(rendition); scratchPlaylistUrls.add(rendition.url);
scratchPlaylistFormats.add(rendition.format);
renditionsHaveCodecs &= rendition.format.codecs != null; renditionsHaveCodecs &= rendition.format.codecs != null;
} }
} }
...@@ -682,7 +691,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -682,7 +691,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
HlsSampleStreamWrapper sampleStreamWrapper = HlsSampleStreamWrapper sampleStreamWrapper =
buildSampleStreamWrapper( buildSampleStreamWrapper(
C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_AUDIO,
scratchRenditionList.toArray(new HlsUrl[0]), scratchPlaylistUrls.toArray(new Uri[0]),
scratchPlaylistFormats.toArray(new Format[0]),
/* muxedAudioFormat= */ null, /* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ Collections.emptyList(), /* muxedCaptionFormats= */ Collections.emptyList(),
overridingDrmInitData, overridingDrmInitData,
...@@ -691,10 +701,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -691,10 +701,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
sampleStreamWrappers.add(sampleStreamWrapper); sampleStreamWrappers.add(sampleStreamWrapper);
if (allowChunklessPreparation && renditionsHaveCodecs) { if (allowChunklessPreparation && renditionsHaveCodecs) {
Format[] renditionFormats = new Format[scratchRenditionList.size()]; Format[] renditionFormats = scratchPlaylistFormats.toArray(new Format[0]);
for (int i = 0; i < renditionFormats.length; i++) {
renditionFormats[i] = scratchRenditionList.get(i).format;
}
sampleStreamWrapper.prepareWithMasterPlaylistInfo( sampleStreamWrapper.prepareWithMasterPlaylistInfo(
new TrackGroupArray(new TrackGroup(renditionFormats)), 0, TrackGroupArray.EMPTY); new TrackGroupArray(new TrackGroup(renditionFormats)), 0, TrackGroupArray.EMPTY);
} }
...@@ -703,7 +710,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -703,7 +710,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private HlsSampleStreamWrapper buildSampleStreamWrapper( private HlsSampleStreamWrapper buildSampleStreamWrapper(
int trackType, int trackType,
HlsUrl[] variants, Uri[] playlistUrls,
Format[] playlistFormats,
Format muxedAudioFormat, Format muxedAudioFormat,
List<Format> muxedCaptionFormats, List<Format> muxedCaptionFormats,
Map<String, DrmInitData> overridingDrmInitData, Map<String, DrmInitData> overridingDrmInitData,
...@@ -712,7 +720,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -712,7 +720,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
new HlsChunkSource( new HlsChunkSource(
extractorFactory, extractorFactory,
playlistTracker, playlistTracker,
variants, playlistUrls,
playlistFormats,
dataSourceFactory, dataSourceFactory,
mediaTransferListener, mediaTransferListener,
timestampAdjusterProvider, timestampAdjusterProvider,
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.hls; package com.google.android.exoplayer2.source.hls;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -37,8 +38,6 @@ import com.google.android.exoplayer2.source.TrackGroup; ...@@ -37,8 +38,6 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; 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.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
...@@ -79,8 +78,7 @@ import java.util.Map; ...@@ -79,8 +78,7 @@ import java.util.Map;
* Called to schedule a {@link #continueLoading(long)} call when the playlist referred by the * Called to schedule a {@link #continueLoading(long)} call when the playlist referred by the
* given url changes. * given url changes.
*/ */
void onPlaylistRefreshRequired(HlsMasterPlaylist.HlsUrl playlistUrl); void onPlaylistRefreshRequired(Uri playlistUrl);
} }
private static final String TAG = "HlsSampleStreamWrapper"; private static final String TAG = "HlsSampleStreamWrapper";
...@@ -451,8 +449,8 @@ import java.util.Map; ...@@ -451,8 +449,8 @@ import java.util.Map;
chunkSource.setIsTimestampMaster(isTimestampMaster); chunkSource.setIsTimestampMaster(isTimestampMaster);
} }
public boolean onPlaylistError(HlsUrl url, long blacklistDurationMs) { public boolean onPlaylistError(Uri playlistUrl, long blacklistDurationMs) {
return chunkSource.onPlaylistError(url, blacklistDurationMs); return chunkSource.onPlaylistError(playlistUrl, blacklistDurationMs);
} }
// SampleStream implementation. // SampleStream implementation.
...@@ -590,7 +588,7 @@ import java.util.Map; ...@@ -590,7 +588,7 @@ import java.util.Map;
chunkSource.getNextChunk(positionUs, loadPositionUs, chunkQueue, nextChunkHolder); chunkSource.getNextChunk(positionUs, loadPositionUs, chunkQueue, nextChunkHolder);
boolean endOfStream = nextChunkHolder.endOfStream; boolean endOfStream = nextChunkHolder.endOfStream;
Chunk loadable = nextChunkHolder.chunk; Chunk loadable = nextChunkHolder.chunk;
HlsMasterPlaylist.HlsUrl playlistToLoad = nextChunkHolder.playlist; Uri playlistUrlToLoad = nextChunkHolder.playlistUrl;
nextChunkHolder.clear(); nextChunkHolder.clear();
if (endOfStream) { if (endOfStream) {
...@@ -600,8 +598,8 @@ import java.util.Map; ...@@ -600,8 +598,8 @@ import java.util.Map;
} }
if (loadable == null) { if (loadable == null) {
if (playlistToLoad != null) { if (playlistUrlToLoad != null) {
callback.onPlaylistRefreshRequired(playlistToLoad); callback.onPlaylistRefreshRequired(playlistUrlToLoad);
} }
return false; return false;
} }
......
...@@ -21,7 +21,6 @@ import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; ...@@ -21,7 +21,6 @@ import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.offline.SegmentDownloader; import com.google.android.exoplayer2.offline.SegmentDownloader;
import com.google.android.exoplayer2.offline.StreamKey; 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;
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.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;
...@@ -81,9 +80,7 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> { ...@@ -81,9 +80,7 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
ArrayList<DataSpec> mediaPlaylistDataSpecs = new ArrayList<>(); ArrayList<DataSpec> mediaPlaylistDataSpecs = new ArrayList<>();
if (playlist instanceof HlsMasterPlaylist) { if (playlist instanceof HlsMasterPlaylist) {
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
addMediaPlaylistDataSpecs(masterPlaylist.variants, mediaPlaylistDataSpecs); addMediaPlaylistDataSpecs(masterPlaylist.mediaPlaylistUrls, mediaPlaylistDataSpecs);
addMediaPlaylistDataSpecs(masterPlaylist.audios, mediaPlaylistDataSpecs);
addMediaPlaylistDataSpecs(masterPlaylist.subtitles, mediaPlaylistDataSpecs);
} else { } else {
mediaPlaylistDataSpecs.add( mediaPlaylistDataSpecs.add(
SegmentDownloader.getCompressibleDataSpec(Uri.parse(playlist.baseUri))); SegmentDownloader.getCompressibleDataSpec(Uri.parse(playlist.baseUri)));
...@@ -118,9 +115,9 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> { ...@@ -118,9 +115,9 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
return segments; return segments;
} }
private void addMediaPlaylistDataSpecs(List<? extends HlsUrl> urls, List<DataSpec> out) { private void addMediaPlaylistDataSpecs(List<Uri> mediaPlaylistUrls, List<DataSpec> out) {
for (int i = 0; i < urls.size(); i++) { for (int i = 0; i < mediaPlaylistUrls.size(); i++) {
out.add(SegmentDownloader.getCompressibleDataSpec(urls.get(i).url)); out.add(SegmentDownloader.getCompressibleDataSpec(mediaPlaylistUrls.get(i)));
} }
} }
......
...@@ -23,7 +23,6 @@ import com.google.android.exoplayer2.C; ...@@ -23,7 +23,6 @@ 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;
import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory; import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
...@@ -53,18 +52,18 @@ public final class DefaultHlsPlaylistTracker ...@@ -53,18 +52,18 @@ public final class DefaultHlsPlaylistTracker
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final HlsPlaylistParserFactory playlistParserFactory; private final HlsPlaylistParserFactory playlistParserFactory;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles; private final IdentityHashMap<Uri, MediaPlaylistBundle> playlistBundles;
private final List<PlaylistEventListener> listeners; private final List<PlaylistEventListener> listeners;
private final double playlistStuckTargetDurationCoefficient; private final double playlistStuckTargetDurationCoefficient;
private @Nullable ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser; @Nullable private ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser;
private @Nullable EventDispatcher eventDispatcher; @Nullable private EventDispatcher eventDispatcher;
private @Nullable Loader initialPlaylistLoader; @Nullable private Loader initialPlaylistLoader;
private @Nullable Handler playlistRefreshHandler; @Nullable private Handler playlistRefreshHandler;
private @Nullable PrimaryPlaylistListener primaryPlaylistListener; @Nullable private PrimaryPlaylistListener primaryPlaylistListener;
private @Nullable HlsMasterPlaylist masterPlaylist; @Nullable private HlsMasterPlaylist masterPlaylist;
private @Nullable HlsUrl primaryHlsUrl; @Nullable private Uri primaryMediaPlaylistUrl;
private @Nullable HlsMediaPlaylist primaryUrlSnapshot; @Nullable private HlsMediaPlaylist primaryMediaPlaylistSnapshot;
private boolean isLive; private boolean isLive;
private long initialStartTimeUs; private long initialStartTimeUs;
...@@ -95,7 +94,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -95,7 +94,7 @@ public final class DefaultHlsPlaylistTracker
* @param playlistStuckTargetDurationCoefficient A coefficient to apply to the target duration of * @param playlistStuckTargetDurationCoefficient A coefficient to apply to the target duration of
* media playlists in order to determine that a non-changing playlist is stuck. Once a * media playlists in order to determine that a non-changing playlist is stuck. Once a
* playlist is deemed stuck, a {@link PlaylistStuckException} is thrown via {@link * playlist is deemed stuck, a {@link PlaylistStuckException} is thrown via {@link
* #maybeThrowPlaylistRefreshError(HlsUrl)}. * #maybeThrowPlaylistRefreshError(Uri)}.
*/ */
public DefaultHlsPlaylistTracker( public DefaultHlsPlaylistTracker(
HlsDataSourceFactory dataSourceFactory, HlsDataSourceFactory dataSourceFactory,
...@@ -142,8 +141,8 @@ public final class DefaultHlsPlaylistTracker ...@@ -142,8 +141,8 @@ public final class DefaultHlsPlaylistTracker
@Override @Override
public void stop() { public void stop() {
primaryHlsUrl = null; primaryMediaPlaylistUrl = null;
primaryUrlSnapshot = null; primaryMediaPlaylistSnapshot = null;
masterPlaylist = null; masterPlaylist = null;
initialStartTimeUs = C.TIME_UNSET; initialStartTimeUs = C.TIME_UNSET;
initialPlaylistLoader.release(); initialPlaylistLoader.release();
...@@ -172,7 +171,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -172,7 +171,7 @@ public final class DefaultHlsPlaylistTracker
} }
@Override @Override
public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url, boolean isForPlayback) { public HlsMediaPlaylist getPlaylistSnapshot(Uri url, boolean isForPlayback) {
HlsMediaPlaylist snapshot = playlistBundles.get(url).getPlaylistSnapshot(); HlsMediaPlaylist snapshot = playlistBundles.get(url).getPlaylistSnapshot();
if (snapshot != null && isForPlayback) { if (snapshot != null && isForPlayback) {
maybeSetPrimaryUrl(url); maybeSetPrimaryUrl(url);
...@@ -186,7 +185,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -186,7 +185,7 @@ public final class DefaultHlsPlaylistTracker
} }
@Override @Override
public boolean isSnapshotValid(HlsUrl url) { public boolean isSnapshotValid(Uri url) {
return playlistBundles.get(url).isSnapshotValid(); return playlistBundles.get(url).isSnapshotValid();
} }
...@@ -195,18 +194,18 @@ public final class DefaultHlsPlaylistTracker ...@@ -195,18 +194,18 @@ public final class DefaultHlsPlaylistTracker
if (initialPlaylistLoader != null) { if (initialPlaylistLoader != null) {
initialPlaylistLoader.maybeThrowError(); initialPlaylistLoader.maybeThrowError();
} }
if (primaryHlsUrl != null) { if (primaryMediaPlaylistUrl != null) {
maybeThrowPlaylistRefreshError(primaryHlsUrl); maybeThrowPlaylistRefreshError(primaryMediaPlaylistUrl);
} }
} }
@Override @Override
public void maybeThrowPlaylistRefreshError(HlsUrl url) throws IOException { public void maybeThrowPlaylistRefreshError(Uri url) throws IOException {
playlistBundles.get(url).maybeThrowPlaylistRefreshError(); playlistBundles.get(url).maybeThrowPlaylistRefreshError();
} }
@Override @Override
public void refreshPlaylist(HlsUrl url) { public void refreshPlaylist(Uri url) {
playlistBundles.get(url).loadPlaylist(); playlistBundles.get(url).loadPlaylist();
} }
...@@ -230,13 +229,9 @@ public final class DefaultHlsPlaylistTracker ...@@ -230,13 +229,9 @@ public final class DefaultHlsPlaylistTracker
} }
this.masterPlaylist = masterPlaylist; this.masterPlaylist = masterPlaylist;
mediaPlaylistParser = playlistParserFactory.createPlaylistParser(masterPlaylist); mediaPlaylistParser = playlistParserFactory.createPlaylistParser(masterPlaylist);
primaryHlsUrl = masterPlaylist.variants.get(0); primaryMediaPlaylistUrl = masterPlaylist.variants.get(0).url;
ArrayList<HlsUrl> urls = new ArrayList<>(); createBundles(masterPlaylist.mediaPlaylistUrls);
urls.addAll(masterPlaylist.variants); MediaPlaylistBundle primaryBundle = playlistBundles.get(primaryMediaPlaylistUrl);
urls.addAll(masterPlaylist.audios);
urls.addAll(masterPlaylist.subtitles);
createBundles(urls);
MediaPlaylistBundle primaryBundle = playlistBundles.get(primaryHlsUrl);
if (isMediaPlaylist) { if (isMediaPlaylist) {
// We don't need to load the playlist again. We can use the same result. // We don't need to load the playlist again. We can use the same result.
primaryBundle.processLoadedPlaylist((HlsMediaPlaylist) result, loadDurationMs); primaryBundle.processLoadedPlaylist((HlsMediaPlaylist) result, loadDurationMs);
...@@ -302,9 +297,9 @@ public final class DefaultHlsPlaylistTracker ...@@ -302,9 +297,9 @@ public final class DefaultHlsPlaylistTracker
int variantsSize = variants.size(); int variantsSize = variants.size();
long currentTimeMs = SystemClock.elapsedRealtime(); long currentTimeMs = SystemClock.elapsedRealtime();
for (int i = 0; i < variantsSize; i++) { for (int i = 0; i < variantsSize; i++) {
MediaPlaylistBundle bundle = playlistBundles.get(variants.get(i)); MediaPlaylistBundle bundle = playlistBundles.get(variants.get(i).url);
if (currentTimeMs > bundle.blacklistUntilMs) { if (currentTimeMs > bundle.blacklistUntilMs) {
primaryHlsUrl = bundle.playlistUrl; primaryMediaPlaylistUrl = bundle.playlistUrl;
bundle.loadPlaylist(); bundle.loadPlaylist();
return true; return true;
} }
...@@ -312,22 +307,33 @@ public final class DefaultHlsPlaylistTracker ...@@ -312,22 +307,33 @@ public final class DefaultHlsPlaylistTracker
return false; return false;
} }
private void maybeSetPrimaryUrl(HlsUrl url) { private void maybeSetPrimaryUrl(Uri url) {
if (url == primaryHlsUrl if (url.equals(primaryMediaPlaylistUrl)
|| !masterPlaylist.variants.contains(url) || !isVariantUrl(url)
|| (primaryUrlSnapshot != null && primaryUrlSnapshot.hasEndTag)) { || (primaryMediaPlaylistSnapshot != null && primaryMediaPlaylistSnapshot.hasEndTag)) {
// Ignore if the primary url is unchanged, if the url is not a variant url, or if the last // Ignore if the primary media playlist URL is unchanged, if the media playlist is not
// primary snapshot contains an end tag. // referenced directly by a variant, or it the last primary snapshot contains an end tag.
return; return;
} }
primaryHlsUrl = url; primaryMediaPlaylistUrl = url;
playlistBundles.get(primaryHlsUrl).loadPlaylist(); playlistBundles.get(primaryMediaPlaylistUrl).loadPlaylist();
} }
private void createBundles(List<HlsUrl> urls) { /** Returns whether any of the variants in the master playlist have the specified playlist URL. */
private boolean isVariantUrl(Uri playlistUrl) {
List<Variant> variants = masterPlaylist.variants;
for (int i = 0; i < variants.size(); i++) {
if (playlistUrl.equals(variants.get(i).url)) {
return true;
}
}
return false;
}
private void createBundles(List<Uri> urls) {
int listSize = urls.size(); int listSize = urls.size();
for (int i = 0; i < listSize; i++) { for (int i = 0; i < listSize; i++) {
HlsUrl url = urls.get(i); Uri url = urls.get(i);
MediaPlaylistBundle bundle = new MediaPlaylistBundle(url); MediaPlaylistBundle bundle = new MediaPlaylistBundle(url);
playlistBundles.put(url, bundle); playlistBundles.put(url, bundle);
} }
...@@ -339,14 +345,14 @@ public final class DefaultHlsPlaylistTracker ...@@ -339,14 +345,14 @@ public final class DefaultHlsPlaylistTracker
* @param url The url of the playlist. * @param url The url of the playlist.
* @param newSnapshot The new snapshot. * @param newSnapshot The new snapshot.
*/ */
private void onPlaylistUpdated(HlsUrl url, HlsMediaPlaylist newSnapshot) { private void onPlaylistUpdated(Uri url, HlsMediaPlaylist newSnapshot) {
if (url == primaryHlsUrl) { if (url.equals(primaryMediaPlaylistUrl)) {
if (primaryUrlSnapshot == null) { if (primaryMediaPlaylistSnapshot == null) {
// This is the first primary url snapshot. // This is the first primary url snapshot.
isLive = !newSnapshot.hasEndTag; isLive = !newSnapshot.hasEndTag;
initialStartTimeUs = newSnapshot.startTimeUs; initialStartTimeUs = newSnapshot.startTimeUs;
} }
primaryUrlSnapshot = newSnapshot; primaryMediaPlaylistSnapshot = newSnapshot;
primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot); primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot);
} }
int listenersSize = listeners.size(); int listenersSize = listeners.size();
...@@ -355,7 +361,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -355,7 +361,7 @@ public final class DefaultHlsPlaylistTracker
} }
} }
private boolean notifyPlaylistError(HlsUrl playlistUrl, long blacklistDurationMs) { private boolean notifyPlaylistError(Uri playlistUrl, long blacklistDurationMs) {
int listenersSize = listeners.size(); int listenersSize = listeners.size();
boolean anyBlacklistingFailed = false; boolean anyBlacklistingFailed = false;
for (int i = 0; i < listenersSize; i++) { for (int i = 0; i < listenersSize; i++) {
...@@ -388,7 +394,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -388,7 +394,7 @@ public final class DefaultHlsPlaylistTracker
return loadedPlaylist.startTimeUs; return loadedPlaylist.startTimeUs;
} }
long primarySnapshotStartTimeUs = long primarySnapshotStartTimeUs =
primaryUrlSnapshot != null ? primaryUrlSnapshot.startTimeUs : 0; primaryMediaPlaylistSnapshot != null ? primaryMediaPlaylistSnapshot.startTimeUs : 0;
if (oldPlaylist == null) { if (oldPlaylist == null) {
return primarySnapshotStartTimeUs; return primarySnapshotStartTimeUs;
} }
...@@ -411,7 +417,9 @@ public final class DefaultHlsPlaylistTracker ...@@ -411,7 +417,9 @@ public final class DefaultHlsPlaylistTracker
} }
// TODO: Improve cross-playlist discontinuity adjustment. // TODO: Improve cross-playlist discontinuity adjustment.
int primaryUrlDiscontinuitySequence = int primaryUrlDiscontinuitySequence =
primaryUrlSnapshot != null ? primaryUrlSnapshot.discontinuitySequence : 0; primaryMediaPlaylistSnapshot != null
? primaryMediaPlaylistSnapshot.discontinuitySequence
: 0;
if (oldPlaylist == null) { if (oldPlaylist == null) {
return primaryUrlDiscontinuitySequence; return primaryUrlDiscontinuitySequence;
} }
...@@ -435,7 +443,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -435,7 +443,7 @@ public final class DefaultHlsPlaylistTracker
private final class MediaPlaylistBundle private final class MediaPlaylistBundle
implements Loader.Callback<ParsingLoadable<HlsPlaylist>>, Runnable { implements Loader.Callback<ParsingLoadable<HlsPlaylist>>, Runnable {
private final HlsUrl playlistUrl; private final Uri playlistUrl;
private final Loader mediaPlaylistLoader; private final Loader mediaPlaylistLoader;
private final ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable; private final ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable;
...@@ -447,13 +455,13 @@ public final class DefaultHlsPlaylistTracker ...@@ -447,13 +455,13 @@ public final class DefaultHlsPlaylistTracker
private boolean loadPending; private boolean loadPending;
private IOException playlistError; private IOException playlistError;
public MediaPlaylistBundle(HlsUrl playlistUrl) { public MediaPlaylistBundle(Uri playlistUrl) {
this.playlistUrl = playlistUrl; this.playlistUrl = playlistUrl;
mediaPlaylistLoader = new Loader("DefaultHlsPlaylistTracker:MediaPlaylist"); mediaPlaylistLoader = new Loader("DefaultHlsPlaylistTracker:MediaPlaylist");
mediaPlaylistLoadable = mediaPlaylistLoadable =
new ParsingLoadable<>( new ParsingLoadable<>(
dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST), dataSourceFactory.createDataSource(C.DATA_TYPE_MANIFEST),
playlistUrl.url, playlistUrl,
C.DATA_TYPE_MANIFEST, C.DATA_TYPE_MANIFEST,
mediaPlaylistParser); mediaPlaylistParser);
} }
...@@ -620,13 +628,13 @@ public final class DefaultHlsPlaylistTracker ...@@ -620,13 +628,13 @@ public final class DefaultHlsPlaylistTracker
// TODO: Allow customization of playlist resets handling. // TODO: Allow customization of playlist resets handling.
// The media sequence jumped backwards. The server has probably reset. We do not try // The media sequence jumped backwards. The server has probably reset. We do not try
// blacklisting in this case. // blacklisting in this case.
playlistError = new PlaylistResetException(playlistUrl.url); playlistError = new PlaylistResetException(playlistUrl);
notifyPlaylistError(playlistUrl, C.TIME_UNSET); notifyPlaylistError(playlistUrl, C.TIME_UNSET);
} else if (currentTimeMs - lastSnapshotChangeMs } else if (currentTimeMs - lastSnapshotChangeMs
> C.usToMs(playlistSnapshot.targetDurationUs) > C.usToMs(playlistSnapshot.targetDurationUs)
* playlistStuckTargetDurationCoefficient) { * playlistStuckTargetDurationCoefficient) {
// TODO: Allow customization of stuck playlists handling. // TODO: Allow customization of stuck playlists handling.
playlistError = new PlaylistStuckException(playlistUrl.url); playlistError = new PlaylistStuckException(playlistUrl);
long blacklistDurationMs = long blacklistDurationMs =
loadErrorHandlingPolicy.getBlacklistDurationMsFor( loadErrorHandlingPolicy.getBlacklistDurationMsFor(
C.DATA_TYPE_MANIFEST, loadDurationMs, playlistError, /* errorCount= */ 1); C.DATA_TYPE_MANIFEST, loadDurationMs, playlistError, /* errorCount= */ 1);
...@@ -647,7 +655,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -647,7 +655,7 @@ public final class DefaultHlsPlaylistTracker
// Schedule a load if this is the primary playlist and it doesn't have an end tag. Else the // Schedule a load if this is the primary playlist and it doesn't have an end tag. Else the
// next load will be scheduled when refreshPlaylist is called, or when this playlist becomes // next load will be scheduled when refreshPlaylist is called, or when this playlist becomes
// the primary. // the primary.
if (playlistUrl == primaryHlsUrl && !playlistSnapshot.hasEndTag) { if (playlistUrl.equals(primaryMediaPlaylistUrl) && !playlistSnapshot.hasEndTag) {
loadPlaylist(); loadPlaylist();
} }
} }
...@@ -661,7 +669,7 @@ public final class DefaultHlsPlaylistTracker ...@@ -661,7 +669,7 @@ public final class DefaultHlsPlaylistTracker
*/ */
private boolean blacklistPlaylist(long blacklistDurationMs) { private boolean blacklistPlaylist(long blacklistDurationMs) {
blacklistUntilMs = SystemClock.elapsedRealtime() + blacklistDurationMs; blacklistUntilMs = SystemClock.elapsedRealtime() + blacklistDurationMs;
return primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl(); return playlistUrl.equals(primaryMediaPlaylistUrl) && !maybeSelectNewPrimaryUrl();
} }
} }
} }
...@@ -50,28 +50,14 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -50,28 +50,14 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
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;
/** Represents a url in an HLS master playlist. */ /** A variant (i.e. an #EXT-X-STREAM-INF tag) in a master playlist. */
public abstract static class HlsUrl { public static final class Variant {
/** The http url from which the media playlist can be obtained. */ /** The variant's url. */
public final Uri 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. */ /** Format information associated with this variant. */
public static final class Variant extends HlsUrl { public final Format format;
/** The video rendition group referenced by this variant, or {@code null}. */ /** The video rendition group referenced by this variant, or {@code null}. */
@Nullable public final String videoGroupId; @Nullable public final String videoGroupId;
...@@ -100,7 +86,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -100,7 +86,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
@Nullable String audioGroupId, @Nullable String audioGroupId,
@Nullable String subtitleGroupId, @Nullable String subtitleGroupId,
@Nullable String captionGroupId) { @Nullable String captionGroupId) {
super(url, format); this.url = url;
this.format = format;
this.videoGroupId = videoGroupId; this.videoGroupId = videoGroupId;
this.audioGroupId = audioGroupId; this.audioGroupId = audioGroupId;
this.subtitleGroupId = subtitleGroupId; this.subtitleGroupId = subtitleGroupId;
...@@ -135,8 +122,14 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -135,8 +122,14 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
} }
} }
/** A rendition in a master playlist. */ /** A rendition (i.e. an #EXT-X-MEDIA tag) in a master playlist. */
public static final class Rendition extends HlsUrl { 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. */ /** The group to which this rendition belongs. */
public final String groupId; public final String groupId;
...@@ -150,14 +143,17 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -150,14 +143,17 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
* @param groupId See {@link #groupId}. * @param groupId See {@link #groupId}.
* @param name See {@link #name}. * @param name See {@link #name}.
*/ */
public Rendition(Uri url, Format format, String groupId, String name) { public Rendition(@Nullable Uri url, Format format, String groupId, String name) {
super(url, format); this.url = url;
this.format = format;
this.groupId = groupId; this.groupId = groupId;
this.name = name; this.name = name;
} }
} }
/** All of the media playlist URLs referenced by the playlist. */
public final List<Uri> mediaPlaylistUrls;
/** The variants declared by the playlist. */ /** The variants declared by the playlist. */
public final List<Variant> variants; public final List<Variant> variants;
/** The video renditions declared by the playlist. */ /** The video renditions declared by the playlist. */
...@@ -213,6 +209,9 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -213,6 +209,9 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
Map<String, String> variableDefinitions, Map<String, String> variableDefinitions,
List<DrmInitData> sessionKeyDrmInitData) { List<DrmInitData> sessionKeyDrmInitData) {
super(baseUri, tags, hasIndependentSegments); super(baseUri, tags, hasIndependentSegments);
this.mediaPlaylistUrls =
Collections.unmodifiableList(
getMediaPlaylistUrls(variants, videos, audios, subtitles, closedCaptions));
this.variants = Collections.unmodifiableList(variants); this.variants = Collections.unmodifiableList(variants);
this.videos = Collections.unmodifiableList(videos); this.videos = Collections.unmodifiableList(videos);
this.audios = Collections.unmodifiableList(audios); this.audios = Collections.unmodifiableList(audios);
...@@ -268,7 +267,36 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -268,7 +267,36 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
/* sessionKeyDrmInitData= */ Collections.emptyList()); /* 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> streams, int groupIndex, List<StreamKey> streamKeys) {
List<T> copiedStreams = new ArrayList<>(streamKeys.size()); List<T> copiedStreams = new ArrayList<>(streamKeys.size());
// TODO: // TODO:
......
...@@ -20,7 +20,6 @@ import androidx.annotation.Nullable; ...@@ -20,7 +20,6 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory; 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 com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import java.io.IOException; import java.io.IOException;
...@@ -81,7 +80,7 @@ public interface HlsPlaylistTracker { ...@@ -81,7 +80,7 @@ public interface HlsPlaylistTracker {
* {@link C#TIME_UNSET} if the playlist should not be blacklisted. * {@link C#TIME_UNSET} if the playlist should not be blacklisted.
* @return True if blacklisting did not encounter errors. False otherwise. * @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. */ /** Thrown when a playlist is considered to be stuck due to a server side error. */
...@@ -164,16 +163,16 @@ public interface HlsPlaylistTracker { ...@@ -164,16 +163,16 @@ public interface HlsPlaylistTracker {
/** /**
* Returns the most recent snapshot available of the playlist referenced by the provided {@link * 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 * @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. * 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 * @return The most recent snapshot of the playlist referenced by the provided {@link Uri}. May be
* be null if no snapshot has been loaded yet. * null if no snapshot has been loaded yet.
*/ */
@Nullable @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 * Returns the start time of the first loaded primary playlist, or {@link C#TIME_UNSET} if no
...@@ -182,15 +181,14 @@ public interface HlsPlaylistTracker { ...@@ -182,15 +181,14 @@ public interface HlsPlaylistTracker {
long getInitialStartTimeUs(); long getInitialStartTimeUs();
/** /**
* Returns whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is * Returns whether the snapshot of the playlist referenced by the provided {@link Uri} is valid,
* valid, meaning all the segments referenced by the playlist are expected to be available. If the * 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. * playlist is not valid then some of the segments may no longer be available.
* *
* @param url The {@link HlsUrl}. * @param url The {@link Uri}.
* @return Whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is * @return Whether the snapshot of the playlist referenced by the provided {@link Uri} is valid.
* valid.
*/ */
boolean isSnapshotValid(HlsUrl url); boolean isSnapshotValid(Uri url);
/** /**
* If the tracker is having trouble refreshing the master playlist or the primary playlist, this * If the tracker is having trouble refreshing the master playlist or the primary playlist, this
...@@ -201,13 +199,13 @@ public interface HlsPlaylistTracker { ...@@ -201,13 +199,13 @@ public interface HlsPlaylistTracker {
void maybeThrowPrimaryPlaylistRefreshError() throws IOException; void maybeThrowPrimaryPlaylistRefreshError() throws IOException;
/** /**
* If the playlist is having trouble refreshing the playlist referenced by the given {@link * If the playlist is having trouble refreshing the playlist referenced by the given {@link Uri},
* HlsUrl}, this method throws the underlying error. * this method throws the underlying error.
* *
* @param url The {@link HlsUrl}. * @param url The {@link Uri}.
* @throws IOException The underyling error. * @throws IOException The underyling error.
*/ */
void maybeThrowPlaylistRefreshError(HlsUrl url) throws IOException; void maybeThrowPlaylistRefreshError(Uri url) throws IOException;
/** /**
* Requests a playlist refresh and whitelists it. * Requests a playlist refresh and whitelists it.
...@@ -215,9 +213,9 @@ public interface HlsPlaylistTracker { ...@@ -215,9 +213,9 @@ public interface HlsPlaylistTracker {
* <p>The playlist tracker may choose the delay the playlist refresh. The request is discarded if * <p>The playlist tracker may choose the delay the playlist refresh. The request is discarded if
* a refresh was already pending. * 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. * Returns whether the tracked playlists describe a live stream.
......
...@@ -290,7 +290,7 @@ public class HlsMasterPlaylistParserTest { ...@@ -290,7 +290,7 @@ public class HlsMasterPlaylistParserTest {
public void testVariableSubstitution() throws IOException { public void testVariableSubstitution() throws IOException {
HlsMasterPlaylist playlistWithSubstitutions = HlsMasterPlaylist playlistWithSubstitutions =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_VARIABLE_SUBSTITUTION); 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.format.codecs).isEqualTo("mp4a.40.5");
assertThat(variant.url) assertThat(variant.url)
.isEqualTo(Uri.parse("http://example.com/This/{$nested}/reference/shouldnt/work")); .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