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