@godscene/visualizer 1.7.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/es/component/blackboard/highlights.mjs +47 -0
- package/dist/es/component/blackboard/index.css +118 -0
- package/dist/es/component/blackboard/index.mjs +122 -0
- package/dist/es/component/config-selector/index.mjs +251 -0
- package/dist/es/component/context-preview/index.mjs +37 -0
- package/dist/es/component/env-config/index.mjs +237 -0
- package/dist/es/component/env-config-reminder/index.css +30 -0
- package/dist/es/component/env-config-reminder/index.mjs +27 -0
- package/dist/es/component/form-field/index.mjs +158 -0
- package/dist/es/component/history-selector/index.css +237 -0
- package/dist/es/component/history-selector/index.mjs +197 -0
- package/dist/es/component/index.mjs +1 -0
- package/dist/es/component/logo/index.css +19 -0
- package/dist/es/component/logo/index.mjs +20 -0
- package/dist/es/component/logo/logo.mjs +2 -0
- package/dist/es/component/misc/index.mjs +96 -0
- package/dist/es/component/nav-actions/index.mjs +14 -0
- package/dist/es/component/nav-actions/style.css +35 -0
- package/dist/es/component/player/index.css +296 -0
- package/dist/es/component/player/index.mjs +702 -0
- package/dist/es/component/player/playback-controls.mjs +4 -0
- package/dist/es/component/player/report-download.mjs +61 -0
- package/dist/es/component/player/scenes/StepScene.mjs +194 -0
- package/dist/es/component/player/scenes/derive-frame-state.mjs +229 -0
- package/dist/es/component/player/scenes/export-branded-video.mjs +360 -0
- package/dist/es/component/player/scenes/frame-calculator.mjs +149 -0
- package/dist/es/component/player/scenes/playback-frame.mjs +6 -0
- package/dist/es/component/player/scenes/playback-layout.mjs +12 -0
- package/dist/es/component/player/scenes/pointer-layout.mjs +36 -0
- package/dist/es/component/player/use-frame-player.mjs +87 -0
- package/dist/es/component/playground/index.css +930 -0
- package/dist/es/component/playground/playground-demo-ui-context.json +290 -0
- package/dist/es/component/playground-result/index.css +92 -0
- package/dist/es/component/playground-result/index.mjs +232 -0
- package/dist/es/component/prompt-input/index.css +832 -0
- package/dist/es/component/prompt-input/index.mjs +959 -0
- package/dist/es/component/screenshot-viewer/index.css +237 -0
- package/dist/es/component/screenshot-viewer/index.mjs +319 -0
- package/dist/es/component/service-mode-control/index.mjs +107 -0
- package/dist/es/component/shiny-text/index.css +107 -0
- package/dist/es/component/shiny-text/index.mjs +15 -0
- package/dist/es/component/universal-playground/empty-state.mjs +5 -0
- package/dist/es/component/universal-playground/index.css +619 -0
- package/dist/es/component/universal-playground/index.mjs +558 -0
- package/dist/es/component/universal-playground/providers/context-provider.mjs +90 -0
- package/dist/es/component/universal-playground/providers/indexeddb-storage-provider.mjs +280 -0
- package/dist/es/component/universal-playground/providers/storage-provider.mjs +279 -0
- package/dist/es/component/universal-playground/universal-playground-electron.mjs +668 -0
- package/dist/es/hooks/useMinimalTypeGate.mjs +47 -0
- package/dist/es/hooks/usePlaygroundExecution.mjs +435 -0
- package/dist/es/hooks/usePlaygroundState.mjs +278 -0
- package/dist/es/hooks/useSafeOverrideAIConfig.mjs +20 -0
- package/dist/es/hooks/useServerValid.mjs +55 -0
- package/dist/es/hooks/useTheme.mjs +25 -0
- package/dist/es/icons/action-chevron.mjs +61 -0
- package/dist/es/icons/avatar.mjs +70 -0
- package/dist/es/icons/close.mjs +61 -0
- package/dist/es/icons/global-perspective.mjs +58 -0
- package/dist/es/icons/history.mjs +72 -0
- package/dist/es/icons/magnifying-glass.mjs +81 -0
- package/dist/es/icons/player-setting.mjs +68 -0
- package/dist/es/icons/prompt-history.mjs +70 -0
- package/dist/es/icons/setting.mjs +62 -0
- package/dist/es/icons/show-marker.mjs +58 -0
- package/dist/es/index.mjs +26 -0
- package/dist/es/static/image/logo.png +0 -0
- package/dist/es/store/history.mjs +128 -0
- package/dist/es/store/store.mjs +277 -0
- package/dist/es/types.mjs +73 -0
- package/dist/es/utils/action-label.mjs +15 -0
- package/dist/es/utils/color.mjs +35 -0
- package/dist/es/utils/constants.mjs +99 -0
- package/dist/es/utils/device-capabilities.mjs +13 -0
- package/dist/es/utils/empty-state-scroll.mjs +8 -0
- package/dist/es/utils/highlight-element.mjs +62 -0
- package/dist/es/utils/index.mjs +13 -0
- package/dist/es/utils/playground-utils.mjs +43 -0
- package/dist/es/utils/progress-action-icon.mjs +30 -0
- package/dist/es/utils/prompt-input-utils.mjs +49 -0
- package/dist/es/utils/prompt-placeholder.mjs +19 -0
- package/dist/es/utils/replay-scripts.mjs +428 -0
- package/dist/lib/component/blackboard/highlights.js +84 -0
- package/dist/lib/component/blackboard/index.css +118 -0
- package/dist/lib/component/blackboard/index.js +169 -0
- package/dist/lib/component/config-selector/index.js +295 -0
- package/dist/lib/component/context-preview/index.js +82 -0
- package/dist/lib/component/env-config/index.js +271 -0
- package/dist/lib/component/env-config-reminder/index.css +30 -0
- package/dist/lib/component/env-config-reminder/index.js +61 -0
- package/dist/lib/component/form-field/index.js +204 -0
- package/dist/lib/component/history-selector/index.css +237 -0
- package/dist/lib/component/history-selector/index.js +243 -0
- package/dist/lib/component/index.js +58 -0
- package/dist/lib/component/logo/index.css +19 -0
- package/dist/lib/component/logo/index.js +67 -0
- package/dist/lib/component/logo/logo.js +24 -0
- package/dist/lib/component/misc/index.js +152 -0
- package/dist/lib/component/nav-actions/index.js +48 -0
- package/dist/lib/component/nav-actions/style.css +35 -0
- package/dist/lib/component/player/index.css +296 -0
- package/dist/lib/component/player/index.js +747 -0
- package/dist/lib/component/player/playback-controls.js +38 -0
- package/dist/lib/component/player/report-download.js +98 -0
- package/dist/lib/component/player/scenes/StepScene.js +228 -0
- package/dist/lib/component/player/scenes/derive-frame-state.js +266 -0
- package/dist/lib/component/player/scenes/export-branded-video.js +403 -0
- package/dist/lib/component/player/scenes/frame-calculator.js +186 -0
- package/dist/lib/component/player/scenes/playback-frame.js +40 -0
- package/dist/lib/component/player/scenes/playback-layout.js +46 -0
- package/dist/lib/component/player/scenes/pointer-layout.js +88 -0
- package/dist/lib/component/player/use-frame-player.js +121 -0
- package/dist/lib/component/playground/index.css +930 -0
- package/dist/lib/component/playground/playground-demo-ui-context.json +290 -0
- package/dist/lib/component/playground-result/index.css +92 -0
- package/dist/lib/component/playground-result/index.js +276 -0
- package/dist/lib/component/prompt-input/index.css +832 -0
- package/dist/lib/component/prompt-input/index.js +1005 -0
- package/dist/lib/component/screenshot-viewer/index.css +237 -0
- package/dist/lib/component/screenshot-viewer/index.js +353 -0
- package/dist/lib/component/service-mode-control/index.js +141 -0
- package/dist/lib/component/shiny-text/index.css +107 -0
- package/dist/lib/component/shiny-text/index.js +49 -0
- package/dist/lib/component/universal-playground/empty-state.js +39 -0
- package/dist/lib/component/universal-playground/index.css +619 -0
- package/dist/lib/component/universal-playground/index.js +607 -0
- package/dist/lib/component/universal-playground/providers/context-provider.js +133 -0
- package/dist/lib/component/universal-playground/providers/indexeddb-storage-provider.js +320 -0
- package/dist/lib/component/universal-playground/providers/storage-provider.js +337 -0
- package/dist/lib/component/universal-playground/universal-playground-electron.js +717 -0
- package/dist/lib/hooks/useMinimalTypeGate.js +81 -0
- package/dist/lib/hooks/usePlaygroundExecution.js +478 -0
- package/dist/lib/hooks/usePlaygroundState.js +312 -0
- package/dist/lib/hooks/useSafeOverrideAIConfig.js +57 -0
- package/dist/lib/hooks/useServerValid.js +89 -0
- package/dist/lib/hooks/useTheme.js +59 -0
- package/dist/lib/icons/action-chevron.js +95 -0
- package/dist/lib/icons/avatar.js +104 -0
- package/dist/lib/icons/close.js +95 -0
- package/dist/lib/icons/global-perspective.js +92 -0
- package/dist/lib/icons/history.js +106 -0
- package/dist/lib/icons/magnifying-glass.js +115 -0
- package/dist/lib/icons/player-setting.js +102 -0
- package/dist/lib/icons/prompt-history.js +104 -0
- package/dist/lib/icons/setting.js +96 -0
- package/dist/lib/icons/show-marker.js +92 -0
- package/dist/lib/index.js +204 -0
- package/dist/lib/static/image/logo.png +0 -0
- package/dist/lib/store/history.js +135 -0
- package/dist/lib/store/store.js +287 -0
- package/dist/lib/types.js +119 -0
- package/dist/lib/utils/action-label.js +52 -0
- package/dist/lib/utils/color.js +75 -0
- package/dist/lib/utils/constants.js +172 -0
- package/dist/lib/utils/device-capabilities.js +50 -0
- package/dist/lib/utils/empty-state-scroll.js +42 -0
- package/dist/lib/utils/highlight-element.js +99 -0
- package/dist/lib/utils/index.js +69 -0
- package/dist/lib/utils/playground-utils.js +86 -0
- package/dist/lib/utils/progress-action-icon.js +67 -0
- package/dist/lib/utils/prompt-input-utils.js +89 -0
- package/dist/lib/utils/prompt-placeholder.js +53 -0
- package/dist/lib/utils/replay-scripts.js +474 -0
- package/dist/types/component/blackboard/highlights.d.ts +11 -0
- package/dist/types/component/blackboard/index.d.ts +10 -0
- package/dist/types/component/config-selector/index.d.ts +15 -0
- package/dist/types/component/context-preview/index.d.ts +9 -0
- package/dist/types/component/env-config/index.d.ts +8 -0
- package/dist/types/component/env-config-reminder/index.d.ts +6 -0
- package/dist/types/component/form-field/index.d.ts +17 -0
- package/dist/types/component/history-selector/index.d.ts +13 -0
- package/dist/types/component/index.d.ts +1 -0
- package/dist/types/component/logo/index.d.ts +5 -0
- package/dist/types/component/misc/index.d.ts +6 -0
- package/dist/types/component/nav-actions/index.d.ts +12 -0
- package/dist/types/component/player/index.d.ts +15 -0
- package/dist/types/component/player/playback-controls.d.ts +1 -0
- package/dist/types/component/player/report-download.d.ts +32 -0
- package/dist/types/component/player/scenes/StepScene.d.ts +9 -0
- package/dist/types/component/player/scenes/derive-frame-state.d.ts +40 -0
- package/dist/types/component/player/scenes/export-branded-video.d.ts +33 -0
- package/dist/types/component/player/scenes/frame-calculator.d.ts +40 -0
- package/dist/types/component/player/scenes/playback-frame.d.ts +3 -0
- package/dist/types/component/player/scenes/playback-layout.d.ts +7 -0
- package/dist/types/component/player/scenes/pointer-layout.d.ts +20 -0
- package/dist/types/component/player/use-frame-player.d.ts +17 -0
- package/dist/types/component/playground-result/index.d.ts +22 -0
- package/dist/types/component/prompt-input/index.d.ts +23 -0
- package/dist/types/component/screenshot-viewer/index.d.ts +23 -0
- package/dist/types/component/service-mode-control/index.d.ts +6 -0
- package/dist/types/component/shiny-text/index.d.ts +12 -0
- package/dist/types/component/universal-playground/empty-state.d.ts +3 -0
- package/dist/types/component/universal-playground/index.d.ts +4 -0
- package/dist/types/component/universal-playground/providers/context-provider.d.ts +37 -0
- package/dist/types/component/universal-playground/providers/indexeddb-storage-provider.d.ts +71 -0
- package/dist/types/component/universal-playground/providers/storage-provider.d.ts +58 -0
- package/dist/types/component/universal-playground/universal-playground-electron.d.ts +4 -0
- package/dist/types/hooks/useMinimalTypeGate.d.ts +72 -0
- package/dist/types/hooks/usePlaygroundExecution.d.ts +40 -0
- package/dist/types/hooks/usePlaygroundState.d.ts +26 -0
- package/dist/types/hooks/useSafeOverrideAIConfig.d.ts +16 -0
- package/dist/types/hooks/useServerValid.d.ts +1 -0
- package/dist/types/hooks/useTheme.d.ts +7 -0
- package/dist/types/index.d.ts +29 -0
- package/dist/types/store/history.d.ts +16 -0
- package/dist/types/store/store.d.ts +57 -0
- package/dist/types/types.d.ts +278 -0
- package/dist/types/utils/action-label.d.ts +11 -0
- package/dist/types/utils/color.d.ts +4 -0
- package/dist/types/utils/constants.d.ts +80 -0
- package/dist/types/utils/device-capabilities.d.ts +9 -0
- package/dist/types/utils/empty-state-scroll.d.ts +11 -0
- package/dist/types/utils/highlight-element.d.ts +3 -0
- package/dist/types/utils/index.d.ts +5 -0
- package/dist/types/utils/playground-utils.d.ts +11 -0
- package/dist/types/utils/progress-action-icon.d.ts +12 -0
- package/dist/types/utils/prompt-input-utils.d.ts +24 -0
- package/dist/types/utils/prompt-placeholder.d.ts +1 -0
- package/dist/types/utils/replay-scripts.d.ts +50 -0
- package/package.json +82 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { mouseLoading, mousePointer } from "../../../utils/index.mjs";
|
|
2
|
+
import { getCenterHighlightBox } from "../../../utils/highlight-element.mjs";
|
|
3
|
+
import { deriveFrameState, shouldRenderCursor } from "./derive-frame-state.mjs";
|
|
4
|
+
import { getPlaybackViewport } from "./playback-layout.mjs";
|
|
5
|
+
import { resolveExportPointerLayout, resolveSpinnerLayout } from "./pointer-layout.mjs";
|
|
6
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
7
|
+
try {
|
|
8
|
+
var info = gen[key](arg);
|
|
9
|
+
var value = info.value;
|
|
10
|
+
} catch (error) {
|
|
11
|
+
reject(error);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (info.done) resolve(value);
|
|
15
|
+
else Promise.resolve(value).then(_next, _throw);
|
|
16
|
+
}
|
|
17
|
+
function _async_to_generator(fn) {
|
|
18
|
+
return function() {
|
|
19
|
+
var self = this, args = arguments;
|
|
20
|
+
return new Promise(function(resolve, reject) {
|
|
21
|
+
var gen = fn.apply(self, args);
|
|
22
|
+
function _next(value) {
|
|
23
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
24
|
+
}
|
|
25
|
+
function _throw(err) {
|
|
26
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
27
|
+
}
|
|
28
|
+
_next(void 0);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function _define_property(obj, key, value) {
|
|
33
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
34
|
+
value: value,
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true
|
|
38
|
+
});
|
|
39
|
+
else obj[key] = value;
|
|
40
|
+
return obj;
|
|
41
|
+
}
|
|
42
|
+
function _object_spread(target) {
|
|
43
|
+
for(var i = 1; i < arguments.length; i++){
|
|
44
|
+
var source = null != arguments[i] ? arguments[i] : {};
|
|
45
|
+
var ownKeys = Object.keys(source);
|
|
46
|
+
if ("function" == typeof Object.getOwnPropertySymbols) ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
47
|
+
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
48
|
+
}));
|
|
49
|
+
ownKeys.forEach(function(key) {
|
|
50
|
+
_define_property(target, key, source[key]);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return target;
|
|
54
|
+
}
|
|
55
|
+
function export_branded_video_ownKeys(object, enumerableOnly) {
|
|
56
|
+
var keys = Object.keys(object);
|
|
57
|
+
if (Object.getOwnPropertySymbols) {
|
|
58
|
+
var symbols = Object.getOwnPropertySymbols(object);
|
|
59
|
+
if (enumerableOnly) symbols = symbols.filter(function(sym) {
|
|
60
|
+
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
61
|
+
});
|
|
62
|
+
keys.push.apply(keys, symbols);
|
|
63
|
+
}
|
|
64
|
+
return keys;
|
|
65
|
+
}
|
|
66
|
+
function _object_spread_props(target, source) {
|
|
67
|
+
source = null != source ? source : {};
|
|
68
|
+
if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
69
|
+
else export_branded_video_ownKeys(Object(source)).forEach(function(key) {
|
|
70
|
+
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
71
|
+
});
|
|
72
|
+
return target;
|
|
73
|
+
}
|
|
74
|
+
const W = 960;
|
|
75
|
+
const H = 540;
|
|
76
|
+
const POINTER_PHASE = 0.375;
|
|
77
|
+
const CROSSFADE_FRAMES = 10;
|
|
78
|
+
const EXPORT_STALL_GRACE_MS = 2000;
|
|
79
|
+
const EXPORT_STALL_GRACE_FRAMES = 10;
|
|
80
|
+
let activeExport = false;
|
|
81
|
+
function clamp(v, lo, hi) {
|
|
82
|
+
return Math.min(Math.max(v, lo), hi);
|
|
83
|
+
}
|
|
84
|
+
function lerp(a, b, t) {
|
|
85
|
+
return a + (b - a) * t;
|
|
86
|
+
}
|
|
87
|
+
function isExportRenderStalled(elapsedSinceLastFrameMs, frameDurationMs) {
|
|
88
|
+
return elapsedSinceLastFrameMs > Math.max(EXPORT_STALL_GRACE_MS, frameDurationMs * EXPORT_STALL_GRACE_FRAMES);
|
|
89
|
+
}
|
|
90
|
+
function resolveExportCamera(prevCamera, camera, imageWidth, progress, autoZoom) {
|
|
91
|
+
if (!autoZoom) return {
|
|
92
|
+
camLeft: 0,
|
|
93
|
+
camTop: 0,
|
|
94
|
+
camWidth: imageWidth
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
camLeft: lerp(prevCamera.left, camera.left, progress),
|
|
98
|
+
camTop: lerp(prevCamera.top, camera.top, progress),
|
|
99
|
+
camWidth: lerp(prevCamera.width, camera.width, progress)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function loadImage(src) {
|
|
103
|
+
return new Promise((resolve, reject)=>{
|
|
104
|
+
const img = new Image();
|
|
105
|
+
img.crossOrigin = 'anonymous';
|
|
106
|
+
img.onload = ()=>resolve(img);
|
|
107
|
+
img.onerror = reject;
|
|
108
|
+
img.src = src;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function projectNativeRectToExportViewport(rect, cameraTransform, viewport) {
|
|
112
|
+
const scaleX = viewport.contentWidth / viewport.imageWidth;
|
|
113
|
+
const scaleY = viewport.contentHeight / viewport.imageHeight;
|
|
114
|
+
return {
|
|
115
|
+
left: viewport.offsetX + (rect.left * scaleX + cameraTransform.tx) * cameraTransform.zoom,
|
|
116
|
+
top: viewport.offsetY + (rect.top * scaleY + cameraTransform.ty) * cameraTransform.zoom,
|
|
117
|
+
width: rect.width * scaleX * cameraTransform.zoom,
|
|
118
|
+
height: rect.height * scaleY * cameraTransform.zoom
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function drawInsightOverlays(ctx, insights, cameraTransform, viewport) {
|
|
122
|
+
ctx.save();
|
|
123
|
+
ctx.beginPath();
|
|
124
|
+
ctx.rect(viewport.offsetX, viewport.offsetY, viewport.contentWidth, viewport.contentHeight);
|
|
125
|
+
ctx.clip();
|
|
126
|
+
for (const insight of insights)if (!(insight.alpha <= 0)) {
|
|
127
|
+
ctx.save();
|
|
128
|
+
ctx.globalAlpha *= insight.alpha;
|
|
129
|
+
if (insight.highlightElement) {
|
|
130
|
+
const highlightBox = getCenterHighlightBox(insight.highlightElement);
|
|
131
|
+
const projected = projectNativeRectToExportViewport(highlightBox, cameraTransform, viewport);
|
|
132
|
+
ctx.fillStyle = 'rgba(253, 89, 7, 0.4)';
|
|
133
|
+
ctx.fillRect(projected.left, projected.top, projected.width, projected.height);
|
|
134
|
+
ctx.strokeStyle = '#fd5907';
|
|
135
|
+
ctx.lineWidth = 1;
|
|
136
|
+
ctx.strokeRect(projected.left, projected.top, projected.width, projected.height);
|
|
137
|
+
ctx.shadowColor = 'rgba(51, 51, 51, 0.4)';
|
|
138
|
+
ctx.shadowBlur = 2;
|
|
139
|
+
ctx.shadowOffsetX = 4;
|
|
140
|
+
ctx.shadowOffsetY = 4;
|
|
141
|
+
ctx.strokeRect(projected.left, projected.top, projected.width, projected.height);
|
|
142
|
+
ctx.shadowBlur = 0;
|
|
143
|
+
ctx.shadowOffsetX = 0;
|
|
144
|
+
ctx.shadowOffsetY = 0;
|
|
145
|
+
}
|
|
146
|
+
if (insight.searchArea) {
|
|
147
|
+
const projected = projectNativeRectToExportViewport(insight.searchArea, cameraTransform, viewport);
|
|
148
|
+
ctx.fillStyle = 'rgba(2, 131, 145, 0.4)';
|
|
149
|
+
ctx.fillRect(projected.left, projected.top, projected.width, projected.height);
|
|
150
|
+
ctx.strokeStyle = '#028391';
|
|
151
|
+
ctx.lineWidth = 1;
|
|
152
|
+
ctx.strokeRect(projected.left, projected.top, projected.width, projected.height);
|
|
153
|
+
}
|
|
154
|
+
ctx.restore();
|
|
155
|
+
}
|
|
156
|
+
ctx.restore();
|
|
157
|
+
}
|
|
158
|
+
function drawSpinningPointer(ctx, img, x, y, layout, elapsedMs) {
|
|
159
|
+
const progress = (Math.sin(elapsedMs / 500 - Math.PI / 2) + 1) / 2;
|
|
160
|
+
const rotation = progress * Math.PI * 2;
|
|
161
|
+
ctx.save();
|
|
162
|
+
ctx.translate(x, y);
|
|
163
|
+
ctx.rotate(rotation);
|
|
164
|
+
ctx.drawImage(img, -layout.centerOffsetX, -layout.centerOffsetY, layout.width, layout.height);
|
|
165
|
+
ctx.restore();
|
|
166
|
+
}
|
|
167
|
+
function drawSteps(ctx, stepsFrame, frameMap, imgCache, pointerCache, spinnerImg, autoZoom) {
|
|
168
|
+
var _pointerCache_get;
|
|
169
|
+
const { scriptFrames, imageWidth: baseW, imageHeight: baseH, fps } = frameMap;
|
|
170
|
+
const st = deriveFrameState(scriptFrames, stepsFrame, baseW, baseH, fps);
|
|
171
|
+
if (!st.img) return;
|
|
172
|
+
const { img, prevImg, imageWidth: imgW, imageHeight: imgH, camera, prevCamera, pointerMoved, imageChanged, rawProgress, frameInScript: fInScript, spinning, spinningElapsedMs, currentPointerImg, pointerVisible, insights } = st;
|
|
173
|
+
const pT = autoZoom ? pointerMoved ? Math.min(rawProgress / POINTER_PHASE, 1) : rawProgress : 1;
|
|
174
|
+
const cT = pointerMoved ? rawProgress <= POINTER_PHASE ? 0 : Math.min((rawProgress - POINTER_PHASE) / (1 - POINTER_PHASE), 1) : rawProgress;
|
|
175
|
+
const { camLeft: camL, camTop: camT2, camWidth: camW } = resolveExportCamera(prevCamera, camera, imgW, cT, autoZoom);
|
|
176
|
+
const ptrX = lerp(prevCamera.pointerLeft, camera.pointerLeft, pT);
|
|
177
|
+
const ptrY = lerp(prevCamera.pointerTop, camera.pointerTop, pT);
|
|
178
|
+
const zoom = imgW / camW;
|
|
179
|
+
const { offsetX, offsetY, contentWidth, contentHeight } = getPlaybackViewport(W, H, imgW, imgH);
|
|
180
|
+
const tx = contentWidth / imgW * -camL;
|
|
181
|
+
const ty = contentHeight / imgH * -camT2;
|
|
182
|
+
const crossAlpha = imageChanged ? clamp(fInScript / CROSSFADE_FRAMES, 0, 1) : 1;
|
|
183
|
+
ctx.fillStyle = '#000';
|
|
184
|
+
ctx.fillRect(0, 0, W, H);
|
|
185
|
+
const drawImg = (src, alpha)=>{
|
|
186
|
+
const imgEl = imgCache.get(src);
|
|
187
|
+
if (!imgEl || alpha <= 0) return;
|
|
188
|
+
ctx.save();
|
|
189
|
+
ctx.globalAlpha = alpha;
|
|
190
|
+
ctx.beginPath();
|
|
191
|
+
ctx.rect(0, 0, W, H);
|
|
192
|
+
ctx.clip();
|
|
193
|
+
ctx.translate(offsetX + tx * zoom, offsetY + ty * zoom);
|
|
194
|
+
ctx.scale(zoom, zoom);
|
|
195
|
+
ctx.drawImage(imgEl, 0, 0, contentWidth, contentHeight);
|
|
196
|
+
ctx.restore();
|
|
197
|
+
};
|
|
198
|
+
if (imageChanged && prevImg && crossAlpha < 1) drawImg(prevImg, 1 - crossAlpha);
|
|
199
|
+
drawImg(img, imageChanged ? crossAlpha : 1);
|
|
200
|
+
if (insights.length > 0) drawInsightOverlays(ctx, insights, {
|
|
201
|
+
zoom,
|
|
202
|
+
tx,
|
|
203
|
+
ty
|
|
204
|
+
}, {
|
|
205
|
+
offsetX,
|
|
206
|
+
offsetY,
|
|
207
|
+
contentWidth,
|
|
208
|
+
contentHeight,
|
|
209
|
+
imageWidth: imgW,
|
|
210
|
+
imageHeight: imgH
|
|
211
|
+
});
|
|
212
|
+
const camH = imgH / imgW * camW;
|
|
213
|
+
const sX = offsetX + (ptrX - camL) / camW * contentWidth;
|
|
214
|
+
const sY = offsetY + (ptrY - camT2) / camH * contentHeight;
|
|
215
|
+
const pointerLayout = resolveExportPointerLayout(imgW, contentWidth);
|
|
216
|
+
const spinnerLayout = resolveSpinnerLayout(pointerLayout);
|
|
217
|
+
const cursorImg = null != (_pointerCache_get = pointerCache.get(currentPointerImg)) ? _pointerCache_get : pointerCache.get(mousePointer);
|
|
218
|
+
const showCursor = shouldRenderCursor(pointerVisible, camera, prevCamera, imgW, imgH);
|
|
219
|
+
if (spinning && spinnerImg) drawSpinningPointer(ctx, spinnerImg, sX, sY, _object_spread_props(_object_spread({}, pointerLayout), {
|
|
220
|
+
width: spinnerLayout.size,
|
|
221
|
+
height: spinnerLayout.size,
|
|
222
|
+
centerOffsetX: spinnerLayout.centerOffset,
|
|
223
|
+
centerOffsetY: spinnerLayout.centerOffset
|
|
224
|
+
}), spinningElapsedMs);
|
|
225
|
+
if (!spinning && showCursor && cursorImg) ctx.drawImage(cursorImg, sX - pointerLayout.hotspotX, sY - pointerLayout.hotspotY, pointerLayout.width, pointerLayout.height);
|
|
226
|
+
}
|
|
227
|
+
function exportBrandedVideo(frameMap, options, onProgress) {
|
|
228
|
+
return _async_to_generator(function*() {
|
|
229
|
+
if (activeExport) throw new Error('Video export is already in progress');
|
|
230
|
+
activeExport = true;
|
|
231
|
+
try {
|
|
232
|
+
yield runExportBrandedVideo(frameMap, options, onProgress);
|
|
233
|
+
} finally{
|
|
234
|
+
activeExport = false;
|
|
235
|
+
}
|
|
236
|
+
})();
|
|
237
|
+
}
|
|
238
|
+
function runExportBrandedVideo(frameMap, options, onProgress) {
|
|
239
|
+
return _async_to_generator(function*() {
|
|
240
|
+
var _ref;
|
|
241
|
+
const { totalDurationInFrames: total, fps } = frameMap;
|
|
242
|
+
const autoZoom = null != (_ref = null == options ? void 0 : options.autoZoom) ? _ref : true;
|
|
243
|
+
const imgSrcs = new Set();
|
|
244
|
+
for (const sf of frameMap.scriptFrames)if (sf.img) imgSrcs.add(sf.img);
|
|
245
|
+
const imgCache = new Map();
|
|
246
|
+
yield Promise.all([
|
|
247
|
+
...imgSrcs
|
|
248
|
+
].map((src)=>_async_to_generator(function*() {
|
|
249
|
+
try {
|
|
250
|
+
imgCache.set(src, (yield loadImage(src)));
|
|
251
|
+
} catch (unused) {}
|
|
252
|
+
})()));
|
|
253
|
+
const pointerSrcs = new Set([
|
|
254
|
+
mousePointer
|
|
255
|
+
]);
|
|
256
|
+
for (const sf of frameMap.scriptFrames)if (sf.pointerImg) pointerSrcs.add(sf.pointerImg);
|
|
257
|
+
const pointerCache = new Map();
|
|
258
|
+
yield Promise.all([
|
|
259
|
+
...pointerSrcs
|
|
260
|
+
].map((src)=>_async_to_generator(function*() {
|
|
261
|
+
try {
|
|
262
|
+
pointerCache.set(src, (yield loadImage(src)));
|
|
263
|
+
} catch (unused) {}
|
|
264
|
+
})()));
|
|
265
|
+
let spinnerImg = null;
|
|
266
|
+
try {
|
|
267
|
+
spinnerImg = yield loadImage(mouseLoading);
|
|
268
|
+
} catch (unused) {}
|
|
269
|
+
const canvas = document.createElement('canvas');
|
|
270
|
+
canvas.width = W;
|
|
271
|
+
canvas.height = H;
|
|
272
|
+
const ctx = canvas.getContext('2d');
|
|
273
|
+
const stream = canvas.captureStream(fps);
|
|
274
|
+
const recorder = new MediaRecorder(stream, {
|
|
275
|
+
mimeType: 'video/webm'
|
|
276
|
+
});
|
|
277
|
+
const chunks = [];
|
|
278
|
+
recorder.ondataavailable = (e)=>{
|
|
279
|
+
if (e.data.size > 0) chunks.push(e.data);
|
|
280
|
+
};
|
|
281
|
+
return new Promise((resolve, reject)=>{
|
|
282
|
+
let stoppedByError = null;
|
|
283
|
+
let settled = false;
|
|
284
|
+
let nextFrame = 0;
|
|
285
|
+
let nextFrameDueAt = performance.now();
|
|
286
|
+
let lastFrameAt = nextFrameDueAt;
|
|
287
|
+
let stopTimer = null;
|
|
288
|
+
let renderTimer = null;
|
|
289
|
+
const frameDuration = 1000 / fps;
|
|
290
|
+
const cleanup = ()=>{
|
|
291
|
+
if (stopTimer) clearTimeout(stopTimer);
|
|
292
|
+
if (renderTimer) clearTimeout(renderTimer);
|
|
293
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
294
|
+
stream.getTracks().forEach((track)=>track.stop());
|
|
295
|
+
};
|
|
296
|
+
const finishWithError = (error)=>{
|
|
297
|
+
if (settled || stoppedByError) return;
|
|
298
|
+
stoppedByError = error;
|
|
299
|
+
if ('inactive' !== recorder.state) recorder.stop();
|
|
300
|
+
else {
|
|
301
|
+
cleanup();
|
|
302
|
+
settled = true;
|
|
303
|
+
reject(error);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const handleVisibilityChange = ()=>{
|
|
307
|
+
if (document.hidden) finishWithError(new Error('Video export was interrupted because the report tab was hidden'));
|
|
308
|
+
};
|
|
309
|
+
recorder.onerror = ()=>{
|
|
310
|
+
finishWithError(new Error('MediaRecorder error'));
|
|
311
|
+
};
|
|
312
|
+
recorder.onstop = ()=>{
|
|
313
|
+
cleanup();
|
|
314
|
+
if (settled) return;
|
|
315
|
+
settled = true;
|
|
316
|
+
if (stoppedByError) return void reject(stoppedByError);
|
|
317
|
+
if (0 === chunks.length) return void reject(new Error('No video data'));
|
|
318
|
+
const blob = new Blob(chunks, {
|
|
319
|
+
type: 'video/webm'
|
|
320
|
+
});
|
|
321
|
+
const url = URL.createObjectURL(blob);
|
|
322
|
+
const a = document.createElement('a');
|
|
323
|
+
a.href = url;
|
|
324
|
+
a.download = 'midscene_replay.webm';
|
|
325
|
+
a.click();
|
|
326
|
+
setTimeout(()=>URL.revokeObjectURL(url), 1000);
|
|
327
|
+
resolve();
|
|
328
|
+
};
|
|
329
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
330
|
+
recorder.start();
|
|
331
|
+
const scheduleNextFrame = ()=>{
|
|
332
|
+
const delay = Math.max(0, nextFrameDueAt - performance.now());
|
|
333
|
+
renderTimer = setTimeout(()=>{
|
|
334
|
+
requestAnimationFrame(renderFrame);
|
|
335
|
+
}, delay);
|
|
336
|
+
};
|
|
337
|
+
const renderFrame = (timestamp)=>{
|
|
338
|
+
if (settled || 'inactive' === recorder.state) return;
|
|
339
|
+
if (nextFrame > 0 && isExportRenderStalled(timestamp - lastFrameAt, frameDuration)) return void finishWithError(new Error('Video export was interrupted because rendering stalled'));
|
|
340
|
+
lastFrameAt = timestamp;
|
|
341
|
+
ctx.clearRect(0, 0, W, H);
|
|
342
|
+
drawSteps(ctx, nextFrame, frameMap, imgCache, pointerCache, spinnerImg, autoZoom);
|
|
343
|
+
null == onProgress || onProgress((nextFrame + 1) / total);
|
|
344
|
+
nextFrame += 1;
|
|
345
|
+
if (nextFrame < total) {
|
|
346
|
+
nextFrameDueAt += frameDuration;
|
|
347
|
+
scheduleNextFrame();
|
|
348
|
+
} else stopTimer = setTimeout(()=>{
|
|
349
|
+
if ('inactive' !== recorder.state) recorder.stop();
|
|
350
|
+
}, 2 * frameDuration);
|
|
351
|
+
};
|
|
352
|
+
requestAnimationFrame((timestamp)=>{
|
|
353
|
+
lastFrameAt = timestamp;
|
|
354
|
+
nextFrameDueAt = timestamp;
|
|
355
|
+
renderFrame(timestamp);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
})();
|
|
359
|
+
}
|
|
360
|
+
export { exportBrandedVideo, isExportRenderStalled, projectNativeRectToExportViewport, resolveExportCamera };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const FPS = 30;
|
|
2
|
+
function calculateFrameMap(scripts, options) {
|
|
3
|
+
let baseImageWidth = (null == options ? void 0 : options.imageWidth) || 1920;
|
|
4
|
+
let baseImageHeight = (null == options ? void 0 : options.imageHeight) || 1080;
|
|
5
|
+
for (const s of scripts)if (('img' === s.type || 'insight' === s.type) && s.img) {
|
|
6
|
+
baseImageWidth = s.imageWidth || baseImageWidth;
|
|
7
|
+
baseImageHeight = s.imageHeight || baseImageHeight;
|
|
8
|
+
break;
|
|
9
|
+
}
|
|
10
|
+
const scriptFrames = [];
|
|
11
|
+
let currentFrame = 0;
|
|
12
|
+
for (const script of scripts){
|
|
13
|
+
const durationMs = script.duration;
|
|
14
|
+
switch(script.type){
|
|
15
|
+
case 'sleep':
|
|
16
|
+
{
|
|
17
|
+
const frames = Math.ceil(durationMs / 1000 * FPS);
|
|
18
|
+
scriptFrames.push({
|
|
19
|
+
type: 'sleep',
|
|
20
|
+
startFrame: currentFrame,
|
|
21
|
+
durationInFrames: frames,
|
|
22
|
+
title: script.title,
|
|
23
|
+
subTitle: script.subTitle,
|
|
24
|
+
taskId: script.taskId
|
|
25
|
+
});
|
|
26
|
+
currentFrame += frames;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case 'img':
|
|
30
|
+
{
|
|
31
|
+
const frames = Math.max(Math.ceil(durationMs / 1000 * FPS), 1);
|
|
32
|
+
const camera = script.camera;
|
|
33
|
+
const iw = script.imageWidth || baseImageWidth;
|
|
34
|
+
const ih = script.imageHeight || baseImageHeight;
|
|
35
|
+
const sf = {
|
|
36
|
+
type: 'img',
|
|
37
|
+
startFrame: currentFrame,
|
|
38
|
+
durationInFrames: frames,
|
|
39
|
+
img: script.img,
|
|
40
|
+
imageWidth: iw,
|
|
41
|
+
imageHeight: ih,
|
|
42
|
+
title: script.title,
|
|
43
|
+
subTitle: script.subTitle,
|
|
44
|
+
taskId: script.taskId
|
|
45
|
+
};
|
|
46
|
+
if (camera) {
|
|
47
|
+
var _camera_pointerLeft, _camera_pointerTop;
|
|
48
|
+
sf.cameraTarget = {
|
|
49
|
+
left: camera.left,
|
|
50
|
+
top: camera.top,
|
|
51
|
+
width: camera.width,
|
|
52
|
+
pointerLeft: null != (_camera_pointerLeft = camera.pointerLeft) ? _camera_pointerLeft : Math.round(iw / 2),
|
|
53
|
+
pointerTop: null != (_camera_pointerTop = camera.pointerTop) ? _camera_pointerTop : Math.round(ih / 2)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
scriptFrames.push(sf);
|
|
57
|
+
currentFrame += frames;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'insight':
|
|
61
|
+
{
|
|
62
|
+
const insightPhaseFrames = Math.max(Math.ceil(durationMs / 1000 * FPS), 1);
|
|
63
|
+
const cameraDurationMs = script.insightCameraDuration || 0;
|
|
64
|
+
const cameraPhaseFrames = Math.ceil(cameraDurationMs / 1000 * FPS);
|
|
65
|
+
const totalFrames = insightPhaseFrames + cameraPhaseFrames;
|
|
66
|
+
const iw = script.imageWidth || baseImageWidth;
|
|
67
|
+
const ih = script.imageHeight || baseImageHeight;
|
|
68
|
+
const camera = script.camera;
|
|
69
|
+
const sf = {
|
|
70
|
+
type: 'insight',
|
|
71
|
+
startFrame: currentFrame,
|
|
72
|
+
durationInFrames: totalFrames,
|
|
73
|
+
img: script.img,
|
|
74
|
+
imageWidth: iw,
|
|
75
|
+
imageHeight: ih,
|
|
76
|
+
insightPhaseFrames,
|
|
77
|
+
cameraPhaseFrames,
|
|
78
|
+
highlightElement: script.highlightElement,
|
|
79
|
+
searchArea: script.searchArea,
|
|
80
|
+
title: script.title,
|
|
81
|
+
subTitle: script.subTitle,
|
|
82
|
+
taskId: script.taskId
|
|
83
|
+
};
|
|
84
|
+
if (camera) {
|
|
85
|
+
var _camera_pointerLeft1, _camera_pointerTop1;
|
|
86
|
+
sf.cameraTarget = {
|
|
87
|
+
left: camera.left,
|
|
88
|
+
top: camera.top,
|
|
89
|
+
width: camera.width,
|
|
90
|
+
pointerLeft: null != (_camera_pointerLeft1 = camera.pointerLeft) ? _camera_pointerLeft1 : Math.round(iw / 2),
|
|
91
|
+
pointerTop: null != (_camera_pointerTop1 = camera.pointerTop) ? _camera_pointerTop1 : Math.round(ih / 2)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
scriptFrames.push(sf);
|
|
95
|
+
currentFrame += totalFrames;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'clear-insight':
|
|
99
|
+
{
|
|
100
|
+
const frames = Math.max(Math.ceil(durationMs / 1000 * FPS), 1);
|
|
101
|
+
scriptFrames.push({
|
|
102
|
+
type: 'clear-insight',
|
|
103
|
+
startFrame: currentFrame,
|
|
104
|
+
durationInFrames: frames,
|
|
105
|
+
title: script.title,
|
|
106
|
+
subTitle: script.subTitle,
|
|
107
|
+
taskId: script.taskId
|
|
108
|
+
});
|
|
109
|
+
currentFrame += frames;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case 'spinning-pointer':
|
|
113
|
+
{
|
|
114
|
+
const frames = Math.max(Math.ceil(durationMs / 1000 * FPS), 1);
|
|
115
|
+
scriptFrames.push({
|
|
116
|
+
type: 'spinning-pointer',
|
|
117
|
+
startFrame: currentFrame,
|
|
118
|
+
durationInFrames: frames,
|
|
119
|
+
title: script.title,
|
|
120
|
+
subTitle: script.subTitle,
|
|
121
|
+
taskId: script.taskId
|
|
122
|
+
});
|
|
123
|
+
currentFrame += frames;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'pointer':
|
|
127
|
+
scriptFrames.push({
|
|
128
|
+
type: 'pointer',
|
|
129
|
+
startFrame: currentFrame,
|
|
130
|
+
durationInFrames: 0,
|
|
131
|
+
pointerImg: script.img,
|
|
132
|
+
title: script.title,
|
|
133
|
+
subTitle: script.subTitle,
|
|
134
|
+
taskId: script.taskId
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const stepsDurationInFrames = Math.max(currentFrame, 1);
|
|
140
|
+
return {
|
|
141
|
+
scriptFrames,
|
|
142
|
+
totalDurationInFrames: stepsDurationInFrames,
|
|
143
|
+
fps: FPS,
|
|
144
|
+
stepsDurationInFrames,
|
|
145
|
+
imageWidth: baseImageWidth,
|
|
146
|
+
imageHeight: baseImageHeight
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export { FPS, calculateFrameMap };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { deriveFrameState } from "./derive-frame-state.mjs";
|
|
2
|
+
function getPlaybackFrameState(frameMap, frame) {
|
|
3
|
+
const state = deriveFrameState(frameMap.scriptFrames, frame, frameMap.imageWidth, frameMap.imageHeight, frameMap.fps);
|
|
4
|
+
return state.img ? state : null;
|
|
5
|
+
}
|
|
6
|
+
export { getPlaybackFrameState };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function getPlaybackViewport(containerWidth, containerHeight, imageWidth, imageHeight) {
|
|
2
|
+
const scale = Math.min(containerWidth / imageWidth, containerHeight / imageHeight);
|
|
3
|
+
const contentWidth = imageWidth * scale;
|
|
4
|
+
const contentHeight = imageHeight * scale;
|
|
5
|
+
return {
|
|
6
|
+
offsetX: (containerWidth - contentWidth) / 2,
|
|
7
|
+
offsetY: (containerHeight - contentHeight) / 2,
|
|
8
|
+
contentWidth,
|
|
9
|
+
contentHeight
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export { getPlaybackViewport };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const POINTER_REFERENCE_IMAGE_WIDTH = 1920;
|
|
2
|
+
const POINTER_WIDTH = 44;
|
|
3
|
+
const POINTER_HEIGHT = 56;
|
|
4
|
+
const POINTER_HOTSPOT_X = 6;
|
|
5
|
+
const POINTER_HOTSPOT_Y = 4;
|
|
6
|
+
function assertPositiveFinite(value, name) {
|
|
7
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive finite number`);
|
|
8
|
+
}
|
|
9
|
+
function buildPointerLayout(scale) {
|
|
10
|
+
return {
|
|
11
|
+
scale,
|
|
12
|
+
width: POINTER_WIDTH * scale,
|
|
13
|
+
height: POINTER_HEIGHT * scale,
|
|
14
|
+
hotspotX: POINTER_HOTSPOT_X * scale,
|
|
15
|
+
hotspotY: POINTER_HOTSPOT_Y * scale,
|
|
16
|
+
centerOffsetX: POINTER_WIDTH * scale / 2,
|
|
17
|
+
centerOffsetY: POINTER_HEIGHT * scale / 2
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function resolvePointerLayout(imageWidth) {
|
|
21
|
+
assertPositiveFinite(imageWidth, 'imageWidth');
|
|
22
|
+
return buildPointerLayout(Math.max(1, Math.sqrt(imageWidth / POINTER_REFERENCE_IMAGE_WIDTH)));
|
|
23
|
+
}
|
|
24
|
+
function resolveExportPointerLayout(imageWidth, contentWidth) {
|
|
25
|
+
assertPositiveFinite(contentWidth, 'contentWidth');
|
|
26
|
+
const liveLayout = resolvePointerLayout(imageWidth);
|
|
27
|
+
return buildPointerLayout(liveLayout.scale * (contentWidth / imageWidth));
|
|
28
|
+
}
|
|
29
|
+
function resolveSpinnerLayout(pointerLayout) {
|
|
30
|
+
const size = pointerLayout.height;
|
|
31
|
+
return {
|
|
32
|
+
size,
|
|
33
|
+
centerOffset: size / 2
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export { POINTER_HEIGHT, POINTER_HOTSPOT_X, POINTER_HOTSPOT_Y, POINTER_WIDTH, resolveExportPointerLayout, resolvePointerLayout, resolveSpinnerLayout };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
function useFramePlayer(options) {
|
|
3
|
+
var _options_playbackRate, _options_playbackRate1;
|
|
4
|
+
const { durationInFrames, fps, autoPlay = false, loop = false } = options;
|
|
5
|
+
const [currentFrame, setCurrentFrame] = useState(0);
|
|
6
|
+
const [playing, setPlaying] = useState(autoPlay);
|
|
7
|
+
const playingRef = useRef(playing);
|
|
8
|
+
const frameRef = useRef(currentFrame);
|
|
9
|
+
const rateRef = useRef(null != (_options_playbackRate = options.playbackRate) ? _options_playbackRate : 1);
|
|
10
|
+
const durationRef = useRef(durationInFrames);
|
|
11
|
+
const fpsRef = useRef(fps);
|
|
12
|
+
const loopRef = useRef(loop);
|
|
13
|
+
playingRef.current = playing;
|
|
14
|
+
frameRef.current = currentFrame;
|
|
15
|
+
rateRef.current = null != (_options_playbackRate1 = options.playbackRate) ? _options_playbackRate1 : 1;
|
|
16
|
+
durationRef.current = durationInFrames;
|
|
17
|
+
fpsRef.current = fps;
|
|
18
|
+
loopRef.current = loop;
|
|
19
|
+
useEffect(()=>{
|
|
20
|
+
if (!playing) return;
|
|
21
|
+
let rafId;
|
|
22
|
+
let lastTime = null;
|
|
23
|
+
let accumulated = 0;
|
|
24
|
+
const tick = (now)=>{
|
|
25
|
+
if (null !== lastTime) {
|
|
26
|
+
const delta = (now - lastTime) * rateRef.current;
|
|
27
|
+
accumulated += delta;
|
|
28
|
+
const frameDuration = 1000 / fpsRef.current;
|
|
29
|
+
while(accumulated >= frameDuration){
|
|
30
|
+
accumulated -= frameDuration;
|
|
31
|
+
const next = frameRef.current + 1;
|
|
32
|
+
if (next >= durationRef.current) if (loopRef.current) {
|
|
33
|
+
frameRef.current = 0;
|
|
34
|
+
setCurrentFrame(0);
|
|
35
|
+
} else {
|
|
36
|
+
frameRef.current = durationRef.current - 1;
|
|
37
|
+
setCurrentFrame(durationRef.current - 1);
|
|
38
|
+
setPlaying(false);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
frameRef.current = next;
|
|
43
|
+
setCurrentFrame(next);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
lastTime = now;
|
|
48
|
+
rafId = requestAnimationFrame(tick);
|
|
49
|
+
};
|
|
50
|
+
rafId = requestAnimationFrame(tick);
|
|
51
|
+
return ()=>cancelAnimationFrame(rafId);
|
|
52
|
+
}, [
|
|
53
|
+
playing
|
|
54
|
+
]);
|
|
55
|
+
const resetIfAtEnd = ()=>{
|
|
56
|
+
if (frameRef.current >= durationRef.current - 1) {
|
|
57
|
+
frameRef.current = 0;
|
|
58
|
+
setCurrentFrame(0);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const play = useCallback(()=>{
|
|
62
|
+
resetIfAtEnd();
|
|
63
|
+
setPlaying(true);
|
|
64
|
+
}, []);
|
|
65
|
+
const pause = useCallback(()=>setPlaying(false), []);
|
|
66
|
+
const toggle = useCallback(()=>{
|
|
67
|
+
if (playingRef.current) setPlaying(false);
|
|
68
|
+
else {
|
|
69
|
+
resetIfAtEnd();
|
|
70
|
+
setPlaying(true);
|
|
71
|
+
}
|
|
72
|
+
}, []);
|
|
73
|
+
const seekTo = useCallback((frame)=>{
|
|
74
|
+
const clamped = Math.max(0, Math.min(frame, durationRef.current - 1));
|
|
75
|
+
frameRef.current = clamped;
|
|
76
|
+
setCurrentFrame(clamped);
|
|
77
|
+
}, []);
|
|
78
|
+
return {
|
|
79
|
+
currentFrame,
|
|
80
|
+
playing,
|
|
81
|
+
play,
|
|
82
|
+
pause,
|
|
83
|
+
toggle,
|
|
84
|
+
seekTo
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export { useFramePlayer };
|