diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TimestampAdjuster.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TimestampAdjuster.java index 9aa35847c6..c2d80fc146 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TimestampAdjuster.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TimestampAdjuster.java @@ -58,13 +58,6 @@ public final class TimestampAdjuster { lastSampleTimestamp = C.TIME_UNSET; } - /** - * Whether this adjuster has been initialized with a first MPEG-2 TS presentation timestamp. - */ - public boolean isInitialized() { - return lastSampleTimestamp != C.TIME_UNSET; - } - /** * Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound. * @@ -92,15 +85,34 @@ public final class TimestampAdjuster { * @return The adjusted timestamp in microseconds. */ public long adjustSampleTimestamp(long timeUs) { - if (firstSampleTimestampUs != DO_NOT_OFFSET && lastSampleTimestamp == C.TIME_UNSET) { - // Calculate the timestamp offset. - timestampOffsetUs = firstSampleTimestampUs - timeUs; - } // Record the adjusted PTS to adjust for wraparound next time. - lastSampleTimestamp = timeUs; + if (lastSampleTimestamp != C.TIME_UNSET) { + lastSampleTimestamp = timeUs; + } else { + if (firstSampleTimestampUs != DO_NOT_OFFSET) { + // Calculate the timestamp offset. + timestampOffsetUs = firstSampleTimestampUs - timeUs; + } + synchronized (this) { + lastSampleTimestamp = timeUs; + // Notify threads waiting for this adjuster to be initialized. + notifyAll(); + } + } return timeUs + timestampOffsetUs; } + /** + * Blocks the calling thread until this adjuster is initialized. + * + * @throws InterruptedException If the thread was interrupted. + */ + public synchronized void waitUntilInitialized() throws InterruptedException { + while (lastSampleTimestamp == C.TIME_UNSET) { + wait(); + } + } + /** * Converts a value in MPEG-2 timestamp units to the corresponding value in microseconds. * 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 b063e27f5e..79205ccbc6 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 @@ -329,6 +329,8 @@ import java.util.Locale; // Configure the extractor that will read the chunk. Extractor extractor; boolean extractorNeedsInit = true; + boolean isTimestampMaster = false; + TimestampAdjuster timestampAdjuster = null; String lastPathSegment = chunkUri.getLastPathSegment(); if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) { // TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner @@ -342,25 +344,16 @@ import java.util.Locale; extractor = new Mp3Extractor(startTimeUs); } else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION) || lastPathSegment.endsWith(VTT_FILE_EXTENSION)) { - TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(false, - segment.discontinuitySequenceNumber, startTimeUs); - if (timestampAdjuster == null) { - // The master source has yet to instantiate an adjuster for the discontinuity sequence. - // TODO: There's probably an edge case if the master starts playback at a chunk belonging to - // a discontinuity sequence greater than the one that this source is trying to start at. - return; - } + timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber, + startTimeUs); extractor = new WebvttExtractor(format.language, timestampAdjuster); } else if (previous == null || previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber || format != previous.trackFormat) { // MPEG-2 TS segments, but we need a new extractor. - TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(true, - segment.discontinuitySequenceNumber, startTimeUs); - if (timestampAdjuster == null) { - // The master source has yet to instantiate an adjuster for the discontinuity sequence. - return; - } + isTimestampMaster = true; + timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber, + startTimeUs); // This flag ensures the change of pid between streams does not affect the sample queues. int workaroundFlags = TsExtractor.WORKAROUND_MAP_BY_TYPE; String codecs = variants[newVariantIndex].format.codecs; @@ -384,8 +377,9 @@ import java.util.Locale; out.chunk = new HlsMediaChunk(dataSource, dataSpec, format, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), - startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber, extractor, - extractorNeedsInit, switchingVariant, encryptionKey, encryptionIv); + startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber, + isTimestampMaster, timestampAdjuster, extractor, extractorNeedsInit, switchingVariant, + encryptionKey, encryptionIv); } /** diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 9a2b785aa6..657b1e3dae 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -19,6 +19,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; +import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster; import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; @@ -51,6 +52,8 @@ import java.util.concurrent.atomic.AtomicInteger; private final boolean isEncrypted; private final boolean extractorNeedsInit; private final boolean shouldSpliceIn; + private final boolean isMasterTimestampSource; + private final TimestampAdjuster timestampAdjuster; private int bytesLoaded; private HlsSampleStreamWrapper extractorOutput; @@ -68,6 +71,8 @@ import java.util.concurrent.atomic.AtomicInteger; * @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param chunkIndex The media sequence number of the chunk. * @param discontinuitySequenceNumber The discontinuity sequence number of the chunk. + * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. + * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. * @param extractor The extractor to decode samples from the data. * @param extractorNeedsInit Whether the extractor needs initializing with the target * {@link HlsSampleStreamWrapper}. @@ -78,12 +83,14 @@ import java.util.concurrent.atomic.AtomicInteger; */ public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, - int chunkIndex, int discontinuitySequenceNumber, Extractor extractor, - boolean extractorNeedsInit, boolean shouldSpliceIn, byte[] encryptionKey, - byte[] encryptionIv) { + int chunkIndex, int discontinuitySequenceNumber, boolean isMasterTimestampSource, + TimestampAdjuster timestampAdjuster, Extractor extractor, boolean extractorNeedsInit, + boolean shouldSpliceIn, byte[] encryptionKey, byte[] encryptionIv) { super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex); this.discontinuitySequenceNumber = discontinuitySequenceNumber; + this.isMasterTimestampSource = isMasterTimestampSource; + this.timestampAdjuster = timestampAdjuster; this.extractor = extractor; this.extractorNeedsInit = extractorNeedsInit; this.shouldSpliceIn = shouldSpliceIn; @@ -166,6 +173,9 @@ import java.util.concurrent.atomic.AtomicInteger; } try { int result = Extractor.RESULT_CONTINUE; + if (!isMasterTimestampSource && timestampAdjuster != null) { + timestampAdjuster.waitUntilInitialized(); + } while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { result = extractor.read(input, null); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java index 248c8ed0e7..53bfa9338a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java @@ -34,22 +34,18 @@ public final class TimestampAdjusterProvider { /** * Returns a {@link TimestampAdjuster} suitable for adjusting the pts timestamps contained in * a chunk with a given discontinuity sequence. - *

- * This method may return null if the master source has yet to initialize a suitable adjuster. * - * @param isMasterSource True if the calling chunk source is the master. * @param discontinuitySequence The chunk's discontinuity sequence. * @param startTimeUs The chunk's start time. * @return A {@link TimestampAdjuster}. */ - public TimestampAdjuster getAdjuster(boolean isMasterSource, int discontinuitySequence, - long startTimeUs) { + public TimestampAdjuster getAdjuster(int discontinuitySequence, long startTimeUs) { TimestampAdjuster adjuster = timestampAdjusters.get(discontinuitySequence); - if (isMasterSource && adjuster == null) { + if (adjuster == null) { adjuster = new TimestampAdjuster(startTimeUs); timestampAdjusters.put(discontinuitySequence, adjuster); } - return isMasterSource || (adjuster != null && adjuster.isInitialized()) ? adjuster : null; + return adjuster; } /**