@bitmagic/cli 0.1.60-dev.3 → 0.1.60-dev.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -3
- package/dist/atomic-json.d.ts +18 -0
- package/dist/atomic-json.js +39 -0
- package/dist/atomic-json.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/commands/dev.js +79 -35
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +1 -1
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/reload.d.ts +1 -1
- package/dist/commands/reload.js +14 -5
- package/dist/commands/reload.js.map +1 -1
- package/dist/commands/verify.js +134 -118
- package/dist/commands/verify.js.map +1 -1
- package/dist/editor/motion-panel.d.ts +2 -2
- package/dist/editor/motion-panel.js +27 -5
- package/dist/editor/motion-panel.js.map +1 -1
- package/dist/editor/reload-bus.d.ts +11 -23
- package/dist/editor/reload-bus.js +55 -8
- package/dist/editor/reload-bus.js.map +1 -1
- package/dist/editor/server.d.ts +10 -0
- package/dist/editor/server.js +46 -0
- package/dist/editor/server.js.map +1 -1
- package/dist/editor/shell-page.d.ts +10 -0
- package/dist/editor/shell-page.js +58 -3
- package/dist/editor/shell-page.js.map +1 -1
- package/dist/editor/tab-reuse.d.ts +23 -0
- package/dist/editor/tab-reuse.js +61 -0
- package/dist/editor/tab-reuse.js.map +1 -0
- package/dist/local-port.d.ts +8 -1
- package/dist/local-port.js +9 -2
- package/dist/local-port.js.map +1 -1
- package/dist/machine-lock.d.ts +56 -0
- package/dist/machine-lock.js +213 -0
- package/dist/machine-lock.js.map +1 -0
- package/dist/port-lane.d.ts +27 -0
- package/dist/port-lane.js +36 -1
- package/dist/port-lane.js.map +1 -1
- package/dist/process-alive.d.ts +13 -0
- package/dist/process-alive.js +24 -0
- package/dist/process-alive.js.map +1 -0
- package/dist/project/dev-handle.d.ts +6 -34
- package/dist/project/dev-handle.js +9 -59
- package/dist/project/dev-handle.js.map +1 -1
- package/dist/project/dev-ports.d.ts +67 -0
- package/dist/project/dev-ports.js +204 -0
- package/dist/project/dev-ports.js.map +1 -0
- package/dist/project/game-name.d.ts +9 -0
- package/dist/project/game-name.js +21 -0
- package/dist/project/game-name.js.map +1 -0
- package/dist/project/jobs.js +2 -1
- package/dist/project/jobs.js.map +1 -1
- package/dist/scaffold/project-files.js +8 -8
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/smoke/run.d.ts +4 -0
- package/dist/smoke/run.js +5 -2
- package/dist/smoke/run.js.map +1 -1
- package/dist/verify/iterate.d.ts +5 -0
- package/dist/verify/iterate.js +23 -20
- package/dist/verify/iterate.js.map +1 -1
- package/dist/verify/lock.d.ts +32 -0
- package/dist/verify/lock.js +54 -0
- package/dist/verify/lock.js.map +1 -0
- package/package.json +4 -4
package/dist/commands/verify.js
CHANGED
|
@@ -14,6 +14,7 @@ import { assertServesGame, waitForServer } from '../verify/wait-for-server.js';
|
|
|
14
14
|
import { computeFingerprint } from '../publish/fingerprint.js';
|
|
15
15
|
import { writeVerifyRecord, decideVerifyOutcome, mergeVerifyPlatformRuns, recordAndGateVerify, toVerifyPlatformRun, } from '../verify/result.js';
|
|
16
16
|
import { runVerifyWatch } from '../verify/iterate.js';
|
|
17
|
+
import { withVerifyLock } from '../verify/lock.js';
|
|
17
18
|
import { allVerifyScreenshotPaths, verifyArtifactPaths } from '../verify/artifacts.js';
|
|
18
19
|
import { describeMobileDevice } from '../verify/device.js';
|
|
19
20
|
import { parsePlatformArg, resolveVerifyPlatforms } from '../verify/platform.js';
|
|
@@ -181,131 +182,146 @@ export const verifyCommand = defineCommand({
|
|
|
181
182
|
if (!keeping.has(stale))
|
|
182
183
|
fs.rmSync(stale, { force: true });
|
|
183
184
|
}
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
server
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
watchForEarlyExit(server, () => viteStderr),
|
|
217
|
-
]);
|
|
218
|
-
// The race proves something answered — this proves it was OUR vite, not another project's
|
|
219
|
-
// dev server sharing the port on the other loopback family. See assertServesGame.
|
|
220
|
-
await assertServesGame(port, context.metadata.gameId);
|
|
221
|
-
if (watch) {
|
|
222
|
-
await runVerifyWatch({
|
|
223
|
-
...runSettings,
|
|
224
|
-
root: context.root,
|
|
225
|
-
tsc,
|
|
226
|
-
artifactDir,
|
|
227
|
-
platforms: plan.platforms,
|
|
228
|
-
mobilePrimary: plan.mobilePrimary,
|
|
229
|
-
output,
|
|
230
|
-
describeRunState,
|
|
185
|
+
// Everything from the port on happens INSIDE the verify lock (verify/lock.ts) for a one-shot
|
|
186
|
+
// run: another project's verify measuring fps while this one's Chrome boots would grade the
|
|
187
|
+
// machine, not the game. The build above stays outside — short, and every `bitmagic dev` runs
|
|
188
|
+
// an unlocked `tsc --watch` beside us anyway. `--watch` holds it per iteration instead
|
|
189
|
+
// (verify/iterate.ts), so two watch loops in two projects take turns rather than one waiting
|
|
190
|
+
// for a loop that never ends.
|
|
191
|
+
const serveAndVerify = async () => {
|
|
192
|
+
// A free port INSIDE the CORS-allowed window, never an OS-assigned ephemeral one. This matters
|
|
193
|
+
// more here than anywhere: verify drives an ORDINARY browser, so an out-of-window origin means
|
|
194
|
+
// the CDN refuses the game's forged `.vwld`, the engine builds an empty voxel world, and verify
|
|
195
|
+
// then reports a game that loads and renders while the player falls through the floor forever.
|
|
196
|
+
// Verify's whole job is to answer "did this break", so it must not be the thing breaking it.
|
|
197
|
+
//
|
|
198
|
+
// A `bitmagic dev` serving this project holds two ports in this same lane, and the README tells
|
|
199
|
+
// creators to leave it open — so its own numbers are the likeliest collision verify has, and
|
|
200
|
+
// the lane split only rules out the OTHER release line's dev server, not this one's. Without
|
|
201
|
+
// excluding them the only thing between a verify run and the dev server it is testing alongside
|
|
202
|
+
// is the scan's random offset and vite's `--strictPort` failing loudly. See local-port.ts.
|
|
203
|
+
const dev = readLiveDevHandle(context.root);
|
|
204
|
+
const port = await reserveLanePort('Stop whatever is holding them and verify again.', dev === null ? new Set() : new Set([dev.gamePort, dev.editorPort]));
|
|
205
|
+
const runSettings = {
|
|
206
|
+
port,
|
|
207
|
+
renderer,
|
|
208
|
+
settleMs,
|
|
209
|
+
fast,
|
|
210
|
+
orientation: plan.orientation,
|
|
211
|
+
};
|
|
212
|
+
let server;
|
|
213
|
+
try {
|
|
214
|
+
server = spawn(vite, ['--port', String(port), '--strictPort'], {
|
|
215
|
+
cwd: context.root,
|
|
216
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
231
217
|
});
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const artifacts = [];
|
|
236
|
-
// The parity verdict is about the game's CODE, not about the emulated device, so a `both`
|
|
237
|
-
// run reports it once. The lead platform runs first, so its verdict is the one kept.
|
|
238
|
-
let mobileParity;
|
|
239
|
-
for (const platform of plan.platforms) {
|
|
240
|
-
const paths = verifyArtifactPaths(artifactDir, platform);
|
|
241
|
-
const checkOptions = verifyCheckOptions(runSettings, platform, paths);
|
|
242
|
-
let observations = await runHeadlessCheck({
|
|
243
|
-
...checkOptions,
|
|
244
|
-
launch: browserLaunchPlan(process.env, renderer),
|
|
218
|
+
let viteStderr = '';
|
|
219
|
+
server.stderr?.on('data', (chunk) => {
|
|
220
|
+
viteStderr += String(chunk);
|
|
245
221
|
});
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
222
|
+
await Promise.race([
|
|
223
|
+
waitForServer(port, 30_000),
|
|
224
|
+
watchForEarlyExit(server, () => viteStderr),
|
|
225
|
+
]);
|
|
226
|
+
// The race proves something answered — this proves it was OUR vite, not another project's
|
|
227
|
+
// dev server sharing the port on the other loopback family. See assertServesGame.
|
|
228
|
+
await assertServesGame(port, context.metadata.gameId);
|
|
229
|
+
if (watch) {
|
|
230
|
+
await runVerifyWatch({
|
|
231
|
+
...runSettings,
|
|
232
|
+
root: context.root,
|
|
233
|
+
tsc,
|
|
234
|
+
artifactDir,
|
|
235
|
+
platforms: plan.platforms,
|
|
236
|
+
mobilePrimary: plan.mobilePrimary,
|
|
237
|
+
output,
|
|
238
|
+
describeRunState,
|
|
239
|
+
lock: (fn) => withVerifyLock(context.root, 'verify --watch', output, fn),
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const runs = [];
|
|
244
|
+
const artifacts = [];
|
|
245
|
+
// The parity verdict is about the game's CODE, not about the emulated device, so a `both`
|
|
246
|
+
// run reports it once. The lead platform runs first, so its verdict is the one kept.
|
|
247
|
+
let mobileParity;
|
|
248
|
+
for (const platform of plan.platforms) {
|
|
249
|
+
const paths = verifyArtifactPaths(artifactDir, platform);
|
|
250
|
+
const checkOptions = verifyCheckOptions(runSettings, platform, paths);
|
|
251
|
+
let observations = await runHeadlessCheck({
|
|
255
252
|
...checkOptions,
|
|
256
|
-
|
|
257
|
-
launch: softwareGlLaunchPlan(process.env, renderer),
|
|
253
|
+
launch: browserLaunchPlan(process.env, renderer),
|
|
258
254
|
});
|
|
259
|
-
result = classifyVerifyRun(observations, { platform, mobilePrimary: plan.mobilePrimary });
|
|
260
|
-
retriedWithSoftwareGl =
|
|
255
|
+
let result = classifyVerifyRun(observations, { platform, mobilePrimary: plan.mobilePrimary });
|
|
256
|
+
let retriedWithSoftwareGl = false;
|
|
257
|
+
if (!result.ok && shouldRetrySoftwareGl(observations, process.env)) {
|
|
258
|
+
// A lost context is a machine/driver problem, not a game problem (browser-gl.ts) — one
|
|
259
|
+
// automatic software-GL re-run answers "is the game broken or is this GPU stack broken"
|
|
260
|
+
// without the creator having to know the env var exists. The retry's console goes to
|
|
261
|
+
// its own file so the first run's evidence survives.
|
|
262
|
+
output.info('The WebGL context was lost — retrying once with software rendering…');
|
|
263
|
+
observations = await runHeadlessCheck({
|
|
264
|
+
...checkOptions,
|
|
265
|
+
consolePath: paths.retryConsolePath,
|
|
266
|
+
launch: softwareGlLaunchPlan(process.env, renderer),
|
|
267
|
+
});
|
|
268
|
+
result = classifyVerifyRun(observations, { platform, mobilePrimary: plan.mobilePrimary });
|
|
269
|
+
retriedWithSoftwareGl = true;
|
|
270
|
+
}
|
|
271
|
+
const observedBackend = observations.renderer?.backend ?? 'unobserved';
|
|
272
|
+
const label = plan.platforms.length > 1 ? `${platform}: ` : '';
|
|
273
|
+
output.info(`${label}Renderer: ${renderer} requested → ${observedBackend} backend`);
|
|
274
|
+
output.info(`${label}${describeRunState(observations)}`);
|
|
275
|
+
if (mobileParity === undefined)
|
|
276
|
+
mobileParity = observations.mobileParity;
|
|
277
|
+
runs.push(toVerifyPlatformRun(platform, result, observations, retriedWithSoftwareGl, paths, fast));
|
|
278
|
+
artifacts.push(paths.consolePath);
|
|
279
|
+
if (!fast)
|
|
280
|
+
artifacts.push(paths.screenshotPath);
|
|
261
281
|
}
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
282
|
+
const merged = mergeVerifyPlatformRuns(runs, mobileParity);
|
|
283
|
+
const outcome = decideVerifyOutcome(merged.result, computeFingerprint(context.root), new Date().toISOString(), merged.extras);
|
|
284
|
+
recordAndGateVerify(artifactDir, outcome, artifacts, {
|
|
285
|
+
write: writeVerifyRecord,
|
|
286
|
+
// Under --json both streams are prose and belong on stderr; the result document below
|
|
287
|
+
// is the only thing stdout carries.
|
|
288
|
+
info: (line) => output.info(line),
|
|
289
|
+
error: (line) => console.error(line),
|
|
290
|
+
});
|
|
291
|
+
// Only reached when the run PASSED — recordAndGateVerify throws on failure, and the
|
|
292
|
+
// wrapper then emits the error document. Both paths leave stdout parseable.
|
|
293
|
+
// The result document keeps reporting ABSOLUTE paths, unchanged: it is consumed in the
|
|
294
|
+
// moment by whatever ran the command, unlike `result.json`, which persists and therefore
|
|
295
|
+
// records file names only.
|
|
296
|
+
const leadPaths = verifyArtifactPaths(artifactDir, runs[0].platform);
|
|
297
|
+
output.result({
|
|
298
|
+
ok: true,
|
|
299
|
+
failures: outcome.record.failures,
|
|
300
|
+
warnings: outcome.record.warnings,
|
|
301
|
+
fingerprint: outcome.record.fingerprint,
|
|
302
|
+
platform: outcome.record.platform,
|
|
303
|
+
touchControls: outcome.record.touchControls,
|
|
304
|
+
platforms: outcome.record.platforms,
|
|
305
|
+
mobileParity: outcome.record.mobileParity,
|
|
306
|
+
renderer: outcome.record.renderer,
|
|
307
|
+
retriedWithSoftwareGl: outcome.record.retriedWithSoftwareGl,
|
|
308
|
+
settle: outcome.record.settle,
|
|
309
|
+
snapshot: outcome.record.snapshot,
|
|
310
|
+
events: outcome.record.events,
|
|
311
|
+
artifactDir,
|
|
312
|
+
consolePath: leadPaths.consolePath,
|
|
313
|
+
screenshotPath: fast ? undefined : leadPaths.screenshotPath,
|
|
314
|
+
});
|
|
272
315
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
info: (line) => output.info(line),
|
|
280
|
-
error: (line) => console.error(line),
|
|
281
|
-
});
|
|
282
|
-
// Only reached when the run PASSED — recordAndGateVerify throws on failure, and the
|
|
283
|
-
// wrapper then emits the error document. Both paths leave stdout parseable.
|
|
284
|
-
// The result document keeps reporting ABSOLUTE paths, unchanged: it is consumed in the
|
|
285
|
-
// moment by whatever ran the command, unlike `result.json`, which persists and therefore
|
|
286
|
-
// records file names only.
|
|
287
|
-
const leadPaths = verifyArtifactPaths(artifactDir, runs[0].platform);
|
|
288
|
-
output.result({
|
|
289
|
-
ok: true,
|
|
290
|
-
failures: outcome.record.failures,
|
|
291
|
-
warnings: outcome.record.warnings,
|
|
292
|
-
fingerprint: outcome.record.fingerprint,
|
|
293
|
-
platform: outcome.record.platform,
|
|
294
|
-
touchControls: outcome.record.touchControls,
|
|
295
|
-
platforms: outcome.record.platforms,
|
|
296
|
-
mobileParity: outcome.record.mobileParity,
|
|
297
|
-
renderer: outcome.record.renderer,
|
|
298
|
-
retriedWithSoftwareGl: outcome.record.retriedWithSoftwareGl,
|
|
299
|
-
settle: outcome.record.settle,
|
|
300
|
-
snapshot: outcome.record.snapshot,
|
|
301
|
-
events: outcome.record.events,
|
|
302
|
-
artifactDir,
|
|
303
|
-
consolePath: leadPaths.consolePath,
|
|
304
|
-
screenshotPath: fast ? undefined : leadPaths.screenshotPath,
|
|
305
|
-
});
|
|
316
|
+
finally {
|
|
317
|
+
server?.kill('SIGTERM');
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
if (watch) {
|
|
321
|
+
await serveAndVerify();
|
|
306
322
|
}
|
|
307
|
-
|
|
308
|
-
|
|
323
|
+
else {
|
|
324
|
+
await withVerifyLock(context.root, 'verify', output, serveAndVerify);
|
|
309
325
|
}
|
|
310
326
|
},
|
|
311
327
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* dot-escape, every colour a `--bm-*` token — `shell-page.test.ts` enforces all three.
|
|
13
13
|
*/
|
|
14
14
|
export declare const MOTION_PANEL_CSS = "\n /* Level with the other dialogs: each opens from its own control and they never stack. */\n #motion-overlay { z-index: 10; }\n #motion-dialog { width: min(520px, 94vw); padding: 20px; }\n #motion-dialog .bm-label { display: block; margin: 14px 0 6px; }\n #motion-stage { position: relative; aspect-ratio: 16 / 9; border-radius: 8px; overflow: hidden;\n background: var(--bm-surface-alt); border: 1px solid var(--bm-border-muted); }\n #motion-preview { width: 100%; height: 100%; object-fit: contain; background: #000000; display: block; }\n #motion-preview.mirror { transform: scaleX(-1); }\n #motion-timer { position: absolute; top: 10px; left: 10px; display: none; padding: 3px 10px;\n border-radius: 999px; background: var(--bm-pink); color: #ffffff; font-size: 12px;\n font-family: var(--bm-font-mono); }\n #motion-timer.show { display: inline-block; }\n #motion-countdown { position: absolute; inset: 0; display: none; flex-direction: column; align-items: center;\n justify-content: center; gap: 6px; background: var(--bm-scrim); pointer-events: none; }\n #motion-countdown.show { display: flex; }\n #motion-countdown-number { font-size: 96px; font-weight: 700; line-height: 1; color: #ffffff; }\n #motion-countdown-hint { font-size: 13px; color: #ffffff; opacity: 0.85; }\n #motion-hint { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;\n text-align: center; padding: 20px; font-size: 13px; color: var(--bm-text-muted); }\n #motion-hint.hidden { display: none; }\n #motion-trim { display: none; margin-top: 10px; }\n #motion-trim.show { display: block; }\n #motion-trimbar { position: relative; height: 14px; border-radius: 7px; background: var(--bm-surface-alt);\n touch-action: none; }\n #motion-trim-kept { position: absolute; top: 0; bottom: 0; border-radius: 7px; background: var(--bm-aqua); }\n #motion-trim-playhead { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--bm-pink);\n pointer-events: none; }\n .motion-trim-handle { position: absolute; top: 50%; width: 14px; height: 22px;\n transform: translate(-50%, -50%); border-radius: 5px; background: var(--bm-text);\n cursor: ew-resize; touch-action: none; }\n #motion-trim-row { display: flex; align-items: center; justify-content: space-between; gap: 8px;\n margin-top: 6px; min-height: 24px; }\n #motion-trim-note { font-size: 11px; color: var(--bm-text-muted); }\n #motion-trim-actions { display: flex; gap: 6px; }\n #motion-sources { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }\n #motion-file { display: none; }\n #motion-guidance { margin: 10px 0 0; font-size: 12px; color: var(--bm-text-muted); }\n #motion-options { display: grid; grid-template-columns: 1fr auto; gap: 10px 12px; align-items: end; }\n #motion-name, #motion-slot { width: 100%; }\n .motion-check { display: flex; align-items: center; gap: 8px; margin-top: 12px; font-size: 13px; cursor: pointer; }\n .motion-check input { accent-color: var(--bm-aqua); }\n #motion-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }\n #motion-note { display: none; margin-top: 12px; padding: 12px; border-radius: 6px; font-size: 12px;\n background: var(--bm-surface-alt); border: 1px solid var(--bm-border-muted);\n color: var(--bm-text-muted); }\n #motion-note.show { display: block; }\n #motion-note code { display: block; margin-top: 6px; color: var(--bm-aqua);\n font-family: var(--bm-font-mono); word-break: break-all; }\n";
|
|
15
|
-
export declare const MOTION_PANEL_HTML = " <div id=\"motion-overlay\" class=\"bm-overlay\">\n <div id=\"motion-dialog\" class=\"bm-dialog\">\n <h2>Record a motion</h2>\n <p>Perform the move on camera, or pick a video. Uthana turns it into an animation clip for\n this game's characters. Costs sparks.</p>\n <div id=\"motion-stage\">\n <video id=\"motion-preview\" playsinline muted></video>\n <span id=\"motion-timer\">0:00</span>\n <div id=\"motion-countdown\"><span id=\"motion-countdown-number\">3</span><span id=\"motion-countdown-hint\">Get in frame</span></div>\n <div id=\"motion-hint\">Whole body in frame, one person, plain background, good light.\n 2–60 seconds.</div>\n </div>\n <div id=\"motion-trim\">\n <div id=\"motion-trimbar\" title=\"Drag the handles to choose what the animation is made from\">\n <div id=\"motion-trim-kept\"></div>\n <div id=\"motion-trim-playhead\"></div>\n <div id=\"motion-trim-start\" class=\"motion-trim-handle\"></div>\n <div id=\"motion-trim-end\" class=\"motion-trim-handle\"></div>\n </div>\n <div id=\"motion-trim-row\">\n <span id=\"motion-trim-note\"></span>\n <span id=\"motion-trim-actions\">\n <button id=\"motion-trim-preview\" class=\"bm-pill\" type=\"button\">▶ Play</button>\n <button id=\"motion-trim-full\" class=\"bm-pill bm-pill--ghost\" type=\"button\" hidden>Use full clip</button>\n </span>\n </div>\n </div>\n <div id=\"motion-sources\">\n <button id=\"motion-record\" class=\"bm-pill bm-pill--primary\" type=\"button\">Record with webcam</button>\n <button id=\"motion-stop\" class=\"bm-pill\" type=\"button\" hidden>Stop</button>\n <button id=\"motion-pick\" class=\"bm-pill\" type=\"button\">Choose a video…</button>\n <button id=\"motion-retake\" class=\"bm-pill bm-pill--ghost\" type=\"button\" hidden>Start over</button>\n <input id=\"motion-file\" type=\"file\" accept=\".mp4,.mov,.avi,.webm,video/mp4,video/quicktime,video/webm\">\n </div>\n <p id=\"motion-guidance\"></p>\n <div id=\"motion-options\">\n <div>\n <label class=\"bm-label\" for=\"motion-name\">Name</label>\n <input id=\"motion-name\" class=\"bm-input\" type=\"text\" placeholder=\"e.g. Victory dance\" maxlength=\"80\">\n </div>\n <div>\n <label class=\"bm-label\" for=\"motion-slot\">Use as</label>\n <select id=\"motion-slot\" class=\"bm-input\">\n <option value=\"\">One-shot clip</option>\n <option value=\"idle\">Idle</option>\n <option value=\"walk\">Walk</option>\n <option value=\"run\">Run</option>\n </select>\n </div>\n </div>\n <label class=\"motion-check\"><input id=\"motion-loop\" type=\"checkbox\"> Loops</label>\n <div id=\"motion-actions\">\n <button id=\"motion-close\" class=\"bm-pill bm-pill--ghost\" type=\"button\">Close</button>\n <button id=\"motion-submit\" class=\"bm-pill bm-pill--primary\" type=\"button\" disabled>Make the animation</button>\n </div>\n <div id=\"motion-note\">\n <span id=\"motion-note-text\"></span>\n <code id=\"motion-command\"></code>\n </div>\n </div>\n </div>\n";
|
|
15
|
+
export declare const MOTION_PANEL_HTML = " <div id=\"motion-overlay\" class=\"bm-overlay\">\n <div id=\"motion-dialog\" class=\"bm-dialog\">\n <h2>Record a motion</h2>\n <p>Perform the move on camera, or pick a video. Uthana turns it into an animation clip for\n this game's characters. Costs sparks.</p>\n <div id=\"motion-stage\">\n <video id=\"motion-preview\" playsinline muted></video>\n <span id=\"motion-timer\">0:00</span>\n <div id=\"motion-countdown\"><span id=\"motion-countdown-number\">3</span><span id=\"motion-countdown-hint\">Get in frame</span></div>\n <div id=\"motion-hint\">Whole body in frame, one person, plain background, good light.\n 2–60 seconds.</div>\n </div>\n <div id=\"motion-trim\">\n <div id=\"motion-trimbar\" title=\"Drag the handles to choose what the animation is made from\">\n <div id=\"motion-trim-kept\"></div>\n <div id=\"motion-trim-playhead\"></div>\n <div id=\"motion-trim-start\" class=\"motion-trim-handle\"></div>\n <div id=\"motion-trim-end\" class=\"motion-trim-handle\"></div>\n </div>\n <div id=\"motion-trim-row\">\n <span id=\"motion-trim-note\"></span>\n <span id=\"motion-trim-actions\">\n <button id=\"motion-trim-preview\" class=\"bm-pill\" type=\"button\">▶ Play</button>\n <button id=\"motion-trim-full\" class=\"bm-pill bm-pill--ghost\" type=\"button\" hidden>Use full clip</button>\n </span>\n </div>\n </div>\n <div id=\"motion-sources\">\n <button id=\"motion-record\" class=\"bm-pill bm-pill--primary\" type=\"button\">Record with webcam</button>\n <button id=\"motion-start\" class=\"bm-pill bm-pill--primary\" type=\"button\" hidden>Start recording</button>\n <button id=\"motion-stop\" class=\"bm-pill\" type=\"button\" hidden>Stop</button>\n <button id=\"motion-pick\" class=\"bm-pill\" type=\"button\">Choose a video…</button>\n <button id=\"motion-retake\" class=\"bm-pill bm-pill--ghost\" type=\"button\" hidden>Start over</button>\n <input id=\"motion-file\" type=\"file\" accept=\".mp4,.mov,.avi,.webm,video/mp4,video/quicktime,video/webm\">\n </div>\n <p id=\"motion-guidance\"></p>\n <div id=\"motion-options\">\n <div>\n <label class=\"bm-label\" for=\"motion-name\">Name</label>\n <input id=\"motion-name\" class=\"bm-input\" type=\"text\" placeholder=\"e.g. Victory dance\" maxlength=\"80\">\n </div>\n <div>\n <label class=\"bm-label\" for=\"motion-slot\">Use as</label>\n <select id=\"motion-slot\" class=\"bm-input\">\n <option value=\"\">One-shot clip</option>\n <option value=\"idle\">Idle</option>\n <option value=\"walk\">Walk</option>\n <option value=\"run\">Run</option>\n </select>\n </div>\n </div>\n <label class=\"motion-check\"><input id=\"motion-loop\" type=\"checkbox\"> Loops</label>\n <div id=\"motion-actions\">\n <button id=\"motion-close\" class=\"bm-pill bm-pill--ghost\" type=\"button\">Close</button>\n <button id=\"motion-submit\" class=\"bm-pill bm-pill--primary\" type=\"button\" disabled>Make the animation</button>\n </div>\n <div id=\"motion-note\">\n <span id=\"motion-note-text\"></span>\n <code id=\"motion-command\"></code>\n </div>\n </div>\n </div>\n";
|
|
16
16
|
/**
|
|
17
17
|
* Behaviour, spliced into the shell's IIFE. Runs after the shell's own helpers are defined, so
|
|
18
18
|
* `setStatus`, `start`, `autoReload`, `banner` and `frame` are in scope.
|
|
19
19
|
*/
|
|
20
|
-
export declare const MOTION_PANEL_JS = "\n // ---- Record motion: webcam or file -> Uthana video-to-motion -> assets[] ----------------------\n var motionToggle = document.getElementById('motion');\n var motionOverlay = document.getElementById('motion-overlay');\n var motionPreview = document.getElementById('motion-preview');\n var motionTimer = document.getElementById('motion-timer');\n var motionCountdown = document.getElementById('motion-countdown');\n var motionCountdownNumber = document.getElementById('motion-countdown-number');\n /** Seconds shown on the live preview before the recorder starts, so the first frames are the pose. */\n var MOTION_COUNTDOWN_S = 3;\n var motionCountdownToken = 0;\n var motionHint = document.getElementById('motion-hint');\n var motionRecordBtn = document.getElementById('motion-record');\n var motionStopBtn = document.getElementById('motion-stop');\n var motionPickBtn = document.getElementById('motion-pick');\n var motionRetakeBtn = document.getElementById('motion-retake');\n var motionFile = document.getElementById('motion-file');\n var motionGuidance = document.getElementById('motion-guidance');\n var motionName = document.getElementById('motion-name');\n var motionSlot = document.getElementById('motion-slot');\n var motionLoop = document.getElementById('motion-loop');\n var motionSubmit = document.getElementById('motion-submit');\n var motionNote = document.getElementById('motion-note');\n var MOTION_MIN_S = 2;\n var MOTION_MAX_S = 60;\n var MOTION_MAX_BYTES = 200 * 1024 * 1024;\n var motionBlob = null;\n // Whether motionBlob came from the webcam recorder (auto-trim applies) or a picked file (it does not).\n var motionFromRecording = false;\n var motionSeconds = 0;\n var motionStream = null;\n var motionRecorder = null;\n var motionChunks = [];\n var motionTickTimer = null;\n var motionStartedAt = 0;\n var motionObjectUrl = null;\n // ---- Trim window: seeded by the sidecar's trim analysis, adjusted on the handles ------------\n var motionTrimWrap = document.getElementById('motion-trim');\n var motionTrimBar = document.getElementById('motion-trimbar');\n var motionTrimKept = document.getElementById('motion-trim-kept');\n var motionTrimPlayhead = document.getElementById('motion-trim-playhead');\n var motionTrimStartHandle = document.getElementById('motion-trim-start');\n var motionTrimEndHandle = document.getElementById('motion-trim-end');\n var motionTrimNote = document.getElementById('motion-trim-note');\n var motionTrimFullBtn = document.getElementById('motion-trim-full');\n var motionTrimPreviewBtn = document.getElementById('motion-trim-preview');\n // True while \"Preview the kept range\" is playing trimStart -> trimEnd.\n var motionPreviewingTrim = false;\n var MOTION_TRIM_MIN_KEPT_S = 2;\n var motionTrimDuration = 0;\n var motionTrimStart = 0;\n var motionTrimEnd = 0;\n var motionPendingId = null;\n var motionAnalysisState = 'idle';\n var motionAnalysisToken = 0;\n var motionAnalyzePollTimer = null;\n\n function motionHasTrim() {\n return motionTrimDuration > 0 && motionTrimEnd > motionTrimStart\n && (motionTrimStart >= 0.1 || motionTrimDuration - motionTrimEnd >= 0.1);\n }\n\n function renderMotionTrim() {\n if (motionTrimDuration <= 0) { motionTrimWrap.classList.remove('show'); return; }\n motionTrimWrap.classList.add('show');\n var pct = function (seconds) {\n return Math.min(Math.max(seconds / motionTrimDuration, 0), 1) * 100 + '%';\n };\n motionTrimKept.style.left = pct(motionTrimStart);\n motionTrimKept.style.width = Math.max(0, (motionTrimEnd - motionTrimStart) / motionTrimDuration) * 100 + '%';\n motionTrimStartHandle.style.left = pct(motionTrimStart);\n motionTrimEndHandle.style.left = pct(motionTrimEnd);\n motionTrimFullBtn.hidden = !motionHasTrim();\n motionTrimPreviewBtn.textContent = motionPreviewingTrim\n ? '\u23F9 Stop'\n : (motionHasTrim() ? '\u25B6 Preview the kept range' : '\u25B6 Play');\n var note;\n if (motionAnalysisState === 'running') {\n note = 'Analyzing the clip for a good trim\u2026';\n } else if (!motionHasTrim()) {\n note = 'Using the full clip.';\n } else {\n var parts = [];\n if (motionTrimStart >= 0.1) parts.push(motionTrimStart.toFixed(1) + 's from the start');\n if (motionTrimDuration - motionTrimEnd >= 0.1) parts.push((motionTrimDuration - motionTrimEnd).toFixed(1) + 's from the end');\n note = 'Trimming ' + parts.join(' and ') + '.';\n }\n motionTrimNote.textContent = note;\n }\n\n function resetMotionTrim() {\n motionPreviewingTrim = false;\n motionAnalysisToken++;\n if (motionAnalyzePollTimer) { clearTimeout(motionAnalyzePollTimer); motionAnalyzePollTimer = null; }\n motionPendingId = null;\n motionAnalysisState = 'idle';\n motionTrimDuration = 0;\n motionTrimStart = 0;\n motionTrimEnd = 0;\n motionTrimWrap.classList.remove('show');\n }\n\n function seedMotionTrim(durationSeconds) {\n if (!(durationSeconds > 0)) return;\n motionTrimDuration = durationSeconds;\n motionTrimStart = 0;\n motionTrimEnd = durationSeconds;\n // Review playback is the trim: the Play/Preview button below drives the\n // video, and a native full-clip scrubber under a trim bar read as \"it\n // plays the whole video anyway\". Controls stay native only while the\n // duration is unknown and the bar cannot render.\n motionPreview.controls = false;\n renderMotionTrim();\n }\n\n function applyMotionSuggestion(suggestion) {\n if (!suggestion) { renderMotionTrim(); return; }\n if (suggestion.durationSeconds > 0) {\n motionTrimDuration = suggestion.durationSeconds;\n motionTrimStart = 0;\n motionTrimEnd = suggestion.durationSeconds;\n }\n if (suggestion.confidence === 'high'\n && typeof suggestion.suggestedStartSeconds === 'number'\n && typeof suggestion.suggestedEndSeconds === 'number'\n && suggestion.suggestedEndSeconds > suggestion.suggestedStartSeconds) {\n motionTrimStart = suggestion.suggestedStartSeconds;\n motionTrimEnd = Math.min(suggestion.suggestedEndSeconds, motionTrimDuration || suggestion.suggestedEndSeconds);\n }\n renderMotionTrim();\n }\n\n function beginMotionTrimDrag(which, event) {\n if (motionTrimDuration <= 0) return;\n var handle = event.currentTarget;\n handle.setPointerCapture(event.pointerId);\n var move = function (e) {\n var rect = motionTrimBar.getBoundingClientRect();\n if (rect.width === 0) return;\n var at = Math.min(Math.max((e.clientX - rect.left) / rect.width, 0), 1) * motionTrimDuration;\n if (which === 'start') motionTrimStart = Math.min(at, motionTrimEnd - MOTION_TRIM_MIN_KEPT_S);\n else motionTrimEnd = Math.max(at, motionTrimStart + MOTION_TRIM_MIN_KEPT_S);\n // Show the exact frame the cut lands on while the handle moves.\n motionPreview.pause();\n motionPreviewingTrim = false;\n motionPreview.currentTime = which === 'start' ? motionTrimStart : motionTrimEnd;\n renderMotionTrim();\n };\n var up = function () {\n handle.removeEventListener('pointermove', move);\n handle.removeEventListener('pointerup', up);\n handle.removeEventListener('pointercancel', up);\n };\n handle.addEventListener('pointermove', move);\n handle.addEventListener('pointerup', up);\n handle.addEventListener('pointercancel', up);\n event.preventDefault();\n }\n\n function pollMotionAnalysis(token) {\n var tick = async function () {\n if (token !== motionAnalysisToken) return;\n var payload;\n try {\n payload = await (await fetch('/api/editor/motion-analyze-status')).json();\n } catch (error) {\n motionAnalyzePollTimer = setTimeout(tick, 2000);\n return;\n }\n if (token !== motionAnalysisToken) return;\n if (payload && payload.status === 'done') {\n motionAnalysisState = 'done';\n applyMotionSuggestion(payload.suggestion);\n return;\n }\n if (payload && payload.status === 'running') {\n motionAnalyzePollTimer = setTimeout(tick, 2000);\n return;\n }\n // failed / unavailable / none: no suggestion, the full-range handles stand.\n motionAnalysisState = 'failed';\n renderMotionTrim();\n };\n motionAnalyzePollTimer = setTimeout(tick, 2000);\n }\n\n /**\n * Hand the clip to the sidecar ahead of submission: it uploads once and runs the unbilled trim\n * analysis while the creator reviews. Degraded, never blocking \u2014 any failure just leaves the\n * handles at full range, and Submit works throughout.\n */\n async function startMotionAnalysis(blob) {\n var token = ++motionAnalysisToken;\n motionPendingId = null;\n motionAnalysisState = 'running';\n renderMotionTrim();\n var result = null;\n try {\n var response = await fetch('/api/editor/motion-analyze', {\n method: 'POST',\n headers: { 'Content-Type': blob.type || 'video/webm' },\n body: blob\n });\n result = await response.json();\n } catch (error) { /* the sidecar said nothing; treat as unavailable */ }\n if (token !== motionAnalysisToken) return;\n if (!result || result.ok !== true || result.analysing !== true) {\n motionAnalysisState = 'idle';\n renderMotionTrim();\n return;\n }\n motionPendingId = result.pendingId || null;\n pollMotionAnalysis(token);\n }\n\n function motionSupported() {\n return !!(window.isSecureContext && navigator.mediaDevices && navigator.mediaDevices.getUserMedia\n && typeof MediaRecorder !== 'undefined');\n }\n\n /** mp4 first (Safari), then the WebM the rest produce; asset-forger re-encodes either. */\n function pickMotionMime() {\n var candidates = ['video/mp4', 'video/webm;codecs=vp9', 'video/webm'];\n for (var i = 0; i < candidates.length; i++) {\n if (MediaRecorder.isTypeSupported(candidates[i])) return candidates[i];\n }\n return '';\n }\n\n function motionCameraError(error) {\n var name = error && error.name;\n if (name === 'NotAllowedError' || name === 'SecurityError') return 'Camera access was refused. Allow it in the address bar, or choose a video file.';\n if (name === 'NotFoundError' || name === 'OverconstrainedError') return 'No camera found. Choose a video file instead.';\n if (name === 'NotReadableError') return 'The camera is in use by another app. Close it, or choose a video file.';\n return 'Could not start the camera: ' + ((error && error.message) || 'unknown error');\n }\n\n function formatMotionClock(seconds) {\n var s = Math.max(0, Math.floor(seconds));\n var rest = s % 60;\n return Math.floor(s / 60) + ':' + (rest < 10 ? '0' : '') + rest;\n }\n\n function showMotionNote(text, command) {\n document.getElementById('motion-note-text').textContent = text;\n document.getElementById('motion-command').textContent = command || '';\n motionNote.classList.add('show');\n }\n\n function releaseMotionObjectUrl() {\n if (motionObjectUrl) { URL.revokeObjectURL(motionObjectUrl); motionObjectUrl = null; }\n }\n\n /** Stop the recorder if one is still live \u2014 it may already have stopped itself. */\n function discardMotionRecorder() {\n if (motionRecorder && motionRecorder.state !== 'inactive') {\n try { motionRecorder.stop(); } catch (error) { /* already stopped */ }\n }\n }\n\n function stopMotionStream() {\n if (motionStream) {\n motionStream.getTracks().forEach(function (track) { track.stop(); });\n motionStream = null;\n }\n if (motionTickTimer) { clearInterval(motionTickTimer); motionTickTimer = null; }\n motionCountdownToken++;\n motionCountdown.classList.remove('show');\n motionTimer.classList.remove('show');\n motionPreview.classList.remove('mirror');\n }\n\n function setMotionButtons(state) {\n motionRecordBtn.hidden = state !== 'idle';\n motionPickBtn.hidden = state !== 'idle';\n motionStopBtn.hidden = state !== 'recording';\n motionRetakeBtn.hidden = state !== 'ready';\n motionSubmit.disabled = state !== 'ready';\n }\n\n function resetMotionPanel() {\n resetMotionTrim();\n stopMotionStream();\n discardMotionRecorder();\n motionRecorder = null;\n motionChunks = [];\n motionBlob = null;\n motionFromRecording = false;\n motionSeconds = 0;\n releaseMotionObjectUrl();\n motionPreview.srcObject = null;\n motionPreview.removeAttribute('src');\n motionPreview.controls = false;\n motionPreview.muted = true;\n motionHint.classList.remove('hidden');\n var supported = motionSupported();\n motionGuidance.textContent = supported ? '' : 'Webcam recording needs a secure page with a camera; choose a video file instead.';\n motionRecordBtn.disabled = !supported;\n motionNote.classList.remove('show');\n setMotionButtons('idle');\n }\n\n function openMotionPanel() {\n resetMotionPanel();\n motionOverlay.classList.add('show');\n motionName.focus();\n }\n\n /** Returns whether it was open \u2014 the Escape handler's contract, like closeAssetDetail. */\n function closeMotionPanel() {\n if (!motionOverlay.classList.contains('show')) return false;\n stopMotionStream();\n discardMotionRecorder();\n motionOverlay.classList.remove('show');\n frame.focus();\n return true;\n }\n\n /** The clip is in hand: show it back, and say whether it is within Uthana's 2-60 s window. */\n function acceptMotionClip(blob, seconds) {\n motionBlob = blob;\n motionSeconds = seconds;\n releaseMotionObjectUrl();\n motionObjectUrl = URL.createObjectURL(blob);\n motionPreview.srcObject = null;\n motionPreview.src = motionObjectUrl;\n motionPreview.controls = true;\n motionPreview.muted = true;\n motionHint.classList.add('hidden');\n var note;\n if (blob.size > MOTION_MAX_BYTES) note = 'That video is over 200 MB. Trim it to the seconds that matter.';\n else if (seconds > 0 && seconds < MOTION_MIN_S) note = 'Too short: Uthana needs at least ' + MOTION_MIN_S + ' seconds.';\n else if (seconds > MOTION_MAX_S) note = 'Too long: Uthana takes at most ' + MOTION_MAX_S + ' seconds. Trim it, or record again.';\n var ok = !note;\n motionGuidance.textContent = ok\n ? (seconds > 0 ? formatMotionClock(seconds) + ' of video ready.' : 'Video ready.')\n : note;\n setMotionButtons('ready');\n motionSubmit.disabled = !ok;\n resetMotionTrim();\n if (ok) {\n seedMotionTrim(seconds);\n // A fresh WebM often reports no duration; the preview's metadata is the fallback scale.\n motionPreview.onloadedmetadata = function () {\n if (motionTrimDuration === 0 && isFinite(motionPreview.duration) && motionPreview.duration > 0) {\n seedMotionTrim(motionPreview.duration);\n }\n };\n void startMotionAnalysis(blob);\n }\n }\n\n async function startMotionRecording() {\n if (!motionSupported()) return;\n motionNote.classList.remove('show');\n try {\n motionStream = await navigator.mediaDevices.getUserMedia({\n video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: 'user' }, audio: false\n });\n } catch (error) {\n motionGuidance.textContent = motionCameraError(error);\n return;\n }\n releaseMotionObjectUrl();\n motionPreview.removeAttribute('src');\n motionPreview.controls = false;\n motionPreview.srcObject = motionStream;\n motionPreview.classList.add('mirror');\n motionHint.classList.add('hidden');\n try { await motionPreview.play(); } catch (error) { /* autoplay refused; the stream still shows on interaction */ }\n var mime = pickMotionMime();\n try {\n motionRecorder = mime ? new MediaRecorder(motionStream, { mimeType: mime }) : new MediaRecorder(motionStream);\n } catch (error) {\n stopMotionStream();\n motionGuidance.textContent = 'This browser cannot record video. Choose a video file instead.';\n return;\n }\n // Count down on the live preview; Stop during the count aborts without recording.\n var token = ++motionCountdownToken;\n setMotionButtons('recording');\n motionGuidance.textContent = 'Get in frame \u2014 recording starts after the count.';\n motionCountdown.classList.add('show');\n for (var left = MOTION_COUNTDOWN_S; left > 0; left--) {\n motionCountdownNumber.textContent = String(left);\n await new Promise(function (resolve) { setTimeout(resolve, 1000); });\n if (token !== motionCountdownToken) return;\n }\n motionCountdown.classList.remove('show');\n motionChunks = [];\n motionRecorder.ondataavailable = function (event) { if (event.data && event.data.size > 0) motionChunks.push(event.data); };\n motionRecorder.onstop = function () {\n var seconds = (Date.now() - motionStartedAt) / 1000;\n var type = motionRecorder.mimeType || mime || 'video/webm';\n stopMotionStream();\n motionFromRecording = true;\n acceptMotionClip(new Blob(motionChunks, { type: type }), seconds);\n };\n motionStartedAt = Date.now();\n motionRecorder.start(1000);\n motionTimer.textContent = '0:00';\n motionTimer.classList.add('show');\n motionGuidance.textContent = 'Recording. Stop when the move is done (auto-stops at ' + MOTION_MAX_S + ' s).';\n motionTickTimer = setInterval(function () {\n var elapsed = (Date.now() - motionStartedAt) / 1000;\n motionTimer.textContent = formatMotionClock(elapsed);\n if (elapsed >= MOTION_MAX_S) stopMotionRecording();\n }, 250);\n }\n\n function stopMotionRecording() {\n if (motionRecorder && motionRecorder.state === 'recording') { motionRecorder.stop(); return; }\n // Stop pressed during the countdown: nothing was recorded, back to the start.\n if (motionCountdown.classList.contains('show')) resetMotionPanel();\n }\n\n function pickMotionFile(file) {\n if (!file) return;\n // Read the duration off the file itself; a picked video knows its length, a fresh WebM often\n // does not (Infinity), so 0 means \"unknown\" and the server's ffprobe is the judge.\n var probe = document.createElement('video');\n probe.preload = 'metadata';\n var url = URL.createObjectURL(file);\n var settled = false;\n var finish = function (seconds) {\n if (settled) return;\n settled = true;\n URL.revokeObjectURL(url);\n motionFromRecording = false;\n acceptMotionClip(file, seconds);\n };\n probe.onloadedmetadata = function () { finish(isFinite(probe.duration) ? probe.duration : 0); };\n probe.onerror = function () { finish(0); };\n probe.src = url;\n setTimeout(function () { finish(0); }, 3000);\n }\n\n var motionPollTimer = null;\n function pollMotionJob() {\n if (motionPollTimer) clearTimeout(motionPollTimer);\n var tick = async function () {\n var job;\n try {\n job = (await (await fetch('/api/editor/motion-status')).json()).job;\n } catch (error) {\n motionPollTimer = setTimeout(tick, 3000);\n return;\n }\n if (!job) return;\n if (job.status === 'running') {\n showMotionNote(job.message, '');\n setStatus('saving', 'Making the animation\u2026');\n motionPollTimer = setTimeout(tick, 3000);\n return;\n }\n if (job.status === 'failed') {\n showMotionNote('Could not make the animation: ' + job.message, '');\n setStatus('error', 'Animation failed');\n setMotionButtons('ready');\n return;\n }\n showMotionNote(job.message + ' Reloading to show it.', '');\n setStatus('saved', 'Animation added');\n setMotionButtons('ready');\n if (autoReload && !voxelSessionActive && !voxelSessionPending) start();\n else banner.classList.add('show');\n };\n void tick();\n }\n\n async function submitMotion() {\n if (!motionBlob) return;\n var name = motionName.value.trim();\n if (name === '') { motionName.focus(); return; }\n // Whatever the handles show is what gets made; a still-running analysis must not move them.\n motionAnalysisToken++;\n if (motionAnalyzePollTimer) { clearTimeout(motionAnalyzePollTimer); motionAnalyzePollTimer = null; }\n var query = '?name=' + encodeURIComponent(name)\n + '&slot=' + encodeURIComponent(motionSlot.value)\n + '&loop=' + (motionLoop.checked ? '1' : '0');\n if (motionHasTrim()) {\n query += '&trimStart=' + encodeURIComponent(String(Math.round(motionTrimStart * 100) / 100))\n + '&trimEnd=' + encodeURIComponent(String(Math.round(motionTrimEnd * 100) / 100));\n }\n if (motionPendingId) {\n query += '&pendingId=' + encodeURIComponent(motionPendingId);\n }\n motionSubmit.disabled = true;\n showMotionNote('Uploading the video\u2026', '');\n var response, result;\n try {\n response = await fetch('/api/editor/motion-video' + query, {\n method: 'POST',\n headers: { 'Content-Type': motionBlob.type || 'video/webm' },\n body: motionBlob\n });\n result = await response.json();\n } catch (error) {\n showMotionNote('Could not send the video to the sidecar.', '');\n motionSubmit.disabled = false;\n return;\n }\n if (!response.ok || result.ok !== true) {\n showMotionNote(result.error || 'The sidecar refused the video.', '');\n motionSubmit.disabled = false;\n return;\n }\n if (result.running !== true) {\n // The one case the sidecar cannot run itself: nobody is logged in.\n showMotionNote(result.reason || '', result.command || '');\n setStatus('saved', 'Video saved');\n return;\n }\n showMotionNote(result.note || 'Making the animation \u2014 this takes a few minutes. You can keep editing.', '');\n setStatus('saving', 'Making the animation\u2026');\n pollMotionJob();\n }\n\n motionToggle.addEventListener('click', openMotionPanel);\n document.getElementById('motion-close').addEventListener('click', function () { closeMotionPanel(); });\n motionOverlay.addEventListener('click', function (event) {\n if (event.target === motionOverlay) closeMotionPanel();\n });\n motionRecordBtn.addEventListener('click', function () { void startMotionRecording(); });\n motionStopBtn.addEventListener('click', stopMotionRecording);\n motionRetakeBtn.addEventListener('click', resetMotionPanel);\n motionPickBtn.addEventListener('click', function () { motionFile.value = ''; motionFile.click(); });\n motionFile.addEventListener('change', function () { pickMotionFile(motionFile.files && motionFile.files[0]); });\n motionSubmit.addEventListener('click', function () { void submitMotion(); });\n motionTrimStartHandle.addEventListener('pointerdown', function (event) { beginMotionTrimDrag('start', event); });\n motionTrimEndHandle.addEventListener('pointerdown', function (event) { beginMotionTrimDrag('end', event); });\n motionTrimFullBtn.addEventListener('click', function () {\n motionTrimStart = 0;\n motionTrimEnd = motionTrimDuration;\n renderMotionTrim();\n });\n // \"Preview the kept range\" plays exactly trimStart -> trimEnd once and stops\n // on the last kept frame: what was just watched IS what the animation is\n // made from.\n motionTrimPreviewBtn.addEventListener('click', function () {\n if (motionPreviewingTrim) {\n motionPreview.pause();\n motionPreviewingTrim = false;\n renderMotionTrim();\n return;\n }\n motionPreview.currentTime = motionTrimStart;\n motionPreviewingTrim = true;\n renderMotionTrim();\n var played = motionPreview.play();\n if (played && played.catch) {\n played.catch(function () { motionPreviewingTrim = false; renderMotionTrim(); });\n }\n });\n motionPreview.addEventListener('pause', function () {\n if (motionPreviewingTrim) { motionPreviewingTrim = false; renderMotionTrim(); }\n });\n motionPreview.addEventListener('ended', function () {\n if (motionPreviewingTrim) { motionPreviewingTrim = false; renderMotionTrim(); }\n });\n motionPreview.addEventListener('timeupdate', function () {\n if (motionTrimDuration > 0) {\n motionTrimPlayhead.style.left = Math.min(Math.max(motionPreview.currentTime / motionTrimDuration, 0), 1) * 100 + '%';\n }\n if (motionPreviewingTrim && motionPreview.currentTime >= motionTrimEnd) {\n motionPreview.pause();\n motionPreview.currentTime = motionTrimEnd;\n motionPreviewingTrim = false;\n renderMotionTrim();\n }\n });\n";
|
|
20
|
+
export declare const MOTION_PANEL_JS = "\n // ---- Record motion: webcam or file -> Uthana video-to-motion -> assets[] ----------------------\n var motionToggle = document.getElementById('motion');\n var motionOverlay = document.getElementById('motion-overlay');\n var motionPreview = document.getElementById('motion-preview');\n var motionTimer = document.getElementById('motion-timer');\n var motionCountdown = document.getElementById('motion-countdown');\n var motionCountdownNumber = document.getElementById('motion-countdown-number');\n /** Seconds shown on the live preview before the recorder starts, so the first frames are the pose. */\n var MOTION_COUNTDOWN_S = 3;\n var motionCountdownToken = 0;\n var motionHint = document.getElementById('motion-hint');\n var motionRecordBtn = document.getElementById('motion-record');\n var motionStartBtn = document.getElementById('motion-start');\n var motionStopBtn = document.getElementById('motion-stop');\n var motionPickBtn = document.getElementById('motion-pick');\n var motionRetakeBtn = document.getElementById('motion-retake');\n var motionFile = document.getElementById('motion-file');\n var motionGuidance = document.getElementById('motion-guidance');\n var motionName = document.getElementById('motion-name');\n var motionSlot = document.getElementById('motion-slot');\n var motionLoop = document.getElementById('motion-loop');\n var motionSubmit = document.getElementById('motion-submit');\n var motionNote = document.getElementById('motion-note');\n var MOTION_MIN_S = 2;\n var MOTION_MAX_S = 60;\n var MOTION_MAX_BYTES = 200 * 1024 * 1024;\n var motionBlob = null;\n // Whether motionBlob came from the webcam recorder (auto-trim applies) or a picked file (it does not).\n var motionFromRecording = false;\n var motionSeconds = 0;\n var motionStream = null;\n // True while the camera is open for framing, before recording starts.\n var motionFraming = false;\n var motionRecorder = null;\n var motionChunks = [];\n var motionTickTimer = null;\n var motionStartedAt = 0;\n var motionObjectUrl = null;\n // ---- Trim window: seeded by the sidecar's trim analysis, adjusted on the handles ------------\n var motionTrimWrap = document.getElementById('motion-trim');\n var motionTrimBar = document.getElementById('motion-trimbar');\n var motionTrimKept = document.getElementById('motion-trim-kept');\n var motionTrimPlayhead = document.getElementById('motion-trim-playhead');\n var motionTrimStartHandle = document.getElementById('motion-trim-start');\n var motionTrimEndHandle = document.getElementById('motion-trim-end');\n var motionTrimNote = document.getElementById('motion-trim-note');\n var motionTrimFullBtn = document.getElementById('motion-trim-full');\n var motionTrimPreviewBtn = document.getElementById('motion-trim-preview');\n // True while \"Preview the kept range\" is playing trimStart -> trimEnd.\n var motionPreviewingTrim = false;\n var MOTION_TRIM_MIN_KEPT_S = 2;\n var motionTrimDuration = 0;\n var motionTrimStart = 0;\n var motionTrimEnd = 0;\n var motionPendingId = null;\n var motionAnalysisState = 'idle';\n var motionAnalysisToken = 0;\n var motionAnalyzePollTimer = null;\n\n function motionHasTrim() {\n return motionTrimDuration > 0 && motionTrimEnd > motionTrimStart\n && (motionTrimStart >= 0.1 || motionTrimDuration - motionTrimEnd >= 0.1);\n }\n\n function renderMotionTrim() {\n if (motionTrimDuration <= 0) { motionTrimWrap.classList.remove('show'); return; }\n motionTrimWrap.classList.add('show');\n var pct = function (seconds) {\n return Math.min(Math.max(seconds / motionTrimDuration, 0), 1) * 100 + '%';\n };\n motionTrimKept.style.left = pct(motionTrimStart);\n motionTrimKept.style.width = Math.max(0, (motionTrimEnd - motionTrimStart) / motionTrimDuration) * 100 + '%';\n motionTrimStartHandle.style.left = pct(motionTrimStart);\n motionTrimEndHandle.style.left = pct(motionTrimEnd);\n motionTrimFullBtn.hidden = !motionHasTrim();\n motionTrimPreviewBtn.textContent = motionPreviewingTrim\n ? '\u23F9 Stop'\n : (motionHasTrim() ? '\u25B6 Preview the kept range' : '\u25B6 Play');\n var note;\n if (motionAnalysisState === 'running') {\n note = 'Analyzing the clip for a good trim\u2026';\n } else if (!motionHasTrim()) {\n note = 'Using the full clip.';\n } else {\n var parts = [];\n if (motionTrimStart >= 0.1) parts.push(motionTrimStart.toFixed(1) + 's from the start');\n if (motionTrimDuration - motionTrimEnd >= 0.1) parts.push((motionTrimDuration - motionTrimEnd).toFixed(1) + 's from the end');\n note = 'Trimming ' + parts.join(' and ') + '.';\n }\n motionTrimNote.textContent = note;\n }\n\n function resetMotionTrim() {\n motionPreviewingTrim = false;\n motionAnalysisToken++;\n if (motionAnalyzePollTimer) { clearTimeout(motionAnalyzePollTimer); motionAnalyzePollTimer = null; }\n motionPendingId = null;\n motionAnalysisState = 'idle';\n motionTrimDuration = 0;\n motionTrimStart = 0;\n motionTrimEnd = 0;\n motionTrimWrap.classList.remove('show');\n }\n\n function seedMotionTrim(durationSeconds) {\n if (!(durationSeconds > 0)) return;\n motionTrimDuration = durationSeconds;\n motionTrimStart = 0;\n motionTrimEnd = durationSeconds;\n // Review playback is the trim: the Play/Preview button below drives the\n // video, and a native full-clip scrubber under a trim bar read as \"it\n // plays the whole video anyway\". Controls stay native only while the\n // duration is unknown and the bar cannot render.\n motionPreview.controls = false;\n renderMotionTrim();\n }\n\n function applyMotionSuggestion(suggestion) {\n if (!suggestion) { renderMotionTrim(); return; }\n if (suggestion.durationSeconds > 0) {\n motionTrimDuration = suggestion.durationSeconds;\n motionTrimStart = 0;\n motionTrimEnd = suggestion.durationSeconds;\n }\n if (suggestion.confidence === 'high'\n && typeof suggestion.suggestedStartSeconds === 'number'\n && typeof suggestion.suggestedEndSeconds === 'number'\n && suggestion.suggestedEndSeconds > suggestion.suggestedStartSeconds) {\n motionTrimStart = suggestion.suggestedStartSeconds;\n motionTrimEnd = Math.min(suggestion.suggestedEndSeconds, motionTrimDuration || suggestion.suggestedEndSeconds);\n }\n renderMotionTrim();\n }\n\n function beginMotionTrimDrag(which, event) {\n if (motionTrimDuration <= 0) return;\n var handle = event.currentTarget;\n handle.setPointerCapture(event.pointerId);\n var move = function (e) {\n var rect = motionTrimBar.getBoundingClientRect();\n if (rect.width === 0) return;\n var at = Math.min(Math.max((e.clientX - rect.left) / rect.width, 0), 1) * motionTrimDuration;\n if (which === 'start') motionTrimStart = Math.min(at, motionTrimEnd - MOTION_TRIM_MIN_KEPT_S);\n else motionTrimEnd = Math.max(at, motionTrimStart + MOTION_TRIM_MIN_KEPT_S);\n // Show the exact frame the cut lands on while the handle moves.\n motionPreview.pause();\n motionPreviewingTrim = false;\n motionPreview.currentTime = which === 'start' ? motionTrimStart : motionTrimEnd;\n renderMotionTrim();\n };\n var up = function () {\n handle.removeEventListener('pointermove', move);\n handle.removeEventListener('pointerup', up);\n handle.removeEventListener('pointercancel', up);\n };\n handle.addEventListener('pointermove', move);\n handle.addEventListener('pointerup', up);\n handle.addEventListener('pointercancel', up);\n event.preventDefault();\n }\n\n function pollMotionAnalysis(token) {\n var tick = async function () {\n if (token !== motionAnalysisToken) return;\n var payload;\n try {\n payload = await (await fetch('/api/editor/motion-analyze-status')).json();\n } catch (error) {\n motionAnalyzePollTimer = setTimeout(tick, 2000);\n return;\n }\n if (token !== motionAnalysisToken) return;\n if (payload && payload.status === 'done') {\n motionAnalysisState = 'done';\n applyMotionSuggestion(payload.suggestion);\n return;\n }\n if (payload && payload.status === 'running') {\n motionAnalyzePollTimer = setTimeout(tick, 2000);\n return;\n }\n // failed / unavailable / none: no suggestion, the full-range handles stand.\n motionAnalysisState = 'failed';\n renderMotionTrim();\n };\n motionAnalyzePollTimer = setTimeout(tick, 2000);\n }\n\n /**\n * Hand the clip to the sidecar ahead of submission: it uploads once and runs the unbilled trim\n * analysis while the creator reviews. Degraded, never blocking \u2014 any failure just leaves the\n * handles at full range, and Submit works throughout.\n */\n async function startMotionAnalysis(blob) {\n var token = ++motionAnalysisToken;\n motionPendingId = null;\n motionAnalysisState = 'running';\n renderMotionTrim();\n var result = null;\n try {\n var response = await fetch('/api/editor/motion-analyze', {\n method: 'POST',\n headers: { 'Content-Type': blob.type || 'video/webm' },\n body: blob\n });\n result = await response.json();\n } catch (error) { /* the sidecar said nothing; treat as unavailable */ }\n if (token !== motionAnalysisToken) return;\n if (!result || result.ok !== true || result.analysing !== true) {\n motionAnalysisState = 'idle';\n renderMotionTrim();\n return;\n }\n motionPendingId = result.pendingId || null;\n pollMotionAnalysis(token);\n }\n\n function motionSupported() {\n return !!(window.isSecureContext && navigator.mediaDevices && navigator.mediaDevices.getUserMedia\n && typeof MediaRecorder !== 'undefined');\n }\n\n /** mp4 first (Safari), then the WebM the rest produce; asset-forger re-encodes either. */\n function pickMotionMime() {\n var candidates = ['video/mp4', 'video/webm;codecs=vp9', 'video/webm'];\n for (var i = 0; i < candidates.length; i++) {\n if (MediaRecorder.isTypeSupported(candidates[i])) return candidates[i];\n }\n return '';\n }\n\n function motionCameraError(error) {\n var name = error && error.name;\n if (name === 'NotAllowedError' || name === 'SecurityError') return 'Camera access was refused. Allow it in the address bar, or choose a video file.';\n if (name === 'NotFoundError' || name === 'OverconstrainedError') return 'No camera found. Choose a video file instead.';\n if (name === 'NotReadableError') return 'The camera is in use by another app. Close it, or choose a video file.';\n return 'Could not start the camera: ' + ((error && error.message) || 'unknown error');\n }\n\n function formatMotionClock(seconds) {\n var s = Math.max(0, Math.floor(seconds));\n var rest = s % 60;\n return Math.floor(s / 60) + ':' + (rest < 10 ? '0' : '') + rest;\n }\n\n function showMotionNote(text, command) {\n document.getElementById('motion-note-text').textContent = text;\n document.getElementById('motion-command').textContent = command || '';\n motionNote.classList.add('show');\n }\n\n function releaseMotionObjectUrl() {\n if (motionObjectUrl) { URL.revokeObjectURL(motionObjectUrl); motionObjectUrl = null; }\n }\n\n /** Stop the recorder if one is still live \u2014 it may already have stopped itself. */\n function discardMotionRecorder() {\n if (motionRecorder && motionRecorder.state !== 'inactive') {\n try { motionRecorder.stop(); } catch (error) { /* already stopped */ }\n }\n }\n\n function stopMotionStream() {\n if (motionStream) {\n motionStream.getTracks().forEach(function (track) { track.stop(); });\n motionStream = null;\n }\n if (motionTickTimer) { clearInterval(motionTickTimer); motionTickTimer = null; }\n motionCountdownToken++;\n motionCountdown.classList.remove('show');\n motionTimer.classList.remove('show');\n motionPreview.classList.remove('mirror');\n }\n\n function setMotionButtons(state) {\n motionRecordBtn.hidden = state !== 'idle';\n motionPickBtn.hidden = state !== 'idle';\n motionStartBtn.hidden = state !== 'framing';\n motionStopBtn.hidden = state !== 'recording' && state !== 'framing';\n motionStopBtn.textContent = state === 'framing' ? 'Cancel' : 'Stop';\n motionRetakeBtn.hidden = state !== 'ready';\n motionSubmit.disabled = state !== 'ready';\n }\n\n function resetMotionPanel() {\n motionFraming = false;\n resetMotionTrim();\n stopMotionStream();\n discardMotionRecorder();\n motionRecorder = null;\n motionChunks = [];\n motionBlob = null;\n motionFromRecording = false;\n motionSeconds = 0;\n releaseMotionObjectUrl();\n motionPreview.srcObject = null;\n motionPreview.removeAttribute('src');\n motionPreview.controls = false;\n motionPreview.muted = true;\n motionHint.classList.remove('hidden');\n var supported = motionSupported();\n motionGuidance.textContent = supported ? '' : 'Webcam recording needs a secure page with a camera; choose a video file instead.';\n motionRecordBtn.disabled = !supported;\n motionNote.classList.remove('show');\n setMotionButtons('idle');\n }\n\n function openMotionPanel() {\n resetMotionPanel();\n motionOverlay.classList.add('show');\n motionName.focus();\n }\n\n /** Returns whether it was open \u2014 the Escape handler's contract, like closeAssetDetail. */\n function closeMotionPanel() {\n if (!motionOverlay.classList.contains('show')) return false;\n stopMotionStream();\n discardMotionRecorder();\n motionOverlay.classList.remove('show');\n frame.focus();\n return true;\n }\n\n /** The clip is in hand: show it back, and say whether it is within Uthana's 2-60 s window. */\n function acceptMotionClip(blob, seconds) {\n motionBlob = blob;\n motionSeconds = seconds;\n releaseMotionObjectUrl();\n motionObjectUrl = URL.createObjectURL(blob);\n motionPreview.srcObject = null;\n motionPreview.src = motionObjectUrl;\n motionPreview.controls = true;\n motionPreview.muted = true;\n motionHint.classList.add('hidden');\n var note;\n if (blob.size > MOTION_MAX_BYTES) note = 'That video is over 200 MB. Trim it to the seconds that matter.';\n else if (seconds > 0 && seconds < MOTION_MIN_S) note = 'Too short: Uthana needs at least ' + MOTION_MIN_S + ' seconds.';\n else if (seconds > MOTION_MAX_S) note = 'Too long: Uthana takes at most ' + MOTION_MAX_S + ' seconds. Trim it, or record again.';\n var ok = !note;\n motionGuidance.textContent = ok\n ? (seconds > 0 ? formatMotionClock(seconds) + ' of video ready.' : 'Video ready.')\n : note;\n setMotionButtons('ready');\n motionSubmit.disabled = !ok;\n resetMotionTrim();\n if (ok) {\n seedMotionTrim(seconds);\n // A fresh WebM often reports no duration; the preview's metadata is the fallback scale.\n motionPreview.onloadedmetadata = function () {\n if (motionTrimDuration === 0 && isFinite(motionPreview.duration) && motionPreview.duration > 0) {\n seedMotionTrim(motionPreview.duration);\n }\n };\n void startMotionAnalysis(blob);\n }\n }\n\n /**\n * Open the camera without recording \u2014 the framing step: the creator sees\n * themselves and gets their whole body in frame for as long as they need.\n * Start then runs the countdown and records.\n */\n async function openMotionCamera() {\n if (!motionSupported()) return;\n motionNote.classList.remove('show');\n try {\n motionStream = await navigator.mediaDevices.getUserMedia({\n video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: 'user' }, audio: false\n });\n } catch (error) {\n motionGuidance.textContent = motionCameraError(error);\n return;\n }\n motionFraming = true;\n releaseMotionObjectUrl();\n motionPreview.removeAttribute('src');\n motionPreview.controls = false;\n motionPreview.srcObject = motionStream;\n motionPreview.classList.add('mirror');\n motionHint.classList.add('hidden');\n try { await motionPreview.play(); } catch (error) { /* autoplay refused; the stream still shows on interaction */ }\n motionGuidance.textContent = 'Get your whole body in frame, then press Start recording.';\n setMotionButtons('framing');\n }\n\n async function startMotionRecording() {\n if (!motionStream || !motionFraming) return;\n motionFraming = false;\n var mime = pickMotionMime();\n try {\n motionRecorder = mime ? new MediaRecorder(motionStream, { mimeType: mime }) : new MediaRecorder(motionStream);\n } catch (error) {\n stopMotionStream();\n motionGuidance.textContent = 'This browser cannot record video. Choose a video file instead.';\n return;\n }\n // Count down on the live preview; Stop during the count aborts without recording.\n var token = ++motionCountdownToken;\n setMotionButtons('recording');\n motionGuidance.textContent = 'Get in frame \u2014 recording starts after the count.';\n motionCountdown.classList.add('show');\n for (var left = MOTION_COUNTDOWN_S; left > 0; left--) {\n motionCountdownNumber.textContent = String(left);\n await new Promise(function (resolve) { setTimeout(resolve, 1000); });\n if (token !== motionCountdownToken) return;\n }\n motionCountdown.classList.remove('show');\n motionChunks = [];\n motionRecorder.ondataavailable = function (event) { if (event.data && event.data.size > 0) motionChunks.push(event.data); };\n motionRecorder.onstop = function () {\n var seconds = (Date.now() - motionStartedAt) / 1000;\n var type = motionRecorder.mimeType || mime || 'video/webm';\n stopMotionStream();\n motionFromRecording = true;\n acceptMotionClip(new Blob(motionChunks, { type: type }), seconds);\n };\n motionStartedAt = Date.now();\n motionRecorder.start(1000);\n motionTimer.textContent = '0:00';\n motionTimer.classList.add('show');\n motionGuidance.textContent = 'Recording. Stop when the move is done (auto-stops at ' + MOTION_MAX_S + ' s).';\n motionTickTimer = setInterval(function () {\n var elapsed = (Date.now() - motionStartedAt) / 1000;\n motionTimer.textContent = formatMotionClock(elapsed);\n if (elapsed >= MOTION_MAX_S) stopMotionRecording();\n }, 250);\n }\n\n function stopMotionRecording() {\n if (motionRecorder && motionRecorder.state === 'recording') { motionRecorder.stop(); return; }\n // Cancel during framing, or Stop during the countdown: nothing was\n // recorded, back to the start.\n if (motionFraming || motionCountdown.classList.contains('show')) resetMotionPanel();\n }\n\n function pickMotionFile(file) {\n if (!file) return;\n // Read the duration off the file itself; a picked video knows its length, a fresh WebM often\n // does not (Infinity), so 0 means \"unknown\" and the server's ffprobe is the judge.\n var probe = document.createElement('video');\n probe.preload = 'metadata';\n var url = URL.createObjectURL(file);\n var settled = false;\n var finish = function (seconds) {\n if (settled) return;\n settled = true;\n URL.revokeObjectURL(url);\n motionFromRecording = false;\n acceptMotionClip(file, seconds);\n };\n probe.onloadedmetadata = function () { finish(isFinite(probe.duration) ? probe.duration : 0); };\n probe.onerror = function () { finish(0); };\n probe.src = url;\n setTimeout(function () { finish(0); }, 3000);\n }\n\n var motionPollTimer = null;\n function pollMotionJob() {\n if (motionPollTimer) clearTimeout(motionPollTimer);\n var tick = async function () {\n var job;\n try {\n job = (await (await fetch('/api/editor/motion-status')).json()).job;\n } catch (error) {\n motionPollTimer = setTimeout(tick, 3000);\n return;\n }\n if (!job) return;\n if (job.status === 'running') {\n showMotionNote(job.message, '');\n setStatus('saving', 'Making the animation\u2026');\n motionPollTimer = setTimeout(tick, 3000);\n return;\n }\n if (job.status === 'failed') {\n showMotionNote('Could not make the animation: ' + job.message, '');\n setStatus('error', 'Animation failed');\n setMotionButtons('ready');\n return;\n }\n showMotionNote(job.message + ' Reloading to show it.', '');\n setStatus('saved', 'Animation added');\n setMotionButtons('ready');\n if (autoReload && !voxelSessionActive && !voxelSessionPending) start();\n else banner.classList.add('show');\n };\n void tick();\n }\n\n async function submitMotion() {\n if (!motionBlob) return;\n var name = motionName.value.trim();\n if (name === '') { motionName.focus(); return; }\n // Whatever the handles show is what gets made; a still-running analysis must not move them.\n motionAnalysisToken++;\n if (motionAnalyzePollTimer) { clearTimeout(motionAnalyzePollTimer); motionAnalyzePollTimer = null; }\n var query = '?name=' + encodeURIComponent(name)\n + '&slot=' + encodeURIComponent(motionSlot.value)\n + '&loop=' + (motionLoop.checked ? '1' : '0');\n if (motionHasTrim()) {\n query += '&trimStart=' + encodeURIComponent(String(Math.round(motionTrimStart * 100) / 100))\n + '&trimEnd=' + encodeURIComponent(String(Math.round(motionTrimEnd * 100) / 100));\n }\n if (motionPendingId) {\n query += '&pendingId=' + encodeURIComponent(motionPendingId);\n }\n motionSubmit.disabled = true;\n showMotionNote('Uploading the video\u2026', '');\n var response, result;\n try {\n response = await fetch('/api/editor/motion-video' + query, {\n method: 'POST',\n headers: { 'Content-Type': motionBlob.type || 'video/webm' },\n body: motionBlob\n });\n result = await response.json();\n } catch (error) {\n showMotionNote('Could not send the video to the sidecar.', '');\n motionSubmit.disabled = false;\n return;\n }\n if (!response.ok || result.ok !== true) {\n showMotionNote(result.error || 'The sidecar refused the video.', '');\n motionSubmit.disabled = false;\n return;\n }\n if (result.running !== true) {\n // The one case the sidecar cannot run itself: nobody is logged in.\n showMotionNote(result.reason || '', result.command || '');\n setStatus('saved', 'Video saved');\n return;\n }\n showMotionNote(result.note || 'Making the animation \u2014 this takes a few minutes. You can keep editing.', '');\n setStatus('saving', 'Making the animation\u2026');\n pollMotionJob();\n }\n\n motionToggle.addEventListener('click', openMotionPanel);\n document.getElementById('motion-close').addEventListener('click', function () { closeMotionPanel(); });\n motionOverlay.addEventListener('click', function (event) {\n if (event.target === motionOverlay) closeMotionPanel();\n });\n motionRecordBtn.addEventListener('click', function () { void openMotionCamera(); });\n motionStartBtn.addEventListener('click', function () { void startMotionRecording(); });\n motionStopBtn.addEventListener('click', stopMotionRecording);\n motionRetakeBtn.addEventListener('click', resetMotionPanel);\n motionPickBtn.addEventListener('click', function () { motionFile.value = ''; motionFile.click(); });\n motionFile.addEventListener('change', function () { pickMotionFile(motionFile.files && motionFile.files[0]); });\n motionSubmit.addEventListener('click', function () { void submitMotion(); });\n motionTrimStartHandle.addEventListener('pointerdown', function (event) { beginMotionTrimDrag('start', event); });\n motionTrimEndHandle.addEventListener('pointerdown', function (event) { beginMotionTrimDrag('end', event); });\n motionTrimFullBtn.addEventListener('click', function () {\n motionTrimStart = 0;\n motionTrimEnd = motionTrimDuration;\n renderMotionTrim();\n });\n // \"Preview the kept range\" plays exactly trimStart -> trimEnd once and stops\n // on the last kept frame: what was just watched IS what the animation is\n // made from.\n motionTrimPreviewBtn.addEventListener('click', function () {\n if (motionPreviewingTrim) {\n motionPreview.pause();\n motionPreviewingTrim = false;\n renderMotionTrim();\n return;\n }\n motionPreview.currentTime = motionTrimStart;\n motionPreviewingTrim = true;\n renderMotionTrim();\n var played = motionPreview.play();\n if (played && played.catch) {\n played.catch(function () { motionPreviewingTrim = false; renderMotionTrim(); });\n }\n });\n motionPreview.addEventListener('pause', function () {\n if (motionPreviewingTrim) { motionPreviewingTrim = false; renderMotionTrim(); }\n });\n motionPreview.addEventListener('ended', function () {\n if (motionPreviewingTrim) { motionPreviewingTrim = false; renderMotionTrim(); }\n });\n motionPreview.addEventListener('timeupdate', function () {\n if (motionTrimDuration > 0) {\n motionTrimPlayhead.style.left = Math.min(Math.max(motionPreview.currentTime / motionTrimDuration, 0), 1) * 100 + '%';\n }\n if (motionPreviewingTrim && motionPreview.currentTime >= motionTrimEnd) {\n motionPreview.pause();\n motionPreview.currentTime = motionTrimEnd;\n motionPreviewingTrim = false;\n renderMotionTrim();\n }\n });\n";
|
|
@@ -90,6 +90,7 @@ export const MOTION_PANEL_HTML = ` <div id="motion-overlay" class="bm-overlay">
|
|
|
90
90
|
</div>
|
|
91
91
|
<div id="motion-sources">
|
|
92
92
|
<button id="motion-record" class="bm-pill bm-pill--primary" type="button">Record with webcam</button>
|
|
93
|
+
<button id="motion-start" class="bm-pill bm-pill--primary" type="button" hidden>Start recording</button>
|
|
93
94
|
<button id="motion-stop" class="bm-pill" type="button" hidden>Stop</button>
|
|
94
95
|
<button id="motion-pick" class="bm-pill" type="button">Choose a video…</button>
|
|
95
96
|
<button id="motion-retake" class="bm-pill bm-pill--ghost" type="button" hidden>Start over</button>
|
|
@@ -140,6 +141,7 @@ export const MOTION_PANEL_JS = `
|
|
|
140
141
|
var motionCountdownToken = 0;
|
|
141
142
|
var motionHint = document.getElementById('motion-hint');
|
|
142
143
|
var motionRecordBtn = document.getElementById('motion-record');
|
|
144
|
+
var motionStartBtn = document.getElementById('motion-start');
|
|
143
145
|
var motionStopBtn = document.getElementById('motion-stop');
|
|
144
146
|
var motionPickBtn = document.getElementById('motion-pick');
|
|
145
147
|
var motionRetakeBtn = document.getElementById('motion-retake');
|
|
@@ -158,6 +160,8 @@ export const MOTION_PANEL_JS = `
|
|
|
158
160
|
var motionFromRecording = false;
|
|
159
161
|
var motionSeconds = 0;
|
|
160
162
|
var motionStream = null;
|
|
163
|
+
// True while the camera is open for framing, before recording starts.
|
|
164
|
+
var motionFraming = false;
|
|
161
165
|
var motionRecorder = null;
|
|
162
166
|
var motionChunks = [];
|
|
163
167
|
var motionTickTimer = null;
|
|
@@ -402,12 +406,15 @@ export const MOTION_PANEL_JS = `
|
|
|
402
406
|
function setMotionButtons(state) {
|
|
403
407
|
motionRecordBtn.hidden = state !== 'idle';
|
|
404
408
|
motionPickBtn.hidden = state !== 'idle';
|
|
405
|
-
|
|
409
|
+
motionStartBtn.hidden = state !== 'framing';
|
|
410
|
+
motionStopBtn.hidden = state !== 'recording' && state !== 'framing';
|
|
411
|
+
motionStopBtn.textContent = state === 'framing' ? 'Cancel' : 'Stop';
|
|
406
412
|
motionRetakeBtn.hidden = state !== 'ready';
|
|
407
413
|
motionSubmit.disabled = state !== 'ready';
|
|
408
414
|
}
|
|
409
415
|
|
|
410
416
|
function resetMotionPanel() {
|
|
417
|
+
motionFraming = false;
|
|
411
418
|
resetMotionTrim();
|
|
412
419
|
stopMotionStream();
|
|
413
420
|
discardMotionRecorder();
|
|
@@ -479,7 +486,12 @@ export const MOTION_PANEL_JS = `
|
|
|
479
486
|
}
|
|
480
487
|
}
|
|
481
488
|
|
|
482
|
-
|
|
489
|
+
/**
|
|
490
|
+
* Open the camera without recording — the framing step: the creator sees
|
|
491
|
+
* themselves and gets their whole body in frame for as long as they need.
|
|
492
|
+
* Start then runs the countdown and records.
|
|
493
|
+
*/
|
|
494
|
+
async function openMotionCamera() {
|
|
483
495
|
if (!motionSupported()) return;
|
|
484
496
|
motionNote.classList.remove('show');
|
|
485
497
|
try {
|
|
@@ -490,6 +502,7 @@ export const MOTION_PANEL_JS = `
|
|
|
490
502
|
motionGuidance.textContent = motionCameraError(error);
|
|
491
503
|
return;
|
|
492
504
|
}
|
|
505
|
+
motionFraming = true;
|
|
493
506
|
releaseMotionObjectUrl();
|
|
494
507
|
motionPreview.removeAttribute('src');
|
|
495
508
|
motionPreview.controls = false;
|
|
@@ -497,6 +510,13 @@ export const MOTION_PANEL_JS = `
|
|
|
497
510
|
motionPreview.classList.add('mirror');
|
|
498
511
|
motionHint.classList.add('hidden');
|
|
499
512
|
try { await motionPreview.play(); } catch (error) { /* autoplay refused; the stream still shows on interaction */ }
|
|
513
|
+
motionGuidance.textContent = 'Get your whole body in frame, then press Start recording.';
|
|
514
|
+
setMotionButtons('framing');
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async function startMotionRecording() {
|
|
518
|
+
if (!motionStream || !motionFraming) return;
|
|
519
|
+
motionFraming = false;
|
|
500
520
|
var mime = pickMotionMime();
|
|
501
521
|
try {
|
|
502
522
|
motionRecorder = mime ? new MediaRecorder(motionStream, { mimeType: mime }) : new MediaRecorder(motionStream);
|
|
@@ -539,8 +559,9 @@ export const MOTION_PANEL_JS = `
|
|
|
539
559
|
|
|
540
560
|
function stopMotionRecording() {
|
|
541
561
|
if (motionRecorder && motionRecorder.state === 'recording') { motionRecorder.stop(); return; }
|
|
542
|
-
// Stop
|
|
543
|
-
|
|
562
|
+
// Cancel during framing, or Stop during the countdown: nothing was
|
|
563
|
+
// recorded, back to the start.
|
|
564
|
+
if (motionFraming || motionCountdown.classList.contains('show')) resetMotionPanel();
|
|
544
565
|
}
|
|
545
566
|
|
|
546
567
|
function pickMotionFile(file) {
|
|
@@ -650,7 +671,8 @@ export const MOTION_PANEL_JS = `
|
|
|
650
671
|
motionOverlay.addEventListener('click', function (event) {
|
|
651
672
|
if (event.target === motionOverlay) closeMotionPanel();
|
|
652
673
|
});
|
|
653
|
-
motionRecordBtn.addEventListener('click', function () { void
|
|
674
|
+
motionRecordBtn.addEventListener('click', function () { void openMotionCamera(); });
|
|
675
|
+
motionStartBtn.addEventListener('click', function () { void startMotionRecording(); });
|
|
654
676
|
motionStopBtn.addEventListener('click', stopMotionRecording);
|
|
655
677
|
motionRetakeBtn.addEventListener('click', resetMotionPanel);
|
|
656
678
|
motionPickBtn.addEventListener('click', function () { motionFile.value = ''; motionFile.click(); });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"motion-panel.js","sourceRoot":"","sources":["../../src/editor/motion-panel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD/B,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG
|
|
1
|
+
{"version":3,"file":"motion-panel.js","sourceRoot":"","sources":["../../src/editor/motion-panel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD/B,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8DhC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAglB9B,CAAC"}
|