diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/UnexpectedDiscontinuityException.java b/library/core/src/main/java/com/google/android/exoplayer2/source/UnexpectedDiscontinuityException.java new file mode 100644 index 0000000000..d6033fef2d --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/UnexpectedDiscontinuityException.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 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.exoplayer2.source; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.source.chunk.MediaChunk; +import com.google.android.exoplayer2.upstream.DataSpec; + +/** + * Thrown from the loader thread when an attempt is made to commit a sample that is far + * deviant from the expected sequence of timestamps in the SampleStream. + * + * This is likely caused by an intra-chunk timestamp discontinuity that was not handled by the + * chunk source (the origin server). + * + * For HLS, the origin server is required to break segments at continuity boundaries by the HLS Pantos spec + * (EXT-X-DISCONTINUITY {@see https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3}) + * + */ +public final class UnexpectedDiscontinuityException extends RuntimeException { + + /** the last in-bounds timestamp committed to the {@link SampleQueue}, or + * {@link C#TIME_UNSET} if this was for the first committed sample + */ + public final long lastValidTimeUs; + + /** The errant timestamp + */ + public final long deviantSampleTimeUs; + + /** The source of the samples that resulted in this error + */ + public final DataSpec dataSpec; + + /** The timeUs that the source of the samples starts (from HLS metadata) + */ + public final long startTimeUs; + + /** + * Construct an UnexpectedDiscontinuityException for a {@link MediaChunk} where an + * unexpected timestamp discontinuity is detected within its sample source (e.g. segment for HLS) + * + * @param mediaChunk the {@link MediaChunk} with the unexpected timestamp value + * @param lastValidTimeUs the last in-bounds timestamp committed to the {@link SampleQueue}, or + * {@link C#TIME_UNSET} if this was for the first committed sample + * @param deviantSampleTimeUs the timestamp that is out of bounds. + */ + public UnexpectedDiscontinuityException(MediaChunk mediaChunk, long lastValidTimeUs, long deviantSampleTimeUs) { + super("Unexpected discontinuity, timeMs: " + C.usToMs(deviantSampleTimeUs) + " loaded from dataSpec: " + mediaChunk.dataSpec); + this.dataSpec = mediaChunk.dataSpec; + this.startTimeUs = mediaChunk.startTimeUs; + this.lastValidTimeUs = lastValidTimeUs; + this.deviantSampleTimeUs = deviantSampleTimeUs; + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/UnreportedDiscontinuityException.java b/library/core/src/main/java/com/google/android/exoplayer2/source/UnreportedDiscontinuityException.java deleted file mode 100644 index 9ed2177e1e..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/UnreportedDiscontinuityException.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.google.android.exoplayer2.source; - -import android.net.Uri; -import com.google.android.exoplayer2.C; - -/** - * Thrown from the loader thread when an attempt is made to commit a sample that is far - * deviant from the expected sequence of timestamps in the SampleStream. This is likely - * caused by a discontinuity in a segment that was not split and reported by metadata in - * an HLS (EXT-X-DISCONTINUITY) or DASH stream. - */ -public class UnreportedDiscontinuityException extends RuntimeException { - - public final long timesUs; - - /** - * Consturct the exception - * - * @param timesUs last timestamp before attempted commit of the deviant sample - * @param uri uri of the segment with the unreported discontinuity - */ - public UnreportedDiscontinuityException(long timesUs, Uri uri) { - super("Unreported discontinuity timeMs: " + C.usToMs(timesUs) + " in URI: " + uri); - this.timesUs = timesUs; - } -} diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsCheckedSampleQueue.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsCheckedSampleQueue.java deleted file mode 100644 index 7630d40680..0000000000 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsCheckedSampleQueue.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2020 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.exoplayer2.source.hls; - -import androidx.annotation.Nullable; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.drm.DrmSessionManager; -import com.google.android.exoplayer2.source.SampleQueue; -import com.google.android.exoplayer2.source.UnreportedDiscontinuityException; -import com.google.android.exoplayer2.upstream.Allocator; -import com.google.android.exoplayer2.util.Log; - -public class HlsCheckedSampleQueue extends SampleQueue { - private static final String TAG = "HlsCheckedSampleQueue"; - - private long lowestTimeUs = C.TIME_UNSET; - private long highestTimeUs = C.TIME_UNSET; - - private HlsMediaChunk chunk; - private boolean loggedFirst = false; - - HlsCheckedSampleQueue(Allocator allocator, DrmSessionManager drmSessionManager) { - super(allocator, drmSessionManager); - } - - void setCurrentLoadingChunk(HlsMediaChunk chunk) { - double tolerance = (chunk.endTimeUs - chunk.startTimeUs) * 0.1; - this.lowestTimeUs = chunk.startTimeUs; - this.highestTimeUs = (long) (chunk.endTimeUs + tolerance); - this.chunk = chunk; - loggedFirst = false; - } - - - @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, @Nullable CryptoData cryptoData) { - if (lowestTimeUs != C.TIME_UNSET && timeUs < lowestTimeUs && ! loggedFirst) { - Log.d(TAG, "sampleMetadata() - committed timeUs: " + timeUs + " is " + C.usToMs(lowestTimeUs - timeUs) + "ms less then segment start time. chunk: " + chunk.dataSpec.uri); - loggedFirst = true; - } - if (lowestTimeUs != C.TIME_UNSET && timeUs < (lowestTimeUs - C.msToUs(50_000))) { - Log.d(TAG, "sampleMetadata() - committed timeUs: " + timeUs + " is " + C.usToMs(lowestTimeUs - timeUs) + "ms less (MUCH!) then segment start time. chunk: " + chunk.dataSpec.uri); - throw new UnreportedDiscontinuityException(timeUs, chunk.dataSpec.uri); - } - if (highestTimeUs != C.TIME_UNSET && timeUs > highestTimeUs) { - Log.d(TAG, "sampleMetadata() - committed timeUs: " + timeUs + " is " + C.usToMs(lowestTimeUs - timeUs) + "ms greater then segment end time. chunk: " + chunk.dataSpec.uri); - throw new UnreportedDiscontinuityException(timeUs, chunk.dataSpec.uri); - } - super.sampleMetadata(timeUs, flags, size, offset, cryptoData); - } - -} diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index a2e4840402..e700068321 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -27,7 +27,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.Id3Decoder; import com.google.android.exoplayer2.metadata.id3.PrivFrame; -import com.google.android.exoplayer2.source.UnreportedDiscontinuityException; +import com.google.android.exoplayer2.source.UnexpectedDiscontinuityException; import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.upstream.DataSource; @@ -379,10 +379,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { result = extractor.read(input, DUMMY_POSITION_HOLDER); } - } catch (UnreportedDiscontinuityException e) { - Log.d(TAG, "Unreported discontinuity at timeUs: "+ e.timesUs + " uri: " + dataSpec.uri); - throw new IOException("Timestamp error", e); - + } catch (UnexpectedDiscontinuityException e) { + Log.d(TAG, "UnexpectedDiscontinuityException - recovering by discarding balance of segment", e); + throw new IOException("load aborted for segment - " + e.dataSpec + " unexpected discontinuity", e); } finally { nextLoadPosition = (int) (input.getPosition() - dataSpec.position); } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleQueue.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleQueue.java new file mode 100644 index 0000000000..c2e22b8344 --- /dev/null +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleQueue.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2020 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.exoplayer2.source.hls; + +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.drm.DrmInitData; +import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.id3.PrivFrame; +import com.google.android.exoplayer2.source.SampleQueue; +import com.google.android.exoplayer2.source.UnexpectedDiscontinuityException; +import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.MediaSourceEventDispatcher; +import com.google.android.exoplayer2.util.TimestampAdjuster; +import java.util.Map; + +/** + * Extend base SampleQueue to add HLS specific processing of the samples, including: + * + *