Add an animated-image element for GIF / APNG / animated WebP - #3
Open
shanerbaner82 wants to merge 1 commit into
Open
Add an animated-image element for GIF / APNG / animated WebP#3shanerbaner82 wants to merge 1 commit into
shanerbaner82 wants to merge 1 commit into
Conversation
Core's `image` element renders only the first frame of an animated file: SwiftUI's `AsyncImage` and Coil's default decoders both stop there. The apparent workaround — pointing `video-player` at an MP4 — does not survive a list, because playback surfaces are scarce. iOS shares a single AVPlayer, so `preparePlayer` tears down the previous one and every earlier surface falls back to the renderer's black fill; Android gives each element its own VideoView but runs out of hardware decoders after a handful. `animated-image` decodes to ordinary bitmaps instead, so a feed can show dozens. iOS drives ImageIO's `CGAnimateImageDataWithBlock` into a UIImageView layer, adding no dependency; Android registers Coil's `AnimatedImageDecoder` (API 28+), falling back to `GifDecoder` at the plugin's API 26 floor. Also register web renderers for both components. The web target has no rendering for a plugin's element types unless the plugin supplies one, and an unknown leaf renders as nothing — so `video-player` was silently invisible in the browser. Both now have HTML equivalents (`<video>` and `<img>`). Drive-by: README `@press` -> `@tap` in the overlay example. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Why
Core's
imageelement renders only the first frame of an animated file — SwiftUI'sAsyncImageand Coil's default decoders both stop there.The apparent workaround, pointing
video-playerat an MP4, does not survive a list. Playback surfaces are scarce:MediaPlayerManageris a singleton holding oneplayer/source. A secondvideo-playerwith a different source hitsteardown(), destroying the AVPlayer the first surface is still displaying. The dead surfaces then renderMediaPlayerVideoRenderer'sColor.blackfill, so extra GIFs show as solid black rectangles.VideoView, but concurrent hardware decoders run out after a handful; later ones quietly stop animating.Found while building a GIF picker into a timeline: past ~4 posted GIFs, previews froze on Android and went black on iOS.
What
<animated-image>decodes to ordinary bitmaps, so it allocates no playback session and a feed can show dozens.CGAnimateImageDataWithBlock(ImageIO) feeding frames to aUIImageViewlayer — no new dependencyAnimatedImageDecoder(API 28+), falling back toGifDecoderat the plugin's API 26 floor<img>, which browsers animate nativelyAttributes:
src,fit(same mapping asimage),alt,autoplay,loop.loopis iOS-only — Android honours the loop count baked into the file. Addsio.coil-kt.coil3:coil-gifto the Android dependencies, since core shipscoil-composebut not the animated decoder.Guidance in the README: use
video-playerfor video with audio and transport controls,animated-imagefor looping imagery, and always prefer the latter when several animations share a screen.Also: web renderers for both components
The web target has no rendering for a plugin's element types unless the plugin supplies one, and an unknown leaf renders as nothing.
video-playerwas therefore silently invisible in the browser.MediaPlayerServiceProvidernow registers HTML renderers for both —<video playsinline>and<img>— guarded byclass_existsso it is a no-op withoutmobile-web.Tests
37 pass. The existing
registers the video_player componenttest assertedtoHaveCount(1)on the manifest, so it now looks components up by type instead of index. Added coverage for the new component, the Coil dependency, and a check that every manifest component ships renderer files for both platforms.Verification status
Verified end to end on the web target (element renders, GIFs load and animate, many coexist) and through component tests. The Swift and Kotlin renderers are not yet exercised on a simulator — they need a full rebuild since native code only compiles at build time. Worth a device pass before merge.
Drive-by: README
@press→@tapin the overlay example.🤖 Generated with Claude Code