Extract SKLottiePlayer and add Blazor Lottie support#365
Extract SKLottiePlayer and add Blazor Lottie support#365
Conversation
source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimationFailedEventArgs.shared.cs
Outdated
Show resolved
Hide resolved
source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimationFailedEventArgs.shared.cs
Outdated
Show resolved
Hide resolved
source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimation.shared.cs
Outdated
Show resolved
Hide resolved
mattleibow
left a comment
There was a problem hiding this comment.
@copilot due to a somewhat unfortunate naming, the maui project has a assembly name of SkiaSharp.Extended.UI. And we also can't move the enum the way it is as the namespace is a breaking change.
Please move the enum back to the maui project.
We could make a new unified SKLottieRepeat readonly struct that has a private ctor and static methods - SKLottieRepeat.Never, SKLottieRepeat.Reverse(int count = -1), SKLottieRepeat.Restart(int count = -1).
internally we can have a private enum maybe. And then we can expose bool properties IsRepeating and IsReverseRepeating/IsRestartRepeating.
Done in 3d1b22a.
|
| player.Repeat = RepeatMode == SKLottieRepeatMode.Reverse | ||
| ? SKLottieRepeat.Reverse(RepeatCount) | ||
| : SKLottieRepeat.Restart(RepeatCount); | ||
| player.AnimationSpeed = AnimationSpeed; |
There was a problem hiding this comment.
@copilot Why are we passing in things here and all that with the syc things. Can we not use the bindable property updates to the player? And maybe we can have an AnimationUpdate event in player to pass things back p to the view? Lets try design this better so we don't need the "locks
or "guard fields"
There was a problem hiding this comment.
Done in 963a708. The design is now fully event-driven with no guard fields:
Settings → Player (via propertyChanged callbacks):
RepeatModeProperty/RepeatCountProperty→OnRepeatPropertyChanged→player.Repeat = ...AnimationSpeedProperty→OnAnimationSpeedPropertyChanged→player.AnimationSpeed = ...
State → View (via AnimationUpdated event):
SKLottiePlayer.Progresssetter raisesAnimationUpdatedafter every change- View's
OnPlayerAnimationUpdatedsetsDuration,Progress,IsCompletefrom the player
Re-entry prevention (no guard field):
OnProgressPropertyChangedchecksplayer.Progress == newProgressand returns early if already in sync (covers the case whereAnimationUpdatedtriggers a bindable set that would otherwise loop back into the player)- MAUI's own
SetValueequality check also prevents re-firing for unchanged values
Update() is now a single line:
protected override void Update(TimeSpan deltaTime) => player.Update(deltaTime);| /// and may trigger completion logic (repeats, AnimationCompleted event). | ||
| /// Raises <see cref="AnimationUpdated"/> after each change. | ||
| /// </summary> | ||
| public TimeSpan Progress |
There was a problem hiding this comment.
@copilot maybe this can be a getter only style? The callers can just use UpdateProgress to indicate things are happening.
There was a problem hiding this comment.
Done in abd8c6a. Progress is now getter-only (public TimeSpan Progress => _progress) and a public Seek(TimeSpan position) method is the single entry point for changing the playback position. All internal callers (Update, Reset, restart-repeat loop) use Seek(), and SKLottieView.OnProgressPropertyChanged calls player.Seek(newProgress) instead of assigning the property.
|
|
||
| private void Reset() | ||
| { | ||
| isResetting = true; |
There was a problem hiding this comment.
Removed in 6c74782. Reset() now sets _progress, Duration, IsComplete, isInForwardPhase, and repeatsCompleted directly without going through Seek(), so the guard flag isn't needed. Also fixed a related bug where AnimationCompleted re-fired on every Update() call after completion — it now only fires on the false → true transition.
mattleibow
left a comment
There was a problem hiding this comment.
@copilot please make sure you are doing good things here. The lottie player needs tests. the player should not be designed around the view, instead the API should be clean, neat and be self-contained.
Make sure you can write tests for this, maybe even start with the tests for the maui view.
Lets make properties simple and getter ownly. The methods can be the ones to trigger complex logic.
- Fix Never repeat mode: isFinishedRun was using reverseFinishPoint (atStart) for Never mode with positive speed, which is always false when moving forward; now uses movingForward ? atEnd : atStart for all non-Reverse modes - Fix null animation in UpdateProgress: was spuriously setting IsComplete=true when Seek() called before SetAnimation(); now returns early (no-op) - Fix double AnimationUpdated per restart cycle: UpdateProgress called Seek() to reset position, which fired the event internally; now resets position directly via Progress + SeekFrameTime to avoid the duplicate event - Fix SKAnimatedCanvasView double loop start: _isAnimationEnabled now initializes to true (matching IsAnimationEnabled parameter default) so OnParametersSet does not detect a change and start the loop before OnAfterRender(firstRender) - Fix SKAnimatedCanvasView.DisposeAsync: broadened catch from OperationCanceledException to Exception to prevent disposal chain failures when a subclass UpdateAsync throws unexpected exceptions - Fix Blazor Lottie sample: store AnimationLoop task and await it in DisposeAsync before disposing CTS, preventing InvokeAsync calls on a disposed renderer - Add regression tests for all fixed issues (33 total, all passing) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Skottie.Animation wraps a native resource (inherits SKObject/IDisposable). The previous DisposeAsync cancelled and awaited the loop task and disposed the CTS, but never disposed loadedAnimation, leaking native memory on every page navigation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Clamp position in Seek() to [Zero, Duration] to match documented behavior - Await _loopTask in StopLoopAsync to prevent concurrent animation loops - Guard against async OnPaintSurface handlers that would race with canvas lifecycle - Wrap UpdateAsync inside InvokeAsync for Blazor Server thread safety - Synthesize exception when Lottie parser returns null Animation - Map RepeatCount==0 to SKLottieRepeat.Never instead of Restart(0) - Add Seek_WithNegativePosition_ClampsToZero and Seek_WithPositionBeyondDuration_ClampsToDuration tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When switching from Reverse to Restart mode while playing with negative speed, isInForwardPhase was stale (false), causing Update() to move in the wrong direction and the Restart reset to put progress at the same boundary -- freezing the animation. Now Repeat is a backing-field property that resets isInForwardPhase, repeatsCompleted, and IsComplete on change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create SKLottieView as a high-level Blazor component that encapsulates SKAnimatedCanvasView + SKLottiePlayer, mirroring the MAUI SKLottieView API. Parameters: Source, RepeatMode, RepeatCount, AnimationSpeed, IsAnimationEnabled. Events: AnimationLoaded, AnimationCompleted, AnimationFailed, AnimationUpdated. Add OnUpdate EventCallback to SKAnimatedCanvasView for composition support. Refactor Lottie.razor sample to use SKLottieView, reducing ~130 lines of manual player/timer/render boilerplate to ~10 lines of component usage. Move inline styles to Lottie.razor.css for CSS isolation. Also widen the speed slider range to support negative speeds (reverse playback). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only reset isInForwardPhase when switching away from Reverse mode (to Restart/Never), not when changing Reverse count. This prevents an unwanted direction flip mid-playback while still guarding against the Reverse→Restart freeze bug. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. SKAnimatedCanvasView now calls Invalidate() on the inner SKCanvasView to force canvas repaint. StateHasChanged() alone doesn't trigger SkiaSharp canvas repainting in Blazor WASM. 2. Move isInForwardPhase reset from Repeat setter to the Restart branch of UpdateProgress. Direction now only changes when the animation actually hits a boundary, not when the Repeat property changes. This prevents abrupt mid-animation direction flips while still fixing the Reverse->Restart freeze bug. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add lottie-player.md: platform-agnostic SKLottiePlayer docs under SkiaSharp.Extended with frame loop, speed, repeat, seeking, events - Add blazor-lottie.md: SKLottieView and SKAnimatedCanvasView for Blazor WebAssembly with quick start, controls, events, sizing - Rework lottie.md: focus on MAUI, add speed/direction section, add cross-links to Player and Blazor docs, remove inline player docs - Update toc.yml: add Lottie Player, new Blazor section, fix hrefs - All three docs cross-link to each other Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename lottie.md → lottie-maui.md and blazor-lottie.md → lottie-blazor.md for consistent naming. Update all cross-links and toc.yml. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Nest Lottie and Confetti under 'Controls' subgroup for both MAUI and Blazor sections in toc.yml for cleaner navigation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move Lottie under a 'CONTROLS' section header in the sidebar nav and add a Controls card group on the homepage, separating it from the core Extended utility demos. Revert docs toc.yml back to flat. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix lottie-player.md links: lottie.md → lottie-maui.md, blazor-lottie.md → lottie-blazor.md (missed in rename) - Change code fences from ```razor to ```cshtml-razor in lottie-blazor.md for proper docfx syntax highlighting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The polling loop was dying because async bash sessions without detach:true are killed on session idle. Added critical note about using detach:true and documented PID-based termination. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add CancellationTokenSource to LoadAnimationAsync to prevent race conditions when Source changes rapidly (stale out-of-order results) - Cancel in-flight HTTP requests on dispose to prevent resource leaks and post-dispose callbacks - Clear player animation reference in DisposeAsync before disposing the native handle to prevent use-after-free during teardown Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add poll_loop.sh for continuous detached background polling - Restructure SKILL.md with cost optimization & agent architecture (cheap monitoring agent + main agent escalation pattern) - Update poll_comments.sh (minor trailing newline fix) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Simplified architecture: main agent launches a cheap gpt-5-mini background agent that runs poll_comments.sh in an inline bash loop (30 iterations, 60s interval). No more detached poll_loop.sh process. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Align Blazor RepeatCount default to 0 (match MAUI play-once behavior) - Fix use-after-free in LoadAnimationAsync: clear player before disposing - Fix MAUI AnimationCompleted event: sync IsComplete/Progress/Duration before firing, so subscribers see consistent state - Fix broken lottie.md link in docs/index.md (renamed to lottie-maui.md) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Looking into this — testing testing |
b397e71 to
2caa4d5
Compare
|
Done ✅ — Rebased on latest |
mattleibow
left a comment
There was a problem hiding this comment.
@copilot please adrees each of my comments in this review. Also, please ensure the projects build and all tests pass.
|
|
||
| <div class="nav-group-header px-3">CONTROLS</div> | ||
|
|
There was a problem hiding this comment.
This needs to be removed
There was a problem hiding this comment.
Done in 6c7ba12. Removed the duplicate CONTROLS group header — both Pixel Comparer and Lottie now appear under a single CONTROLS heading.
| .bi-play-circle-nav-menu { | ||
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M6.271 5.055a.5.5 0 0 1 .52.038l3.5 2.5a.5.5 0 0 1 0 .814l-3.5 2.5A.5.5 0 0 1 6 10.5v-5a.5.5 0 0 1 .271-.445z'/%3E%3C/svg%3E"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Duplicate closing brace?
There was a problem hiding this comment.
Fixed in 6c7ba12. Removed the extra } after .bi-play-circle-nav-menu.
| <ProjectReference Include="..\..\source\SkiaSharp.Extended\SkiaSharp.Extended.csproj" /> | ||
| <ProjectReference Include="..\..\source\SkiaSharp.Extended.UI.Blazor\SkiaSharp.Extended.UI.Blazor.csproj" /> |
There was a problem hiding this comment.
Done in 6c7ba12. Added an explicit ProjectReference to SkiaSharp.Extended core alongside SkiaSharp.Extended.UI.Blazor.
| /// previous frame. Use this to update animation state from a parent component. | ||
| /// </summary> | ||
| [Parameter] | ||
| public EventCallback<TimeSpan> OnUpdate { get; set; } |
There was a problem hiding this comment.
This needs to be Action of T
There was a problem hiding this comment.
Done in 6c7ba12. Changed OnUpdate to Action<TimeSpan>?. Updated SKLottieView.HandleUpdate to match the new void signature.
| /// Subscribe here to render your content onto the <see cref="SKCanvas"/>. | ||
| /// </summary> | ||
| [Parameter] | ||
| public EventCallback<SKPaintSurfaceEventArgs> OnPaintSurface { get; set; } |
There was a problem hiding this comment.
This needs to be Action of T
There was a problem hiding this comment.
Done in 6c7ba12. Changed OnPaintSurface to Action<SKPaintSurfaceEventArgs>?. The synchronous-only constraint is now enforced by the type system rather than a runtime check.
…oject ref, change EventCallback to Action<T> for SKAnimatedCanvasView Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
All review comments addressed in Security summary: CodeQL found 0 alerts. |
CONTROLSgroup header inNavMenu.razor(both Pixel Comparer and Lottie now appear under one header)NavMenu.razor.css.bi-play-circle-nav-menuruleProjectReferencetoSkiaSharp.Extendedcore inSkiaSharpDemo.Blazor.csproj(alongsideSkiaSharp.Extended.UI.Blazor)SKAnimatedCanvasView.OnUpdatefromEventCallback<TimeSpan>toAction<TimeSpan>?SKAnimatedCanvasView.OnPaintSurfacefromEventCallback<SKPaintSurfaceEventArgs>toAction<SKPaintSurfaceEventArgs>?SKLottieView.HandleUpdatetovoidto match newAction<TimeSpan>signature; use fire-and-forget forEventCallbackinvocations💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.