diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/BundledChunkExtractor.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/BundledChunkExtractor.java index 5ef9c0cf7c..92a5ab1d18 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/BundledChunkExtractor.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/BundledChunkExtractor.java @@ -62,6 +62,7 @@ public final class BundledChunkExtractor implements ExtractorOutput, ChunkExtrac private SubtitleParser.Factory subtitleParserFactory; private boolean parseSubtitlesDuringExtraction; + private boolean parseWithinGopSampleDependencies; public Factory() { subtitleParserFactory = new DefaultSubtitleParserFactory(); @@ -147,6 +148,9 @@ public final class BundledChunkExtractor implements ExtractorOutput, ChunkExtrac if (!parseSubtitlesDuringExtraction) { flags |= FragmentedMp4Extractor.FLAG_EMIT_RAW_SUBTITLE_DATA; } + if (parseWithinGopSampleDependencies) { + flags |= FragmentedMp4Extractor.FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES; + } extractor = new FragmentedMp4Extractor( subtitleParserFactory, @@ -164,6 +168,26 @@ public final class BundledChunkExtractor implements ExtractorOutput, ChunkExtrac } return new BundledChunkExtractor(extractor, primaryTrackType, representationFormat); } + + /** + * Sets whether within GOP sample dependency information should be parsed as part of extraction. + * Defaults to {@code false}. + * + *

Having access to additional sample dependency information can speed up seeking. See {@link + * FragmentedMp4Extractor#FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES}. + * + *

This method is experimental and will be renamed or removed in a future release. + * + * @param parseWithinGopSampleDependencies Whether to parse within GOP sample dependencies + * during extraction. + * @return This factory, for convenience. + */ + @CanIgnoreReturnValue + public Factory experimentalParseWithinGopSampleDependencies( + boolean parseWithinGopSampleDependencies) { + this.parseWithinGopSampleDependencies = parseWithinGopSampleDependencies; + return this; + } } /** {@link Factory} for {@link BundledChunkExtractor}. */ diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java index a378cd90db..2de304b61c 100644 --- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java +++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java @@ -22,13 +22,16 @@ import android.graphics.SurfaceTexture; import android.view.Surface; import androidx.media3.common.MediaItem; import androidx.media3.common.Player; +import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DefaultDataSource; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.Renderer; import androidx.media3.exoplayer.RenderersFactory; import androidx.media3.exoplayer.dash.DashMediaSource; +import androidx.media3.exoplayer.dash.DefaultDashChunkSource; import androidx.media3.exoplayer.metadata.MetadataDecoderFactory; import androidx.media3.exoplayer.metadata.MetadataRenderer; +import androidx.media3.exoplayer.source.chunk.BundledChunkExtractor; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; import androidx.media3.test.utils.CapturingRenderersFactory; import androidx.media3.test.utils.DumpFileAsserts; @@ -365,4 +368,38 @@ public final class DashPlaybackTest { DumpFileAsserts.assertOutput( applicationContext, playbackOutput, "playbackdumps/dash/image_with_seek_after_eos.dump"); } + + @Test + public void playVideo_usingWithinGopSampleDependencies_withSeek() throws Exception { + Context applicationContext = ApplicationProvider.getApplicationContext(); + CapturingRenderersFactory capturingRenderersFactory = + new CapturingRenderersFactory(applicationContext); + BundledChunkExtractor.Factory chunkExtractorFactory = + new BundledChunkExtractor.Factory().experimentalParseWithinGopSampleDependencies(true); + DataSource.Factory defaultDataSourceFactory = new DefaultDataSource.Factory(applicationContext); + DashMediaSource.Factory dashMediaSourceFactory = + new DashMediaSource.Factory( + /* chunkSourceFactory= */ new DefaultDashChunkSource.Factory( + chunkExtractorFactory, defaultDataSourceFactory, /* maxSegmentsPerLoad= */ 1), + /* manifestDataSourceFactory= */ defaultDataSourceFactory); + ExoPlayer player = + new ExoPlayer.Builder(applicationContext, capturingRenderersFactory) + .setMediaSourceFactory(dashMediaSourceFactory) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) + .build(); + Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1)); + player.setVideoSurface(surface); + PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); + + player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd")); + player.seekTo(500L); + player.prepare(); + player.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + player.release(); + surface.release(); + + DumpFileAsserts.assertOutput( + applicationContext, playbackOutput, "playbackdumps/dash/optimized_seek.dump"); + } } diff --git a/libraries/test_data/src/test/assets/playbackdumps/dash/optimized_seek.dump b/libraries/test_data/src/test/assets/playbackdumps/dash/optimized_seek.dump new file mode 100644 index 0000000000..7cba48fc9a --- /dev/null +++ b/libraries/test_data/src/test/assets/playbackdumps/dash/optimized_seek.dump @@ -0,0 +1,170 @@ +MediaCodecAdapter (exotest.video.avc): + inputBuffers: + count = 24 + input buffer #0: + timeUs = 1000000000000 + contents = length 36692, hash D216076E + input buffer #1: + timeUs = 1000000066733 + contents = length 5312, hash D45D3CA0 + input buffer #2: + timeUs = 1000000200200 + contents = length 7735, hash 4490F110 + input buffer #3: + timeUs = 1000000133466 + contents = length 987, hash 560B5036 + input buffer #4: + timeUs = 1000000333666 + contents = length 6061, hash 736C72B2 + input buffer #5: + timeUs = 1000000266933 + contents = length 992, hash FE132F23 + input buffer #6: + timeUs = 1000000433766 + contents = length 4899, hash F72F86A1 + input buffer #7: + timeUs = 1000000400400 + contents = length 568, hash 519A8E50 + input buffer #8: + timeUs = 1000000567233 + contents = length 5450, hash F06EC4AA + input buffer #9: + timeUs = 1000000500500 + contents = length 1051, hash 92DFA63A + input buffer #10: + timeUs = 1000000533866 + contents = length 781, hash 36BE495B + input buffer #11: + timeUs = 1000000700700 + contents = length 4725, hash AC0C8CD3 + input buffer #12: + timeUs = 1000000633966 + contents = length 1022, hash 5D8BFF34 + input buffer #13: + timeUs = 1000000600600 + contents = length 790, hash 99413A99 + input buffer #14: + timeUs = 1000000667333 + contents = length 610, hash 5E129290 + input buffer #15: + timeUs = 1000000834166 + contents = length 2751, hash 769974CB + input buffer #16: + timeUs = 1000000767433 + contents = length 745, hash B78A477A + input buffer #17: + timeUs = 1000000734066 + contents = length 621, hash CF741E7A + input buffer #18: + timeUs = 1000000800800 + contents = length 505, hash 1DB4894E + input buffer #19: + timeUs = 1000000967633 + contents = length 1268, hash C15348DC + input buffer #20: + timeUs = 1000000900900 + contents = length 880, hash C2DE85D0 + input buffer #21: + timeUs = 1000000867533 + contents = length 530, hash C98BC6A8 + input buffer #22: + timeUs = 1000000934266 + contents = length 568, hash 4FE5C8EA + input buffer #23: + timeUs = 0 + flags = 4 + contents = length 0, hash 1 + outputBuffers: + count = 23 + output buffer #0: + timeUs = 1000000000000 + size = 36692 + rendered = false + output buffer #1: + timeUs = 1000000066733 + size = 5312 + rendered = false + output buffer #2: + timeUs = 1000000200200 + size = 7735 + rendered = false + output buffer #3: + timeUs = 1000000133466 + size = 987 + rendered = false + output buffer #4: + timeUs = 1000000333666 + size = 6061 + rendered = false + output buffer #5: + timeUs = 1000000266933 + size = 992 + rendered = false + output buffer #6: + timeUs = 1000000433766 + size = 4899 + rendered = false + output buffer #7: + timeUs = 1000000400400 + size = 568 + rendered = false + output buffer #8: + timeUs = 1000000567233 + size = 5450 + rendered = true + output buffer #9: + timeUs = 1000000500500 + size = 1051 + rendered = true + output buffer #10: + timeUs = 1000000533866 + size = 781 + rendered = true + output buffer #11: + timeUs = 1000000700700 + size = 4725 + rendered = true + output buffer #12: + timeUs = 1000000633966 + size = 1022 + rendered = true + output buffer #13: + timeUs = 1000000600600 + size = 790 + rendered = true + output buffer #14: + timeUs = 1000000667333 + size = 610 + rendered = true + output buffer #15: + timeUs = 1000000834166 + size = 2751 + rendered = true + output buffer #16: + timeUs = 1000000767433 + size = 745 + rendered = true + output buffer #17: + timeUs = 1000000734066 + size = 621 + rendered = true + output buffer #18: + timeUs = 1000000800800 + size = 505 + rendered = true + output buffer #19: + timeUs = 1000000967633 + size = 1268 + rendered = true + output buffer #20: + timeUs = 1000000900900 + size = 880 + rendered = true + output buffer #21: + timeUs = 1000000867533 + size = 530 + rendered = true + output buffer #22: + timeUs = 1000000934266 + size = 568 + rendered = true