From 9c612f94c5f8aee93b36365dc9d495eeb38a3502 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 25 Nov 2016 07:30:23 -0800 Subject: [PATCH] Chose timestamp adjustment master based on track selection Select the timestamp master depending on track availability. If a variant is being loaded, then that is the timestmap master. Otherwise, if an audio track is being loaded, then the responsible chunk source is the timestmap master. If no variant or audio rendition is enabled, then a subtitle chunk source is selected as timestamp master. This CL will become specially relevant once ID3 PRIV timestamps are used for audio renditions. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=140201385 --- .../exoplayer2/source/hls/HlsChunkSource.java | 14 +++- .../exoplayer2/source/hls/HlsMediaPeriod.java | 73 +++++++++++-------- .../source/hls/HlsSampleStreamWrapper.java | 4 + 3 files changed, 59 insertions(+), 32 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index 70174b8105..80c378a666 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -103,6 +103,7 @@ import java.util.Locale; private final HlsPlaylistTracker playlistTracker; private final TrackGroup trackGroup; + private boolean isTimestampMaster; private byte[] scratchSpace; private IOException fatalError; @@ -176,6 +177,16 @@ import java.util.Locale; fatalError = null; } + /** + * Sets whether this chunk source is responsible for initializing timestamp adjusters. + * + * @param isTimestampMaster True if this chunk source is responsible for initializing timestamp + * adjusters. + */ + public void setIsTimestampMaster(boolean isTimestampMaster) { + this.isTimestampMaster = isTimestampMaster; + } + /** * Returns the next chunk to load. *

@@ -280,7 +291,6 @@ import java.util.Locale; || previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber || format != previous.trackFormat; boolean extractorNeedsInit = true; - boolean isTimestampMaster = false; TimestampAdjuster timestampAdjuster = null; String lastPathSegment = chunkUri.getLastPathSegment(); if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) { @@ -299,7 +309,6 @@ import java.util.Locale; startTimeUs); extractor = new WebvttExtractor(format.language, timestampAdjuster); } else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)) { - isTimestampMaster = true; if (needNewExtractor) { timestampAdjuster = timestampAdjusterProvider.getAdjuster( segment.discontinuitySequenceNumber, startTimeUs); @@ -310,7 +319,6 @@ import java.util.Locale; } } else if (needNewExtractor) { // MPEG-2 TS segments, but we need a new extractor. - isTimestampMaster = true; timestampAdjuster = timestampAdjusterProvider.getAdjuster( segment.discontinuitySequenceNumber, startTimeUs); // This flag ensures the change of pid between streams does not affect the sample queues. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index 3951b30a78..be07b3410e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.TrackGroup; 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.HlsUrl; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.Allocator; @@ -166,6 +167,18 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper // Update the local state. enabledSampleStreamWrappers = new HlsSampleStreamWrapper[enabledSampleStreamWrapperList.size()]; enabledSampleStreamWrapperList.toArray(enabledSampleStreamWrappers); + + // The first enabled sample stream wrapper is responsible for intializing the timestamp + // adjuster. This way, if present, variants are responsible. Otherwise, audio renditions are. + // If only subtitles are present, then text renditions are used for timestamp adjustment + // initialization. + if (enabledSampleStreamWrappers.length > 0) { + enabledSampleStreamWrappers[0].setIsTimestampMaster(true); + for (int i = 1; i < enabledSampleStreamWrappers.length; i++) { + enabledSampleStreamWrappers[i].setIsTimestampMaster(false); + } + } + sequenceableLoader = new CompositeSequenceableLoader(enabledSampleStreamWrappers); if (seenFirstTrackSelection && selectedNewTracks) { seekToUs(positionUs); @@ -241,7 +254,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper } @Override - public void onPlaylistRefreshRequired(HlsMasterPlaylist.HlsUrl url) { + public void onPlaylistRefreshRequired(HlsUrl url) { playlistTracker.refreshPlaylist(url, this); } @@ -269,7 +282,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper } @Override - public void onPlaylistLoadError(HlsMasterPlaylist.HlsUrl url, IOException error) { + public void onPlaylistLoadError(HlsUrl url, IOException error) { for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) { sampleStreamWrapper.onPlaylistLoadError(url, error); } @@ -281,11 +294,11 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper private void buildAndPrepareSampleStreamWrappers() { HlsMasterPlaylist masterPlaylist = playlistTracker.getMasterPlaylist(); // Build the default stream wrapper. - List selectedVariants = new ArrayList<>(masterPlaylist.variants); - ArrayList definiteVideoVariants = new ArrayList<>(); - ArrayList definiteAudioOnlyVariants = new ArrayList<>(); + List selectedVariants = new ArrayList<>(masterPlaylist.variants); + ArrayList definiteVideoVariants = new ArrayList<>(); + ArrayList definiteAudioOnlyVariants = new ArrayList<>(); for (int i = 0; i < selectedVariants.size(); i++) { - HlsMasterPlaylist.HlsUrl variant = selectedVariants.get(i); + HlsUrl variant = selectedVariants.get(i); if (variant.format.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) { definiteVideoVariants.add(variant); } else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) { @@ -304,41 +317,44 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper } else { // Leave the enabled variants unchanged. They're likely either all video or all audio. } - List audioVariants = masterPlaylist.audios; - List subtitleVariants = masterPlaylist.subtitles; - sampleStreamWrappers = new HlsSampleStreamWrapper[(selectedVariants.isEmpty() ? 0 : 1) - + audioVariants.size() + subtitleVariants.size()]; + List audioRenditions = masterPlaylist.audios; + List subtitleRenditions = masterPlaylist.subtitles; + sampleStreamWrappers = new HlsSampleStreamWrapper[1 /* variants */ + audioRenditions.size() + + subtitleRenditions.size()]; int currentWrapperIndex = 0; pendingPrepareCount = sampleStreamWrappers.length; - if (!selectedVariants.isEmpty()) { - HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()]; - selectedVariants.toArray(variants); - HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, - variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat); - sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; - sampleStreamWrapper.continuePreparing(); - } + + Assertions.checkArgument(!selectedVariants.isEmpty()); + HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()]; + selectedVariants.toArray(variants); + HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, + variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat); + sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; + sampleStreamWrapper.setIsTimestampMaster(true); + sampleStreamWrapper.continuePreparing(); + + // TODO: Build video stream wrappers here. // Build audio stream wrappers. - for (int i = 0; i < audioVariants.size(); i++) { - HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, - new HlsMasterPlaylist.HlsUrl[] {audioVariants.get(i)}, null, null); + for (int i = 0; i < audioRenditions.size(); i++) { + sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, + new HlsUrl[] {audioRenditions.get(i)}, null, null); sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; sampleStreamWrapper.continuePreparing(); } // Build subtitle stream wrappers. - for (int i = 0; i < subtitleVariants.size(); i++) { - HlsMasterPlaylist.HlsUrl url = subtitleVariants.get(i); - HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, - new HlsMasterPlaylist.HlsUrl[] {url}, null, null); + for (int i = 0; i < subtitleRenditions.size(); i++) { + HlsUrl url = subtitleRenditions.get(i); + sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, new HlsUrl[] {url}, null, + null); sampleStreamWrapper.prepareSingleTrack(url.format); sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; } } - private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, - HlsMasterPlaylist.HlsUrl[] variants, Format muxedAudioFormat, Format muxedCaptionFormat) { + private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants, + Format muxedAudioFormat, Format muxedCaptionFormat) { DataSource dataSource = dataSourceFactory.createDataSource(); HlsChunkSource defaultChunkSource = new HlsChunkSource(playlistTracker, variants, dataSource, timestampAdjusterProvider); @@ -347,8 +363,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper eventDispatcher); } - private static boolean variantHasExplicitCodecWithPrefix(HlsMasterPlaylist.HlsUrl variant, - String prefix) { + private static boolean variantHasExplicitCodecWithPrefix(HlsUrl variant, String prefix) { String codecs = variant.format.codecs; if (TextUtils.isEmpty(codecs)) { return false; diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index cdd212df3a..bc44c84e39 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -275,6 +275,10 @@ import java.util.LinkedList; return largestQueuedTimestampUs; } + public void setIsTimestampMaster(boolean isTimestampMaster) { + chunkSource.setIsTimestampMaster(isTimestampMaster); + } + public void onPlaylistLoadError(HlsUrl url, IOException error) { chunkSource.onPlaylistLoadError(url, error); }