@webreel/core 0.1.0

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.
Files changed (94) hide show
  1. package/README.md +188 -0
  2. package/assets/click-1.mp3 +0 -0
  3. package/assets/click-2.mp3 +0 -0
  4. package/assets/click-3.mp3 +0 -0
  5. package/assets/click-4.mp3 +0 -0
  6. package/assets/key-1.mp3 +0 -0
  7. package/assets/key-2.mp3 +0 -0
  8. package/assets/key-3.mp3 +0 -0
  9. package/assets/key-4.mp3 +0 -0
  10. package/dist/__tests__/actions.test.d.ts +2 -0
  11. package/dist/__tests__/actions.test.d.ts.map +1 -0
  12. package/dist/__tests__/actions.test.js +252 -0
  13. package/dist/__tests__/actions.test.js.map +1 -0
  14. package/dist/__tests__/chrome.test.d.ts +2 -0
  15. package/dist/__tests__/chrome.test.d.ts.map +1 -0
  16. package/dist/__tests__/chrome.test.js +29 -0
  17. package/dist/__tests__/chrome.test.js.map +1 -0
  18. package/dist/__tests__/cursor-motion.test.d.ts +2 -0
  19. package/dist/__tests__/cursor-motion.test.d.ts.map +1 -0
  20. package/dist/__tests__/cursor-motion.test.js +39 -0
  21. package/dist/__tests__/cursor-motion.test.js.map +1 -0
  22. package/dist/__tests__/ffmpeg.test.d.ts +2 -0
  23. package/dist/__tests__/ffmpeg.test.d.ts.map +1 -0
  24. package/dist/__tests__/ffmpeg.test.js +90 -0
  25. package/dist/__tests__/ffmpeg.test.js.map +1 -0
  26. package/dist/__tests__/media.test.d.ts +2 -0
  27. package/dist/__tests__/media.test.d.ts.map +1 -0
  28. package/dist/__tests__/media.test.js +98 -0
  29. package/dist/__tests__/media.test.js.map +1 -0
  30. package/dist/__tests__/overlays.test.d.ts +2 -0
  31. package/dist/__tests__/overlays.test.d.ts.map +1 -0
  32. package/dist/__tests__/overlays.test.js +109 -0
  33. package/dist/__tests__/overlays.test.js.map +1 -0
  34. package/dist/__tests__/recording-context.test.d.ts +2 -0
  35. package/dist/__tests__/recording-context.test.d.ts.map +1 -0
  36. package/dist/__tests__/recording-context.test.js +46 -0
  37. package/dist/__tests__/recording-context.test.js.map +1 -0
  38. package/dist/__tests__/timeline.test.d.ts +2 -0
  39. package/dist/__tests__/timeline.test.d.ts.map +1 -0
  40. package/dist/__tests__/timeline.test.js +88 -0
  41. package/dist/__tests__/timeline.test.js.map +1 -0
  42. package/dist/actions.d.ts +65 -0
  43. package/dist/actions.d.ts.map +1 -0
  44. package/dist/actions.js +729 -0
  45. package/dist/actions.js.map +1 -0
  46. package/dist/cdp.d.ts +3 -0
  47. package/dist/cdp.d.ts.map +1 -0
  48. package/dist/cdp.js +5 -0
  49. package/dist/cdp.js.map +1 -0
  50. package/dist/chrome.d.ts +12 -0
  51. package/dist/chrome.d.ts.map +1 -0
  52. package/dist/chrome.js +241 -0
  53. package/dist/chrome.js.map +1 -0
  54. package/dist/compositor.d.ts +8 -0
  55. package/dist/compositor.d.ts.map +1 -0
  56. package/dist/compositor.js +224 -0
  57. package/dist/compositor.js.map +1 -0
  58. package/dist/cursor-motion.d.ts +17 -0
  59. package/dist/cursor-motion.d.ts.map +1 -0
  60. package/dist/cursor-motion.js +138 -0
  61. package/dist/cursor-motion.js.map +1 -0
  62. package/dist/download.d.ts +6 -0
  63. package/dist/download.d.ts.map +1 -0
  64. package/dist/download.js +78 -0
  65. package/dist/download.js.map +1 -0
  66. package/dist/ffmpeg.d.ts +5 -0
  67. package/dist/ffmpeg.d.ts.map +1 -0
  68. package/dist/ffmpeg.js +106 -0
  69. package/dist/ffmpeg.js.map +1 -0
  70. package/dist/index.d.ts +12 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +11 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/media.d.ts +23 -0
  75. package/dist/media.d.ts.map +1 -0
  76. package/dist/media.js +155 -0
  77. package/dist/media.js.map +1 -0
  78. package/dist/overlays.d.ts +21 -0
  79. package/dist/overlays.d.ts.map +1 -0
  80. package/dist/overlays.js +97 -0
  81. package/dist/overlays.js.map +1 -0
  82. package/dist/recorder.d.ts +41 -0
  83. package/dist/recorder.d.ts.map +1 -0
  84. package/dist/recorder.js +223 -0
  85. package/dist/recorder.js.map +1 -0
  86. package/dist/timeline.d.ts +82 -0
  87. package/dist/timeline.d.ts.map +1 -0
  88. package/dist/timeline.js +140 -0
  89. package/dist/timeline.js.map +1 -0
  90. package/dist/types.d.ts +90 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +16 -0
  93. package/dist/types.js.map +1 -0
  94. package/package.json +51 -0
@@ -0,0 +1,97 @@
1
+ import { DEFAULT_CURSOR_SVG, OFFSCREEN_MARGIN, DEFAULT_CURSOR_SIZE, DEFAULT_HUD_THEME, } from "./types.js";
2
+ export async function injectOverlays(client, theme, initialPosition) {
3
+ const cursorSize = theme?.cursorSize ?? DEFAULT_CURSOR_SIZE;
4
+ const cursorSvg = theme?.cursorSvg ?? DEFAULT_CURSOR_SVG;
5
+ const hotspotOffset = theme?.cursorHotspot === "center" ? cursorSize / 2 : 0;
6
+ const hudBg = theme?.hud?.background ?? DEFAULT_HUD_THEME.background;
7
+ const hudColor = theme?.hud?.color ?? DEFAULT_HUD_THEME.color;
8
+ const hudFontSize = theme?.hud?.fontSize ?? DEFAULT_HUD_THEME.fontSize;
9
+ const hudFontFamily = theme?.hud?.fontFamily ?? DEFAULT_HUD_THEME.fontFamily;
10
+ const hudBorderRadius = theme?.hud?.borderRadius ?? DEFAULT_HUD_THEME.borderRadius;
11
+ const hudPosition = theme?.hud?.position ?? DEFAULT_HUD_THEME.position;
12
+ await client.Runtime.evaluate({
13
+ expression: `(() => {
14
+ const zoom = parseFloat(getComputedStyle(document.documentElement).zoom) || 1;
15
+
16
+ const cursor = document.createElement("div");
17
+ cursor.id = "__demo-cursor";
18
+ cursor.style.cssText = [
19
+ "position:fixed",
20
+ "left:0",
21
+ "top:0",
22
+ "z-index:999999",
23
+ "pointer-events:none",
24
+ "width:${cursorSize}px",
25
+ "height:${cursorSize}px",
26
+ "margin-left:${-hotspotOffset}px",
27
+ "margin-top:${-hotspotOffset}px",
28
+ "transform-origin:top left",
29
+ "will-change:transform",
30
+ "transform:translate(${initialPosition?.x ?? -OFFSCREEN_MARGIN}px,${initialPosition?.y ?? -OFFSCREEN_MARGIN}px)",
31
+ "filter:drop-shadow(0 1px 2px rgba(0,0,0,0.5))",
32
+ ].join(";");
33
+ cursor.dataset.cx = "${initialPosition?.x ?? -OFFSCREEN_MARGIN}";
34
+ cursor.dataset.cy = "${initialPosition?.y ?? -OFFSCREEN_MARGIN}";
35
+ cursor.innerHTML = ${JSON.stringify(cursorSvg)};
36
+ document.body.appendChild(cursor);
37
+
38
+ const z = (v) => (v / zoom) + "px";
39
+ const keys = document.createElement("div");
40
+ keys.id = "__demo-keys";
41
+ keys.style.cssText = [
42
+ "position:fixed",
43
+ "z-index:999999",
44
+ "pointer-events:none",
45
+ "${hudPosition}:" + z(48),
46
+ "left:50%",
47
+ "transform:translateX(-50%)",
48
+ "display:flex",
49
+ "gap:" + z(14),
50
+ "padding:" + z(16) + " " + z(36),
51
+ "border-radius:" + z(${hudBorderRadius}),
52
+ "background:${hudBg}",
53
+ "opacity:0",
54
+ ].join(";");
55
+ document.body.appendChild(keys);
56
+
57
+ const style = document.createElement("style");
58
+ style.textContent = \`
59
+ .__demo-key {
60
+ display: inline-flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ color: ${hudColor};
64
+ font-family: ${hudFontFamily};
65
+ font-size: \${${hudFontSize} / zoom}px;
66
+ font-weight: 500;
67
+ white-space: nowrap;
68
+ }
69
+ \`;
70
+ document.head.appendChild(style);
71
+ })()`,
72
+ });
73
+ }
74
+ export async function showKeys(client, labels) {
75
+ await client.Runtime.evaluate({
76
+ expression: `(() => {
77
+ const container = document.getElementById("__demo-keys");
78
+ if (!container) return;
79
+ container.innerHTML = ${JSON.stringify(labels)}
80
+ .map(k => {
81
+ const e = k.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
82
+ return '<span class="__demo-key">' + e + '</span>';
83
+ })
84
+ .join("");
85
+ container.style.opacity = "1";
86
+ })()`,
87
+ });
88
+ }
89
+ export async function hideKeys(client) {
90
+ await client.Runtime.evaluate({
91
+ expression: `(() => {
92
+ const container = document.getElementById("__demo-keys");
93
+ if (container) container.style.opacity = "0";
94
+ })()`,
95
+ });
96
+ }
97
+ //# sourceMappingURL=overlays.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlays.js","sourceRoot":"","sources":["../src/overlays.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAgBpB,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAiB,EACjB,KAAoB,EACpB,eAA0C;IAE1C,MAAM,UAAU,GAAG,KAAK,EAAE,UAAU,IAAI,mBAAmB,CAAC;IAC5D,MAAM,SAAS,GAAG,KAAK,EAAE,SAAS,IAAI,kBAAkB,CAAC;IACzD,MAAM,aAAa,GAAG,KAAK,EAAE,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,KAAK,GAAG,KAAK,EAAE,GAAG,EAAE,UAAU,IAAI,iBAAiB,CAAC,UAAU,CAAC;IACrE,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,iBAAiB,CAAC,KAAK,CAAC;IAC9D,MAAM,WAAW,GAAG,KAAK,EAAE,GAAG,EAAE,QAAQ,IAAI,iBAAiB,CAAC,QAAQ,CAAC;IACvE,MAAM,aAAa,GAAG,KAAK,EAAE,GAAG,EAAE,UAAU,IAAI,iBAAiB,CAAC,UAAU,CAAC;IAC7E,MAAM,eAAe,GAAG,KAAK,EAAE,GAAG,EAAE,YAAY,IAAI,iBAAiB,CAAC,YAAY,CAAC;IACnF,MAAM,WAAW,GAAG,KAAK,EAAE,GAAG,EAAE,QAAQ,IAAI,iBAAiB,CAAC,QAAQ,CAAC;IAEvE,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC5B,UAAU,EAAE;;;;;;;;;;;iBAWC,UAAU;kBACT,UAAU;uBACL,CAAC,aAAa;sBACf,CAAC,aAAa;;;+BAGL,eAAe,EAAE,CAAC,IAAI,CAAC,gBAAgB,MAAM,eAAe,EAAE,CAAC,IAAI,CAAC,gBAAgB;;;6BAGtF,eAAe,EAAE,CAAC,IAAI,CAAC,gBAAgB;6BACvC,eAAe,EAAE,CAAC,IAAI,CAAC,gBAAgB;2BACzC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;;;;;;;;WAUzC,WAAW;;;;;;+BAMS,eAAe;sBACxB,KAAK;;;;;;;;;;;mBAWR,QAAQ;yBACF,aAAa;0BACZ,WAAW;;;;;;SAM5B;KACN,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAgB;IAChE,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC5B,UAAU,EAAE;;;8BAGc,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;;;;;;;SAO3C;KACN,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAiB;IAC9C,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC5B,UAAU,EAAE;;;SAGP;KACN,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { CDPClient } from "./types.js";
2
+ import type { RecordingContext } from "./actions.js";
3
+ import { type SfxConfig } from "./media.js";
4
+ import type { InteractionTimeline, TimelineData } from "./timeline.js";
5
+ export declare class Recorder {
6
+ private outputPath;
7
+ private frameCount;
8
+ private running;
9
+ private capturePromise;
10
+ private events;
11
+ private outputWidth;
12
+ private outputHeight;
13
+ private sfx;
14
+ private fps;
15
+ private frameMs;
16
+ private crf;
17
+ private ffmpegPath;
18
+ private ffmpegProcess;
19
+ private tempVideo;
20
+ private drainResolve;
21
+ private droppedFrames;
22
+ private timeline;
23
+ private ctx;
24
+ private framesDir;
25
+ constructor(outputWidth?: number, outputHeight?: number, options?: {
26
+ sfx?: SfxConfig;
27
+ fps?: number;
28
+ crf?: number;
29
+ framesDir?: string;
30
+ });
31
+ setTimeline(timeline: InteractionTimeline): void;
32
+ getTimeline(): InteractionTimeline | null;
33
+ getTimelineData(): TimelineData | null;
34
+ addEvent(type: "click" | "key"): void;
35
+ start(client: CDPClient, outputPath: string, ctx?: RecordingContext): Promise<void>;
36
+ private writeFrame;
37
+ private captureLoop;
38
+ getTempVideoPath(): string;
39
+ stop(): Promise<void>;
40
+ }
41
+ //# sourceMappingURL=recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAA0C,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEvE,qBAAa,QAAQ;IACnB,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,GAAG,CAAwB;IACnC,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,GAAG,CAAiC;IAC5C,OAAO,CAAC,SAAS,CAAuB;gBAGtC,WAAW,SAAwB,EACnC,YAAY,SAAwB,EACpC,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,SAAS,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAc/E,WAAW,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAIhD,WAAW,IAAI,mBAAmB,GAAG,IAAI;IAIzC,eAAe,IAAI,YAAY,GAAG,IAAI;IAItC,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK;IAOxB,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,gBAAgB;YAyD3D,UAAU;YAcV,WAAW;IAuDzB,gBAAgB,IAAI,MAAM;IAIpB,IAAI;CAmEX"}
@@ -0,0 +1,223 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { resolve, extname } from "node:path";
5
+ import { TARGET_FPS, DEFAULT_VIEWPORT_SIZE } from "./types.js";
6
+ import { ensureFfmpeg } from "./ffmpeg.js";
7
+ import { finalizeMp4, finalizeWebm, finalizeGif } from "./media.js";
8
+ export class Recorder {
9
+ outputPath = "";
10
+ frameCount = 0;
11
+ running = false;
12
+ capturePromise = null;
13
+ events = [];
14
+ outputWidth;
15
+ outputHeight;
16
+ sfx;
17
+ fps;
18
+ frameMs;
19
+ crf;
20
+ ffmpegPath = "ffmpeg";
21
+ ffmpegProcess = null;
22
+ tempVideo = "";
23
+ drainResolve = null;
24
+ droppedFrames = 0;
25
+ timeline = null;
26
+ ctx = null;
27
+ framesDir = null;
28
+ constructor(outputWidth = DEFAULT_VIEWPORT_SIZE, outputHeight = DEFAULT_VIEWPORT_SIZE, options) {
29
+ this.outputWidth = outputWidth;
30
+ this.outputHeight = outputHeight;
31
+ this.sfx = options?.sfx;
32
+ this.fps = options?.fps ?? TARGET_FPS;
33
+ this.frameMs = 1000 / this.fps;
34
+ this.crf = options?.crf ?? 18;
35
+ if (options?.framesDir) {
36
+ this.framesDir = options.framesDir;
37
+ mkdirSync(this.framesDir, { recursive: true });
38
+ }
39
+ }
40
+ setTimeline(timeline) {
41
+ this.timeline = timeline;
42
+ }
43
+ getTimeline() {
44
+ return this.timeline;
45
+ }
46
+ getTimelineData() {
47
+ return this.timeline?.toJSON() ?? null;
48
+ }
49
+ addEvent(type) {
50
+ if (this.running) {
51
+ const timeMs = (this.frameCount / this.fps) * 1000;
52
+ this.events.push({ type, timeMs });
53
+ }
54
+ }
55
+ async start(client, outputPath, ctx) {
56
+ this.ffmpegPath = await ensureFfmpeg();
57
+ this.outputPath = outputPath;
58
+ this.frameCount = 0;
59
+ this.droppedFrames = 0;
60
+ this.running = true;
61
+ this.events = [];
62
+ this.ctx = ctx ?? null;
63
+ if (this.ctx)
64
+ this.ctx.setRecorder(this);
65
+ const workDir = resolve(homedir(), ".webreel");
66
+ mkdirSync(workDir, { recursive: true });
67
+ this.tempVideo = resolve(workDir, `_rec_${Date.now()}.mp4`);
68
+ this.ffmpegProcess = spawn(this.ffmpegPath, [
69
+ "-y",
70
+ "-f",
71
+ "image2pipe",
72
+ "-framerate",
73
+ String(this.fps),
74
+ "-c:v",
75
+ "mjpeg",
76
+ "-i",
77
+ "pipe:0",
78
+ "-c:v",
79
+ "libx264",
80
+ "-preset",
81
+ "ultrafast",
82
+ "-crf",
83
+ String(this.crf),
84
+ "-pix_fmt",
85
+ "yuv420p",
86
+ "-r",
87
+ String(this.fps),
88
+ this.tempVideo,
89
+ ], { stdio: ["pipe", "pipe", "pipe"] });
90
+ const resolveDrain = () => {
91
+ const resolve = this.drainResolve;
92
+ if (resolve) {
93
+ this.drainResolve = null;
94
+ resolve();
95
+ }
96
+ };
97
+ const stdin = this.ffmpegProcess.stdin;
98
+ if (!stdin)
99
+ throw new Error("ffmpeg process has no stdin pipe");
100
+ stdin.on("drain", resolveDrain);
101
+ this.ffmpegProcess.on("close", resolveDrain);
102
+ this.capturePromise = this.captureLoop(client);
103
+ }
104
+ async writeFrame(buffer) {
105
+ const stdin = this.ffmpegProcess?.stdin;
106
+ if (!stdin?.writable) {
107
+ this.droppedFrames++;
108
+ return;
109
+ }
110
+ const ok = stdin.write(buffer);
111
+ if (!ok) {
112
+ await new Promise((res) => {
113
+ this.drainResolve = res;
114
+ });
115
+ }
116
+ }
117
+ async captureLoop(client) {
118
+ let lastFrameTime = Date.now();
119
+ let consecutiveErrors = 0;
120
+ while (this.running) {
121
+ try {
122
+ if (this.timeline) {
123
+ this.timeline.tick();
124
+ }
125
+ else {
126
+ await client.Runtime.evaluate({
127
+ expression: "window.__tickCursor&&window.__tickCursor()",
128
+ });
129
+ }
130
+ const { data } = await client.Page.captureScreenshot({
131
+ format: "jpeg",
132
+ quality: 60,
133
+ optimizeForSpeed: true,
134
+ });
135
+ const buffer = Buffer.from(data, "base64");
136
+ const now = Date.now();
137
+ const elapsed = now - lastFrameTime;
138
+ const frameSlots = Math.min(3, Math.max(1, Math.round(elapsed / this.frameMs)));
139
+ if (frameSlots > 1) {
140
+ for (let i = 0; i < frameSlots - 1; i++) {
141
+ if (this.timeline)
142
+ this.timeline.tickDuplicate();
143
+ await this.writeFrame(buffer);
144
+ this.frameCount++;
145
+ }
146
+ }
147
+ await this.writeFrame(buffer);
148
+ this.frameCount++;
149
+ if (this.framesDir) {
150
+ const padded = String(this.frameCount).padStart(5, "0");
151
+ writeFileSync(resolve(this.framesDir, `frame-${padded}.jpg`), buffer);
152
+ }
153
+ lastFrameTime = now;
154
+ consecutiveErrors = 0;
155
+ }
156
+ catch (err) {
157
+ if (!this.running)
158
+ break;
159
+ consecutiveErrors++;
160
+ if (consecutiveErrors >= 10) {
161
+ console.error(`Recording aborted after ${consecutiveErrors} consecutive capture failures:`, err);
162
+ break;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ getTempVideoPath() {
168
+ return this.tempVideo;
169
+ }
170
+ async stop() {
171
+ this.running = false;
172
+ if (this.ctx)
173
+ this.ctx.setRecorder(null);
174
+ await this.capturePromise;
175
+ if (this.droppedFrames > 0) {
176
+ console.warn(`Warning: ${this.droppedFrames} frame(s) dropped during recording`);
177
+ }
178
+ if (this.ffmpegProcess) {
179
+ const proc = this.ffmpegProcess;
180
+ await new Promise((res) => {
181
+ if (proc.exitCode !== null) {
182
+ res();
183
+ return;
184
+ }
185
+ proc.once("close", () => res());
186
+ try {
187
+ proc.stdin?.end();
188
+ }
189
+ catch (err) {
190
+ console.warn("Failed to close ffmpeg stdin:", err);
191
+ res();
192
+ }
193
+ });
194
+ this.ffmpegProcess = null;
195
+ }
196
+ if (this.frameCount === 0) {
197
+ rmSync(this.tempVideo, { force: true });
198
+ return;
199
+ }
200
+ // When a timeline is set, the caller is responsible for the temp video
201
+ // (e.g. renaming it for later compositing). Don't delete or finalize it.
202
+ if (this.timeline) {
203
+ return;
204
+ }
205
+ try {
206
+ const durationSec = this.frameCount / this.fps;
207
+ const ext = extname(this.outputPath).toLowerCase();
208
+ if (ext === ".webm") {
209
+ finalizeWebm(this.ffmpegPath, this.tempVideo, this.outputPath, this.events, durationSec, this.sfx);
210
+ }
211
+ else if (ext === ".gif") {
212
+ finalizeGif(this.ffmpegPath, this.tempVideo, this.outputPath, this.outputWidth);
213
+ }
214
+ else {
215
+ finalizeMp4(this.ffmpegPath, this.tempVideo, this.outputPath, this.events, durationSec, { sfx: this.sfx });
216
+ }
217
+ }
218
+ finally {
219
+ rmSync(this.tempVideo, { force: true });
220
+ }
221
+ }
222
+ }
223
+ //# sourceMappingURL=recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.js","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import type { Point, SoundEvent } from "./types.js";
2
+ interface CursorState {
3
+ x: number;
4
+ y: number;
5
+ scale: number;
6
+ }
7
+ interface HudState {
8
+ labels: string[];
9
+ }
10
+ interface FrameData {
11
+ cursor: CursorState;
12
+ hud: HudState | null;
13
+ }
14
+ export interface TimelineData {
15
+ fps: number;
16
+ width: number;
17
+ height: number;
18
+ zoom: number;
19
+ theme: {
20
+ cursorSvg: string;
21
+ cursorSize: number;
22
+ cursorHotspot: "top-left" | "center";
23
+ hud: {
24
+ background: string;
25
+ color: string;
26
+ fontSize: number;
27
+ fontFamily: string;
28
+ borderRadius: number;
29
+ position: "top" | "bottom";
30
+ };
31
+ };
32
+ frames: FrameData[];
33
+ events: SoundEvent[];
34
+ }
35
+ export declare class InteractionTimeline {
36
+ private cursorPath;
37
+ private pathIndex;
38
+ private currentCursor;
39
+ private currentHud;
40
+ private frames;
41
+ private events;
42
+ private frameCount;
43
+ private tickResolvers;
44
+ private width;
45
+ private height;
46
+ private zoom;
47
+ private fps;
48
+ private cursorSvg;
49
+ private cursorSize;
50
+ private cursorHotspot;
51
+ private hudConfig;
52
+ constructor(width?: number, height?: number, options?: {
53
+ zoom?: number;
54
+ fps?: number;
55
+ initialCursor?: {
56
+ x: number;
57
+ y: number;
58
+ };
59
+ cursorSvg?: string;
60
+ cursorSize?: number;
61
+ cursorHotspot?: "top-left" | "center";
62
+ hud?: Partial<TimelineData["theme"]["hud"]>;
63
+ loadedFrames?: FrameData[];
64
+ loadedEvents?: SoundEvent[];
65
+ });
66
+ setCursorPath(positions: Point[]): void;
67
+ setCursorScale(scale: number): void;
68
+ showHud(labels: string[]): void;
69
+ hideHud(): void;
70
+ addEvent(type: "click" | "key"): void;
71
+ waitForNextTick(): Promise<void>;
72
+ tick(): void;
73
+ tickDuplicate(): void;
74
+ private pushCurrentState;
75
+ getEvents(): SoundEvent[];
76
+ getFrameCount(): number;
77
+ toJSON(): TimelineData;
78
+ save(path: string): void;
79
+ static load(json: TimelineData): InteractionTimeline;
80
+ }
81
+ export {};
82
+ //# sourceMappingURL=timeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { TARGET_FPS, DEFAULT_CURSOR_SVG, DEFAULT_VIEWPORT_SIZE, OFFSCREEN_MARGIN, DEFAULT_CURSOR_SIZE, DEFAULT_HUD_THEME, } from "./types.js";
3
+ export class InteractionTimeline {
4
+ cursorPath = null;
5
+ pathIndex = 0;
6
+ currentCursor = {
7
+ x: -OFFSCREEN_MARGIN,
8
+ y: -OFFSCREEN_MARGIN,
9
+ scale: 1,
10
+ };
11
+ currentHud = null;
12
+ frames = [];
13
+ events = [];
14
+ frameCount = 0;
15
+ tickResolvers = [];
16
+ width;
17
+ height;
18
+ zoom;
19
+ fps;
20
+ cursorSvg;
21
+ cursorSize;
22
+ cursorHotspot;
23
+ hudConfig;
24
+ constructor(width = DEFAULT_VIEWPORT_SIZE, height = DEFAULT_VIEWPORT_SIZE, options) {
25
+ this.width = width;
26
+ this.height = height;
27
+ this.zoom = options?.zoom ?? 1;
28
+ this.fps = options?.fps ?? TARGET_FPS;
29
+ if (options?.initialCursor) {
30
+ this.currentCursor = {
31
+ x: options.initialCursor.x,
32
+ y: options.initialCursor.y,
33
+ scale: 1,
34
+ };
35
+ }
36
+ this.cursorSvg = options?.cursorSvg ?? DEFAULT_CURSOR_SVG;
37
+ this.cursorSize = options?.cursorSize ?? DEFAULT_CURSOR_SIZE;
38
+ this.cursorHotspot = options?.cursorHotspot ?? "top-left";
39
+ this.hudConfig = {
40
+ background: options?.hud?.background ?? DEFAULT_HUD_THEME.background,
41
+ color: options?.hud?.color ?? DEFAULT_HUD_THEME.color,
42
+ fontSize: options?.hud?.fontSize ?? DEFAULT_HUD_THEME.fontSize,
43
+ fontFamily: options?.hud?.fontFamily ?? DEFAULT_HUD_THEME.fontFamily,
44
+ borderRadius: options?.hud?.borderRadius ?? DEFAULT_HUD_THEME.borderRadius,
45
+ position: options?.hud?.position ?? DEFAULT_HUD_THEME.position,
46
+ };
47
+ if (options?.loadedFrames) {
48
+ this.frames = options.loadedFrames;
49
+ this.frameCount = options.loadedFrames.length;
50
+ }
51
+ if (options?.loadedEvents) {
52
+ this.events = options.loadedEvents;
53
+ }
54
+ }
55
+ setCursorPath(positions) {
56
+ this.cursorPath = positions;
57
+ this.pathIndex = 0;
58
+ }
59
+ setCursorScale(scale) {
60
+ this.currentCursor.scale = scale;
61
+ }
62
+ showHud(labels) {
63
+ this.currentHud = { labels };
64
+ }
65
+ hideHud() {
66
+ this.currentHud = null;
67
+ }
68
+ addEvent(type) {
69
+ const timeMs = (this.frameCount / this.fps) * 1000;
70
+ this.events.push({ type, timeMs });
71
+ }
72
+ waitForNextTick() {
73
+ return new Promise((resolve) => {
74
+ this.tickResolvers.push(resolve);
75
+ });
76
+ }
77
+ tick() {
78
+ if (this.cursorPath && this.pathIndex < this.cursorPath.length) {
79
+ const p = this.cursorPath[this.pathIndex++];
80
+ this.currentCursor.x = p.x;
81
+ this.currentCursor.y = p.y;
82
+ if (this.pathIndex >= this.cursorPath.length) {
83
+ this.cursorPath = null;
84
+ }
85
+ }
86
+ this.pushCurrentState();
87
+ const resolvers = this.tickResolvers;
88
+ this.tickResolvers = [];
89
+ for (const resolve of resolvers)
90
+ resolve();
91
+ }
92
+ tickDuplicate() {
93
+ this.pushCurrentState();
94
+ }
95
+ pushCurrentState() {
96
+ this.frames.push({
97
+ cursor: { ...this.currentCursor },
98
+ hud: this.currentHud ? { labels: [...this.currentHud.labels] } : null,
99
+ });
100
+ this.frameCount++;
101
+ }
102
+ getEvents() {
103
+ return this.events;
104
+ }
105
+ getFrameCount() {
106
+ return this.frameCount;
107
+ }
108
+ toJSON() {
109
+ return {
110
+ fps: this.fps,
111
+ width: this.width,
112
+ height: this.height,
113
+ zoom: this.zoom,
114
+ theme: {
115
+ cursorSvg: this.cursorSvg,
116
+ cursorSize: this.cursorSize,
117
+ cursorHotspot: this.cursorHotspot,
118
+ hud: this.hudConfig,
119
+ },
120
+ frames: this.frames,
121
+ events: this.events,
122
+ };
123
+ }
124
+ save(path) {
125
+ writeFileSync(path, JSON.stringify(this.toJSON()));
126
+ }
127
+ static load(json) {
128
+ return new InteractionTimeline(json.width, json.height, {
129
+ zoom: json.zoom,
130
+ fps: json.fps,
131
+ cursorSvg: json.theme.cursorSvg,
132
+ cursorSize: json.theme.cursorSize,
133
+ cursorHotspot: json.theme.cursorHotspot,
134
+ hud: json.theme.hud,
135
+ loadedFrames: json.frames,
136
+ loadedEvents: json.events,
137
+ });
138
+ }
139
+ }
140
+ //# sourceMappingURL=timeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}