@walkrstudio/recorder 0.2.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 (45) hide show
  1. package/dist/cdp.d.ts +14 -0
  2. package/dist/cdp.d.ts.map +1 -0
  3. package/dist/cdp.js +86 -0
  4. package/dist/cdp.js.map +1 -0
  5. package/dist/chromium.d.ts +12 -0
  6. package/dist/chromium.d.ts.map +1 -0
  7. package/dist/chromium.js +75 -0
  8. package/dist/chromium.js.map +1 -0
  9. package/dist/embed.d.ts +3 -0
  10. package/dist/embed.d.ts.map +1 -0
  11. package/dist/embed.js +548 -0
  12. package/dist/embed.js.map +1 -0
  13. package/dist/encoder.d.ts +6 -0
  14. package/dist/encoder.d.ts.map +1 -0
  15. package/dist/encoder.js +121 -0
  16. package/dist/encoder.js.map +1 -0
  17. package/dist/index.d.ts +6 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/realtime-recorder.d.ts +4 -0
  22. package/dist/realtime-recorder.d.ts.map +1 -0
  23. package/dist/realtime-recorder.js +109 -0
  24. package/dist/realtime-recorder.js.map +1 -0
  25. package/dist/recorder.d.ts +4 -0
  26. package/dist/recorder.d.ts.map +1 -0
  27. package/dist/recorder.js +180 -0
  28. package/dist/recorder.js.map +1 -0
  29. package/dist/recording-session.d.ts +26 -0
  30. package/dist/recording-session.d.ts.map +1 -0
  31. package/dist/recording-session.js +88 -0
  32. package/dist/recording-session.js.map +1 -0
  33. package/dist/static-server.d.ts +9 -0
  34. package/dist/static-server.d.ts.map +1 -0
  35. package/dist/static-server.js +212 -0
  36. package/dist/static-server.js.map +1 -0
  37. package/dist/streaming-encoder.d.ts +26 -0
  38. package/dist/streaming-encoder.d.ts.map +1 -0
  39. package/dist/streaming-encoder.js +89 -0
  40. package/dist/streaming-encoder.js.map +1 -0
  41. package/dist/types.d.ts +15 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +2 -0
  44. package/dist/types.js.map +1 -0
  45. package/package.json +34 -0
package/dist/embed.js ADDED
@@ -0,0 +1,548 @@
1
+ const escapeHtml = (value) => value
2
+ .replaceAll("&", "&")
3
+ .replaceAll("<", "&lt;")
4
+ .replaceAll(">", "&gt;")
5
+ .replaceAll('"', "&quot;")
6
+ .replaceAll("'", "&#39;");
7
+ const escapeComment = (value) => value.replaceAll("--", "- -");
8
+ export function buildEmbedHtml(frames, fps, walkthrough) {
9
+ const rawTitle = walkthrough.title ?? "Walkr Capture";
10
+ const title = escapeHtml(rawTitle);
11
+ const commentTitle = escapeComment(rawTitle);
12
+ const safeFps = Number.isFinite(fps) && fps > 0 ? Math.round(fps) : 30;
13
+ const frameData = frames.map((frame) => `data:image/jpeg;base64,${frame.toString("base64")}`);
14
+ const maxFrame = Math.max(frameData.length - 1, 0);
15
+ return `<!-- Generated by Walkr v0.1.0 | Title: ${commentTitle} | Frames: ${frameData.length} | FPS: ${safeFps} -->
16
+ <!doctype html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="utf-8" />
20
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
21
+ <title>${title}</title>
22
+ <style>
23
+ * {
24
+ box-sizing: border-box;
25
+ }
26
+ body {
27
+ margin: 0;
28
+ min-height: 100dvh;
29
+ font-family: "Avenir Next", "Segoe UI", "Helvetica Neue", sans-serif;
30
+ color: #f8fafc;
31
+ background:
32
+ radial-gradient(1200px 600px at 20% -10%, rgba(56, 189, 248, 0.28), transparent 60%),
33
+ radial-gradient(900px 500px at 95% 100%, rgba(45, 212, 191, 0.22), transparent 55%),
34
+ linear-gradient(160deg, #030712 0%, #0f172a 100%);
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ padding: 20px;
39
+ }
40
+ .player {
41
+ width: min(1120px, 100%);
42
+ background: rgba(15, 23, 42, 0.58);
43
+ border: 1px solid rgba(148, 163, 184, 0.24);
44
+ border-radius: 18px;
45
+ overflow: hidden;
46
+ box-shadow: 0 28px 70px rgba(2, 6, 23, 0.55);
47
+ backdrop-filter: blur(12px);
48
+ }
49
+ .player-header {
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: space-between;
53
+ gap: 12px;
54
+ padding: 14px 16px;
55
+ border-bottom: 1px solid rgba(148, 163, 184, 0.18);
56
+ background: linear-gradient(180deg, rgba(15, 23, 42, 0.78), rgba(15, 23, 42, 0.42));
57
+ }
58
+ .title-row {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: 10px;
62
+ min-width: 0;
63
+ }
64
+ .title {
65
+ margin: 0;
66
+ font-size: 14px;
67
+ line-height: 1.4;
68
+ font-weight: 700;
69
+ color: #e2e8f0;
70
+ overflow: hidden;
71
+ text-overflow: ellipsis;
72
+ white-space: nowrap;
73
+ }
74
+ .badge {
75
+ font-size: 11px;
76
+ line-height: 1;
77
+ letter-spacing: 0.04em;
78
+ text-transform: uppercase;
79
+ color: #cbd5e1;
80
+ background: rgba(15, 23, 42, 0.9);
81
+ border: 1px solid rgba(148, 163, 184, 0.3);
82
+ border-radius: 999px;
83
+ padding: 5px 8px;
84
+ }
85
+ .copy-link {
86
+ border: 1px solid rgba(125, 211, 252, 0.5);
87
+ color: #e0f2fe;
88
+ background: rgba(2, 132, 199, 0.24);
89
+ border-radius: 999px;
90
+ padding: 7px 12px;
91
+ font-size: 12px;
92
+ font-weight: 700;
93
+ cursor: pointer;
94
+ }
95
+ .frame-display {
96
+ position: relative;
97
+ width: 100%;
98
+ aspect-ratio: 16 / 9;
99
+ min-height: 280px;
100
+ background: #020617;
101
+ }
102
+ #frame {
103
+ width: 100%;
104
+ height: 100%;
105
+ display: block;
106
+ object-fit: contain;
107
+ opacity: 1;
108
+ transition: opacity 120ms ease-out;
109
+ }
110
+ .is-swapping {
111
+ opacity: 0.94;
112
+ }
113
+ .loading {
114
+ position: absolute;
115
+ inset: 0;
116
+ display: grid;
117
+ place-items: center;
118
+ background: rgba(2, 6, 23, 0.62);
119
+ color: #e2e8f0;
120
+ font-size: 13px;
121
+ letter-spacing: 0.01em;
122
+ }
123
+ .loading.hidden {
124
+ display: none;
125
+ }
126
+ .loading-label {
127
+ display: inline-flex;
128
+ align-items: center;
129
+ gap: 8px;
130
+ padding: 8px 12px;
131
+ border-radius: 999px;
132
+ border: 1px solid rgba(148, 163, 184, 0.3);
133
+ background: rgba(15, 23, 42, 0.9);
134
+ }
135
+ .loading-label::before {
136
+ content: "";
137
+ width: 8px;
138
+ height: 8px;
139
+ border-radius: 999px;
140
+ background: #22d3ee;
141
+ animation: pulse 1s ease-in-out infinite;
142
+ }
143
+ @keyframes pulse {
144
+ 0%, 100% { transform: scale(0.8); opacity: 0.6; }
145
+ 50% { transform: scale(1); opacity: 1; }
146
+ }
147
+ .controls {
148
+ display: flex;
149
+ flex-wrap: wrap;
150
+ gap: 12px;
151
+ align-items: center;
152
+ padding: 12px 16px 16px;
153
+ border-top: 1px solid rgba(148, 163, 184, 0.16);
154
+ background: rgba(15, 23, 42, 0.46);
155
+ }
156
+ .left-controls {
157
+ display: inline-flex;
158
+ align-items: center;
159
+ gap: 8px;
160
+ }
161
+ .control-btn {
162
+ width: 34px;
163
+ height: 34px;
164
+ border: 1px solid rgba(148, 163, 184, 0.4);
165
+ border-radius: 999px;
166
+ background: rgba(15, 23, 42, 0.86);
167
+ color: #e2e8f0;
168
+ cursor: pointer;
169
+ font-size: 14px;
170
+ font-weight: 700;
171
+ }
172
+ .control-btn:hover {
173
+ background: rgba(30, 41, 59, 0.95);
174
+ }
175
+ .progress-wrap {
176
+ flex: 1 1 220px;
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 10px;
180
+ }
181
+ input[type="range"] {
182
+ width: 100%;
183
+ accent-color: #22d3ee;
184
+ }
185
+ .time {
186
+ min-width: 92px;
187
+ text-align: right;
188
+ font-variant-numeric: tabular-nums;
189
+ font-size: 12px;
190
+ color: #cbd5e1;
191
+ }
192
+ .status {
193
+ display: inline-flex;
194
+ gap: 10px;
195
+ align-items: center;
196
+ font-size: 12px;
197
+ color: #cbd5e1;
198
+ }
199
+ .counter {
200
+ font-variant-numeric: tabular-nums;
201
+ }
202
+ .loop-wrap {
203
+ display: inline-flex;
204
+ align-items: center;
205
+ gap: 6px;
206
+ }
207
+ .hint {
208
+ width: 100%;
209
+ font-size: 11px;
210
+ color: rgba(203, 213, 225, 0.74);
211
+ }
212
+ @media (max-width: 720px) {
213
+ body {
214
+ padding: 10px;
215
+ }
216
+ .player-header {
217
+ flex-wrap: wrap;
218
+ }
219
+ .controls {
220
+ gap: 10px;
221
+ }
222
+ .hint {
223
+ margin-top: -2px;
224
+ }
225
+ }
226
+ </style>
227
+ </head>
228
+ <body>
229
+ <main class="player">
230
+ <header class="player-header">
231
+ <div class="title-row">
232
+ <h1 class="title">${title}</h1>
233
+ <span class="badge">Powered by Walkr</span>
234
+ </div>
235
+ <button id="copyLink" class="copy-link" type="button">Copy Frame URL</button>
236
+ </header>
237
+ <section class="frame-display">
238
+ <img id="frame" alt="Walkr capture frame" />
239
+ <div id="loading" class="loading">
240
+ <span id="loadingLabel" class="loading-label">Loading 0 / ${frameData.length}</span>
241
+ </div>
242
+ </section>
243
+ <section class="controls" aria-label="player controls">
244
+ <div class="left-controls">
245
+ <button id="prev" class="control-btn" type="button" aria-label="Previous frame">←</button>
246
+ <button id="playPause" class="control-btn" type="button" aria-label="Play">▶</button>
247
+ <button id="next" class="control-btn" type="button" aria-label="Next frame">→</button>
248
+ </div>
249
+ <div class="progress-wrap">
250
+ <input id="progress" type="range" min="0" max="${maxFrame}" step="1" value="0" />
251
+ <span id="time" class="time">00:00 / 00:00</span>
252
+ </div>
253
+ <div class="status">
254
+ <span id="counter" class="counter">0 / ${frameData.length}</span>
255
+ <label class="loop-wrap"><input id="loop" type="checkbox" checked /> <span>🔁</span></label>
256
+ </div>
257
+ <div class="hint">Shortcuts: Space play/pause, ←/→ step, L loop toggle</div>
258
+ </section>
259
+ </main>
260
+ <script>
261
+ const frames = ${JSON.stringify(frameData)};
262
+ const fps = ${safeFps};
263
+ const frameDuration = 1000 / Math.max(1, fps);
264
+ const maxFrame = Math.max(frames.length - 1, 0);
265
+ const frameImage = document.getElementById("frame");
266
+ const progress = document.getElementById("progress");
267
+ const playPause = document.getElementById("playPause");
268
+ const prev = document.getElementById("prev");
269
+ const next = document.getElementById("next");
270
+ const counter = document.getElementById("counter");
271
+ const loopCheckbox = document.getElementById("loop");
272
+ const time = document.getElementById("time");
273
+ const copyLink = document.getElementById("copyLink");
274
+ const loading = document.getElementById("loading");
275
+ const loadingLabel = document.getElementById("loadingLabel");
276
+
277
+ const loaded = new Array(frames.length).fill(false);
278
+ let loadedCount = 0;
279
+ let currentFrame = 0;
280
+ let playing = false;
281
+ let raf = 0;
282
+ let lastTick = 0;
283
+
284
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
285
+ const hasInteger = (value) => /^-?\\d+$/.test(value);
286
+
287
+ const parseFrameFromLocation = () => {
288
+ const url = new URL(window.location.href);
289
+ const queryFrame = url.searchParams.get("frame");
290
+ if (queryFrame && hasInteger(queryFrame)) {
291
+ return Number(queryFrame);
292
+ }
293
+
294
+ const hash = window.location.hash.replace(/^#/, "").trim();
295
+ if (!hash) {
296
+ return 0;
297
+ }
298
+
299
+ const hashValue = hash.toLowerCase().startsWith("frame=") ? hash.slice(6) : hash;
300
+ if (hasInteger(hashValue)) {
301
+ return Number(hashValue);
302
+ }
303
+
304
+ return 0;
305
+ };
306
+
307
+ const formatTime = (seconds) => {
308
+ const safe = Number.isFinite(seconds) ? Math.max(0, seconds) : 0;
309
+ const mins = Math.floor(safe / 60);
310
+ const secs = Math.floor(safe % 60);
311
+ return String(mins).padStart(2, "0") + ":" + String(secs).padStart(2, "0");
312
+ };
313
+
314
+ const updateLoading = () => {
315
+ if (frames.length === 0) {
316
+ loading.classList.remove("hidden");
317
+ loadingLabel.textContent = "No frames";
318
+ return;
319
+ }
320
+
321
+ if (loadedCount >= frames.length) {
322
+ loading.classList.add("hidden");
323
+ return;
324
+ }
325
+
326
+ loading.classList.remove("hidden");
327
+ loadingLabel.textContent = "Loading " + loadedCount + " / " + frames.length;
328
+ };
329
+
330
+ const setFrameInUrl = () => {
331
+ const url = new URL(window.location.href);
332
+ url.searchParams.set("frame", String(currentFrame));
333
+ url.hash = "";
334
+ history.replaceState(null, "", url.toString());
335
+ };
336
+
337
+ const getShareUrl = () => {
338
+ const url = new URL(window.location.href);
339
+ url.searchParams.set("frame", String(currentFrame));
340
+ url.hash = "";
341
+ return url.toString();
342
+ };
343
+
344
+ const renderFrame = (nextFrame, syncUrl = true) => {
345
+ if (frames.length === 0) {
346
+ progress.value = "0";
347
+ counter.textContent = "0 / 0";
348
+ time.textContent = "00:00 / 00:00";
349
+ frameImage.removeAttribute("src");
350
+ return;
351
+ }
352
+
353
+ currentFrame = clamp(nextFrame, 0, maxFrame);
354
+ frameImage.src = frames[currentFrame];
355
+ frameImage.classList.add("is-swapping");
356
+ requestAnimationFrame(() => frameImage.classList.remove("is-swapping"));
357
+
358
+ progress.value = String(currentFrame);
359
+ counter.textContent = String(currentFrame + 1) + " / " + String(frames.length);
360
+ time.textContent = formatTime(currentFrame / fps) + " / " + formatTime(maxFrame / fps);
361
+
362
+ if (syncUrl) {
363
+ setFrameInUrl();
364
+ }
365
+ };
366
+
367
+ const setPlaying = (nextPlaying) => {
368
+ if (playing === nextPlaying) {
369
+ return;
370
+ }
371
+
372
+ playing = nextPlaying;
373
+ playPause.textContent = playing ? "⏸" : "▶";
374
+ playPause.setAttribute("aria-label", playing ? "Pause" : "Play");
375
+
376
+ if (playing) {
377
+ lastTick = 0;
378
+ cancelAnimationFrame(raf);
379
+ raf = requestAnimationFrame(tick);
380
+ } else {
381
+ cancelAnimationFrame(raf);
382
+ }
383
+ };
384
+
385
+ const stepFrame = (delta) => {
386
+ if (frames.length === 0) {
387
+ return;
388
+ }
389
+
390
+ if (delta === 0) {
391
+ return;
392
+ }
393
+
394
+ if (delta < 0 && currentFrame <= 0) {
395
+ if (loopCheckbox.checked) {
396
+ renderFrame(maxFrame);
397
+ } else {
398
+ renderFrame(0);
399
+ }
400
+ return;
401
+ }
402
+
403
+ if (delta > 0 && currentFrame >= maxFrame) {
404
+ if (loopCheckbox.checked) {
405
+ renderFrame(0);
406
+ } else {
407
+ renderFrame(maxFrame);
408
+ }
409
+ return;
410
+ }
411
+
412
+ renderFrame(currentFrame + delta);
413
+ };
414
+
415
+ const tick = (timestamp) => {
416
+ if (!playing) {
417
+ return;
418
+ }
419
+
420
+ if (!lastTick) {
421
+ lastTick = timestamp;
422
+ }
423
+
424
+ if (timestamp - lastTick >= frameDuration) {
425
+ const framesToAdvance = Math.floor((timestamp - lastTick) / frameDuration);
426
+ lastTick += framesToAdvance * frameDuration;
427
+
428
+ const nextFrame = currentFrame + framesToAdvance;
429
+ if (nextFrame > maxFrame) {
430
+ if (loopCheckbox.checked && frames.length > 0) {
431
+ renderFrame(nextFrame % frames.length);
432
+ } else {
433
+ renderFrame(maxFrame);
434
+ setPlaying(false);
435
+ return;
436
+ }
437
+ } else {
438
+ renderFrame(nextFrame);
439
+ }
440
+ }
441
+
442
+ raf = requestAnimationFrame(tick);
443
+ };
444
+
445
+ const preload = () => {
446
+ updateLoading();
447
+ frames.forEach((src, frameIndex) => {
448
+ const image = new Image();
449
+ const onDone = () => {
450
+ if (!loaded[frameIndex]) {
451
+ loaded[frameIndex] = true;
452
+ loadedCount += 1;
453
+ updateLoading();
454
+ }
455
+ };
456
+ image.onload = onDone;
457
+ image.onerror = onDone;
458
+ image.src = src;
459
+ });
460
+ };
461
+
462
+ const copyShareUrl = async () => {
463
+ const url = getShareUrl();
464
+ try {
465
+ if (navigator.clipboard && navigator.clipboard.writeText) {
466
+ await navigator.clipboard.writeText(url);
467
+ } else {
468
+ throw new Error("clipboard unavailable");
469
+ }
470
+ copyLink.textContent = "Copied";
471
+ setTimeout(() => {
472
+ copyLink.textContent = "Copy Frame URL";
473
+ }, 1200);
474
+ } catch {
475
+ const fallback = document.createElement("textarea");
476
+ fallback.value = url;
477
+ fallback.style.position = "fixed";
478
+ fallback.style.left = "-9999px";
479
+ document.body.appendChild(fallback);
480
+ fallback.focus();
481
+ fallback.select();
482
+ try {
483
+ document.execCommand("copy");
484
+ copyLink.textContent = "Copied";
485
+ setTimeout(() => {
486
+ copyLink.textContent = "Copy Frame URL";
487
+ }, 1200);
488
+ } finally {
489
+ fallback.remove();
490
+ }
491
+ }
492
+ };
493
+
494
+ const initialFrame = clamp(parseFrameFromLocation(), 0, maxFrame);
495
+ currentFrame = initialFrame;
496
+ renderFrame(initialFrame, false);
497
+ preload();
498
+ setFrameInUrl();
499
+
500
+ playPause.addEventListener("click", () => setPlaying(!playing));
501
+ prev.addEventListener("click", () => stepFrame(-1));
502
+ next.addEventListener("click", () => stepFrame(1));
503
+ copyLink.addEventListener("click", () => {
504
+ void copyShareUrl();
505
+ });
506
+ progress.addEventListener("input", () => {
507
+ const value = Number(progress.value);
508
+ renderFrame(value);
509
+ });
510
+ document.addEventListener("keydown", (event) => {
511
+ const target = event.target;
512
+ const isInputTarget =
513
+ target instanceof HTMLElement &&
514
+ (target.isContentEditable || target.tagName === "INPUT" || target.tagName === "TEXTAREA");
515
+
516
+ if (isInputTarget) {
517
+ return;
518
+ }
519
+
520
+ if (event.code === "Space") {
521
+ event.preventDefault();
522
+ setPlaying(!playing);
523
+ return;
524
+ }
525
+
526
+ if (event.key === "ArrowLeft") {
527
+ event.preventDefault();
528
+ setPlaying(false);
529
+ stepFrame(-1);
530
+ return;
531
+ }
532
+
533
+ if (event.key === "ArrowRight") {
534
+ event.preventDefault();
535
+ setPlaying(false);
536
+ stepFrame(1);
537
+ return;
538
+ }
539
+
540
+ if (event.key === "l" || event.key === "L") {
541
+ loopCheckbox.checked = !loopCheckbox.checked;
542
+ }
543
+ });
544
+ </script>
545
+ </body>
546
+ </html>`;
547
+ }
548
+ //# sourceMappingURL=embed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed.js","sourceRoot":"","sources":["../src/embed.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE,CAC3C,KAAK;KACF,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;KACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;KACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;KACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;KACzB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAE9B,MAAM,aAAa,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAE/E,MAAM,UAAU,cAAc,CAAC,MAAgB,EAAE,GAAW,EAAE,WAAwB;IACpF,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,IAAI,eAAe,CAAC;IACtD,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,YAAY,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvE,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,0BAA0B,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC9F,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAEnD,OAAO,2CAA2C,YAAY,cAAc,SAAS,CAAC,MAAM,WAAW,OAAO;;;;;;SAMvG,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAmNc,KAAK;;;;;;;;oEAQmC,SAAS,CAAC,MAAM;;;;;;;;;;yDAU3B,QAAQ;;;;iDAIhB,SAAS,CAAC,MAAM;;;;;;;iBAOhD,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;cAC5B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4Rb,CAAC;AACT,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Walkthrough } from "@walkrstudio/core";
2
+ import type { RecordOptions, RecordResult } from "./types.js";
3
+ export declare function encodeFrames(frames: Buffer[], options: RecordOptions & {
4
+ walkthrough?: Walkthrough;
5
+ }): Promise<RecordResult>;
6
+ //# sourceMappingURL=encoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoder.d.ts","sourceRoot":"","sources":["../src/encoder.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAkE9D,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,aAAa,GAAG;IAAE,WAAW,CAAC,EAAE,WAAW,CAAA;CAAE,GACrD,OAAO,CAAC,YAAY,CAAC,CAoFvB"}
@@ -0,0 +1,121 @@
1
+ import { exec } from "node:child_process";
2
+ import * as fs from "node:fs/promises";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { buildEmbedHtml } from "./embed.js";
6
+ const DEFAULT_FPS = 30;
7
+ const DEFAULT_FORMAT = "mp4";
8
+ const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
9
+ const shellEscape = (value) => `"${value.replace(/["\\$`]/g, "\\$&")}"`;
10
+ const getDefaultOutput = (format) => {
11
+ if (format === "gif") {
12
+ return "output.gif";
13
+ }
14
+ if (format === "webm") {
15
+ return "output.webm";
16
+ }
17
+ if (format === "embed") {
18
+ return "output.html";
19
+ }
20
+ return "output.mp4";
21
+ };
22
+ const runFfmpeg = async (command, onProgress, frameCount, fps) => {
23
+ const totalSeconds = frameCount / Math.max(1, fps);
24
+ await new Promise((resolve, reject) => {
25
+ const child = exec(command, { maxBuffer: 16 * 1024 * 1024 }, (error) => {
26
+ if (error) {
27
+ reject(error);
28
+ return;
29
+ }
30
+ resolve();
31
+ });
32
+ if (!child.stderr || !onProgress) {
33
+ return;
34
+ }
35
+ child.stderr.on("data", (chunk) => {
36
+ const output = chunk.toString();
37
+ const match = /time=(\d+):(\d+):(\d+(?:\.\d+)?)/.exec(output);
38
+ if (!match) {
39
+ return;
40
+ }
41
+ const hours = Number(match[1]);
42
+ const minutes = Number(match[2]);
43
+ const seconds = Number(match[3]);
44
+ const elapsedSeconds = hours * 3600 + minutes * 60 + seconds;
45
+ const percent = totalSeconds > 0 ? Math.max(0, Math.min(100, (elapsedSeconds / totalSeconds) * 100)) : 100;
46
+ onProgress(percent);
47
+ });
48
+ });
49
+ };
50
+ export async function encodeFrames(frames, options) {
51
+ const format = options.format ?? DEFAULT_FORMAT;
52
+ const fps = isFiniteNumber(options.fps) ? Math.max(1, Math.round(options.fps)) : DEFAULT_FPS;
53
+ const outputPath = path.resolve(options.output ?? getDefaultOutput(format));
54
+ const frameCount = frames.length;
55
+ if (frameCount === 0) {
56
+ throw new Error("No frames captured.");
57
+ }
58
+ if (format === "embed") {
59
+ const html = buildEmbedHtml(frames, fps, options.walkthrough ?? { url: "about:blank", steps: [] });
60
+ await fs.writeFile(outputPath, html, "utf8");
61
+ options.onProgress?.(100);
62
+ return {
63
+ outputPath,
64
+ duration: Math.round((frameCount / fps) * 1000),
65
+ frameCount,
66
+ };
67
+ }
68
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "walkr-frames-"));
69
+ try {
70
+ await Promise.all(frames.map((frame, index) => {
71
+ const frameName = `frame-${String(index + 1).padStart(4, "0")}.jpg`;
72
+ return fs.writeFile(path.join(tempDir, frameName), frame);
73
+ }));
74
+ const inputPattern = path.join(tempDir, "frame-%04d.jpg");
75
+ const escapedInput = shellEscape(inputPattern);
76
+ const escapedOutput = shellEscape(outputPath);
77
+ let ffmpegCommand = "";
78
+ if (format === "gif") {
79
+ ffmpegCommand = [
80
+ "ffmpeg",
81
+ "-y",
82
+ `-r ${fps}`,
83
+ `-i ${escapedInput}`,
84
+ '-vf "fps=15,scale=1280:-1:flags=lanczos"',
85
+ escapedOutput,
86
+ ].join(" ");
87
+ }
88
+ else if (format === "webm") {
89
+ ffmpegCommand = [
90
+ "ffmpeg",
91
+ "-y",
92
+ `-r ${fps}`,
93
+ `-i ${escapedInput}`,
94
+ "-c:v libvpx-vp9",
95
+ escapedOutput,
96
+ ].join(" ");
97
+ }
98
+ else {
99
+ ffmpegCommand = [
100
+ "ffmpeg",
101
+ "-y",
102
+ `-r ${fps}`,
103
+ `-i ${escapedInput}`,
104
+ "-c:v libx264",
105
+ "-pix_fmt yuv420p",
106
+ escapedOutput,
107
+ ].join(" ");
108
+ }
109
+ await runFfmpeg(ffmpegCommand, options.onProgress, frameCount, fps);
110
+ options.onProgress?.(100);
111
+ return {
112
+ outputPath,
113
+ duration: Math.round((frameCount / fps) * 1000),
114
+ frameCount,
115
+ };
116
+ }
117
+ finally {
118
+ await fs.rm(tempDir, { recursive: true, force: true });
119
+ }
120
+ }
121
+ //# sourceMappingURL=encoder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoder.js","sourceRoot":"","sources":["../src/encoder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { buildEmbedHtml } from "./embed.js";
2
+ export { encodeFrames } from "./encoder.js";
3
+ export { recordWalkthrough } from "./recorder.js";
4
+ export { StreamingEncoder } from "./streaming-encoder.js";
5
+ export type * from "./types.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,mBAAmB,YAAY,CAAC"}