Commit 400619c1 by aquilescanta Committed by Oliver Woodman

Preemptively declare an ID3 track for HLS chunkless preparation

Issue:#3149
Issue:#4016

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=193664294
parent a1b8aa15
......@@ -73,6 +73,8 @@
not include all of the playlist's variants.
* Fix SAMPLE-AES-CENC and SAMPLE-AES-CTR EXT-X-KEY methods
([#4145](https://github.com/google/ExoPlayer/issues/4145)).
* Preeptively declare an ID3 track in chunkless preparation
([#4016](https://github.com/google/ExoPlayer/issues/4016)).
* Fix ClearKey decryption error if the key contains a forward slash
([#4075](https://github.com/google/ExoPlayer/issues/4075)).
* Fix crash when switching surface on Huawei P9 Lite
......
......@@ -344,7 +344,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
Format renditionFormat = audioRendition.format;
if (allowChunklessPreparation && renditionFormat.codecs != null) {
sampleStreamWrapper.prepareWithMasterPlaylistInfo(
new TrackGroupArray(new TrackGroup(audioRendition.format)), 0);
new TrackGroupArray(new TrackGroup(audioRendition.format)), 0, TrackGroupArray.EMPTY);
} else {
sampleStreamWrapper.continuePreparing();
}
......@@ -362,7 +362,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
positionUs);
sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
sampleStreamWrapper.prepareWithMasterPlaylistInfo(
new TrackGroupArray(new TrackGroup(url.format)), 0);
new TrackGroupArray(new TrackGroup(url.format)), 0, TrackGroupArray.EMPTY);
}
// All wrappers are enabled during preparation.
......@@ -386,7 +386,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
* master playlist either contains an EXT-X-MEDIA tag without the URI attribute or does not
* contain any EXT-X-MEDIA tag.
* <li>Closed captions will only be exposed if they are declared by the master playlist.
* <li>ID3 tracks are not exposed.
* <li>An ID3 track is exposed preemptively, in case the segments contain an ID3 track.
* </ul>
*
* @param masterPlaylist The HLS master playlist.
......@@ -463,8 +463,21 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
// Variants contain codecs but no video or audio entries could be identified.
throw new IllegalArgumentException("Unexpected codecs attribute: " + codecs);
}
TrackGroup id3TrackGroup =
new TrackGroup(
Format.createSampleFormat(
/* id= */ "ID3",
MimeTypes.APPLICATION_ID3,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
/* drmInitData= */ null));
muxedTrackGroups.add(id3TrackGroup);
sampleStreamWrapper.prepareWithMasterPlaylistInfo(
new TrackGroupArray(muxedTrackGroups.toArray(new TrackGroup[0])), 0);
new TrackGroupArray(muxedTrackGroups.toArray(new TrackGroup[0])),
0,
new TrackGroupArray(id3TrackGroup));
} else {
sampleStreamWrapper.setIsTimestampMaster(true);
sampleStreamWrapper.continuePreparing();
......
......@@ -33,13 +33,13 @@ import java.io.IOException;
public HlsSampleStream(HlsSampleStreamWrapper sampleStreamWrapper, int trackGroupIndex) {
this.sampleStreamWrapper = sampleStreamWrapper;
this.trackGroupIndex = trackGroupIndex;
sampleQueueIndex = C.INDEX_UNSET;
sampleQueueIndex = HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING;
}
public void unbindSampleQueue() {
if (sampleQueueIndex != C.INDEX_UNSET) {
if (sampleQueueIndex != HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING) {
sampleStreamWrapper.unbindSampleQueue(trackGroupIndex);
sampleQueueIndex = C.INDEX_UNSET;
sampleQueueIndex = HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING;
}
}
......@@ -47,12 +47,14 @@ import java.io.IOException;
@Override
public boolean isReady() {
return ensureBoundSampleQueue() && sampleStreamWrapper.isReady(sampleQueueIndex);
return sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_NON_FATAL
|| (maybeMapToSampleQueue() && sampleStreamWrapper.isReady(sampleQueueIndex));
}
@Override
public void maybeThrowError() throws IOException {
if (!ensureBoundSampleQueue() && sampleStreamWrapper.isMappingFinished()) {
maybeMapToSampleQueue();
if (sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_FATAL) {
throw new SampleQueueMappingException(
sampleStreamWrapper.getTrackGroups().get(trackGroupIndex).getFormat(0).sampleMimeType);
}
......@@ -61,27 +63,24 @@ import java.io.IOException;
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
if (!ensureBoundSampleQueue()) {
return C.RESULT_NOTHING_READ;
}
return sampleStreamWrapper.readData(sampleQueueIndex, formatHolder, buffer, requireFormat);
return maybeMapToSampleQueue()
? sampleStreamWrapper.readData(sampleQueueIndex, formatHolder, buffer, requireFormat)
: C.RESULT_NOTHING_READ;
}
@Override
public int skipData(long positionUs) {
if (!ensureBoundSampleQueue()) {
return 0;
}
return sampleStreamWrapper.skipData(sampleQueueIndex, positionUs);
return maybeMapToSampleQueue() ? sampleStreamWrapper.skipData(sampleQueueIndex, positionUs) : 0;
}
// Internal methods.
private boolean ensureBoundSampleQueue() {
if (sampleQueueIndex != C.INDEX_UNSET) {
return true;
private boolean maybeMapToSampleQueue() {
if (sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING) {
sampleQueueIndex = sampleStreamWrapper.bindSampleQueueToSampleStream(trackGroupIndex);
}
sampleQueueIndex = sampleStreamWrapper.bindSampleQueueToSampleStream(trackGroupIndex);
return sampleQueueIndex != C.INDEX_UNSET;
return sampleQueueIndex != HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING
&& sampleQueueIndex != HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_NON_FATAL
&& sampleQueueIndex != HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_FATAL;
}
}
......@@ -76,6 +76,10 @@ import java.util.Arrays;
private static final String TAG = "HlsSampleStreamWrapper";
public static final int SAMPLE_QUEUE_INDEX_PENDING = -1;
public static final int SAMPLE_QUEUE_INDEX_NO_MAPPING_FATAL = -2;
public static final int SAMPLE_QUEUE_INDEX_NO_MAPPING_NON_FATAL = -3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({PRIMARY_TYPE_NONE, PRIMARY_TYPE_TEXT, PRIMARY_TYPE_AUDIO, PRIMARY_TYPE_VIDEO})
private @interface PrimaryTrackType {}
......@@ -114,6 +118,7 @@ import java.util.Arrays;
// Tracks are complicated in HLS. See documentation of buildTracks for details.
// Indexed by track (as exposed by this source).
private TrackGroupArray trackGroups;
private TrackGroupArray optionalTrackGroups;
// Indexed by track group.
private int[] trackGroupToSampleQueueIndex;
private int primaryTrackGroupIndex;
......@@ -189,13 +194,18 @@ import java.util.Arrays;
/**
* Prepares the sample stream wrapper with master playlist information.
*
* @param trackGroups This {@link TrackGroupArray} to expose.
* @param trackGroups The {@link TrackGroupArray} to expose.
* @param primaryTrackGroupIndex The index of the adaptive track group.
* @param optionalTrackGroups A subset of {@code trackGroups} that should not trigger a failure if
* not found in the media playlist's segments.
*/
public void prepareWithMasterPlaylistInfo(
TrackGroupArray trackGroups, int primaryTrackGroupIndex) {
TrackGroupArray trackGroups,
int primaryTrackGroupIndex,
TrackGroupArray optionalTrackGroups) {
prepared = true;
this.trackGroups = trackGroups;
this.optionalTrackGroups = optionalTrackGroups;
this.primaryTrackGroupIndex = primaryTrackGroupIndex;
callback.onPrepared();
}
......@@ -208,21 +218,19 @@ import java.util.Arrays;
return trackGroups;
}
public boolean isMappingFinished() {
return trackGroupToSampleQueueIndex != null;
}
public int bindSampleQueueToSampleStream(int trackGroupIndex) {
if (!isMappingFinished()) {
return C.INDEX_UNSET;
if (trackGroupToSampleQueueIndex == null) {
return SAMPLE_QUEUE_INDEX_PENDING;
}
int sampleQueueIndex = trackGroupToSampleQueueIndex[trackGroupIndex];
if (sampleQueueIndex == C.INDEX_UNSET) {
return C.INDEX_UNSET;
return optionalTrackGroups.indexOf(trackGroups.get(trackGroupIndex)) == C.INDEX_UNSET
? SAMPLE_QUEUE_INDEX_NO_MAPPING_FATAL
: SAMPLE_QUEUE_INDEX_NO_MAPPING_NON_FATAL;
}
if (sampleQueuesEnabledStates[sampleQueueIndex]) {
// This sample queue is already bound to a different sample stream.
return C.INDEX_UNSET;
return SAMPLE_QUEUE_INDEX_NO_MAPPING_FATAL;
}
sampleQueuesEnabledStates[sampleQueueIndex] = true;
return sampleQueueIndex;
......@@ -780,7 +788,7 @@ import java.util.Arrays;
mapSampleQueuesToMatchTrackGroups();
} else {
// Tracks are created using media segment information.
buildTracks();
buildTracksFromSampleStreams();
prepared = true;
callback.onPrepared();
}
......@@ -804,33 +812,34 @@ import java.util.Arrays;
/**
* Builds tracks that are exposed by this {@link HlsSampleStreamWrapper} instance, as well as
* internal data-structures required for operation.
* <p>
* Tracks in HLS are complicated. A HLS master playlist contains a number of "variants". Each
*
* <p>Tracks in HLS are complicated. A HLS master playlist contains a number of "variants". Each
* variant stream typically contains muxed video, audio and (possibly) additional audio, metadata
* and caption tracks. We wish to allow the user to select between an adaptive track that spans
* all variants, as well as each individual variant. If multiple audio tracks are present within
* each variant then we wish to allow the user to select between those also.
* <p>
* To do this, tracks are constructed as follows. The {@link HlsChunkSource} exposes (N+1) tracks,
* where N is the number of variants defined in the HLS master playlist. These consist of one
* adaptive track defined to span all variants and a track for each individual variant. The
*
* <p>To do this, tracks are constructed as follows. The {@link HlsChunkSource} exposes (N+1)
* tracks, where N is the number of variants defined in the HLS master playlist. These consist of
* one adaptive track defined to span all variants and a track for each individual variant. The
* adaptive track is initially selected. The extractor is then prepared to discover the tracks
* inside of each variant stream. The two sets of tracks are then combined by this method to
* create a third set, which is the set exposed by this {@link HlsSampleStreamWrapper}:
*
* <ul>
* <li>The extractor tracks are inspected to infer a "primary" track type. If a video track is
* present then it is always the primary type. If not, audio is the primary type if present.
* Else text is the primary type if present. Else there is no primary type.</li>
* <li>If there is exactly one extractor track of the primary type, it's expanded into (N+1)
* exposed tracks, all of which correspond to the primary extractor track and each of which
* corresponds to a different chunk source track. Selecting one of these tracks has the effect
* of switching the selected track on the chunk source.</li>
* <li>All other extractor tracks are exposed directly. Selecting one of these tracks has the
* effect of selecting an extractor track, leaving the selected track on the chunk source
* unchanged.</li>
* <li>The extractor tracks are inspected to infer a "primary" track type. If a video track is
* present then it is always the primary type. If not, audio is the primary type if present.
* Else text is the primary type if present. Else there is no primary type.
* <li>If there is exactly one extractor track of the primary type, it's expanded into (N+1)
* exposed tracks, all of which correspond to the primary extractor track and each of which
* corresponds to a different chunk source track. Selecting one of these tracks has the
* effect of switching the selected track on the chunk source.
* <li>All other extractor tracks are exposed directly. Selecting one of these tracks has the
* effect of selecting an extractor track, leaving the selected track on the chunk source
* unchanged.
* </ul>
*/
private void buildTracks() {
private void buildTracksFromSampleStreams() {
// Iterate through the extractor tracks to discover the "primary" track type, and the index
// of the single track of this type.
@PrimaryTrackType int primaryExtractorTrackType = PRIMARY_TYPE_NONE;
......@@ -887,6 +896,8 @@ import java.util.Arrays;
}
}
this.trackGroups = new TrackGroupArray(trackGroups);
Assertions.checkState(optionalTrackGroups == null);
optionalTrackGroups = TrackGroupArray.EMPTY;
}
private HlsMediaChunk getLastMediaChunk() {
......
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