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); }