@kvman/kvvideo 0.1.0 → 0.1.2

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 (258) hide show
  1. package/README.md +1 -1
  2. package/dist/api.d.ts +31 -0
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/brand/brand.d.ts +17 -0
  5. package/dist/brand/brand.d.ts.map +1 -0
  6. package/dist/brand/brand.js +44 -0
  7. package/dist/brand/brand.js.map +1 -0
  8. package/dist/browser/launch.d.ts +2 -0
  9. package/dist/browser/launch.d.ts.map +1 -1
  10. package/dist/browser/launch.js +7 -1
  11. package/dist/browser/launch.js.map +1 -1
  12. package/dist/compose/ass-file.d.ts +5 -0
  13. package/dist/compose/ass-file.d.ts.map +1 -1
  14. package/dist/compose/ass-file.js +32 -3
  15. package/dist/compose/ass-file.js.map +1 -1
  16. package/dist/compose/background.d.ts +23 -0
  17. package/dist/compose/background.d.ts.map +1 -0
  18. package/dist/compose/background.js +31 -0
  19. package/dist/compose/background.js.map +1 -0
  20. package/dist/compose/camera.d.ts +32 -0
  21. package/dist/compose/camera.d.ts.map +1 -0
  22. package/dist/compose/camera.js +98 -0
  23. package/dist/compose/camera.js.map +1 -0
  24. package/dist/compose/caption-font.d.ts +2 -0
  25. package/dist/compose/caption-font.d.ts.map +1 -1
  26. package/dist/compose/caption-font.js +2 -1
  27. package/dist/compose/caption-font.js.map +1 -1
  28. package/dist/compose/compose-drawing.d.ts +19 -0
  29. package/dist/compose/compose-drawing.d.ts.map +1 -0
  30. package/dist/compose/compose-drawing.js +38 -0
  31. package/dist/compose/compose-drawing.js.map +1 -0
  32. package/dist/compose/compose-input.d.ts +139 -0
  33. package/dist/compose/compose-input.d.ts.map +1 -1
  34. package/dist/compose/compose-input.js +48 -9
  35. package/dist/compose/compose-input.js.map +1 -1
  36. package/dist/compose/compose-plan.d.ts +36 -0
  37. package/dist/compose/compose-plan.d.ts.map +1 -0
  38. package/dist/compose/compose-plan.js +55 -0
  39. package/dist/compose/compose-plan.js.map +1 -0
  40. package/dist/compose/compose-size.d.ts +49 -0
  41. package/dist/compose/compose-size.d.ts.map +1 -0
  42. package/dist/compose/compose-size.js +67 -0
  43. package/dist/compose/compose-size.js.map +1 -0
  44. package/dist/compose/drawn-images.d.ts +19 -0
  45. package/dist/compose/drawn-images.d.ts.map +1 -0
  46. package/dist/compose/drawn-images.js +75 -0
  47. package/dist/compose/drawn-images.js.map +1 -0
  48. package/dist/compose/final-pass.d.ts +34 -0
  49. package/dist/compose/final-pass.d.ts.map +1 -0
  50. package/dist/compose/final-pass.js +106 -0
  51. package/dist/compose/final-pass.js.map +1 -0
  52. package/dist/compose/frame-layout.d.ts +30 -0
  53. package/dist/compose/frame-layout.d.ts.map +1 -0
  54. package/dist/compose/frame-layout.js +33 -0
  55. package/dist/compose/frame-layout.js.map +1 -0
  56. package/dist/compose/overlays.d.ts +20 -0
  57. package/dist/compose/overlays.d.ts.map +1 -0
  58. package/dist/compose/overlays.js +30 -0
  59. package/dist/compose/overlays.js.map +1 -0
  60. package/dist/compose/page-again.d.ts +26 -0
  61. package/dist/compose/page-again.d.ts.map +1 -0
  62. package/dist/compose/page-again.js +50 -0
  63. package/dist/compose/page-again.js.map +1 -0
  64. package/dist/compose/player-copies.d.ts +5 -1
  65. package/dist/compose/player-copies.d.ts.map +1 -1
  66. package/dist/compose/player-copies.js +19 -16
  67. package/dist/compose/player-copies.js.map +1 -1
  68. package/dist/compose/register-compose.d.ts.map +1 -1
  69. package/dist/compose/register-compose.js +104 -20
  70. package/dist/compose/register-compose.js.map +1 -1
  71. package/dist/compose/render.d.ts +13 -4
  72. package/dist/compose/render.d.ts.map +1 -1
  73. package/dist/compose/render.js +48 -22
  74. package/dist/compose/render.js.map +1 -1
  75. package/dist/compose/scene-args.d.ts +32 -7
  76. package/dist/compose/scene-args.d.ts.map +1 -1
  77. package/dist/compose/scene-args.js +77 -16
  78. package/dist/compose/scene-args.js.map +1 -1
  79. package/dist/compose/scene-look.d.ts +31 -0
  80. package/dist/compose/scene-look.d.ts.map +1 -0
  81. package/dist/compose/scene-look.js +46 -0
  82. package/dist/compose/scene-look.js.map +1 -0
  83. package/dist/compose/scene-plan.d.ts +0 -2
  84. package/dist/compose/scene-plan.d.ts.map +1 -1
  85. package/dist/compose/scene-plan.js +7 -5
  86. package/dist/compose/scene-plan.js.map +1 -1
  87. package/dist/compose/sound-effects.d.ts +23 -0
  88. package/dist/compose/sound-effects.d.ts.map +1 -0
  89. package/dist/compose/sound-effects.js +40 -0
  90. package/dist/compose/sound-effects.js.map +1 -0
  91. package/dist/compose/transitions.d.ts +16 -0
  92. package/dist/compose/transitions.d.ts.map +1 -0
  93. package/dist/compose/transitions.js +38 -0
  94. package/dist/compose/transitions.js.map +1 -0
  95. package/dist/connector.d.ts +79 -14
  96. package/dist/connector.d.ts.map +1 -1
  97. package/dist/connector.js +33 -8
  98. package/dist/connector.js.map +1 -1
  99. package/dist/files/sidecar.d.ts +209 -0
  100. package/dist/files/sidecar.d.ts.map +1 -0
  101. package/dist/files/sidecar.js +66 -0
  102. package/dist/files/sidecar.js.map +1 -0
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +9 -1
  105. package/dist/index.js.map +1 -1
  106. package/dist/look/look-at.d.ts +15 -0
  107. package/dist/look/look-at.d.ts.map +1 -0
  108. package/dist/look/look-at.js +20 -0
  109. package/dist/look/look-at.js.map +1 -0
  110. package/dist/look/look-input.d.ts +22 -0
  111. package/dist/look/look-input.d.ts.map +1 -0
  112. package/dist/look/look-input.js +25 -0
  113. package/dist/look/look-input.js.map +1 -0
  114. package/dist/look/register-look.d.ts +3 -0
  115. package/dist/look/register-look.d.ts.map +1 -0
  116. package/dist/look/register-look.js +52 -0
  117. package/dist/look/register-look.js.map +1 -0
  118. package/dist/narration/alignment.d.ts +15 -0
  119. package/dist/narration/alignment.d.ts.map +1 -0
  120. package/dist/narration/alignment.js +21 -0
  121. package/dist/narration/alignment.js.map +1 -0
  122. package/dist/narration/captions.d.ts +22 -4
  123. package/dist/narration/captions.d.ts.map +1 -1
  124. package/dist/narration/captions.js +44 -16
  125. package/dist/narration/captions.js.map +1 -1
  126. package/dist/narration/join-args.d.ts +3 -0
  127. package/dist/narration/join-args.d.ts.map +1 -0
  128. package/dist/narration/join-args.js +19 -0
  129. package/dist/narration/join-args.js.map +1 -0
  130. package/dist/narration/narration-cache.d.ts +25 -0
  131. package/dist/narration/narration-cache.d.ts.map +1 -0
  132. package/dist/narration/narration-cache.js +36 -0
  133. package/dist/narration/narration-cache.js.map +1 -0
  134. package/dist/narration/piper.d.ts +2 -6
  135. package/dist/narration/piper.d.ts.map +1 -1
  136. package/dist/narration/piper.js +9 -15
  137. package/dist/narration/piper.js.map +1 -1
  138. package/dist/narration/pronunciations.d.ts +11 -0
  139. package/dist/narration/pronunciations.d.ts.map +1 -0
  140. package/dist/narration/pronunciations.js +40 -0
  141. package/dist/narration/pronunciations.js.map +1 -0
  142. package/dist/narration/register-narration.d.ts +8 -0
  143. package/dist/narration/register-narration.d.ts.map +1 -1
  144. package/dist/narration/register-narration.js +54 -26
  145. package/dist/narration/register-narration.js.map +1 -1
  146. package/dist/narration/register-voice.d.ts +27 -0
  147. package/dist/narration/register-voice.d.ts.map +1 -0
  148. package/dist/narration/register-voice.js +82 -0
  149. package/dist/narration/register-voice.js.map +1 -0
  150. package/dist/narration/sentence-speaker.d.ts +35 -0
  151. package/dist/narration/sentence-speaker.d.ts.map +1 -0
  152. package/dist/narration/sentence-speaker.js +44 -0
  153. package/dist/narration/sentence-speaker.js.map +1 -0
  154. package/dist/narration/speech-services.d.ts +23 -3
  155. package/dist/narration/speech-services.d.ts.map +1 -1
  156. package/dist/narration/speech-services.js +27 -10
  157. package/dist/narration/speech-services.js.map +1 -1
  158. package/dist/narration/voice-args.d.ts +3 -0
  159. package/dist/narration/voice-args.d.ts.map +1 -0
  160. package/dist/narration/voice-args.js +10 -0
  161. package/dist/narration/voice-args.js.map +1 -0
  162. package/dist/programs/media-duration.d.ts +12 -0
  163. package/dist/programs/media-duration.d.ts.map +1 -1
  164. package/dist/programs/media-duration.js +13 -0
  165. package/dist/programs/media-duration.js.map +1 -1
  166. package/dist/programs/run-program.d.ts +1 -0
  167. package/dist/programs/run-program.d.ts.map +1 -1
  168. package/dist/programs/run-program.js +1 -0
  169. package/dist/programs/run-program.js.map +1 -1
  170. package/dist/record/address.d.ts +1 -1
  171. package/dist/record/address.d.ts.map +1 -1
  172. package/dist/record/address.js +5 -7
  173. package/dist/record/address.js.map +1 -1
  174. package/dist/record/cursor-script.d.ts +2 -1
  175. package/dist/record/cursor-script.d.ts.map +1 -1
  176. package/dist/record/cursor-script.js +52 -14
  177. package/dist/record/cursor-script.js.map +1 -1
  178. package/dist/record/encoder-args.js +1 -1
  179. package/dist/record/motion.d.ts +19 -0
  180. package/dist/record/motion.d.ts.map +1 -0
  181. package/dist/record/motion.js +59 -0
  182. package/dist/record/motion.js.map +1 -0
  183. package/dist/record/narration-cues.d.ts +14 -0
  184. package/dist/record/narration-cues.d.ts.map +1 -0
  185. package/dist/record/narration-cues.js +12 -0
  186. package/dist/record/narration-cues.js.map +1 -0
  187. package/dist/record/page-clock.d.ts +7 -0
  188. package/dist/record/page-clock.d.ts.map +1 -0
  189. package/dist/record/page-clock.js +13 -0
  190. package/dist/record/page-clock.js.map +1 -0
  191. package/dist/record/page-ready.d.ts +4 -0
  192. package/dist/record/page-ready.d.ts.map +1 -0
  193. package/dist/record/page-ready.js +15 -0
  194. package/dist/record/page-ready.js.map +1 -0
  195. package/dist/record/page-route.d.ts +13 -0
  196. package/dist/record/page-route.d.ts.map +1 -0
  197. package/dist/record/page-route.js +64 -0
  198. package/dist/record/page-route.js.map +1 -0
  199. package/dist/record/pointer.d.ts +6 -7
  200. package/dist/record/pointer.d.ts.map +1 -1
  201. package/dist/record/pointer.js +11 -10
  202. package/dist/record/pointer.js.map +1 -1
  203. package/dist/record/record-source.d.ts +16 -0
  204. package/dist/record/record-source.d.ts.map +1 -0
  205. package/dist/record/record-source.js +11 -0
  206. package/dist/record/record-source.js.map +1 -0
  207. package/dist/record/recorder.d.ts +19 -7
  208. package/dist/record/recorder.d.ts.map +1 -1
  209. package/dist/record/recorder.js +52 -18
  210. package/dist/record/recorder.js.map +1 -1
  211. package/dist/record/register-record.d.ts.map +1 -1
  212. package/dist/record/register-record.js +45 -13
  213. package/dist/record/register-record.js.map +1 -1
  214. package/dist/record/run-step.d.ts +16 -3
  215. package/dist/record/run-step.d.ts.map +1 -1
  216. package/dist/record/run-step.js +43 -23
  217. package/dist/record/run-step.js.map +1 -1
  218. package/dist/record/screencast.js +1 -1
  219. package/dist/record/steps.d.ts +56 -1
  220. package/dist/record/steps.d.ts.map +1 -1
  221. package/dist/record/steps.js +36 -18
  222. package/dist/record/steps.js.map +1 -1
  223. package/dist/review/analysis.d.ts +19 -0
  224. package/dist/review/analysis.d.ts.map +1 -0
  225. package/dist/review/analysis.js +36 -0
  226. package/dist/review/analysis.js.map +1 -0
  227. package/dist/review/frames.d.ts +9 -0
  228. package/dist/review/frames.d.ts.map +1 -0
  229. package/dist/review/frames.js +28 -0
  230. package/dist/review/frames.js.map +1 -0
  231. package/dist/review/register-review.d.ts +24 -0
  232. package/dist/review/register-review.d.ts.map +1 -0
  233. package/dist/review/register-review.js +81 -0
  234. package/dist/review/register-review.js.map +1 -0
  235. package/dist/review/sheet-args.d.ts +11 -0
  236. package/dist/review/sheet-args.d.ts.map +1 -0
  237. package/dist/review/sheet-args.js +24 -0
  238. package/dist/review/sheet-args.js.map +1 -0
  239. package/dist/settings.d.ts +4 -0
  240. package/dist/settings.d.ts.map +1 -1
  241. package/dist/settings.js +4 -2
  242. package/dist/settings.js.map +1 -1
  243. package/dist/setup/ffmpeg-readiness.js +1 -1
  244. package/dist/setup/ffmpeg-readiness.js.map +1 -1
  245. package/dist/ui.d.ts.map +1 -1
  246. package/dist/ui.js +3 -2
  247. package/dist/ui.js.map +1 -1
  248. package/dist/web/components/player.css +1 -1
  249. package/dist/web/components/player.js +33 -21
  250. package/docs/guide.md +39 -10
  251. package/locales/ar.json +12 -1
  252. package/locales/en.json +12 -1
  253. package/package.json +11 -5
  254. package/sounds/SOURCES.md +13 -0
  255. package/sounds/click.wav +0 -0
  256. package/sounds/keys.wav +0 -0
  257. package/sounds/pop.wav +0 -0
  258. package/sounds/whoosh.wav +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"cursor-script.js","sourceRoot":"","sources":["../../src/record/cursor-script.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAClH,oGAAoG;AAEpG,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;MAiBtB,CAAC"}
1
+ {"version":3,"file":"cursor-script.js","sourceRoot":"","sources":["../../src/record/cursor-script.ts"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,qHAAqH;AACrH,sHAAsH;AACtH,qDAAqD;AAErD,MAAM,SAAS,GAAG,oEAAoE,CAAC;AAEvF,MAAM,MAAM,GAAG;;;;;;;;;;;;KAYV,CAAC;AAEN,MAAM,OAAO,GAAG;;;;;;;;;yCASyB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;;;;;;;;;;;KAa7D,CAAC;AAEN,MAAM,KAAK,GAAG;;;;;;;;;;KAUT,CAAC;AAEN,4CAA4C;AAC5C,MAAM,UAAU,YAAY,CAAC,MAA2B;IACtD,OAAO,WAAW,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO;;MAE3D,CAAC;AACP,CAAC"}
@@ -6,7 +6,7 @@ export function recordEncoderArgs(width, height, output) {
6
6
  '-hide_banner', '-loglevel', 'error',
7
7
  '-f', 'image2pipe', '-framerate', String(framesPerSecond), '-c:v', 'mjpeg', '-i', '-',
8
8
  '-vf', fit,
9
- '-c:v', 'libx264', '-preset', 'veryfast', '-crf', '18', '-r', String(framesPerSecond),
9
+ '-c:v', 'libx264', '-preset', 'veryfast', '-crf', '14', '-r', String(framesPerSecond),
10
10
  '-an', '-movflags', '+faststart', '-y', output,
11
11
  ];
12
12
  }
@@ -0,0 +1,19 @@
1
+ export declare const frameMs = 16;
2
+ export type Point = {
3
+ x: number;
4
+ y: number;
5
+ };
6
+ /** Cubic easing in and out, from 0 to 1. */
7
+ export declare function easeInOut(share: number): number;
8
+ /** How long the pointer takes to travel `distance` pixels. */
9
+ export declare function glideMs(distance: number): number;
10
+ /** The pointer's positions, one a frame, from `from` (left out) to `to`, along a gentle curve. */
11
+ export declare function glidePath(from: Point, to: Point): Point[];
12
+ /** The wait before each character of `text`: 35 to 110 ms, and 150 ms more after a space or a punctuation mark. */
13
+ export declare function typingDelays(text: string): number[];
14
+ /** An eased scroll of `pixels`: its length and its move in each frame, which add up to `pixels`. */
15
+ export declare function scrollPlan(pixels: number): {
16
+ durationMs: number;
17
+ moves: number[];
18
+ };
19
+ //# sourceMappingURL=motion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motion.d.ts","sourceRoot":"","sources":["../../src/record/motion.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,KAAK,CAAC;AAE1B,MAAM,MAAM,KAAK,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,4CAA4C;AAC5C,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAID,8DAA8D;AAC9D,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,kGAAkG;AAClG,wBAAgB,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,KAAK,EAAE,CAYzD;AAgBD,mHAAmH;AACnH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAInD;AAED,oGAAoG;AACpG,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAWlF"}
@@ -0,0 +1,59 @@
1
+ // How things move in a recording (plan 16 §16.5; ADR 0115, 9): the pointer's eased, gently curved path, a typing
2
+ // pace that varies but is the same for the same text, and an eased scroll. Each is planned in frames of `frameMs`.
3
+ export const frameMs = 16;
4
+ /** Cubic easing in and out, from 0 to 1. */
5
+ export function easeInOut(share) {
6
+ return share < 0.5 ? 4 * share ** 3 : 1 - (-2 * share + 2) ** 3 / 2;
7
+ }
8
+ const frameCount = (durationMs) => Math.max(1, Math.round(durationMs / frameMs));
9
+ /** How long the pointer takes to travel `distance` pixels. */
10
+ export function glideMs(distance) {
11
+ return Math.min(900, Math.max(400, Math.round(300 + 0.6 * distance)));
12
+ }
13
+ /** The pointer's positions, one a frame, from `from` (left out) to `to`, along a gentle curve. */
14
+ export function glidePath(from, to) {
15
+ const dx = to.x - from.x;
16
+ const dy = to.y - from.y;
17
+ const bend = 0.08;
18
+ const control = { x: (from.x + to.x) / 2 - dy * bend, y: (from.y + to.y) / 2 + dx * bend };
19
+ const frames = frameCount(glideMs(Math.hypot(dx, dy)));
20
+ return Array.from({ length: frames }, (_, index) => {
21
+ const share = easeInOut((index + 1) / frames);
22
+ const toControl = 2 * (1 - share) * share;
23
+ const toEnd = share * share;
24
+ return { x: from.x + toControl * (control.x - from.x) + toEnd * dx, y: from.y + toControl * (control.y - from.y) + toEnd * dy };
25
+ });
26
+ }
27
+ // A small seeded generator (mulberry32 over an FNV-1a hash of the text), so the same text types the same way.
28
+ function seededRandom(text) {
29
+ let seed = 0x811c9dc5;
30
+ for (const unit of text)
31
+ seed = Math.imul(seed ^ (unit.codePointAt(0) ?? 0), 0x01000193);
32
+ return () => {
33
+ seed = (seed + 0x6d2b79f5) | 0;
34
+ let mixed = Math.imul(seed ^ (seed >>> 15), seed | 1);
35
+ mixed ^= mixed + Math.imul(mixed ^ (mixed >>> 7), mixed | 61);
36
+ return ((mixed ^ (mixed >>> 14)) >>> 0) / 4_294_967_296;
37
+ };
38
+ }
39
+ const pausesAfter = (character) => /[\s\p{P}]/u.test(character);
40
+ /** The wait before each character of `text`: 35 to 110 ms, and 150 ms more after a space or a punctuation mark. */
41
+ export function typingDelays(text) {
42
+ const random = seededRandom(text);
43
+ const characters = [...text];
44
+ return characters.map((_, index) => 35 + Math.floor(random() * 76) + (index > 0 && pausesAfter(characters[index - 1] ?? '') ? 150 : 0));
45
+ }
46
+ /** An eased scroll of `pixels`: its length and its move in each frame, which add up to `pixels`. */
47
+ export function scrollPlan(pixels) {
48
+ const durationMs = Math.min(3000, Math.round(400 + 0.5 * Math.abs(pixels)));
49
+ const frames = frameCount(durationMs);
50
+ let before = 0;
51
+ const moves = Array.from({ length: frames }, (_, index) => {
52
+ const at = Math.round(pixels * easeInOut((index + 1) / frames));
53
+ const move = at - before;
54
+ before = at;
55
+ return move;
56
+ });
57
+ return { durationMs, moves };
58
+ }
59
+ //# sourceMappingURL=motion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motion.js","sourceRoot":"","sources":["../../src/record/motion.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export declare const narrationLeadMs = 300;
2
+ export type Cues = {
3
+ lineStartsMs: number[];
4
+ leastLengthMs: number;
5
+ };
6
+ export declare function cuesOf(narration: {
7
+ durationMs: number;
8
+ captions: readonly {
9
+ startMs: number;
10
+ }[];
11
+ }): Cues;
12
+ /** How long to wait, `nowMs` into the recording, until `atMs`; nothing once it has passed. */
13
+ export declare function waitUntilMs(atMs: number, nowMs: number): number;
14
+ //# sourceMappingURL=narration-cues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narration-cues.d.ts","sourceRoot":"","sources":["../../src/record/narration-cues.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC,MAAM,MAAM,IAAI,GAAG;IAAE,YAAY,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE,wBAAgB,MAAM,CAAC,SAAS,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,SAAS;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,GAAG,IAAI,CAExG;AAED,8FAA8F;AAC9F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/D"}
@@ -0,0 +1,12 @@
1
+ // When a recording's steps start, from its narration (plan 16 §16.5; ADR 0115, 16 and 41): the narration starts
2
+ // 300 ms into the scene, so line `n` is heard at 300 ms plus its start, and the recording lasts at least the
3
+ // narration with 300 ms before and after it.
4
+ export const narrationLeadMs = 300;
5
+ export function cuesOf(narration) {
6
+ return { lineStartsMs: narration.captions.map((caption) => narrationLeadMs + caption.startMs), leastLengthMs: narrationLeadMs + narration.durationMs + narrationLeadMs };
7
+ }
8
+ /** How long to wait, `nowMs` into the recording, until `atMs`; nothing once it has passed. */
9
+ export function waitUntilMs(atMs, nowMs) {
10
+ return Math.max(0, Math.round(atMs - nowMs));
11
+ }
12
+ //# sourceMappingURL=narration-cues.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narration-cues.js","sourceRoot":"","sources":["../../src/record/narration-cues.ts"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,6GAA6G;AAC7G,6CAA6C;AAE7C,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AAInC,MAAM,UAAU,MAAM,CAAC,SAA2E;IAChG,OAAO,EAAE,YAAY,EAAE,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,eAAe,GAAG,SAAS,CAAC,UAAU,GAAG,eAAe,EAAE,CAAC;AAC3K,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,KAAa;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Page } from 'playwright-core';
2
+ export type PageClock = {
3
+ start(): Promise<void>;
4
+ seek(ms: number): Promise<void>;
5
+ };
6
+ export declare function holdPageClock(page: Page): Promise<PageClock>;
7
+ //# sourceMappingURL=page-clock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-clock.d.ts","sourceRoot":"","sources":["../../src/record/page-clock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAO5C,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC;AAEpF,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAWlE"}
@@ -0,0 +1,13 @@
1
+ export async function holdPageClock(page) {
2
+ const session = await page.context().newCDPSession(page);
3
+ await session.send('Animation.setPlaybackRate', { playbackRate: 0 });
4
+ return {
5
+ start: async () => {
6
+ await session.send('Animation.setPlaybackRate', { playbackRate: 1 });
7
+ },
8
+ seek: async (ms) => {
9
+ await page.evaluate(`for (const animation of document.getAnimations()) animation.currentTime = ${String(ms)};`);
10
+ },
11
+ };
12
+ }
13
+ //# sourceMappingURL=page-clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-clock.js","sourceRoot":"","sources":["../../src/record/page-clock.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAU;IAC5C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,OAAO,CAAC,IAAI,CAAC,2BAA2B,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;IACrE,OAAO;QACL,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,MAAM,OAAO,CAAC,IAAI,CAAC,2BAA2B,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YACjB,MAAM,IAAI,CAAC,QAAQ,CAAC,6EAA6E,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QAClH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Page } from 'playwright-core';
2
+ export type ReadyPage = Pick<Page, 'waitForLoadState' | 'evaluate'>;
3
+ export declare function waitUntilReady(page: ReadyPage): Promise<void>;
4
+ //# sourceMappingURL=page-ready.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-ready.d.ts","sourceRoot":"","sources":["../../src/record/page-ready.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAO5C,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,kBAAkB,GAAG,UAAU,CAAC,CAAC;AAIpE,wBAAsB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAOnE"}
@@ -0,0 +1,15 @@
1
+ // A page is ready to record once its network has been idle for 500 ms, waited for at most 10 s, and all its fonts
2
+ // have loaded (plan 16 §16.5). A network that never goes idle doesn't stop the recording; any other failure does.
3
+ const idleTimeoutMs = 10_000;
4
+ const isTimeout = (error) => error instanceof Error && error.name === 'TimeoutError';
5
+ export async function waitUntilReady(page) {
6
+ try {
7
+ await page.waitForLoadState('networkidle', { timeout: idleTimeoutMs });
8
+ }
9
+ catch (error) {
10
+ if (!isTimeout(error))
11
+ throw error;
12
+ }
13
+ await page.evaluate('document.fonts.ready.then(() => true)');
14
+ }
15
+ //# sourceMappingURL=page-ready.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-ready.js","sourceRoot":"","sources":["../../src/record/page-ready.ts"],"names":[],"mappings":"AAEA,kHAAkH;AAClH,kHAAkH;AAElH,MAAM,aAAa,GAAG,MAAM,CAAC;AAI7B,MAAM,SAAS,GAAG,CAAC,KAAc,EAAW,EAAE,CAAC,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC;AAEvG,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAe;IAClD,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,gBAAgB,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;IACzE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC;IACrC,CAAC;IACD,MAAM,IAAI,CAAC,QAAQ,CAAC,uCAAuC,CAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,13 @@
1
+ export declare const pageOrigin = "http://page.kvvideo.invalid";
2
+ export type PageAnswer = {
3
+ status: 200;
4
+ file: string;
5
+ contentType: string;
6
+ } | {
7
+ status: 404;
8
+ };
9
+ /** The address a page file is opened at. */
10
+ export declare function pageAddress(page: string): string;
11
+ /** What a request of the page's origin is answered with, from the page's folder. */
12
+ export declare function answerPageRequest(folder: string, address: string): PageAnswer;
13
+ //# sourceMappingURL=page-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-route.d.ts","sourceRoot":"","sources":["../../src/record/page-route.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,UAAU,gCAAgC,CAAC;AA6BxD,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC;AAE9F,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAiBD,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAO7E"}
@@ -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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"}
@@ -1,9 +1,8 @@
1
1
  import type { Page } from 'playwright-core';
2
- export type Pointer = {
3
- x: number;
4
- y: number;
5
- };
6
- export declare function glideTo(page: Page, from: Pointer, to: Pointer): Promise<Pointer>;
7
- /** The center of the first visible match of a target, scrolled into view. */
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;AAQ5C,MAAM,MAAM,OAAO,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,wBAAsB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAOtF;AAED,6EAA6E;AAC7E,wBAAsB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO3E"}
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"}
@@ -1,23 +1,24 @@
1
- // The mouse as the viewer sees it (ADR 0107, 19): it glides to each target over 600 ms, in 30 moves, so the drawn
2
- // cursor travels instead of jumping.
3
- const glideMoves = 30;
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 (let move = 1; move <= glideMoves; move += 1) {
7
- const share = move / glideMoves;
8
- await page.mouse.move(from.x + (to.x - from.x) * share, from.y + (to.y - from.y) * share);
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 center of the first visible match of a target, scrolled into view. */
14
- export async function centerOf(page, target) {
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":"AAEA,kHAAkH;AAClH,qCAAqC;AAErC,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,MAAM,OAAO,GAAG,GAAG,CAAC;AAIpB,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAU,EAAE,IAAa,EAAE,EAAW;IAClE,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,UAAU,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,IAAI,GAAG,UAAU,CAAC;QAChC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;QAC1F,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,GAAG,UAAU,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAU,EAAE,MAAc;IACvD,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,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
+ {"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"}
@@ -0,0 +1,16 @@
1
+ import type { RecordSource } from './recorder.ts';
2
+ export declare const defaultSize: {
3
+ desktop: {
4
+ width: number;
5
+ height: number;
6
+ };
7
+ phone: {
8
+ width: number;
9
+ height: number;
10
+ };
11
+ };
12
+ export declare function sourceOf(root: string, input: {
13
+ url?: string;
14
+ page?: string;
15
+ }, ownPort: number | null): RecordSource;
16
+ //# sourceMappingURL=record-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-source.d.ts","sourceRoot":"","sources":["../../src/record/record-source.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAKlD,eAAO,MAAM,WAAW;;;;;;;;;CAAgF,CAAC;AAEzG,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,YAAY,CAGnH"}
@@ -0,0 +1,11 @@
1
+ import { inputPath } from "../files/workspace-paths.js";
2
+ import { recordableAddress } from "./address.js";
3
+ // What `record` and `look` open (plan 16 §16.5): a page of the workspace, or a website that isn't kvman's own address,
4
+ // at the device's size unless one is given.
5
+ export const defaultSize = { desktop: { width: 1280, height: 720 }, phone: { width: 390, height: 844 } };
6
+ export function sourceOf(root, input, ownPort) {
7
+ if (input.page !== undefined)
8
+ return { kind: 'page', file: inputPath(root, input.page, ['.html', '.htm']) };
9
+ return { kind: 'site', url: recordableAddress(input.url ?? '', ownPort).href };
10
+ }
11
+ //# sourceMappingURL=record-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-source.js","sourceRoot":"","sources":["../../src/record/record-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD,uHAAuH;AACvH,4CAA4C;AAE5C,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;AAEzG,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,KAAsC,EAAE,OAAsB;IACnG,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;IAC5G,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;AACjF,CAAC"}
@@ -1,24 +1,36 @@
1
- import type { Browser } from 'playwright-core';
1
+ import type { Browser, BrowserContext, Page } from 'playwright-core';
2
2
  import { type Program } from '../programs/run-program.ts';
3
- import type { Step } from './steps.ts';
4
- export type RecordPlan = {
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
- steps: {
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>;
33
+ /** Opens the website or the page and waits until it is ready, or fails `kvvideo/STEP_FAILED` with step 0. */
34
+ export declare function openSource(page: Page, source: RecordSource): Promise<void>;
23
35
  export declare function record(browser: Browser, plan: RecordPlan): Promise<Recording>;
24
36
  //# 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,EAAwB,MAAM,iBAAiB,CAAC;AAErE,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAQrE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAMvC,MAAM,MAAM,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,GAAG,IAAI,MAAM,CAAA;CAAE,CAAC;AAE7L,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC;AAqCzG,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAqBnF"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/record/recorder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAErE,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAMrE,OAAO,EAAe,KAAK,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAM7D,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAQ/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;AAED,6GAA6G;AAC7G,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhF;AAgCD,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAwBnF"}
@@ -6,32 +6,60 @@ 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 { holdPageClock } from "./page-clock.js";
12
+ import { waitUntilReady } from "./page-ready.js";
9
13
  import { pausesAfter, runStep } from "./run-step.js";
10
14
  import { startScreencast } from "./screencast.js";
15
+ const scale = 2;
11
16
  const firstLine = (error) => (error instanceof Error ? (error.message.split('\n').find((line) => line.trim() !== '') ?? error.name) : String(error)).trim();
12
- async function contextFor(browser, plan) {
13
- const context = await browser.newContext({ viewport: { width: plan.width, height: plan.height }, deviceScaleFactor: 1 });
17
+ /** A fresh browser context for a recording: its size and device, kvman's port aborted, a page's files, the pointer. */
18
+ export async function recordingContext(browser, plan) {
19
+ const phone = plan.device === 'phone';
20
+ const context = await browser.newContext({ viewport: { width: plan.width, height: plan.height }, deviceScaleFactor: scale, isMobile: phone, hasTouch: phone });
14
21
  await context.route((url) => isOwnAddress(url, plan.ownPort), (route) => route.abort('blockedbyclient'));
15
- await context.addInitScript(cursorScript);
22
+ const { source } = plan;
23
+ if (source.kind === 'page') {
24
+ await context.route(`${pageOrigin}/**`, async (route) => {
25
+ const answer = answerPageRequest(path.dirname(source.file), route.request().url());
26
+ await (answer.status === 404 ? route.fulfill({ status: 404, body: '' }) : route.fulfill({ status: 200, path: answer.file, contentType: answer.contentType }));
27
+ });
28
+ }
29
+ await context.addInitScript(cursorScript(plan.device));
16
30
  return context;
17
31
  }
18
- async function open(page, url) {
32
+ /** Opens the website or the page and waits until it is ready, or fails `kvvideo/STEP_FAILED` with step 0. */
33
+ export async function openSource(page, source) {
34
+ const address = source.kind === 'site' ? source.url : pageAddress(source.file);
19
35
  try {
20
- await page.goto(url, { waitUntil: 'load', timeout: 30_000 });
36
+ await page.goto(address, { waitUntil: 'load', timeout: 30_000 });
37
+ await waitUntilReady(page);
21
38
  }
22
39
  catch (error) {
23
- throw kvvideoProblem('STEP_FAILED', `The page ${url} didn't load: ${firstLine(error)}`, { step: 0, reason: firstLine(error) });
40
+ throw kvvideoProblem('STEP_FAILED', `The page ${address} didn't load: ${firstLine(error)}`, { step: 0, reason: firstLine(error) });
24
41
  }
25
42
  }
43
+ async function waitForCue(page, plan, step, elapsed) {
44
+ if (step.do === 'wait' || step.atLine === undefined || plan.cues === undefined)
45
+ return;
46
+ const wait = waitUntilMs(plan.cues.lineStartsMs[step.atLine - 1] ?? 0, elapsed());
47
+ if (wait > 0)
48
+ await page.waitForTimeout(wait);
49
+ }
26
50
  async function play(page, plan, startedAt) {
51
+ const elapsed = () => Math.round(plan.now() - startedAt);
27
52
  await page.waitForTimeout(500);
28
- let pointer = { x: plan.width / 2, y: plan.height / 2 };
29
- await page.mouse.move(pointer.x, pointer.y);
30
- const done = [];
53
+ const state = { page, device: plan.device, pointer: { x: plan.width / 2, y: plan.height / 2 }, elapsed };
54
+ const actions = [];
31
55
  for (const [index, step] of plan.steps.entries()) {
32
- done.push({ do: step.do, atMs: Math.round(plan.now() - startedAt) });
56
+ plan.stepStarted(index + 1, plan.steps.length);
57
+ await waitForCue(page, plan, step, elapsed);
58
+ const atMs = elapsed();
33
59
  try {
34
- pointer = await runStep(page, step, pointer);
60
+ const result = await runStep(state, step);
61
+ state.pointer = result.pointer;
62
+ actions.push({ do: step.do, atMs, endMs: elapsed(), ...(result.act === undefined ? {} : { actMs: result.act.atMs, box: result.act.box }) });
35
63
  }
36
64
  catch (error) {
37
65
  throw kvvideoProblem('STEP_FAILED', `Step ${String(index + 1)} (${step.do}) failed: ${firstLine(error)}`, { step: index + 1, reason: firstLine(error) });
@@ -40,26 +68,32 @@ async function play(page, plan, startedAt) {
40
68
  await page.waitForTimeout(400);
41
69
  }
42
70
  await page.waitForTimeout(1000);
43
- return done;
71
+ const rest = plan.cues === undefined ? 0 : waitUntilMs(plan.cues.leastLengthMs, elapsed());
72
+ if (rest > 0)
73
+ await page.waitForTimeout(rest);
74
+ return actions;
44
75
  }
45
76
  export async function record(browser, plan) {
46
- const context = await contextFor(browser, plan);
77
+ const context = await recordingContext(browser, plan);
47
78
  const page = await context.newPage();
48
- await open(page, plan.url);
79
+ const clock = await holdPageClock(page);
80
+ await openSource(page, plan.source);
81
+ const size = { width: plan.width * scale, height: plan.height * scale };
49
82
  const video = path.join(plan.folder, 'recording.mp4');
50
83
  const stopEncoder = new AbortController();
51
- const encoder = await startPiped(plan.ffmpeg, recordEncoderArgs(plan.width, plan.height, video), AbortSignal.any([plan.signal, stopEncoder.signal]));
84
+ const encoder = await startPiped(plan.ffmpeg, recordEncoderArgs(size.width, size.height, video), AbortSignal.any([plan.signal, stopEncoder.signal]));
52
85
  try {
53
86
  const startedAt = plan.now();
87
+ await clock.start();
54
88
  const writer = frameWriter((frame) => encoder.write(frame), startedAt);
55
- const screencast = await startScreencast(page, plan, (frame, atMs) => writer.frame(frame, atMs), plan.now);
56
- const steps = await play(page, plan, startedAt).finally(() => screencast.stop());
89
+ const screencast = await startScreencast(page, size, (frame, atMs) => writer.frame(frame, atMs), plan.now);
90
+ const actions = await play(page, plan, startedAt).finally(() => screencast.stop());
57
91
  const endedAt = plan.now();
58
92
  await writer.finish(endedAt);
59
93
  const run = await encoder.end();
60
94
  if (run.exitCode !== 0)
61
95
  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), steps };
96
+ return { video, durationMs: Math.round(endedAt - startedAt), actions };
63
97
  }
64
98
  finally {
65
99
  stopEncoder.abort();
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.js","sourceRoot":"","sources":["../../src/record/recorder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"recorder.js","sourceRoot":"","sources":["../../src/record/recorder.ts"],"names":[],"mappings":"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"}