Skip to content

Commit

Permalink
Preemptively declare an ID3 track for HLS chunkless preparation
Browse files Browse the repository at this point in the history
Issue:#3149
Issue:#4016

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=193664294
  • Loading branch information
AquilesCanta authored and ojw28 committed May 7, 2018
1 parent a1b8aa1 commit 400619c
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 50 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ private void buildAndPrepareSampleStreamWrappers(long positionUs) {
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();
}
Expand All @@ -362,7 +362,7 @@ private void buildAndPrepareSampleStreamWrappers(long positionUs) {
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.
Expand All @@ -386,7 +386,7 @@ private void buildAndPrepareSampleStreamWrappers(long positionUs) {
* 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.
Expand Down Expand Up @@ -463,8 +463,21 @@ private void buildAndPrepareMainSampleStreamWrapper(
// 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,28 @@
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;
}
}

// SampleStream implementation.

@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);
}
Expand All @@ -61,27 +63,24 @@ public void maybeThrowError() throws 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ public interface Callback extends SequenceableLoader.Callback<HlsSampleStreamWra

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 {}
Expand Down Expand Up @@ -114,6 +118,7 @@ public interface Callback extends SequenceableLoader.Callback<HlsSampleStreamWra
// 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;
Expand Down Expand Up @@ -189,13 +194,18 @@ public void continuePreparing() {
/**
* 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();
}
Expand All @@ -208,21 +218,19 @@ public TrackGroupArray getTrackGroups() {
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;
Expand Down Expand Up @@ -780,7 +788,7 @@ private void maybeFinishPrepare() {
mapSampleQueuesToMatchTrackGroups();
} else {
// Tracks are created using media segment information.
buildTracks();
buildTracksFromSampleStreams();
prepared = true;
callback.onPrepared();
}
Expand All @@ -804,33 +812,34 @@ private void mapSampleQueuesToMatchTrackGroups() {
/**
* 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;
Expand Down Expand Up @@ -887,6 +896,8 @@ private void buildTracks() {
}
}
this.trackGroups = new TrackGroupArray(trackGroups);
Assertions.checkState(optionalTrackGroups == null);
optionalTrackGroups = TrackGroupArray.EMPTY;
}

private HlsMediaChunk getLastMediaChunk() {
Expand Down

0 comments on commit 400619c

Please sign in to comment.