fix: audit src/js and add a frame-accurate export path
Two halves of one pass; TODO-FIXES.md lists every finding. The audit fixed, among ~90 items: MP4 export dead-ending and locking the UI behind a stuck "Downloading..." button; height keyframes storing the width; shadow defaults and keyframes storing undefined because fabric's get() is not a path getter; every letter animation writing to the last letter; keyframe times drifting on each drag because data-time was layer-relative on expanded rows; O(n^2 log n) playback, now indexed once per frame; and a save() that rebuilt the record canvas on every edit, now debounced. Exports also lost audio-layer sound outright: MediaRecorder records only the first audio track, so every source now mixes through one AudioContext into one destination. The new half is src/js/render.js, an offline renderer. Real-time capture was the root cause of dropped frames on heavy scenes and smeared video layers: a bare currentTime assignment is async, so drawing straight after it captures the previous frame. Each frame is now seeked, awaited on 'seeked', drawn, and pushed through a VideoEncoder; audio is mixed in one OfflineAudioContext pass and encoded to Opus. webm-writer2.js gained a second Opus track and lost three bugs, including a MAX_CLUSTER_DURATION_MSEC of ~58 days that overflowed the signed 16-bit block timecode past ~32s. Real-time capture remains the fallback: the hand-rolled muxer's output is decoded in a <video> element before being handed over, and any failure returns null so record() falls back transparently. test/webm-muxer.test.js parses the muxer output with an EBML reader in plain Node. It caught the fixed-256-byte header overflow that inspection missed. Not verified in a browser: node --check passes on every script and the muxer test passes, but a running Chrome held the Playwright profile lock, so the manual pass listed at the end of TODO-FIXES.md is still outstanding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+31
-15
@@ -1,15 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<meta charset="utf-8">
|
||||
<!-- Third-party assets live in vendor/ so the app works offline. -->
|
||||
<!-- Run `npm run vendor` to populate that directory. -->
|
||||
<link rel="stylesheet" href="vendor/inter.css">
|
||||
<link rel="stylesheet" href="nice-select.css">
|
||||
<link ref="stylesheet" href="range-slider.min.css">
|
||||
<link rel="stylesheet" href="magic-check.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/nano.min.css"/>
|
||||
<link rel="stylesheet" href="vendor/pickr-nano.min.css"/>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>Motionity - The web-based motion graphics editor for everyone</title>
|
||||
<meta name="title" content="Motionity - The web-based motion graphics editor for everyone">
|
||||
@@ -79,6 +81,18 @@
|
||||
<input class="magic-radio" type="radio" name="radio" value="image" id="image-format">
|
||||
<label for="image-format">Image</label>
|
||||
</div>
|
||||
<div id="framerate-row">
|
||||
<p class="subheader">Frame rate</p>
|
||||
<select id="framerate">
|
||||
<option value="12">12 fps</option>
|
||||
<option value="15">15 fps</option>
|
||||
<option value="24">24 fps</option>
|
||||
<option value="25">25 fps</option>
|
||||
<option value="30" selected>30 fps</option>
|
||||
<option value="50">50 fps</option>
|
||||
<option value="60">60 fps</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="download-real">Download</div>
|
||||
</div>
|
||||
<div id="import-export-modal">
|
||||
@@ -168,7 +182,7 @@
|
||||
<div id="filters">
|
||||
<div id="filters-container">
|
||||
<div id="filters-header">
|
||||
<div id="filters-title">Filters</div>
|
||||
<div class="filters-title">Filters</div>
|
||||
<img src="assets/close.svg" id="filters-close">
|
||||
</div>
|
||||
<select id="filters-list">
|
||||
@@ -183,7 +197,7 @@
|
||||
<option value="Polaroid">Polaroid</option>
|
||||
</select>
|
||||
<hr>
|
||||
<div id="filters-title">Adjustments</div>
|
||||
<div class="filters-title">Adjustments</div>
|
||||
<div id="reset-filters"><img src="assets/repeat.svg"> Reset</div>
|
||||
<div class="filter-row">
|
||||
<th class="name-col">Brightness</th>
|
||||
@@ -216,7 +230,7 @@
|
||||
</th>
|
||||
</div>
|
||||
<hr>
|
||||
<div id="filters-title">Chroma key</div>
|
||||
<div class="filters-title">Chroma key</div>
|
||||
<div class="filter-row">
|
||||
<th class="name-col">Status</th>
|
||||
<th class="value-col">
|
||||
@@ -242,7 +256,7 @@
|
||||
</th>
|
||||
</div>
|
||||
<hr>
|
||||
<div id="filters-title">Stylize</div>
|
||||
<div class="filters-title">Stylize</div>
|
||||
<div class="filter-row">
|
||||
<th class="name-col">Noise</th>
|
||||
<th class="value-col">
|
||||
@@ -289,7 +303,7 @@
|
||||
<div id="keyframe-properties">
|
||||
<div id="easing">
|
||||
<p class="property-title">Keyframe easing</p>
|
||||
<select id="easing">
|
||||
<select id="easing-select">
|
||||
<option value="linear">Linear</option>
|
||||
<option value="easeInQuad">Ease in</option>
|
||||
<option value="easeOutQuad">Ease out</option>
|
||||
@@ -366,16 +380,16 @@
|
||||
<video id="test-video"></video>
|
||||
<input id="emptyInput" value=" " style="opacity:0">
|
||||
<script src="js/libraries/localbase.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.9.6/lottie.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@simonwep/selection-js/lib/selection.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="vendor/lottie.min.js"></script>
|
||||
<script src="vendor/selection.min.js"></script>
|
||||
<script src="vendor/jquery.min.js"></script>
|
||||
<script src="js/libraries/sortable.min.js"></script>
|
||||
<script src="js/libraries/range-slider.min.js"></script>
|
||||
<script src="js/libraries/jquery.nice-select.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/460/fabric.min.js"></script>
|
||||
<script src="vendor/pickr.min.js"></script>
|
||||
<script src="vendor/fabric.min.js"></script>
|
||||
<script src="js/libraries/anime.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
|
||||
<script src="vendor/webfont.js"></script>
|
||||
<script src="js/init.js"></script>
|
||||
<script src="js/ui.js"></script>
|
||||
<script src="js/align.js"></script>
|
||||
@@ -383,6 +397,8 @@
|
||||
<script src="js/database.js"></script>
|
||||
<script src="js/lottie.js"></script>
|
||||
<script src="js/text.js"></script>
|
||||
<script src="js/webm-writer2.js"></script>
|
||||
<script src="js/render.js"></script>
|
||||
<script src="js/recorder.js"></script>
|
||||
<script src="js/functions.js"></script>
|
||||
<script src="js/events.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user