fix(player): harden animated AVIF capture and player lifecycle - #98
Closed
bee-san wants to merge 3 commits into
Closed
fix(player): harden animated AVIF capture and player lifecycle#98bee-san wants to merge 3 commits into
bee-san wants to merge 3 commits into
Conversation
Accept ten-bit SDR input, normalize MediaCodec AV1 packets, remux them into animated AVIF, preserve native cancellation and cleanup, report fallback causes, and use FFmpegKit's SAF protocol for local video input. This squashes the complete fix/animated-avif-mediacodec-full series so AVIF work remains separate from the statistics feature.
* fix(player): bound isolated scene callback output * fix(player): isolate scene FFmpeg execution * fix(player): harden animated scene capture across devices * fix(player): guard startup and picture-in-picture * refactor(player): show scene mining progress as a top overlay * refactor(player): trim incidental complexity in scene isolation Fold SceneCommandCallbackDelivery into IsolatedSceneCommandService: the 128 KB Binder payload cap is kept, but the separate payload data class and the retry-with-empty-payload path go away — the retry only ever guarded an arbitrary exception, never a real TransactionTooLargeException, which the size cap already prevents. An oversized successful output now logs a distinct "dropping oversized output" line instead of masquerading as an ffmpeg/ffprobe failure. Drop the duplicate-request bookkeeping in the service: request IDs come from a single AtomicLong in the one main-process executor, so they cannot collide within a service process lifetime. Document why FFmpegKit runs in :scene_processing. The previous one-liner undersold an ABI-level duplicate-SONAME linker conflict between libmpv's and ffmpeg-kit's bundled libav*.so, which is not fixable by a mutex, load ordering, symbol visibility, or dlopen flags. Also record that AnimeDownloader and FFmpegUtils still run FFmpegKit in the main process, so this isolation protects only the scene-capture path. Cross-reference the manifest's android:process with SceneCommandProcess.SUFFIX so renaming one does not silently reintroduce full DI init in the FFmpeg-only child process. Remove selectAv1Encoder's maxOutputDimension parameter, a test-only knob production never passed. --------- Co-authored-by: Autumn Skerritt <skerraut@amazon.com>
* fix(player): use patched ffmpeg-kit for portable animated AVIF Point the ffmpeg-kit dependency at com.github.bee-san:ffmpeg-kit:1.17.1, a fork of jmir1/ffmpeg-kit 1.17 that backports one FFmpeg fix needed for animated AVIF scene mining: avformat/av1: fix uvlc loop past end of bitstream (FFmpeg e44d76f61f) libavformat/av1.c:uvlc() looped on 'while (get_bits_left(gb))'. When an earlier skip_bits_long() has already pushed the reader past the end of a truncated AV1 sequence header, get_bits_left() is negative -- which is truthy -- so the loop never terminates and leading_zeros climbs toward INT_MAX. uvlc() is reached from parse_sequence_header() via ff_isom_write_av1c(), the exact path that builds the av1C box when muxing av1_mediacodec output into AVIF. The fork is otherwise identical to upstream 1.17 (same FFmpeg n7.1 base, same components, same aniyomi SAF/custom-protocol patches). Fork sources and the idempotent android.sh patch loop: bee-san/ffmpeg-kit @ tag 1.17.1. Also correct the IsolatedSceneCommandExecutor doc comment: it claimed the process split avoids a duplicate-SONAME conflict because both AARs ship competing libav*.so. That is false -- aniyomi-mpv-lib ships no libav*.so; its libmpv.so DT_NEEDEDs the SONAMEs that ffmpeg-kit alone provides, so the two share one FFmpeg build (as the main-process AnimeDownloader/FFmpegUtils callers already do). The comment now records the real, unproven-here rationale (FFmpegKit global-state isolation, crash containment). * [verified] fix(player): mux MediaCodec AV1 directly to AVIF * fix(player): harden MediaCodec AVIF portability * build: consume MediaCodec reset ffmpeg-kit * build: consume immutable ffmpeg-kit artifact * build: consume AVIF first-frame guard artifact
This was referenced Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important
DRAFT — ACTIVELY BEING TESTED. This is not a merge-ready claim.
The Kotlin/JVM, native FFmpeg, and CI paths have substantial automated coverage, but the exact upstream branch still needs a real-device matrix across Qualcomm, MediaTek, Exynos, and Tensor MediaCodec implementations. Frame-zero decode, looping, cancellation, process death, SAF providers, and player lifecycle behavior are still being tested. Devices without a qualifying hardware AV1 encoder intentionally use the existing still-image fallback.
What this PR does
This upstream-focused branch contains only the three animated-scene/player-hardening commits from the fork. It deliberately excludes the unrelated immersion-statistics work.
It hardens the complete player → capture → FFmpegKit → MediaCodec → animated-AVIF path rather than changing only the AVIF container writer:
av1_mediacodecencoder;Unsupported, unsafe, or failed animated captures still degrade to the already-captured still-image fallback.
Pipeline and architecture
Freeze capture state
Resolve and constrain input
content://documents, and public HTTP(S)/HLS inputs.Probe and encode in a private worker process
:scene_processingservice.saf:protocol inside the worker.Select hardware AV1 geometry
Encode once and validate
Bugs found and fixes
Native AV1 and animated AVIF
FFmpeg could hang while constructing
av1Cfrom malformed or truncated AV1 sequence-header data.com.github.bee-san:ffmpeg-kit:1.17.8.1, which includes the AV1 UVLC bounds fix.MediaCodec AV1 configuration was not consistently usable by the AVIF muxer.
AV1CodecConfigurationRecord.Dummy-frame extradata generation could leave encoder GOP state referring to the discarded frame.
flush()as undefined. The fork now performsstop → configure → startafter extradata generation.A vendor could emit a non-key first packet and FFmpeg could omit
stssboth for all-key and zero-key streams.stsstable unless sample 1 is a sync sample.The earlier raw-OBU normalization/remux design required an intermediate file and a second FFmpeg pass.
Ten-bit SDR input was rejected solely because it was ten-bit.
yuv420p; this does not claim ten-bit output preservation.Device portability
A fixed 640×640 capability check did not represent portrait, landscape, square, odd-sized, minimum-size, alignment, or block-budget-constrained encoders.
Codec alignment could squash visible content or request unsupported dimensions.
Selecting the first advertised AV1 encoder could pick a lower-quality or unusable implementation.
Embedded MP4 text subtitles could fail under the decoder whitelist even though subtitles are not output.
mov_textto the bounded decoder whitelist.Input, security, and diagnostics
Reopening a SAF descriptor via
/proc/self/fd/Ncould lose the grant and fail withEACCES.saf:protocol in the same worker process that executes FFmpeg.Remote scene processing could forward credentials or unconstrained extension arguments into FFmpeg.
Scene failures silently fell back to a still, making vendor-specific failures difficult to diagnose.
SceneMiningtraces for request rejection, probe results, encoder selection, native failure, validation, timeout, cancellation, and cleanup.Large ffprobe callbacks could exceed Binder transaction limits.
Cancellation and resource ownership
Coroutine cancellation could close an input descriptor or delete output while native FFmpeg still used it.
Cancellation after successful validation but before result delivery could orphan a file.
Argument construction, probing, validation, partial output, binding failure, or native exceptions could leak descriptors/files or bypass the still fallback.
Broader player and app lifecycle fixes
These are included because scene capture exposed races and framework failures outside the AVIF parser itself:
Playback could call
loadfilebefore the MPV surface existed or while it was being torn down.Video changes could arrive off the main thread.
setVideodispatches to the UI thread before touching player/activity state.External MPV config resolution could crash after a missing path or lost storage permission.
PiP framework calls could throw
IllegalStateExceptioneven after feature detection.The FFmpeg-only worker process repeated full DI, Conscrypt, and WebView initialization.
A modal scene dialog obscured playback and made progress/cancellation state unclear.
Dependency and provenance
The app pins
com.github.bee-san:ffmpeg-kit:1.17.8.1.fe66560bd45e28f1a80449568d31b54986601d8836d0c4b3d2158cde68caf4b2The fork also pins and checksum-verifies the native inputs, including
gas-preprocessor, and preserves the requiredsmart-exception-java:0.2.1POM dependency.Automated coverage
The affected test files contain 70 tests, 52 more than upstream, covering:
mov_text;Native FFmpeg regression coverage independently verifies the full patch stack against pinned FFmpeg n7.1, including RED/GREEN checks for MediaCodec restart behavior and rejection of a non-key first AVIF packet.
Current verification status
git diff --check upstream/main...HEAD./gradlew :app:assembleDebug --no-build-cachefate-movencregression review passed independently.upstream/main:ReaderOcrSourceTest.kt:40-41: missingmokuroAvailableargument.spotlessCheck: currently blocked by unrelated formatting violations already present onupstream/main, including untouched files underdomainandpresentation-widget(for exampleHideAnimeCategory.kt,RenameAnimeCategory.kt,ReorderAnimeCategory.kt,UpdatesWidget.kt, andGlanceUtils.kt).30687964886stopped at that baseline format check before build/test execution. None of the files reported by the failingdomain:spotlessKotlinChecktask are changed by this PR.The unit-compile and formatting blockers were reproduced in a clean
upstream/mainworktree. They are reported here so this draft does not imply that every upstream check currently passes.Active testing before marking ready
adb logcat -s SceneMiningremains useful without exposing signed paths or credentials.Known limitations / non-claims
aniyomi-mpv-lib.Review history
This work was developed and reviewed in the fork through:
This upstream PR remains a draft specifically so the active device/integration test matrix can be completed and results added without presenting the current evidence as universal portability proof.