diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/BundledHlsMediaChunkExtractor.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/BundledHlsMediaChunkExtractor.java index 78fc9ae732..3311d263c6 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/BundledHlsMediaChunkExtractor.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/BundledHlsMediaChunkExtractor.java @@ -101,4 +101,9 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract return new BundledHlsMediaChunkExtractor( newExtractorInstance, masterPlaylistFormat, timestampAdjuster); } + + @Override + public void onTruncatedSegmentParsed() { + extractor.seek(/* position= */ 0, /* timeUs= */ 0); + } } 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 643f5e5dd5..cc5e60dd51 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 @@ -428,6 +428,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } try { while (!loadCanceled && extractor.read(input)) {} + } catch (EOFException e) { + if ((trackFormat.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) { + // See onTruncatedSegmentParsed's javadoc for more info on why we are swallowing the EOF + // exception for trick play tracks. + extractor.onTruncatedSegmentParsed(); + } else { + throw e; + } } finally { nextLoadPosition = (int) (input.getPosition() - dataSpec.position); } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunkExtractor.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunkExtractor.java index 0ca5c5d0ad..084a3450ba 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunkExtractor.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunkExtractor.java @@ -59,4 +59,15 @@ public interface HlsMediaChunkExtractor { * instances that are not {@link #isReusable() reusable}. */ HlsMediaChunkExtractor recreate(); + + /** + * Resets the sample parsing state. + * + *
Resetting the parsing state allows support for Fragmented MP4 EXT-X-I-FRAME-STREAM-INF + * segments. EXT-X-I-FRAME-STREAM-INF segments are truncated to include only a leading key frame. + * After parsing said keyframe, an extractor may reach an unexpected end of file. By resetting its + * state, we can continue feeding samples from the following segments to the extractor. See #7512 for context. + */ + void onTruncatedSegmentParsed(); } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/MediaParserHlsMediaChunkExtractor.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/MediaParserHlsMediaChunkExtractor.java index 06de8544f2..893bfc0a32 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/MediaParserHlsMediaChunkExtractor.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/MediaParserHlsMediaChunkExtractor.java @@ -29,6 +29,7 @@ import android.annotation.SuppressLint; import android.media.MediaFormat; import android.media.MediaParser; import android.media.MediaParser.OutputConsumer; +import android.media.MediaParser.SeekPoint; import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -211,6 +212,11 @@ public final class MediaParserHlsMediaChunkExtractor implements HlsMediaChunkExt /* leadingBytesToSkip= */ 0); } + @Override + public void onTruncatedSegmentParsed() { + mediaParser.seek(SeekPoint.START); + } + // Allow constants that are not part of the public MediaParser API. @SuppressLint({"WrongConstant"}) private static MediaParser createMediaParserInstance(