diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java index ccdd3c4034..585e6d45a8 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java @@ -30,6 +30,7 @@ import com.google.android.exoplayer.hls.HlsMasterPlaylist; import com.google.android.exoplayer.hls.HlsPlaylist; import com.google.android.exoplayer.hls.HlsPlaylistParser; import com.google.android.exoplayer.hls.HlsSampleSource; +import com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider; import com.google.android.exoplayer.metadata.Id3Parser; import com.google.android.exoplayer.metadata.MetadataTrackRenderer; import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer; @@ -147,7 +148,7 @@ public class HlsRendererBuilder implements RendererBuilder { DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, - variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE); + new PtsTimestampAdjusterProvider(), variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE); HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl, BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO); MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/PtsTimestampAdjuster.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/PtsTimestampAdjuster.java index aec94ec9a0..c26f52bfc9 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/PtsTimestampAdjuster.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/PtsTimestampAdjuster.java @@ -37,7 +37,9 @@ public final class PtsTimestampAdjuster { private final long firstSampleTimestampUs; private long timestampOffsetUs; - private long lastPts; + + // Volatile to allow isInitialized to be called on a different thread to adjustTimestamp. + private volatile long lastPts; /** * @param firstSampleTimestampUs The desired result of the first call to diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 60e49dcf39..42431329bd 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -123,6 +123,7 @@ public class HlsChunkSource { private final DataSource dataSource; private final HlsPlaylistParser playlistParser; private final BandwidthMeter bandwidthMeter; + private final PtsTimestampAdjusterProvider timestampAdjusterProvider; private final int adaptiveMode; private final String baseUri; private final int adaptiveMaxWidth; @@ -145,24 +146,43 @@ public class HlsChunkSource { private boolean live; private long durationUs; private IOException fatalError; - private PtsTimestampAdjuster ptsTimestampAdjuster; private Uri encryptionKeyUri; private byte[] encryptionKey; private String encryptionIvString; private byte[] encryptionIv; + /** + * @param dataSource A {@link DataSource} suitable for loading the media data. + * @param playlistUrl The playlist URL. + * @param playlist The hls playlist. + * @param bandwidthMeter Provides an estimate of the currently available bandwidth. + * @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If + * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the + * same provider. + * @param variantIndices If {@code playlist} is a {@link HlsMasterPlaylist}, the subset of variant + * indices to consider, or null to consider all of the variants. For other playlist types + * this parameter is ignored. + * @param adaptiveMode The mode for switching from one variant to another. One of + * {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and + * {@link #ADAPTIVE_MODE_SPLICE}. + */ public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist, - BandwidthMeter bandwidthMeter, int[] variantIndices, int adaptiveMode) { - this(dataSource, playlistUrl, playlist, bandwidthMeter, variantIndices, adaptiveMode, - DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS, DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS); + BandwidthMeter bandwidthMeter, PtsTimestampAdjusterProvider timestampAdjusterProvider, + int[] variantIndices, int adaptiveMode) { + this(dataSource, playlistUrl, playlist, bandwidthMeter, timestampAdjusterProvider, + variantIndices, adaptiveMode, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS, + DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS); } /** * @param dataSource A {@link DataSource} suitable for loading the media data. * @param playlistUrl The playlist URL. * @param playlist The hls playlist. - * @param bandwidthMeter provides an estimate of the currently available bandwidth. + * @param bandwidthMeter Provides an estimate of the currently available bandwidth. + * @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If + * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the + * same provider. * @param variantIndices If {@code playlist} is a {@link HlsMasterPlaylist}, the subset of variant * indices to consider, or null to consider all of the variants. For other playlist types * this parameter is ignored. @@ -175,10 +195,12 @@ public class HlsChunkSource { * for a switch to a lower quality variant to be considered. */ public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist, - BandwidthMeter bandwidthMeter, int[] variantIndices, int adaptiveMode, - long minBufferDurationToSwitchUpMs, long maxBufferDurationToSwitchDownMs) { + BandwidthMeter bandwidthMeter, PtsTimestampAdjusterProvider timestampAdjusterProvider, + int[] variantIndices, int adaptiveMode, long minBufferDurationToSwitchUpMs, + long maxBufferDurationToSwitchDownMs) { this.dataSource = dataSource; this.bandwidthMeter = bandwidthMeter; + this.timestampAdjusterProvider = timestampAdjusterProvider; this.adaptiveMode = adaptiveMode; minBufferDurationToSwitchUpUs = minBufferDurationToSwitchUpMs * 1000; maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000; @@ -353,6 +375,9 @@ public class HlsChunkSource { // Configure the extractor that will read the chunk. HlsExtractorWrapper extractorWrapper; if (chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)) { + // TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner + // identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3 + // case below. Extractor extractor = new AdtsExtractor(startTimeUs); extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE); @@ -364,13 +389,9 @@ public class HlsChunkSource { || previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber || !format.equals(previousTsChunk.format)) { // MPEG-2 TS segments, but we need a new extractor. - if (previousTsChunk == null - || previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber) { - // TODO: Use this for AAC as well, along with the ID3 PRIV priv tag values with owner - // identifier com.apple.streaming.transportStreamTimestamp. - ptsTimestampAdjuster = new PtsTimestampAdjuster(startTimeUs); - } - Extractor extractor = new TsExtractor(ptsTimestampAdjuster); + PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(true, + segment.discontinuitySequenceNumber, startTimeUs); + Extractor extractor = new TsExtractor(timestampAdjuster); extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight); } else { @@ -454,6 +475,7 @@ public class HlsChunkSource { } public void reset() { + timestampAdjusterProvider.reset(); fatalError = null; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index f29eb095c4..fd16dcc4b0 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -358,6 +358,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader, for (int i = 0; i < pendingDiscontinuities.length; i++) { pendingDiscontinuities[i] = true; } + chunkSource.reset(); restartFrom(positionUs); } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/PtsTimestampAdjusterProvider.java b/library/src/main/java/com/google/android/exoplayer/hls/PtsTimestampAdjusterProvider.java new file mode 100644 index 0000000000..d626a728c5 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/hls/PtsTimestampAdjusterProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer.hls; + +import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster; + +import android.util.SparseArray; + +/** + * Provides {@link PtsTimestampAdjuster} instances for use during HLS playbacks. + */ +public final class PtsTimestampAdjusterProvider { + + // TODO: Prevent this array from growing indefinitely large by removing adjusters that are no + // longer required. + private final SparseArray ptsTimestampAdjusters; + + public PtsTimestampAdjusterProvider() { + ptsTimestampAdjusters = new SparseArray<>(); + } + + /** + * Gets a {@link PtsTimestampAdjuster} 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 PtsTimestampAdjuster}. + */ + public PtsTimestampAdjuster getAdjuster(boolean isMasterSource, int discontinuitySequence, + long startTimeUs) { + PtsTimestampAdjuster adjuster = ptsTimestampAdjusters.get(discontinuitySequence); + if (isMasterSource && adjuster == null) { + adjuster = new PtsTimestampAdjuster(startTimeUs); + ptsTimestampAdjusters.put(discontinuitySequence, adjuster); + } + return isMasterSource || (adjuster != null && adjuster.isInitialized()) ? adjuster : null; + } + + /** + * Resets the provider. + */ + public void reset() { + ptsTimestampAdjusters.clear(); + } + +}