Resuming or seeking a large MKV on the Android FFmpeg demux path buffered for minutes with no error and no fallback. The cause was the seam, not the container: libavformat's demuxers own their seeking — end-of-file index, back to the header, binary search — while media3's Extractor is forward only, so every backward jump had to abort the in-flight libavformat call with a synthetic IO error and replay the whole open. matroskadec attempts its deferred Cues parse exactly once per context, so that abort burned the index for the session and every later seek fell back to libavformat's linear generic scan, reading the file up to the target. That protocol had produced a bug of this shape repeatedly (a NULL deref resuming find_stream_info across an abort, a use-after-free on fallback teardown, matroskadec resync skipping the keyframe cluster) and it was held together by four empirical retry budgets whose exhaustion degraded silently — which is why #2096 looked like an unbounded spinner instead of an error. Give libavformat what it requires instead. FfmpegRandomAccessSource serves any absolute position from a second DataSource built by the same factory media3 uses, cloning the DataSpec media3 opened for the item so Cronet, download caches, SAF and per-item request headers still apply. Reads that the loader's ExtractorInput can serve still go through it, so sample delivery keeps feeding media3's byte accounting, back-pressure and load-error policy; the extractor nudges the loader to follow the demuxer with one RESULT_SEEK, which is an optimization that can never stall playback because the read succeeds either way. Deleted with the protocol: the synthetic AVERROR_NEED_SEEK unwind, the 24 MB replay cache, the Cues priming seek and its read guard, the sticky AVIO error clearing, and all four budgets (seekAttempts, stickyRecovers, MAX_RECONCILES, MAX_OPEN_LOADER_ROUND_TRIPS). Opens run straight through, seeks are one avformat_seek_file on the loader thread, and an IO failure is an IOException that reaches media3's retry policy instead of a silent degradation. The demuxer is now the only component that resolves seeks, so the extractor's sample-derived seek index is gone too. Verified on a SEI Robotics Android TV box against Jellyfin: the DV P8.1 28.4 GB MKV resume that started at a 4m28s spinner opens in 1.1 s and resolves its seek in one call, a mid-playback scrub resolves in 1 ms from the in-memory index, a 2 GB MKV resume opens in 0.5 s, fresh playback needs no index read at all, and MP4/AVI still open and deliver. New coverage: FfmpegRandomAccessSourceTest pins the read/reopen contract, and FfmpegExtractorSeekTest asserts on-device that a cued seek lands on target without walking the file and that a cueless file still reaches it. close #2096
32 lines
1.7 KiB
Prolog
32 lines
1.7 KiB
Prolog
# Flutter turns minification on for every release build (FlutterPlugin sets
|
|
# releaseBuildType.isMinifyEnabled), and appends this file when it exists. Anything the
|
|
# app reaches only by name — reflection or JNI — therefore needs an explicit keep here.
|
|
|
|
# The bundled Media3 FFmpeg audio decoder (ALAC, DTS, DTS-HD, TrueHD, ...).
|
|
#
|
|
# DefaultRenderersFactory instantiates FfmpegAudioRenderer through Class.forName and no
|
|
# app code references it, so R8 shrinks the class away; media3's own consumer rules only
|
|
# -keepclassmembers its constructor, which neither keeps the class nor pins its name.
|
|
# ffmpeg_jni.cc separately resolves FfmpegAudioDecoder and its growOutputBuffer callback
|
|
# by name in JNI_OnLoad, and returns JNI_ERR when either is missing, which fails the whole
|
|
# System.loadLibrary("ffmpegJNI") call.
|
|
#
|
|
# Without these keeps a release build silently loses every codec this decoder adds:
|
|
# TrueHD/DTS-HD land on MediaCodecAudioRenderer, which has no decoder for them, and
|
|
# playback bails to the mpv fallback and loses ExoPlayer's Dolby Vision handling (#1703).
|
|
-keep class androidx.media3.decoder.ffmpeg.** { *; }
|
|
|
|
# growOutputBuffer's JNI descriptor names this type, so it may not be renamed either.
|
|
-keep class androidx.media3.decoder.SimpleDecoderOutputBuffer { *; }
|
|
|
|
# ffmpeg_demuxer_jni.cc resolves the AVIO input proxy's callbacks by name
|
|
# (FindClass on the interface, GetMethodID for position/read/readAt/length).
|
|
# Keeping the interface members pins the names on every implementation,
|
|
# including the anonymous proxy inside FfmpegExtractor.
|
|
-keep interface com.edde746.plezy.exoplayer.FfmpegDemuxerJni$Input {
|
|
long position();
|
|
int read(byte[], int);
|
|
int readAt(long, byte[], int);
|
|
long length();
|
|
}
|