@kvman/kvvideo 0.1.0 → 0.1.1
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 +1 -1
- package/dist/api.d.ts +26 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/brand/brand.d.ts +17 -0
- package/dist/brand/brand.d.ts.map +1 -0
- package/dist/brand/brand.js +44 -0
- package/dist/brand/brand.js.map +1 -0
- package/dist/browser/launch.d.ts +2 -0
- package/dist/browser/launch.d.ts.map +1 -1
- package/dist/browser/launch.js +7 -1
- package/dist/browser/launch.js.map +1 -1
- package/dist/compose/ass-file.d.ts +5 -0
- package/dist/compose/ass-file.d.ts.map +1 -1
- package/dist/compose/ass-file.js +32 -3
- package/dist/compose/ass-file.js.map +1 -1
- package/dist/compose/background.d.ts +23 -0
- package/dist/compose/background.d.ts.map +1 -0
- package/dist/compose/background.js +31 -0
- package/dist/compose/background.js.map +1 -0
- package/dist/compose/camera.d.ts +32 -0
- package/dist/compose/camera.d.ts.map +1 -0
- package/dist/compose/camera.js +98 -0
- package/dist/compose/camera.js.map +1 -0
- package/dist/compose/caption-font.d.ts +2 -0
- package/dist/compose/caption-font.d.ts.map +1 -1
- package/dist/compose/caption-font.js +2 -1
- package/dist/compose/caption-font.js.map +1 -1
- package/dist/compose/compose-drawing.d.ts +19 -0
- package/dist/compose/compose-drawing.d.ts.map +1 -0
- package/dist/compose/compose-drawing.js +38 -0
- package/dist/compose/compose-drawing.js.map +1 -0
- package/dist/compose/compose-input.d.ts +139 -0
- package/dist/compose/compose-input.d.ts.map +1 -1
- package/dist/compose/compose-input.js +48 -9
- package/dist/compose/compose-input.js.map +1 -1
- package/dist/compose/compose-plan.d.ts +36 -0
- package/dist/compose/compose-plan.d.ts.map +1 -0
- package/dist/compose/compose-plan.js +55 -0
- package/dist/compose/compose-plan.js.map +1 -0
- package/dist/compose/compose-size.d.ts +50 -0
- package/dist/compose/compose-size.d.ts.map +1 -0
- package/dist/compose/compose-size.js +72 -0
- package/dist/compose/compose-size.js.map +1 -0
- package/dist/compose/drawn-images.d.ts +19 -0
- package/dist/compose/drawn-images.d.ts.map +1 -0
- package/dist/compose/drawn-images.js +75 -0
- package/dist/compose/drawn-images.js.map +1 -0
- package/dist/compose/final-pass.d.ts +34 -0
- package/dist/compose/final-pass.d.ts.map +1 -0
- package/dist/compose/final-pass.js +106 -0
- package/dist/compose/final-pass.js.map +1 -0
- package/dist/compose/frame-layout.d.ts +30 -0
- package/dist/compose/frame-layout.d.ts.map +1 -0
- package/dist/compose/frame-layout.js +33 -0
- package/dist/compose/frame-layout.js.map +1 -0
- package/dist/compose/overlays.d.ts +20 -0
- package/dist/compose/overlays.d.ts.map +1 -0
- package/dist/compose/overlays.js +30 -0
- package/dist/compose/overlays.js.map +1 -0
- package/dist/compose/page-again.d.ts +26 -0
- package/dist/compose/page-again.d.ts.map +1 -0
- package/dist/compose/page-again.js +50 -0
- package/dist/compose/page-again.js.map +1 -0
- package/dist/compose/player-copies.d.ts +5 -1
- package/dist/compose/player-copies.d.ts.map +1 -1
- package/dist/compose/player-copies.js +19 -16
- package/dist/compose/player-copies.js.map +1 -1
- package/dist/compose/register-compose.d.ts.map +1 -1
- package/dist/compose/register-compose.js +106 -20
- package/dist/compose/register-compose.js.map +1 -1
- package/dist/compose/render.d.ts +13 -4
- package/dist/compose/render.d.ts.map +1 -1
- package/dist/compose/render.js +48 -22
- package/dist/compose/render.js.map +1 -1
- package/dist/compose/scene-args.d.ts +32 -7
- package/dist/compose/scene-args.d.ts.map +1 -1
- package/dist/compose/scene-args.js +77 -16
- package/dist/compose/scene-args.js.map +1 -1
- package/dist/compose/scene-look.d.ts +31 -0
- package/dist/compose/scene-look.d.ts.map +1 -0
- package/dist/compose/scene-look.js +46 -0
- package/dist/compose/scene-look.js.map +1 -0
- package/dist/compose/scene-plan.d.ts +0 -2
- package/dist/compose/scene-plan.d.ts.map +1 -1
- package/dist/compose/scene-plan.js +7 -5
- package/dist/compose/scene-plan.js.map +1 -1
- package/dist/compose/sound-effects.d.ts +23 -0
- package/dist/compose/sound-effects.d.ts.map +1 -0
- package/dist/compose/sound-effects.js +40 -0
- package/dist/compose/sound-effects.js.map +1 -0
- package/dist/compose/srt.d.ts +3 -0
- package/dist/compose/srt.d.ts.map +1 -0
- package/dist/compose/srt.js +10 -0
- package/dist/compose/srt.js.map +1 -0
- package/dist/compose/transitions.d.ts +16 -0
- package/dist/compose/transitions.d.ts.map +1 -0
- package/dist/compose/transitions.js +38 -0
- package/dist/compose/transitions.js.map +1 -0
- package/dist/connector.d.ts +67 -14
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js +27 -8
- package/dist/connector.js.map +1 -1
- package/dist/files/sidecar.d.ts +209 -0
- package/dist/files/sidecar.d.ts.map +1 -0
- package/dist/files/sidecar.js +66 -0
- package/dist/files/sidecar.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/narration/alignment.d.ts +15 -0
- package/dist/narration/alignment.d.ts.map +1 -0
- package/dist/narration/alignment.js +21 -0
- package/dist/narration/alignment.js.map +1 -0
- package/dist/narration/captions.d.ts +22 -4
- package/dist/narration/captions.d.ts.map +1 -1
- package/dist/narration/captions.js +44 -16
- package/dist/narration/captions.js.map +1 -1
- package/dist/narration/join-args.d.ts +3 -0
- package/dist/narration/join-args.d.ts.map +1 -0
- package/dist/narration/join-args.js +19 -0
- package/dist/narration/join-args.js.map +1 -0
- package/dist/narration/narration-cache.d.ts +25 -0
- package/dist/narration/narration-cache.d.ts.map +1 -0
- package/dist/narration/narration-cache.js +36 -0
- package/dist/narration/narration-cache.js.map +1 -0
- package/dist/narration/piper.d.ts +2 -6
- package/dist/narration/piper.d.ts.map +1 -1
- package/dist/narration/piper.js +9 -15
- package/dist/narration/piper.js.map +1 -1
- package/dist/narration/pronunciations.d.ts +11 -0
- package/dist/narration/pronunciations.d.ts.map +1 -0
- package/dist/narration/pronunciations.js +40 -0
- package/dist/narration/pronunciations.js.map +1 -0
- package/dist/narration/register-narration.d.ts +8 -0
- package/dist/narration/register-narration.d.ts.map +1 -1
- package/dist/narration/register-narration.js +54 -26
- package/dist/narration/register-narration.js.map +1 -1
- package/dist/narration/register-voice.d.ts +27 -0
- package/dist/narration/register-voice.d.ts.map +1 -0
- package/dist/narration/register-voice.js +82 -0
- package/dist/narration/register-voice.js.map +1 -0
- package/dist/narration/sentence-speaker.d.ts +35 -0
- package/dist/narration/sentence-speaker.d.ts.map +1 -0
- package/dist/narration/sentence-speaker.js +44 -0
- package/dist/narration/sentence-speaker.js.map +1 -0
- package/dist/narration/speech-services.d.ts +23 -3
- package/dist/narration/speech-services.d.ts.map +1 -1
- package/dist/narration/speech-services.js +27 -10
- package/dist/narration/speech-services.js.map +1 -1
- package/dist/narration/voice-args.d.ts +3 -0
- package/dist/narration/voice-args.d.ts.map +1 -0
- package/dist/narration/voice-args.js +10 -0
- package/dist/narration/voice-args.js.map +1 -0
- package/dist/programs/media-duration.d.ts +12 -0
- package/dist/programs/media-duration.d.ts.map +1 -1
- package/dist/programs/media-duration.js +13 -0
- package/dist/programs/media-duration.js.map +1 -1
- package/dist/programs/run-program.d.ts +1 -0
- package/dist/programs/run-program.d.ts.map +1 -1
- package/dist/programs/run-program.js +1 -0
- package/dist/programs/run-program.js.map +1 -1
- package/dist/record/address.d.ts +1 -1
- package/dist/record/address.d.ts.map +1 -1
- package/dist/record/address.js +5 -7
- package/dist/record/address.js.map +1 -1
- package/dist/record/cursor-script.d.ts +2 -1
- package/dist/record/cursor-script.d.ts.map +1 -1
- package/dist/record/cursor-script.js +52 -14
- package/dist/record/cursor-script.js.map +1 -1
- package/dist/record/encoder-args.js +1 -1
- package/dist/record/motion.d.ts +19 -0
- package/dist/record/motion.d.ts.map +1 -0
- package/dist/record/motion.js +59 -0
- package/dist/record/motion.js.map +1 -0
- package/dist/record/narration-cues.d.ts +14 -0
- package/dist/record/narration-cues.d.ts.map +1 -0
- package/dist/record/narration-cues.js +12 -0
- package/dist/record/narration-cues.js.map +1 -0
- package/dist/record/page-ready.d.ts +4 -0
- package/dist/record/page-ready.d.ts.map +1 -0
- package/dist/record/page-ready.js +15 -0
- package/dist/record/page-ready.js.map +1 -0
- package/dist/record/page-route.d.ts +13 -0
- package/dist/record/page-route.d.ts.map +1 -0
- package/dist/record/page-route.js +64 -0
- package/dist/record/page-route.js.map +1 -0
- package/dist/record/pointer.d.ts +6 -7
- package/dist/record/pointer.d.ts.map +1 -1
- package/dist/record/pointer.js +11 -10
- package/dist/record/pointer.js.map +1 -1
- package/dist/record/recorder.d.ts +17 -7
- package/dist/record/recorder.d.ts.map +1 -1
- package/dist/record/recorder.js +48 -18
- package/dist/record/recorder.js.map +1 -1
- package/dist/record/register-record.d.ts.map +1 -1
- package/dist/record/register-record.js +47 -13
- package/dist/record/register-record.js.map +1 -1
- package/dist/record/run-step.d.ts +16 -3
- package/dist/record/run-step.d.ts.map +1 -1
- package/dist/record/run-step.js +43 -23
- package/dist/record/run-step.js.map +1 -1
- package/dist/record/screencast.js +1 -1
- package/dist/record/steps.d.ts +54 -1
- package/dist/record/steps.d.ts.map +1 -1
- package/dist/record/steps.js +34 -16
- package/dist/record/steps.js.map +1 -1
- package/dist/review/analysis.d.ts +19 -0
- package/dist/review/analysis.d.ts.map +1 -0
- package/dist/review/analysis.js +36 -0
- package/dist/review/analysis.js.map +1 -0
- package/dist/review/frames.d.ts +9 -0
- package/dist/review/frames.d.ts.map +1 -0
- package/dist/review/frames.js +28 -0
- package/dist/review/frames.js.map +1 -0
- package/dist/review/register-review.d.ts +24 -0
- package/dist/review/register-review.d.ts.map +1 -0
- package/dist/review/register-review.js +81 -0
- package/dist/review/register-review.js.map +1 -0
- package/dist/review/sheet-args.d.ts +11 -0
- package/dist/review/sheet-args.d.ts.map +1 -0
- package/dist/review/sheet-args.js +24 -0
- package/dist/review/sheet-args.js.map +1 -0
- package/dist/settings.d.ts +4 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +4 -2
- package/dist/settings.js.map +1 -1
- package/dist/setup/ffmpeg-readiness.js +1 -1
- package/dist/setup/ffmpeg-readiness.js.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +3 -2
- package/dist/ui.js.map +1 -1
- package/dist/web/components/player.css +1 -1
- package/dist/web/components/player.js +33 -21
- package/docs/guide.md +21 -9
- package/locales/ar.json +12 -1
- package/locales/en.json +12 -1
- package/package.json +11 -5
- package/sounds/SOURCES.md +13 -0
- package/sounds/click.wav +0 -0
- package/sounds/keys.wav +0 -0
- package/sounds/pop.wav +0 -0
- package/sounds/whoosh.wav +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync, realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
// An HTML page of the workspace, recorded without a server (plan 16 §16.5; ADR 0115, 32): it is opened on an origin
|
|
4
|
+
// that never resolves, and each request to that origin is answered with the file at its path under the page's folder.
|
|
5
|
+
// A path that leaves the folder, through `..` or a symlink, or a file that doesn't exist, is answered 404.
|
|
6
|
+
export const pageOrigin = 'http://page.kvvideo.invalid';
|
|
7
|
+
const contentTypes = {
|
|
8
|
+
'.html': 'text/html; charset=utf-8',
|
|
9
|
+
'.htm': 'text/html; charset=utf-8',
|
|
10
|
+
'.css': 'text/css; charset=utf-8',
|
|
11
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
12
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
13
|
+
'.json': 'application/json',
|
|
14
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
15
|
+
'.svg': 'image/svg+xml',
|
|
16
|
+
'.png': 'image/png',
|
|
17
|
+
'.jpg': 'image/jpeg',
|
|
18
|
+
'.jpeg': 'image/jpeg',
|
|
19
|
+
'.gif': 'image/gif',
|
|
20
|
+
'.webp': 'image/webp',
|
|
21
|
+
'.avif': 'image/avif',
|
|
22
|
+
'.ico': 'image/x-icon',
|
|
23
|
+
'.woff': 'font/woff',
|
|
24
|
+
'.woff2': 'font/woff2',
|
|
25
|
+
'.ttf': 'font/ttf',
|
|
26
|
+
'.otf': 'font/otf',
|
|
27
|
+
'.mp4': 'video/mp4',
|
|
28
|
+
'.webm': 'video/webm',
|
|
29
|
+
'.mp3': 'audio/mpeg',
|
|
30
|
+
'.wav': 'audio/wav',
|
|
31
|
+
'.ogg': 'audio/ogg',
|
|
32
|
+
};
|
|
33
|
+
/** The address a page file is opened at. */
|
|
34
|
+
export function pageAddress(page) {
|
|
35
|
+
return `${pageOrigin}/${encodeURIComponent(path.basename(page))}`;
|
|
36
|
+
}
|
|
37
|
+
function inside(folder, candidate) {
|
|
38
|
+
const relative = path.relative(folder, candidate);
|
|
39
|
+
return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
40
|
+
}
|
|
41
|
+
function decodedPath(address) {
|
|
42
|
+
const { pathname } = new URL(address);
|
|
43
|
+
try {
|
|
44
|
+
return decodeURIComponent(pathname).replace(/^\/+/, '');
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof URIError)
|
|
48
|
+
return undefined;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** What a request of the page's origin is answered with, from the page's folder. */
|
|
53
|
+
export function answerPageRequest(folder, address) {
|
|
54
|
+
const relative = decodedPath(address);
|
|
55
|
+
if (relative === undefined)
|
|
56
|
+
return { status: 404 };
|
|
57
|
+
const file = path.resolve(folder, relative);
|
|
58
|
+
if (!inside(folder, file) || !existsSync(file))
|
|
59
|
+
return { status: 404 };
|
|
60
|
+
if (!inside(realpathSync(folder), realpathSync(file)) || !statSync(file).isFile())
|
|
61
|
+
return { status: 404 };
|
|
62
|
+
return { status: 200, file, contentType: contentTypes[path.extname(file).toLowerCase()] ?? 'application/octet-stream' };
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=page-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-route.js","sourceRoot":"","sources":["../../src/record/page-route.ts"],"names":[],"mappings":"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"}
|
package/dist/record/pointer.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { Page } from 'playwright-core';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export declare function
|
|
7
|
-
|
|
8
|
-
export declare function centerOf(page: Page, target: string): Promise<Pointer>;
|
|
2
|
+
import { type Point } from './motion.ts';
|
|
3
|
+
import type { Box } from './steps.ts';
|
|
4
|
+
export declare function glideTo(page: Page, from: Point, to: Point): Promise<Point>;
|
|
5
|
+
/** The box of the first visible match of a target, scrolled into view, in whole CSS pixels of the viewport. */
|
|
6
|
+
export declare function targetBox(page: Page, target: string): Promise<Box>;
|
|
7
|
+
export declare function centerOf(box: Box): Point;
|
|
9
8
|
//# sourceMappingURL=pointer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pointer.d.ts","sourceRoot":"","sources":["../../src/record/pointer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"pointer.d.ts","sourceRoot":"","sources":["../../src/record/pointer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAsB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAKtC,wBAAsB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAMhF;AAED,+GAA+G;AAC/G,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAOxE;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,KAAK,CAExC"}
|
package/dist/record/pointer.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
const glideMs = 600;
|
|
1
|
+
import { frameMs, glidePath } from "./motion.js";
|
|
2
|
+
// The mouse as the viewer sees it (plan 16 §16.5; ADR 0115, 9): it glides to each target along an eased, gently
|
|
3
|
+
// curved path, one move a frame, so the drawn pointer travels instead of jumping.
|
|
5
4
|
export async function glideTo(page, from, to) {
|
|
6
|
-
for (
|
|
7
|
-
|
|
8
|
-
await page.
|
|
9
|
-
await page.waitForTimeout(glideMs / glideMoves);
|
|
5
|
+
for (const point of glidePath(from, to)) {
|
|
6
|
+
await page.mouse.move(point.x, point.y);
|
|
7
|
+
await page.waitForTimeout(frameMs);
|
|
10
8
|
}
|
|
11
9
|
return to;
|
|
12
10
|
}
|
|
13
|
-
/** The
|
|
14
|
-
export async function
|
|
11
|
+
/** The box of the first visible match of a target, scrolled into view, in whole CSS pixels of the viewport. */
|
|
12
|
+
export async function targetBox(page, target) {
|
|
15
13
|
const element = page.locator(target).first();
|
|
16
14
|
await element.waitFor({ state: 'visible', timeout: 10_000 });
|
|
17
15
|
await element.scrollIntoViewIfNeeded({ timeout: 10_000 });
|
|
18
16
|
const box = await element.boundingBox({ timeout: 10_000 });
|
|
19
17
|
if (box === null)
|
|
20
18
|
throw new Error(`${target} has no box on the page`);
|
|
19
|
+
return { x: Math.round(box.x), y: Math.round(box.y), width: Math.round(box.width), height: Math.round(box.height) };
|
|
20
|
+
}
|
|
21
|
+
export function centerOf(box) {
|
|
21
22
|
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
22
23
|
}
|
|
23
24
|
//# sourceMappingURL=pointer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pointer.js","sourceRoot":"","sources":["../../src/record/pointer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pointer.js","sourceRoot":"","sources":["../../src/record/pointer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAc,MAAM,aAAa,CAAC;AAG7D,gHAAgH;AAChH,kFAAkF;AAElF,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAU,EAAE,IAAW,EAAE,EAAS;IAC9D,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,+GAA+G;AAC/G,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAU,EAAE,MAAc;IACxD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;IAC7C,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7D,MAAM,OAAO,CAAC,sBAAsB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3D,IAAI,GAAG,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,yBAAyB,CAAC,CAAC;IACtE,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;AACtH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAQ;IAC/B,OAAO,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;AACjE,CAAC"}
|
|
@@ -1,24 +1,34 @@
|
|
|
1
|
-
import type { Browser } from 'playwright-core';
|
|
1
|
+
import type { Browser, BrowserContext } from 'playwright-core';
|
|
2
2
|
import { type Program } from '../programs/run-program.ts';
|
|
3
|
-
import type
|
|
4
|
-
|
|
3
|
+
import { type Cues } from './narration-cues.ts';
|
|
4
|
+
import type { Action, Step } from './steps.ts';
|
|
5
|
+
export type RecordSource = {
|
|
6
|
+
kind: 'site';
|
|
5
7
|
url: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'page';
|
|
10
|
+
file: string;
|
|
11
|
+
};
|
|
12
|
+
export type RecordPlan = {
|
|
13
|
+
source: RecordSource;
|
|
6
14
|
steps: readonly Step[];
|
|
7
15
|
width: number;
|
|
8
16
|
height: number;
|
|
17
|
+
device: 'desktop' | 'phone';
|
|
18
|
+
cues: Cues | undefined;
|
|
9
19
|
folder: string;
|
|
10
20
|
ownPort: number | null;
|
|
11
21
|
ffmpeg: Program;
|
|
12
22
|
signal: AbortSignal;
|
|
13
23
|
now(): number;
|
|
24
|
+
stepStarted(number: number, of: number): void;
|
|
14
25
|
};
|
|
15
26
|
export type Recording = {
|
|
16
27
|
video: string;
|
|
17
28
|
durationMs: number;
|
|
18
|
-
|
|
19
|
-
do: Step['do'];
|
|
20
|
-
atMs: number;
|
|
21
|
-
}[];
|
|
29
|
+
actions: Action[];
|
|
22
30
|
};
|
|
31
|
+
/** A fresh browser context for a recording: its size and device, kvman's port aborted, a page's files, the pointer. */
|
|
32
|
+
export declare function recordingContext(browser: Browser, plan: Pick<RecordPlan, 'source' | 'width' | 'height' | 'device' | 'ownPort'>): Promise<BrowserContext>;
|
|
23
33
|
export declare function record(browser: Browser, plan: RecordPlan): Promise<Recording>;
|
|
24
34
|
//# sourceMappingURL=recorder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/record/recorder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/record/recorder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAQ,MAAM,iBAAiB,CAAC;AAErE,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAMrE,OAAO,EAAe,KAAK,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAK7D,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAO/C,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1F,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5B,IAAI,EAAE,IAAI,GAAG,SAAS,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,WAAW,CAAC;IACpB,GAAG,IAAI,MAAM,CAAC;IACd,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAMjF,uHAAuH;AACvH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAa9J;AA0CD,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAsBnF"}
|
package/dist/record/recorder.js
CHANGED
|
@@ -6,32 +6,58 @@ import { isOwnAddress } from "./address.js";
|
|
|
6
6
|
import { cursorScript } from "./cursor-script.js";
|
|
7
7
|
import { recordEncoderArgs } from "./encoder-args.js";
|
|
8
8
|
import { frameWriter } from "./frame-writer.js";
|
|
9
|
+
import { waitUntilMs } from "./narration-cues.js";
|
|
10
|
+
import { answerPageRequest, pageAddress, pageOrigin } from "./page-route.js";
|
|
11
|
+
import { waitUntilReady } from "./page-ready.js";
|
|
9
12
|
import { pausesAfter, runStep } from "./run-step.js";
|
|
10
13
|
import { startScreencast } from "./screencast.js";
|
|
14
|
+
const scale = 2;
|
|
11
15
|
const firstLine = (error) => (error instanceof Error ? (error.message.split('\n').find((line) => line.trim() !== '') ?? error.name) : String(error)).trim();
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
/** A fresh browser context for a recording: its size and device, kvman's port aborted, a page's files, the pointer. */
|
|
17
|
+
export async function recordingContext(browser, plan) {
|
|
18
|
+
const phone = plan.device === 'phone';
|
|
19
|
+
const context = await browser.newContext({ viewport: { width: plan.width, height: plan.height }, deviceScaleFactor: scale, isMobile: phone, hasTouch: phone });
|
|
14
20
|
await context.route((url) => isOwnAddress(url, plan.ownPort), (route) => route.abort('blockedbyclient'));
|
|
15
|
-
|
|
21
|
+
const { source } = plan;
|
|
22
|
+
if (source.kind === 'page') {
|
|
23
|
+
await context.route(`${pageOrigin}/**`, async (route) => {
|
|
24
|
+
const answer = answerPageRequest(path.dirname(source.file), route.request().url());
|
|
25
|
+
await (answer.status === 404 ? route.fulfill({ status: 404, body: '' }) : route.fulfill({ status: 200, path: answer.file, contentType: answer.contentType }));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
await context.addInitScript(cursorScript(plan.device));
|
|
16
29
|
return context;
|
|
17
30
|
}
|
|
18
|
-
async function open(page,
|
|
31
|
+
async function open(page, source) {
|
|
32
|
+
const address = source.kind === 'site' ? source.url : pageAddress(source.file);
|
|
19
33
|
try {
|
|
20
|
-
await page.goto(
|
|
34
|
+
await page.goto(address, { waitUntil: 'load', timeout: 30_000 });
|
|
35
|
+
await waitUntilReady(page);
|
|
21
36
|
}
|
|
22
37
|
catch (error) {
|
|
23
|
-
throw kvvideoProblem('STEP_FAILED', `The page ${
|
|
38
|
+
throw kvvideoProblem('STEP_FAILED', `The page ${address} didn't load: ${firstLine(error)}`, { step: 0, reason: firstLine(error) });
|
|
24
39
|
}
|
|
25
40
|
}
|
|
41
|
+
async function waitForCue(page, plan, step, elapsed) {
|
|
42
|
+
if (step.do === 'wait' || step.atLine === undefined || plan.cues === undefined)
|
|
43
|
+
return;
|
|
44
|
+
const wait = waitUntilMs(plan.cues.lineStartsMs[step.atLine - 1] ?? 0, elapsed());
|
|
45
|
+
if (wait > 0)
|
|
46
|
+
await page.waitForTimeout(wait);
|
|
47
|
+
}
|
|
26
48
|
async function play(page, plan, startedAt) {
|
|
49
|
+
const elapsed = () => Math.round(plan.now() - startedAt);
|
|
27
50
|
await page.waitForTimeout(500);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const done = [];
|
|
51
|
+
const state = { page, device: plan.device, pointer: { x: plan.width / 2, y: plan.height / 2 }, elapsed };
|
|
52
|
+
const actions = [];
|
|
31
53
|
for (const [index, step] of plan.steps.entries()) {
|
|
32
|
-
|
|
54
|
+
plan.stepStarted(index + 1, plan.steps.length);
|
|
55
|
+
await waitForCue(page, plan, step, elapsed);
|
|
56
|
+
const atMs = elapsed();
|
|
33
57
|
try {
|
|
34
|
-
|
|
58
|
+
const result = await runStep(state, step);
|
|
59
|
+
state.pointer = result.pointer;
|
|
60
|
+
actions.push({ do: step.do, atMs, endMs: elapsed(), ...(result.act === undefined ? {} : { actMs: result.act.atMs, box: result.act.box }) });
|
|
35
61
|
}
|
|
36
62
|
catch (error) {
|
|
37
63
|
throw kvvideoProblem('STEP_FAILED', `Step ${String(index + 1)} (${step.do}) failed: ${firstLine(error)}`, { step: index + 1, reason: firstLine(error) });
|
|
@@ -40,26 +66,30 @@ async function play(page, plan, startedAt) {
|
|
|
40
66
|
await page.waitForTimeout(400);
|
|
41
67
|
}
|
|
42
68
|
await page.waitForTimeout(1000);
|
|
43
|
-
|
|
69
|
+
const rest = plan.cues === undefined ? 0 : waitUntilMs(plan.cues.leastLengthMs, elapsed());
|
|
70
|
+
if (rest > 0)
|
|
71
|
+
await page.waitForTimeout(rest);
|
|
72
|
+
return actions;
|
|
44
73
|
}
|
|
45
74
|
export async function record(browser, plan) {
|
|
46
|
-
const context = await
|
|
75
|
+
const context = await recordingContext(browser, plan);
|
|
47
76
|
const page = await context.newPage();
|
|
48
|
-
await open(page, plan.
|
|
77
|
+
await open(page, plan.source);
|
|
78
|
+
const size = { width: plan.width * scale, height: plan.height * scale };
|
|
49
79
|
const video = path.join(plan.folder, 'recording.mp4');
|
|
50
80
|
const stopEncoder = new AbortController();
|
|
51
|
-
const encoder = await startPiped(plan.ffmpeg, recordEncoderArgs(
|
|
81
|
+
const encoder = await startPiped(plan.ffmpeg, recordEncoderArgs(size.width, size.height, video), AbortSignal.any([plan.signal, stopEncoder.signal]));
|
|
52
82
|
try {
|
|
53
83
|
const startedAt = plan.now();
|
|
54
84
|
const writer = frameWriter((frame) => encoder.write(frame), startedAt);
|
|
55
|
-
const screencast = await startScreencast(page,
|
|
56
|
-
const
|
|
85
|
+
const screencast = await startScreencast(page, size, (frame, atMs) => writer.frame(frame, atMs), plan.now);
|
|
86
|
+
const actions = await play(page, plan, startedAt).finally(() => screencast.stop());
|
|
57
87
|
const endedAt = plan.now();
|
|
58
88
|
await writer.finish(endedAt);
|
|
59
89
|
const run = await encoder.end();
|
|
60
90
|
if (run.exitCode !== 0)
|
|
61
91
|
throw kvvideoProblem('RENDER_FAILED', `ffmpeg couldn't encode the recording (exit ${String(run.exitCode)}).`, { reason: lastLines(run.output) });
|
|
62
|
-
return { video, durationMs: Math.round(endedAt - startedAt),
|
|
92
|
+
return { video, durationMs: Math.round(endedAt - startedAt), actions };
|
|
63
93
|
}
|
|
64
94
|
finally {
|
|
65
95
|
stopEncoder.abort();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recorder.js","sourceRoot":"","sources":["../../src/record/recorder.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAgB,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"recorder.js","sourceRoot":"","sources":["../../src/record/recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-record.d.ts","sourceRoot":"","sources":["../../src/record/register-record.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"register-record.d.ts","sourceRoot":"","sources":["../../src/record/register-record.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAuCtC,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAwC7C"}
|
|
@@ -3,17 +3,44 @@ import { launchChannel } from "../browser/launch.js";
|
|
|
3
3
|
import { startBrowser } from "../browser/start-browser.js";
|
|
4
4
|
import { answerPath } from "../files/answer-path.js";
|
|
5
5
|
import { placeOutput, workFolder } from "../files/place-output.js";
|
|
6
|
-
import {
|
|
6
|
+
import { readSidecar, sidecarOutputPath, writeSidecar } from "../files/sidecar.js";
|
|
7
|
+
import { inputPath, outputPath } from "../files/workspace-paths.js";
|
|
7
8
|
import { programOf } from "../programs/programs.js";
|
|
8
|
-
import {
|
|
9
|
+
import { invalid } from "../problems.js";
|
|
10
|
+
import { audioExtensions } from "../compose/scene-plan.js";
|
|
11
|
+
import { recordableAddress } from "./address.js";
|
|
12
|
+
import { cuesOf } from "./narration-cues.js";
|
|
9
13
|
import { ownPortOf } from "./own-port.js";
|
|
10
14
|
import { record } from "./recorder.js";
|
|
11
15
|
import { recordInputSchema, recordOutputSchema } from "./steps.js";
|
|
12
|
-
// `video record` (plan 16 §16.5):
|
|
13
|
-
// closed when the call ends, fails, or is cancelled, and the work folder removed.
|
|
16
|
+
// `video record` (plan 16 §16.5): the addresses, the page, the narration, and the outputs are checked before a browser
|
|
17
|
+
// starts; the browser is closed when the call ends, fails, or is cancelled, and the work folder removed. The
|
|
18
|
+
// recording's sidecar is placed beside it (ADR 0115, 31).
|
|
19
|
+
const defaultSize = { desktop: { width: 1280, height: 720 }, phone: { width: 390, height: 844 } };
|
|
20
|
+
function sourceOf(root, input, ownPort) {
|
|
21
|
+
for (const step of input.steps ?? [])
|
|
22
|
+
if (step.do === 'goto')
|
|
23
|
+
recordableAddress(step.url, ownPort);
|
|
24
|
+
if (input.page !== undefined)
|
|
25
|
+
return { kind: 'page', file: inputPath(root, input.page, ['.html', '.htm']) };
|
|
26
|
+
return { kind: 'site', url: recordableAddress(input.url ?? '', ownPort).href };
|
|
27
|
+
}
|
|
28
|
+
function cuesFor(root, input) {
|
|
29
|
+
if (input.narration === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
const narration = readSidecar(inputPath(root, input.narration, audioExtensions));
|
|
32
|
+
if (narration?.kind !== 'narration')
|
|
33
|
+
throw invalid(`${input.narration} has no captions beside it; make it with narrate or voice.`, { path: input.narration });
|
|
34
|
+
const lines = narration.captions.length;
|
|
35
|
+
for (const step of input.steps ?? []) {
|
|
36
|
+
if (step.do !== 'wait' && step.atLine !== undefined && step.atLine > lines)
|
|
37
|
+
throw invalid(`atLine ${String(step.atLine)} is past the narration's ${String(lines)} caption lines.`, { atLine: step.atLine, lines });
|
|
38
|
+
}
|
|
39
|
+
return cuesOf(narration);
|
|
40
|
+
}
|
|
14
41
|
export function registerRecord(ctx) {
|
|
15
42
|
ctx.registerCommand('kvvideo.clip.record', {
|
|
16
|
-
description: 'Records a
|
|
43
|
+
description: 'Records a website or an HTML page of the workspace, with its steps, as an MP4 file in the workspace.',
|
|
17
44
|
input: recordInputSchema,
|
|
18
45
|
output: recordOutputSchema,
|
|
19
46
|
public: true,
|
|
@@ -22,21 +49,28 @@ export function registerRecord(ctx) {
|
|
|
22
49
|
handle: async (input) => {
|
|
23
50
|
const root = ctx.job.workspace.path;
|
|
24
51
|
const ownPort = ownPortOf(process.env['KVMAN_HOME']);
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
52
|
+
const source = sourceOf(root, input, ownPort);
|
|
53
|
+
const cues = cuesFor(root, input);
|
|
54
|
+
const replace = input.replace === true;
|
|
55
|
+
const output = outputPath(root, input.file, '.mp4', replace);
|
|
56
|
+
const sidecar = sidecarOutputPath(output, replace, input.file);
|
|
57
|
+
const device = input.device ?? 'desktop';
|
|
58
|
+
const width = input.width ?? defaultSize[device].width;
|
|
59
|
+
const height = input.height ?? defaultSize[device].height;
|
|
30
60
|
const ffmpeg = await programOf(ctx, 'ffmpeg');
|
|
31
61
|
const browser = await startBrowser(await ctx.settings.get('kvvideo.browser'), launchChannel);
|
|
32
62
|
const work = workFolder();
|
|
33
63
|
const stop = () => void browser.close();
|
|
34
64
|
ctx.job.signal.addEventListener('abort', stop, { once: true });
|
|
35
65
|
try {
|
|
36
|
-
const
|
|
37
|
-
const recording = await record(browser,
|
|
66
|
+
const stepStarted = (n, of) => ctx.job.progress({ key: 'kvvideo.progress.step', params: { n, of } });
|
|
67
|
+
const recording = await record(browser, { source, steps: input.steps ?? [], width, height, device, cues, folder: work.folder, ownPort, ffmpeg, signal: ctx.job.signal, now: () => performance.now(), stepStarted });
|
|
68
|
+
const steps = input.steps ?? [];
|
|
69
|
+
const sourceName = source.kind === 'site' ? source.url : answerPath(root, source.file);
|
|
70
|
+
const made = writeSidecar(work.folder, { kind: source.kind, source: sourceName, width, height, device, scale: 2, durationMs: recording.durationMs, steps: [...steps], actions: recording.actions });
|
|
38
71
|
placeOutput(recording.video, output);
|
|
39
|
-
|
|
72
|
+
placeOutput(made, sidecar);
|
|
73
|
+
return { file: answerPath(root, output), durationMs: recording.durationMs, steps: recording.actions };
|
|
40
74
|
}
|
|
41
75
|
finally {
|
|
42
76
|
ctx.job.signal.removeEventListener('abort', stop);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-record.js","sourceRoot":"","sources":["../../src/record/register-record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"register-record.js","sourceRoot":"","sources":["../../src/record/register-record.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import type { Page } from 'playwright-core';
|
|
2
|
-
import { type
|
|
3
|
-
import type { Step } from './steps.ts';
|
|
4
|
-
export
|
|
2
|
+
import { type Point } from './motion.ts';
|
|
3
|
+
import type { Box, Step } from './steps.ts';
|
|
4
|
+
export type StepState = {
|
|
5
|
+
page: Page;
|
|
6
|
+
device: 'desktop' | 'phone';
|
|
7
|
+
pointer: Point;
|
|
8
|
+
elapsed(): number;
|
|
9
|
+
};
|
|
10
|
+
export type StepResult = {
|
|
11
|
+
pointer: Point;
|
|
12
|
+
act?: {
|
|
13
|
+
atMs: number;
|
|
14
|
+
box: Box;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export declare function runStep(state: StepState, step: Step): Promise<StepResult>;
|
|
5
18
|
/** Whether the page is held a moment after the step, so the viewer sees what it did. */
|
|
6
19
|
export declare function pausesAfter(step: Step): boolean;
|
|
7
20
|
//# sourceMappingURL=run-step.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-step.d.ts","sourceRoot":"","sources":["../../src/record/run-step.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,
|
|
1
|
+
{"version":3,"file":"run-step.d.ts","sourceRoot":"","sources":["../../src/record/run-step.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAqC,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAK5C,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC;IAAC,OAAO,IAAI,MAAM,CAAA;CAAE,CAAC;AAEvG,MAAM,MAAM,UAAU,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,GAAG,CAAA;KAAE,CAAA;CAAE,CAAC;AA+B9E,wBAAsB,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAgC/E;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAE/C"}
|
package/dist/record/run-step.js
CHANGED
|
@@ -1,45 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { frameMs, scrollPlan, typingDelays } from "./motion.js";
|
|
2
|
+
import { centerOf, glideTo, targetBox } from "./pointer.js";
|
|
3
|
+
async function typeText(page, text) {
|
|
4
|
+
const delays = typingDelays(text);
|
|
5
|
+
for (const [index, character] of [...text].entries()) {
|
|
6
|
+
await page.waitForTimeout(delays[index] ?? 0);
|
|
7
|
+
await page.keyboard.type(character);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
4
10
|
async function scroll(page, pixels) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
await page.
|
|
9
|
-
await page.waitForTimeout(30);
|
|
10
|
-
left -= part;
|
|
11
|
+
for (const move of scrollPlan(pixels).moves) {
|
|
12
|
+
if (move !== 0)
|
|
13
|
+
await page.mouse.wheel(0, move);
|
|
14
|
+
await page.waitForTimeout(frameMs);
|
|
11
15
|
}
|
|
12
16
|
}
|
|
13
|
-
|
|
17
|
+
// Brings the pointer or the finger to the target, and answers where it is and when it arrived.
|
|
18
|
+
async function reach(state, target) {
|
|
19
|
+
const box = await targetBox(state.page, target);
|
|
20
|
+
const center = centerOf(box);
|
|
21
|
+
if (state.device === 'phone')
|
|
22
|
+
return { pointer: center, act: { atMs: state.elapsed(), box } };
|
|
23
|
+
const pointer = await glideTo(state.page, state.pointer, center);
|
|
24
|
+
return { pointer, act: { atMs: state.elapsed(), box } };
|
|
25
|
+
}
|
|
26
|
+
async function press(state, at) {
|
|
27
|
+
if (state.device === 'phone')
|
|
28
|
+
await state.page.touchscreen.tap(at.x, at.y);
|
|
29
|
+
else
|
|
30
|
+
await state.page.mouse.click(at.x, at.y);
|
|
31
|
+
}
|
|
32
|
+
export async function runStep(state, step) {
|
|
33
|
+
const { page } = state;
|
|
14
34
|
switch (step.do) {
|
|
15
35
|
case 'goto':
|
|
16
36
|
await page.goto(step.url, { waitUntil: 'load', timeout: 30_000 });
|
|
17
|
-
return pointer;
|
|
37
|
+
return { pointer: state.pointer };
|
|
18
38
|
case 'click': {
|
|
19
|
-
const
|
|
20
|
-
await
|
|
21
|
-
return
|
|
39
|
+
const reached = await reach(state, step.target);
|
|
40
|
+
await press(state, reached.pointer);
|
|
41
|
+
return reached;
|
|
22
42
|
}
|
|
23
43
|
case 'type': {
|
|
24
|
-
const
|
|
25
|
-
await
|
|
26
|
-
await page
|
|
27
|
-
return
|
|
44
|
+
const reached = await reach(state, step.target);
|
|
45
|
+
await press(state, reached.pointer);
|
|
46
|
+
await typeText(page, step.text);
|
|
47
|
+
return reached;
|
|
28
48
|
}
|
|
29
49
|
case 'press':
|
|
30
50
|
await page.keyboard.press(step.key);
|
|
31
|
-
return pointer;
|
|
51
|
+
return { pointer: state.pointer };
|
|
32
52
|
case 'hover':
|
|
33
|
-
return
|
|
53
|
+
return reach(state, step.target);
|
|
34
54
|
case 'scroll':
|
|
35
55
|
await scroll(page, step.pixels);
|
|
36
|
-
return pointer;
|
|
56
|
+
return { pointer: state.pointer };
|
|
37
57
|
case 'wait':
|
|
38
58
|
await page.waitForTimeout(step.ms);
|
|
39
|
-
return pointer;
|
|
59
|
+
return { pointer: state.pointer };
|
|
40
60
|
case 'waitFor':
|
|
41
61
|
await page.locator(step.target).first().waitFor({ state: 'visible', timeout: 10_000 });
|
|
42
|
-
return pointer;
|
|
62
|
+
return { pointer: state.pointer };
|
|
43
63
|
}
|
|
44
64
|
}
|
|
45
65
|
/** Whether the page is held a moment after the step, so the viewer sees what it did. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-step.js","sourceRoot":"","sources":["../../src/record/run-step.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"run-step.js","sourceRoot":"","sources":["../../src/record/run-step.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@ export async function startScreencast(page, size, onFrame, now) {
|
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
});
|
|
26
|
-
await session.send('Page.startScreencast', { format: 'jpeg', quality:
|
|
26
|
+
await session.send('Page.startScreencast', { format: 'jpeg', quality: 95, maxWidth: size.width, maxHeight: size.height, everyNthFrame: 1 });
|
|
27
27
|
return {
|
|
28
28
|
stop: async () => {
|
|
29
29
|
stopped = true;
|