* Add a WebCodecs H.264 encoder and MP4 muxer for web video export Browsers cannot spawn the bundled ffmpeg, so the web beta needs its own encoder. Two building blocks, not wired into the exporter yet: - Mp4H264Muxer: pure-Dart faststart MP4 writer for one H.264 track (ftyp, moov, mdat; run-length stts, stss, one chunk). Verified against ffprobe and an ffmpeg decode of a real libx264 stream. - WebCodecsMp4Encoder: VideoEncoder bindings that turn RGBA frames into a constant-frame-rate MP4, repeating each frame for its duration, scaling through an OffscreenCanvas, and waiting on the encode queue. The export exceptions move to video_export_errors.dart so the web file avoids dart:io; ffmpeg_video_encoder.dart re-exports them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Export video on the web beta, and of cloud strategies anywhere Video export was blocked on the web because it encodes with a bundled ffmpeg process, which a browser cannot start. It was also broken for cloud strategies everywhere: the dialog listed pages from, and rendered, the local Hive box, where cloud strategies never are, so it showed no pages and exported nothing. Encoding moves behind VideoFrameSink. VideoExporter renders frames and hands them to a sink: - FfmpegVideoSink (desktop) is the existing PNG-sequence + ffconcat + ffmpeg pipeline, moved out of the exporter unchanged. - The browser sink encodes with WebCodecs into an MP4 as frames arrive (previous commit) and hands the bytes back, which the dialog downloads through FilePicker.saveFile(bytes:). Presets keep their sizes and bitrates; the browser has no second pass, so there is no size retry. A browser without H.264 WebCodecs is told so before anything renders. What gets rendered (loadVideoExportSource): a local strategy is saved and read back from the library as before. A cloud strategy's other pages live only on the server, so the export saves, waits (up to 20 s) for this device's changes to land, i.e. for the strategy to show as synced, and then reads the whole strategy from the server. If the work does not land it stops and says so rather than export a video missing edits the user can see. A signed-out reader who opened a share link reads through that link (fetchFullSnapshot takes the shareToken the server already accepts). Images go through the same resolution as screenshots: files as they are, cloud URLs fetched and decoded before rendering, held for the whole export and handed to the capture container through captureImageSourcesProvider. The dialog's page list comes from wherever the strategy lives, and it shows its progress dialog from the start, so saving and syncing are visible and cancellable. Video export leaves PlatformPolicy.webBeta.desktopOnly. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Say what colour web videos are, and export only what has landed Review and browser-run follow-ups to web video export. Colour: Edge's hardware H.264 encoder writes full-range BT.709 samples with no colour description in the bitstream. Players then assume limited range and crush Icarus's darks to black (the map background went from 15 to 0). The encoder reports the colour it wrote in its decoder config, and the muxer now carries it in the sample entry's colr/nclx box, which ffmpeg, Chromium and QuickTime read. Measured in Edge: frames decode within 2 levels of the screenshot's pixels, in ffmpeg and in browser playback. Cloud source: forceSaveNow on a cloud strategy with nothing to send marks the save state pending, and since no queue state changes, nothing clears it, so waiting on canLeaveSafely could time out on a synced strategy and leave the chip saying syncing. The export now flushes the open page through the page session (which sets no chip state) and reads the op and media queues directly: nothing queued, in flight or uploading means sent. Work held for review fails at once rather than after 20 s. Cancelling now reaches the sync wait and image loading, and a cancel that arrives while the export wraps up no longer downloads the video. A selected page a teammate deleted fails the export with a message and refreshes the page list instead of being left out silently. The ffmpeg integration tests skip when ffmpeg lacks the H.264 encoder they need. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Stop a video export between images, and trust only a readable media outbox Second review follow-up. Cancelling now also stops image loading between images (and after the page flush), releasing what was already decoded, instead of downloading and decoding the rest first. And the export only counts this device's work as sent when the media outbox is readable too: an upload record it could not read is not in its job list, so an empty list alone cannot say the upload landed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Render video exports in the capture container that leaves cloud sync alone The exporter built its offscreen container by hand, so its strategy provider built a real auth provider, which set or cleared auth on the app's one Convex client and tore it down on dispose: every video export signed the editor out of cloud sync. That was true on desktop before this branch too, for anyone signed in. It now uses the screenshot's createCaptureContainer, with its inert auth, and a test requires an export to make no call to the Convex auth API. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Expect downloads to start together when a video export is cancelled Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Check browser video support at the size and bitrate the export will use Greptile follow-up: the preflight asked whether the browser could encode 1080p at 8 Mbps whatever the preset and length, so a browser that could encode what the export actually needs (a long Potato video drops to 720p at 250 kbps) was refused. The check and the encoder now share one function for the output size and bitrate, and the dialog passes the planned length. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Download a video export's images through the capture's own client The fetcher now receives the capture's client (#232); passing it to downloadCloudImageBytes keeps one finished download from closing it for the rest. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
23 files changedLine totals unavailable