@midscene/visualizer 1.5.2 → 1.5.3-beta-20260305031416.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 (43) hide show
  1. package/dist/es/component/blackboard/index.css +82 -4
  2. package/dist/es/component/blackboard/index.mjs +73 -301
  3. package/dist/es/component/player/index.css +144 -119
  4. package/dist/es/component/player/index.mjs +468 -830
  5. package/dist/es/component/player/remotion/StepScene.mjs +190 -0
  6. package/dist/es/component/player/remotion/derive-frame-state.mjs +207 -0
  7. package/dist/es/component/player/remotion/export-branded-video.mjs +210 -0
  8. package/dist/es/component/player/remotion/frame-calculator.mjs +149 -0
  9. package/dist/es/component/player/use-frame-player.mjs +88 -0
  10. package/dist/es/component/universal-playground/index.mjs +14 -1
  11. package/dist/es/hooks/usePlaygroundExecution.mjs +11 -7
  12. package/dist/es/index.mjs +2 -2
  13. package/dist/es/store/store.mjs +9 -0
  14. package/dist/es/utils/replay-scripts.mjs +78 -59
  15. package/dist/lib/component/blackboard/index.css +82 -4
  16. package/dist/lib/component/blackboard/index.js +73 -307
  17. package/dist/lib/component/player/index.css +144 -119
  18. package/dist/lib/component/player/index.js +466 -828
  19. package/dist/lib/component/player/remotion/StepScene.js +224 -0
  20. package/dist/lib/component/player/remotion/derive-frame-state.js +241 -0
  21. package/dist/lib/component/player/remotion/export-branded-video.js +244 -0
  22. package/dist/lib/component/player/remotion/frame-calculator.js +186 -0
  23. package/dist/lib/component/player/use-frame-player.js +122 -0
  24. package/dist/lib/component/universal-playground/index.js +14 -1
  25. package/dist/lib/hooks/usePlaygroundExecution.js +11 -7
  26. package/dist/lib/index.js +3 -0
  27. package/dist/lib/store/store.js +9 -0
  28. package/dist/lib/utils/replay-scripts.js +80 -58
  29. package/dist/types/component/blackboard/index.d.ts +0 -4
  30. package/dist/types/component/player/index.d.ts +0 -1
  31. package/dist/types/component/player/remotion/StepScene.d.ts +9 -0
  32. package/dist/types/component/player/remotion/derive-frame-state.d.ts +38 -0
  33. package/dist/types/component/player/remotion/export-branded-video.d.ts +2 -0
  34. package/dist/types/component/player/remotion/frame-calculator.d.ts +35 -0
  35. package/dist/types/component/player/use-frame-player.d.ts +17 -0
  36. package/dist/types/hooks/usePlaygroundExecution.d.ts +15 -1
  37. package/dist/types/index.d.ts +1 -1
  38. package/dist/types/store/store.d.ts +2 -0
  39. package/dist/types/utils/replay-scripts.d.ts +16 -1
  40. package/package.json +5 -8
  41. package/dist/es/utils/pixi-loader.mjs +0 -42
  42. package/dist/lib/utils/pixi-loader.js +0 -82
  43. package/dist/types/utils/pixi-loader.d.ts +0 -5
@@ -1,17 +1,17 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import "pixi.js/unsafe-eval";
4
- import { Application, Container, Sprite } from "pixi.js";
5
- import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
4
  import "./index.css";
7
- import { mouseLoading, mousePointer } from "../../utils/index.mjs";
8
- import { CaretRightOutlined, DownloadOutlined, ExportOutlined, LoadingOutlined, ThunderboltOutlined } from "@ant-design/icons";
5
+ import { CaretRightOutlined, CompressOutlined, DownloadOutlined, ExpandOutlined, ExportOutlined, FontSizeOutlined, PauseOutlined, ThunderboltOutlined } from "@ant-design/icons";
9
6
  import { Dropdown, Spin, Switch, Tooltip, message } from "antd";
10
7
  import global_perspective from "../../icons/global-perspective.mjs";
11
8
  import player_setting from "../../icons/player-setting.mjs";
12
9
  import { useGlobalPreference } from "../../store/store.mjs";
13
- import { getTextureFromCache, loadTexture } from "../../utils/pixi-loader.mjs";
14
- import { rectMarkForItem } from "../blackboard/index.mjs";
10
+ import { StepsTimeline } from "./remotion/StepScene.mjs";
11
+ import { deriveFrameState } from "./remotion/derive-frame-state.mjs";
12
+ import { exportBrandedVideo } from "./remotion/export-branded-video.mjs";
13
+ import { calculateFrameMap } from "./remotion/frame-calculator.mjs";
14
+ import { useFramePlayer } from "./use-frame-player.mjs";
15
15
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
16
16
  try {
17
17
  var info = gen[key](arg);
@@ -38,101 +38,6 @@ function _async_to_generator(fn) {
38
38
  });
39
39
  };
40
40
  }
41
- function _define_property(obj, key, value) {
42
- if (key in obj) Object.defineProperty(obj, key, {
43
- value: value,
44
- enumerable: true,
45
- configurable: true,
46
- writable: true
47
- });
48
- else obj[key] = value;
49
- return obj;
50
- }
51
- function _object_spread(target) {
52
- for(var i = 1; i < arguments.length; i++){
53
- var source = null != arguments[i] ? arguments[i] : {};
54
- var ownKeys = Object.keys(source);
55
- if ("function" == typeof Object.getOwnPropertySymbols) ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
56
- return Object.getOwnPropertyDescriptor(source, sym).enumerable;
57
- }));
58
- ownKeys.forEach(function(key) {
59
- _define_property(target, key, source[key]);
60
- });
61
- }
62
- return target;
63
- }
64
- function player_ownKeys(object, enumerableOnly) {
65
- var keys = Object.keys(object);
66
- if (Object.getOwnPropertySymbols) {
67
- var symbols = Object.getOwnPropertySymbols(object);
68
- if (enumerableOnly) symbols = symbols.filter(function(sym) {
69
- return Object.getOwnPropertyDescriptor(object, sym).enumerable;
70
- });
71
- keys.push.apply(keys, symbols);
72
- }
73
- return keys;
74
- }
75
- function _object_spread_props(target, source) {
76
- source = null != source ? source : {};
77
- if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
78
- else player_ownKeys(Object(source)).forEach(function(key) {
79
- Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
80
- });
81
- return target;
82
- }
83
- const canvasPaddingLeft = 0;
84
- const canvasPaddingTop = 0;
85
- const cubicBezier = (t, p0, p1, p2, p3)=>{
86
- const t2 = 1 - t;
87
- return p0 * t2 * t2 * t2 + 3 * p1 * t * t2 * t2 + 3 * p2 * t * t * t2 + p3 * t * t * t;
88
- };
89
- const cubicImage = (t)=>linear(t);
90
- const cubicInsightElement = (t)=>cubicBezier(t, 0, 0.5, 0.5, 1);
91
- const cubicMouse = (t)=>linear(t);
92
- const linear = (t)=>t;
93
- const ERROR_FRAME_CANCEL = 'frame cancel (this is an error on purpose)';
94
- const frameKit = ()=>{
95
- let cancelFlag = false;
96
- const pendingTimeouts = [];
97
- return {
98
- frame: (callback)=>{
99
- if (cancelFlag) throw new Error(ERROR_FRAME_CANCEL);
100
- requestAnimationFrame(()=>{
101
- if (cancelFlag) return;
102
- callback(performance.now());
103
- });
104
- },
105
- timeout: (callback, ms)=>{
106
- if (cancelFlag) throw new Error(ERROR_FRAME_CANCEL);
107
- const timeoutId = window.setTimeout(()=>{
108
- if (cancelFlag) return;
109
- callback();
110
- }, ms);
111
- pendingTimeouts.push(timeoutId);
112
- },
113
- sleep: (ms)=>{
114
- if (cancelFlag) return Promise.reject(new Error(ERROR_FRAME_CANCEL));
115
- return new Promise((resolve, reject)=>{
116
- const timeoutId = window.setTimeout(()=>{
117
- if (cancelFlag) reject(new Error(ERROR_FRAME_CANCEL));
118
- else resolve();
119
- }, ms);
120
- pendingTimeouts.push(timeoutId);
121
- });
122
- },
123
- isCancelled: ()=>cancelFlag,
124
- cancel: ()=>{
125
- cancelFlag = true;
126
- for (const id of pendingTimeouts)clearTimeout(id);
127
- pendingTimeouts.length = 0;
128
- }
129
- };
130
- };
131
- const singleElementFadeInDuration = 80;
132
- const LAYER_ORDER_IMG = 0;
133
- const LAYER_ORDER_INSIGHT = 1;
134
- const LAYER_ORDER_POINTER = 2;
135
- const LAYER_ORDER_SPINNING_POINTER = 3;
136
41
  const downloadReport = (content)=>{
137
42
  const blob = new Blob([
138
43
  content
@@ -144,67 +49,32 @@ const downloadReport = (content)=>{
144
49
  a.href = url;
145
50
  a.download = 'midscene_report.html';
146
51
  a.click();
52
+ setTimeout(()=>URL.revokeObjectURL(url), 0);
147
53
  };
148
- class RecordingSession {
149
- start() {
150
- const stream = this.canvas.captureStream(60);
151
- const mediaRecorder = new MediaRecorder(stream, {
152
- mimeType: 'video/webm'
153
- });
154
- mediaRecorder.ondataavailable = (event)=>{
155
- if (event.data.size > 0) this.chunks.push(event.data);
156
- };
157
- mediaRecorder.onerror = (event)=>{
158
- console.error('MediaRecorder error:', event);
159
- message.error('Video recording failed. Please try again.');
160
- this.recording = false;
161
- this.mediaRecorder = null;
162
- };
163
- this.mediaRecorder = mediaRecorder;
164
- this.recording = true;
165
- return this.mediaRecorder.start();
166
- }
167
- stop() {
168
- if (!this.recording || !this.mediaRecorder) return void console.warn('not recording');
169
- this.mediaRecorder.onstop = ()=>{
170
- if (0 === this.chunks.length) {
171
- console.error('No video data captured');
172
- message.error('Video export failed: No data captured.');
173
- return;
174
- }
175
- const blob = new Blob(this.chunks, {
176
- type: 'video/webm'
177
- });
178
- if (0 === blob.size) {
179
- console.error('Video blob is empty');
180
- message.error('Video export failed: Empty file.');
181
- return;
54
+ function deriveTaskId(scriptFrames, stepsFrame) {
55
+ let taskId = null;
56
+ for (const sf of scriptFrames){
57
+ if (0 === sf.durationInFrames) {
58
+ if (sf.startFrame <= stepsFrame) {
59
+ var _sf_taskId;
60
+ taskId = null != (_sf_taskId = sf.taskId) ? _sf_taskId : taskId;
182
61
  }
183
- const url = URL.createObjectURL(blob);
184
- const a = document.createElement('a');
185
- a.href = url;
186
- a.download = 'midscene_replay.webm';
187
- a.click();
188
- URL.revokeObjectURL(url);
189
- };
190
- this.mediaRecorder.stop();
191
- this.recording = false;
192
- this.mediaRecorder = null;
193
- }
194
- constructor(canvas){
195
- _define_property(this, "canvas", void 0);
196
- _define_property(this, "mediaRecorder", null);
197
- _define_property(this, "chunks", void 0);
198
- _define_property(this, "recording", false);
199
- this.canvas = canvas;
200
- this.chunks = [];
62
+ continue;
63
+ }
64
+ if (stepsFrame < sf.startFrame) break;
65
+ var _sf_taskId1;
66
+ taskId = null != (_sf_taskId1 = sf.taskId) ? _sf_taskId1 : taskId;
201
67
  }
68
+ return taskId;
69
+ }
70
+ function formatTime(frame, fps) {
71
+ const totalSeconds = Math.floor(frame / fps);
72
+ const m = Math.floor(totalSeconds / 60);
73
+ const s = totalSeconds % 60;
74
+ return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
202
75
  }
203
76
  function Player(props) {
204
- var _scripts_;
205
- const [titleText, setTitleText] = useState('');
206
- const [subTitleText, setSubTitleText] = useState('');
207
- const { autoZoom, setAutoZoom, playbackSpeed, setPlaybackSpeed } = useGlobalPreference();
77
+ const { autoZoom, setAutoZoom, playbackSpeed, setPlaybackSpeed, subtitleEnabled, setSubtitleEnabled } = useGlobalPreference();
208
78
  useEffect(()=>{
209
79
  if ((null == props ? void 0 : props.autoZoom) !== void 0) setAutoZoom(props.autoZoom);
210
80
  }, [
@@ -212,628 +82,330 @@ function Player(props) {
212
82
  setAutoZoom
213
83
  ]);
214
84
  const scripts = null == props ? void 0 : props.replayScripts;
215
- const imageWidth = (null == props ? void 0 : props.imageWidth) || 1920;
216
- const imageHeight = (null == props ? void 0 : props.imageHeight) || 1080;
217
- const fitMode = (null == props ? void 0 : props.fitMode) || 'height';
218
- const currentImg = useRef((null == scripts ? void 0 : null == (_scripts_ = scripts[0]) ? void 0 : _scripts_.img) || null);
219
- const divContainerRef = useRef(null);
220
- const app = useMemo(()=>new Application(), []);
221
- const pointerSprite = useRef(null);
222
- const spinningPointerSprite = useRef(null);
223
- const [replayMark, setReplayMark] = useState(0);
224
- const triggerReplay = ()=>{
225
- setReplayMark(Date.now());
226
- };
227
- const windowContentContainer = useMemo(()=>{
228
- const container = new Container();
229
- return container;
230
- }, []);
231
- const insightMarkContainer = useMemo(()=>{
232
- const container = new Container();
233
- container.zIndex = LAYER_ORDER_INSIGHT;
234
- return container;
235
- }, []);
236
- const basicCameraState = {
237
- left: 0,
238
- top: 0,
239
- width: imageWidth,
240
- pointerLeft: Math.round(imageWidth / 2),
241
- pointerTop: Math.round(imageHeight / 2)
242
- };
243
- const [animationProgress, setAnimationProgress] = useState(-1);
244
- const cancelFlag = useRef(false);
245
- const appInitialized = useRef(false);
85
+ const frameMap = useMemo(()=>{
86
+ if (!scripts || 0 === scripts.length) return null;
87
+ return calculateFrameMap(scripts);
88
+ }, [
89
+ scripts
90
+ ]);
91
+ const wrapperRef = useRef(null);
92
+ const renderLayerRef = useRef(null);
93
+ const lastTaskIdRef = useRef(null);
94
+ const [containerSize, setContainerSize] = useState({
95
+ width: 0,
96
+ height: 0
97
+ });
246
98
  useEffect(()=>{
247
- cancelFlag.current = false;
248
- return ()=>{
249
- cancelFlag.current = true;
250
- };
251
- }, []);
252
- const cameraState = useRef(_object_spread({}, basicCameraState));
253
- const resizeCanvasIfNeeded = (newWidth, newHeight)=>_async_to_generator(function*() {
254
- if (!appInitialized.current || !app.screen) return;
255
- if (app.screen.width !== newWidth || app.screen.height !== newHeight) {
256
- app.renderer.resize(newWidth, newHeight);
257
- if (divContainerRef.current) {
258
- const aspectRatio = newWidth / newHeight;
259
- divContainerRef.current.style.setProperty('--canvas-aspect-ratio', aspectRatio.toString());
260
- }
261
- const newBasicCameraState = {
262
- left: 0,
263
- top: 0,
264
- width: newWidth,
265
- pointerLeft: Math.round(newWidth / 2),
266
- pointerTop: Math.round(newHeight / 2)
267
- };
268
- cameraState.current = newBasicCameraState;
269
- }
270
- })();
271
- const repaintImage = (scriptWidth, scriptHeight)=>_async_to_generator(function*() {
272
- const imgToUpdate = currentImg.current;
273
- if (!imgToUpdate) return void console.warn('no image to update');
274
- const targetWidth = scriptWidth || imageWidth;
275
- const targetHeight = scriptHeight || imageHeight;
276
- yield resizeCanvasIfNeeded(targetWidth, targetHeight);
277
- if (!getTextureFromCache(imgToUpdate)) {
278
- console.warn('image not loaded', imgToUpdate);
279
- yield loadTexture(imgToUpdate);
99
+ const el = renderLayerRef.current;
100
+ if (!el) return;
101
+ const ro = new ResizeObserver((entries)=>{
102
+ for (const entry of entries){
103
+ const { width, height } = entry.contentRect;
104
+ setContainerSize((prev)=>prev.width === width && prev.height === height ? prev : {
105
+ width,
106
+ height
107
+ });
280
108
  }
281
- const texture = getTextureFromCache(imgToUpdate);
282
- if (!texture) throw new Error('texture not found');
283
- const sprite = Sprite.from(texture);
284
- if (!sprite) throw new Error('sprite not found');
285
- const mainImgLabel = 'main-img';
286
- const child = windowContentContainer.getChildByLabel(mainImgLabel);
287
- if (child) windowContentContainer.removeChild(child);
288
- sprite.label = mainImgLabel;
289
- sprite.zIndex = LAYER_ORDER_IMG;
290
- sprite.width = targetWidth;
291
- sprite.height = targetHeight;
292
- windowContentContainer.addChild(sprite);
293
- })();
294
- const spinningPointer = (frame)=>{
295
- var _pointerSprite_current, _pointerSprite_current1;
296
- if (!spinningPointerSprite.current) {
297
- spinningPointerSprite.current = Sprite.from(mouseLoading);
298
- spinningPointerSprite.current.zIndex = LAYER_ORDER_SPINNING_POINTER;
299
- spinningPointerSprite.current.anchor.set(0.5, 0.5);
300
- spinningPointerSprite.current.scale.set(0.5);
301
- spinningPointerSprite.current.label = 'spinning-pointer';
109
+ });
110
+ ro.observe(el);
111
+ return ()=>ro.disconnect();
112
+ }, []);
113
+ var _frameMap_totalDurationInFrames, _frameMap_fps;
114
+ const player = useFramePlayer({
115
+ durationInFrames: Math.max(null != (_frameMap_totalDurationInFrames = null == frameMap ? void 0 : frameMap.totalDurationInFrames) ? _frameMap_totalDurationInFrames : 1, 1),
116
+ fps: null != (_frameMap_fps = null == frameMap ? void 0 : frameMap.fps) ? _frameMap_fps : 30,
117
+ autoPlay: true,
118
+ loop: false,
119
+ playbackRate: playbackSpeed
120
+ });
121
+ useEffect(()=>{
122
+ if (!frameMap || !(null == props ? void 0 : props.onTaskChange)) return;
123
+ const taskId = deriveTaskId(frameMap.scriptFrames, player.currentFrame);
124
+ if (taskId !== lastTaskIdRef.current) {
125
+ lastTaskIdRef.current = taskId;
126
+ props.onTaskChange(taskId);
302
127
  }
303
- spinningPointerSprite.current.x = (null == (_pointerSprite_current = pointerSprite.current) ? void 0 : _pointerSprite_current.x) || 0;
304
- spinningPointerSprite.current.y = (null == (_pointerSprite_current1 = pointerSprite.current) ? void 0 : _pointerSprite_current1.y) || 0;
305
- windowContentContainer.addChild(spinningPointerSprite.current);
306
- let startTime;
307
- let isCancelled = false;
308
- const animate = (currentTime)=>{
309
- if (isCancelled) return;
310
- if (!startTime) startTime = currentTime;
311
- const elapsedTime = currentTime - startTime;
312
- const progress = (Math.sin(elapsedTime / 500 - Math.PI / 2) + 1) / 2;
313
- const rotation = progress * Math.PI * 2;
314
- if (spinningPointerSprite.current) spinningPointerSprite.current.rotation = rotation;
315
- frame(animate);
316
- };
317
- frame(animate);
318
- const stopFn = ()=>{
319
- if (spinningPointerSprite.current) windowContentContainer.removeChild(spinningPointerSprite.current);
320
- isCancelled = true;
128
+ }, [
129
+ frameMap,
130
+ null == props ? void 0 : props.onTaskChange,
131
+ player.currentFrame
132
+ ]);
133
+ const subtitle = useMemo(()=>{
134
+ if (!frameMap) return null;
135
+ const state = deriveFrameState(frameMap.scriptFrames, player.currentFrame, frameMap.imageWidth, frameMap.imageHeight, frameMap.fps);
136
+ if (!state.title && !state.subTitle) return null;
137
+ return {
138
+ title: state.title,
139
+ subTitle: state.subTitle
321
140
  };
322
- return stopFn;
323
- };
324
- const updatePointer = (img, x, y)=>_async_to_generator(function*() {
325
- var _pointerSprite_current, _pointerSprite_current1;
326
- if (!getTextureFromCache(img)) {
327
- console.warn('image not loaded', img);
328
- yield loadTexture(img);
329
- }
330
- const texture = getTextureFromCache(img);
331
- if (!texture) throw new Error('texture not found');
332
- const sprite = Sprite.from(texture);
333
- let targetX = null == (_pointerSprite_current = pointerSprite.current) ? void 0 : _pointerSprite_current.x;
334
- let targetY = null == (_pointerSprite_current1 = pointerSprite.current) ? void 0 : _pointerSprite_current1.y;
335
- if ('number' == typeof x) targetX = x;
336
- if ('number' == typeof y) targetY = y;
337
- if (void 0 === targetX || void 0 === targetY) return void console.warn('invalid pointer position', x, y);
338
- if (pointerSprite.current) {
339
- const pointer = windowContentContainer.getChildByLabel('pointer');
340
- if (pointer) windowContentContainer.removeChild(pointer);
341
- }
342
- pointerSprite.current = sprite;
343
- pointerSprite.current.x = targetX;
344
- pointerSprite.current.y = targetY;
345
- pointerSprite.current.label = 'pointer';
346
- pointerSprite.current.zIndex = LAYER_ORDER_POINTER;
347
- windowContentContainer.addChild(pointerSprite.current);
348
- })();
349
- const updateCamera = (state, currentWidth)=>{
350
- var _app_screen;
351
- cameraState.current = state;
352
- const effectiveWidth = currentWidth || (null == (_app_screen = app.screen) ? void 0 : _app_screen.width) || imageWidth;
353
- const newScale = autoZoom ? Math.max(1, effectiveWidth / state.width) : 1;
354
- windowContentContainer.scale.set(newScale);
355
- windowContentContainer.x = autoZoom ? Math.round(canvasPaddingLeft - state.left * newScale) : canvasPaddingLeft;
356
- windowContentContainer.y = autoZoom ? Math.round(canvasPaddingTop - state.top * newScale) : canvasPaddingTop;
357
- const pointer = windowContentContainer.getChildByLabel('pointer');
358
- if (pointer) {
359
- pointer.scale.set(1 / newScale);
360
- if ('number' == typeof state.pointerLeft && 'number' == typeof state.pointerTop) {
361
- pointer.x = state.pointerLeft;
362
- pointer.y = state.pointerTop;
363
- }
364
- }
365
- };
366
- const cameraAnimation = (targetState, duration, frame)=>_async_to_generator(function*() {
367
- var _app_screen, _app_screen1;
368
- const currentCanvasWidth = (null == (_app_screen = app.screen) ? void 0 : _app_screen.width) || imageWidth;
369
- const currentCanvasHeight = (null == (_app_screen1 = app.screen) ? void 0 : _app_screen1.height) || imageHeight;
370
- if (!autoZoom) {
371
- const currentState = _object_spread({}, cameraState.current);
372
- const startPointerLeft = currentState.pointerLeft;
373
- const startPointerTop = currentState.pointerTop;
374
- const startTime = performance.now();
375
- const shouldMovePointer = 'number' == typeof targetState.pointerLeft && 'number' == typeof targetState.pointerTop && (targetState.pointerLeft !== startPointerLeft || targetState.pointerTop !== startPointerTop);
376
- if (!shouldMovePointer) return;
377
- yield new Promise((resolve)=>{
378
- const animate = (currentTime)=>{
379
- const elapsedTime = currentTime - startTime;
380
- const rawProgress = Math.min(elapsedTime / duration, 1);
381
- const progress = cubicMouse(rawProgress);
382
- const nextState = _object_spread_props(_object_spread({}, currentState), {
383
- pointerLeft: startPointerLeft + (targetState.pointerLeft - startPointerLeft) * progress,
384
- pointerTop: startPointerTop + (targetState.pointerTop - startPointerTop) * progress
385
- });
386
- updateCamera(nextState, currentCanvasWidth);
387
- if (elapsedTime < duration) frame(animate);
388
- else resolve();
389
- };
390
- frame(animate);
391
- });
392
- return;
393
- }
394
- const currentState = _object_spread({}, cameraState.current);
395
- const startLeft = currentState.left;
396
- const startTop = currentState.top;
397
- const startPointerLeft = currentState.pointerLeft;
398
- const startPointerTop = currentState.pointerTop;
399
- const startScale = currentState.width / currentCanvasWidth;
400
- const startTime = performance.now();
401
- const shouldMovePointer = 'number' == typeof targetState.pointerLeft && 'number' == typeof targetState.pointerTop && (targetState.pointerLeft !== startPointerLeft || targetState.pointerTop !== startPointerTop);
402
- const pointerMoveDuration = shouldMovePointer ? 0.375 * duration : 0;
403
- const cameraMoveStart = pointerMoveDuration;
404
- const cameraMoveDuration = duration - pointerMoveDuration;
405
- yield new Promise((resolve)=>{
406
- const animate = (currentTime)=>{
407
- const nextState = _object_spread({}, cameraState.current);
408
- const elapsedTime = currentTime - startTime;
409
- if (shouldMovePointer) if (elapsedTime <= pointerMoveDuration) {
410
- const rawMouseProgress = Math.min(elapsedTime / pointerMoveDuration, 1);
411
- const mouseProgress = cubicMouse(rawMouseProgress);
412
- nextState.pointerLeft = startPointerLeft + (targetState.pointerLeft - startPointerLeft) * mouseProgress;
413
- nextState.pointerTop = startPointerTop + (targetState.pointerTop - startPointerTop) * mouseProgress;
414
- } else {
415
- nextState.pointerLeft = targetState.pointerLeft;
416
- nextState.pointerTop = targetState.pointerTop;
417
- }
418
- if (elapsedTime > cameraMoveStart) {
419
- const cameraElapsedTime = elapsedTime - cameraMoveStart;
420
- const rawCameraProgress = Math.min(cameraElapsedTime / cameraMoveDuration, 1);
421
- const cameraProgress = cubicImage(rawCameraProgress);
422
- const targetScale = targetState.width / currentCanvasWidth;
423
- const progressScale = startScale + (targetScale - startScale) * cameraProgress;
424
- const progressWidth = currentCanvasWidth * progressScale;
425
- const progressHeight = currentCanvasHeight * progressScale;
426
- nextState.width = progressWidth;
427
- const progressLeft = startLeft + (targetState.left - startLeft) * cameraProgress;
428
- const progressTop = startTop + (targetState.top - startTop) * cameraProgress;
429
- const horizontalExceed = progressLeft + progressWidth - currentCanvasWidth;
430
- const verticalExceed = progressTop + progressHeight - currentCanvasHeight;
431
- nextState.left = horizontalExceed > 0 ? progressLeft + horizontalExceed : progressLeft;
432
- nextState.top = verticalExceed > 0 ? progressTop + verticalExceed : progressTop;
433
- }
434
- updateCamera(nextState, currentCanvasWidth);
435
- if (elapsedTime < duration) frame(animate);
436
- else resolve();
437
- };
438
- frame(animate);
439
- });
440
- })();
441
- const fadeInGraphics = (graphics, duration, frame, targetAlpha = 1)=>new Promise((resolve)=>{
442
- const startTime = performance.now();
443
- const animate = (currentTime)=>{
444
- const elapsedTime = currentTime - startTime;
445
- const progress = Math.min(elapsedTime / duration, 1);
446
- graphics.alpha = 0 === targetAlpha ? 1 - linear(progress) : linear(progress);
447
- if (elapsedTime < duration) frame(animate);
448
- else resolve();
449
- };
450
- frame(animate);
451
- });
452
- const fadeOutItem = (graphics, duration, frame)=>_async_to_generator(function*() {
453
- return fadeInGraphics(graphics, duration, frame, 0);
454
- })();
455
- const insightElementsAnimation = (elements, highlightElements, searchArea, duration, frame)=>_async_to_generator(function*() {
456
- insightMarkContainer.removeChildren();
457
- const elementsToAdd = [
458
- ...elements
459
- ];
460
- const totalLength = elementsToAdd.length;
461
- let childrenCount = 0;
462
- yield new Promise((resolve)=>{
463
- const startTime = performance.now();
464
- const animate = (currentTime)=>{
465
- const elapsedTime = currentTime - startTime;
466
- const progress = cubicInsightElement(Math.min(elapsedTime / duration, 1));
467
- const elementsToAddNow = Math.floor(progress * totalLength);
468
- while(childrenCount < elementsToAddNow){
469
- const randomIndex = Math.floor(Math.random() * elementsToAdd.length);
470
- const element = elementsToAdd.splice(randomIndex, 1)[0];
471
- if (element) {
472
- const [insightMarkGraphic] = rectMarkForItem(element.rect, element.content, 'element');
473
- insightMarkGraphic.alpha = 0;
474
- insightMarkContainer.addChild(insightMarkGraphic);
475
- childrenCount++;
476
- fadeInGraphics(insightMarkGraphic, singleElementFadeInDuration, frame);
477
- }
478
- }
479
- if (elapsedTime < duration) frame(animate);
480
- else {
481
- while(elementsToAdd.length > 0){
482
- const randomIndex = Math.floor(Math.random() * elementsToAdd.length);
483
- const element = elementsToAdd.splice(randomIndex, 1)[0];
484
- const [insightMarkGraphic] = rectMarkForItem(element.rect, element.content, 'element');
485
- insightMarkGraphic.alpha = 1;
486
- insightMarkContainer.addChild(insightMarkGraphic);
487
- }
488
- if (searchArea) {
489
- const [searchAreaGraphic] = rectMarkForItem(searchArea, 'Search Area', 'searchArea');
490
- searchAreaGraphic.alpha = 1;
491
- insightMarkContainer.addChild(searchAreaGraphic);
492
- }
493
- highlightElements.map((element)=>{
494
- const [insightMarkGraphic] = rectMarkForItem(element.rect, element.content || '', 'highlight');
495
- insightMarkGraphic.alpha = 1;
496
- insightMarkContainer.addChild(insightMarkGraphic);
497
- });
498
- resolve();
499
- }
500
- };
501
- frame(animate);
502
- });
503
- })();
504
- const init = ()=>_async_to_generator(function*() {
505
- if (!divContainerRef.current || !scripts) return;
506
- yield app.init({
507
- width: imageWidth,
508
- height: imageHeight,
509
- background: 0xf4f4f4,
510
- autoDensity: true,
511
- antialias: true
512
- });
513
- appInitialized.current = true;
514
- if (!divContainerRef.current) return;
515
- divContainerRef.current.appendChild(app.canvas);
516
- windowContentContainer.x = 0;
517
- windowContentContainer.y = 0;
518
- app.stage.addChild(windowContentContainer);
519
- insightMarkContainer.x = 0;
520
- insightMarkContainer.y = 0;
521
- windowContentContainer.addChild(insightMarkContainer);
522
- })();
523
- const [isRecording, setIsRecording] = useState(false);
524
- const recorderSessionRef = useRef(null);
525
- const cancelAnimationRef = useRef(null);
526
- const playbackSessionIdRef = useRef(0);
527
- const handleExport = ()=>_async_to_generator(function*() {
528
- if (recorderSessionRef.current) return void console.warn('recorderSession exists');
529
- if (!app.canvas) return void console.warn('canvas is not initialized');
530
- if (cancelAnimationRef.current) {
531
- cancelAnimationRef.current();
532
- cancelAnimationRef.current = null;
533
- yield new Promise((resolve)=>setTimeout(resolve, 100));
534
- }
535
- recorderSessionRef.current = new RecordingSession(app.canvas);
536
- setIsRecording(true);
537
- triggerReplay();
538
- })();
539
- const play = ()=>{
540
- let cancelFn;
541
- const currentSessionId = ++playbackSessionIdRef.current;
542
- const safeOnTaskChange = (taskId)=>{
543
- if (playbackSessionIdRef.current === currentSessionId) {
544
- var _props_onTaskChange;
545
- null == props || null == (_props_onTaskChange = props.onTaskChange) || _props_onTaskChange.call(props, taskId);
546
- }
141
+ }, [
142
+ frameMap,
143
+ player.currentFrame
144
+ ]);
145
+ const [controlsVisible, setControlsVisible] = useState(true);
146
+ const hideTimerRef = useRef(null);
147
+ const showControls = useCallback(()=>{
148
+ setControlsVisible(true);
149
+ if (hideTimerRef.current) clearTimeout(hideTimerRef.current);
150
+ hideTimerRef.current = setTimeout(()=>setControlsVisible(false), 3000);
151
+ }, []);
152
+ const onMouseEnter = useCallback(()=>{
153
+ setControlsVisible(true);
154
+ if (hideTimerRef.current) clearTimeout(hideTimerRef.current);
155
+ }, []);
156
+ const onMouseLeave = useCallback(()=>{
157
+ if (hideTimerRef.current) clearTimeout(hideTimerRef.current);
158
+ hideTimerRef.current = setTimeout(()=>setControlsVisible(false), 1000);
159
+ }, []);
160
+ const seekBarRef = useRef(null);
161
+ const handleSeekPointerDown = useCallback((e)=>{
162
+ if (!frameMap || !seekBarRef.current) return;
163
+ const bar = seekBarRef.current;
164
+ bar.setPointerCapture(e.pointerId);
165
+ const seek = (clientX)=>{
166
+ const rect = bar.getBoundingClientRect();
167
+ const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
168
+ player.seekTo(Math.round(ratio * (frameMap.totalDurationInFrames - 1)));
547
169
  };
548
- Promise.resolve((()=>_async_to_generator(function*() {
549
- var _scripts_;
550
- if (!app || !appInitialized.current) throw new Error('app is not initialized');
551
- if (!scripts) throw new Error("scripts is required");
552
- const { frame, cancel, timeout, sleep: baseSleep } = frameKit();
553
- const scaleByPlaybackSpeed = (duration)=>duration / playbackSpeed;
554
- const sleep = (ms)=>baseSleep(scaleByPlaybackSpeed(ms));
555
- cancelFn = cancel;
556
- cancelAnimationRef.current = cancel;
557
- const allImages = scripts.filter((item)=>!!item.img).map((item)=>item.img);
558
- yield Promise.all([
559
- ...allImages,
560
- mouseLoading,
561
- mousePointer
562
- ].map(loadTexture));
563
- insightMarkContainer.removeChildren();
564
- yield updatePointer(mousePointer, imageWidth / 2, imageHeight / 2);
565
- yield repaintImage();
566
- yield updateCamera(_object_spread({}, basicCameraState));
567
- const totalDuration = scaleByPlaybackSpeed(scripts.reduce((acc, item)=>acc + item.duration + (item.camera && item.insightCameraDuration ? item.insightCameraDuration : 0), 0));
568
- const progressUpdateInterval = 200;
569
- const startTime = performance.now();
570
- setAnimationProgress(0);
571
- const updateProgress = ()=>{
572
- const progress = Math.min((performance.now() - startTime) / totalDuration, 1);
573
- setAnimationProgress(progress);
574
- if (progress < 1) return timeout(updateProgress, progressUpdateInterval);
575
- };
576
- frame(updateProgress);
577
- if (recorderSessionRef.current) recorderSessionRef.current.start();
578
- let currentTaskId = null;
579
- var _scripts__taskId;
580
- const firstTaskId = null != (_scripts__taskId = null == (_scripts_ = scripts[0]) ? void 0 : _scripts_.taskId) ? _scripts__taskId : null;
581
- if (firstTaskId) {
582
- currentTaskId = firstTaskId;
583
- safeOnTaskChange(currentTaskId);
584
- }
585
- for(const index in scripts){
586
- const item = scripts[index];
587
- setTitleText(item.title || '');
588
- setSubTitleText(item.subTitle || '');
589
- var _item_taskId;
590
- const newTaskId = null != (_item_taskId = item.taskId) ? _item_taskId : null;
591
- if (newTaskId !== currentTaskId) {
592
- currentTaskId = newTaskId;
593
- safeOnTaskChange(currentTaskId);
594
- }
595
- if ('sleep' === item.type) yield sleep(item.duration);
596
- else if ('insight' === item.type) {
597
- if (!item.img) throw new Error('img is required');
598
- currentImg.current = item.img;
599
- yield repaintImage(item.imageWidth, item.imageHeight);
600
- const highlightElements = item.highlightElement ? [
601
- item.highlightElement
602
- ] : [];
603
- yield insightElementsAnimation([], highlightElements, item.searchArea, scaleByPlaybackSpeed(item.duration), frame);
604
- if (item.camera) {
605
- if (!item.insightCameraDuration) throw new Error('insightCameraDuration is required');
606
- yield cameraAnimation(item.camera, scaleByPlaybackSpeed(item.insightCameraDuration), frame);
607
- }
608
- } else if ('clear-insight' === item.type) {
609
- yield fadeOutItem(insightMarkContainer, scaleByPlaybackSpeed(item.duration), frame);
610
- insightMarkContainer.removeChildren();
611
- insightMarkContainer.alpha = 1;
612
- } else if ('img' === item.type) {
613
- if (item.img && item.img !== currentImg.current) {
614
- currentImg.current = item.img;
615
- yield repaintImage(item.imageWidth, item.imageHeight);
616
- }
617
- if (item.camera) yield cameraAnimation(item.camera, scaleByPlaybackSpeed(item.duration), frame);
618
- else yield sleep(item.duration);
619
- } else if ('pointer' === item.type) {
620
- if (!item.img) throw new Error('pointer img is required');
621
- yield updatePointer(item.img);
622
- } else if ('spinning-pointer' === item.type) {
623
- const stop = spinningPointer(frame);
624
- yield sleep(item.duration);
625
- stop();
626
- }
627
- }
628
- safeOnTaskChange(null);
629
- if (recorderSessionRef.current) {
630
- yield sleep(1200);
631
- recorderSessionRef.current.stop();
632
- recorderSessionRef.current = null;
633
- setIsRecording(false);
634
- }
635
- })())().catch((e)=>{
636
- console.error('player error', e);
637
- if ((null == e ? void 0 : e.message) === ERROR_FRAME_CANCEL) {
638
- console.log('Animation cancelled (expected behavior)');
639
- safeOnTaskChange(null);
640
- return;
641
- }
642
- const wasRecording = !!recorderSessionRef.current;
643
- if (recorderSessionRef.current) {
644
- try {
645
- recorderSessionRef.current.stop();
646
- } catch (stopError) {
647
- console.error('Error stopping recorder:', stopError);
648
- }
649
- recorderSessionRef.current = null;
650
- }
651
- setIsRecording(false);
652
- safeOnTaskChange(null);
653
- if (wasRecording) message.error('Failed to export video. Please try again.');
654
- }));
655
- return ()=>{
656
- null == cancelFn || cancelFn();
657
- cancelAnimationRef.current = null;
170
+ seek(e.clientX);
171
+ const onMove = (ev)=>seek(ev.clientX);
172
+ const onUp = ()=>{
173
+ bar.removeEventListener('pointermove', onMove);
174
+ bar.removeEventListener('pointerup', onUp);
658
175
  };
659
- };
176
+ bar.addEventListener('pointermove', onMove);
177
+ bar.addEventListener('pointerup', onUp);
178
+ }, [
179
+ frameMap,
180
+ player
181
+ ]);
182
+ const [isFullscreen, setIsFullscreen] = useState(false);
183
+ const toggleFullscreen = useCallback(()=>{
184
+ const el = wrapperRef.current;
185
+ if (!el) return;
186
+ if (document.fullscreenElement) document.exitFullscreen().then(()=>setIsFullscreen(false));
187
+ else el.requestFullscreen().then(()=>setIsFullscreen(true));
188
+ }, []);
660
189
  useEffect(()=>{
661
- Promise.resolve((()=>_async_to_generator(function*() {
662
- yield init();
663
- if (divContainerRef.current && imageWidth && imageHeight) {
664
- const aspectRatio = imageWidth / imageHeight;
665
- divContainerRef.current.style.setProperty('--canvas-aspect-ratio', aspectRatio.toString());
666
- divContainerRef.current.setAttribute('data-fit-mode', fitMode);
667
- const playerContainer = divContainerRef.current.closest('.player-container');
668
- if (playerContainer) playerContainer.setAttribute('data-fit-mode', fitMode);
669
- }
670
- triggerReplay();
671
- })())());
672
- return ()=>{
673
- appInitialized.current = false;
190
+ const handler = ()=>setIsFullscreen(!!document.fullscreenElement);
191
+ document.addEventListener('fullscreenchange', handler);
192
+ return ()=>document.removeEventListener('fullscreenchange', handler);
193
+ }, []);
194
+ const [isExporting, setIsExporting] = useState(false);
195
+ const [exportProgress, setExportProgress] = useState(0);
196
+ const handleExportVideo = useCallback(()=>_async_to_generator(function*() {
197
+ if (!frameMap || isExporting) return;
198
+ setIsExporting(true);
199
+ setExportProgress(0);
674
200
  try {
675
- app.destroy(true, {
676
- children: true,
677
- texture: true
678
- });
201
+ yield exportBrandedVideo(frameMap, (pct)=>setExportProgress(Math.round(100 * pct)));
202
+ message.success('Video exported');
679
203
  } catch (e) {
680
- console.warn('destroy failed', e);
204
+ console.error('Export failed:', e);
205
+ message.error('Export failed');
206
+ } finally{
207
+ setIsExporting(false);
208
+ setExportProgress(0);
681
209
  }
682
- };
683
- }, [
684
- imageWidth,
685
- imageHeight,
686
- fitMode
210
+ })(), [
211
+ frameMap,
212
+ isExporting
687
213
  ]);
688
- useEffect(()=>{
689
- if (replayMark) return play();
690
- }, [
691
- replayMark
692
- ]);
693
- const [mouseOverStatusIcon, setMouseOverStatusIcon] = useState(false);
694
- const [mouseOverSettingsIcon, setMouseOverSettingsIcon] = useState(false);
695
- const progressString = Math.round(100 * animationProgress);
696
- const transitionStyle = 0 === animationProgress ? 'none' : '0.3s';
697
- const canReplayNow = 1 === animationProgress;
698
- useEffect(()=>{
699
- if (canReplayNow) {
700
- const listener = (event)=>{
701
- if (' ' === event.key) triggerReplay();
702
- };
703
- window.addEventListener('keydown', listener);
704
- return ()=>{
705
- window.removeEventListener('keydown', listener);
706
- };
214
+ const chapterMarkers = useMemo(()=>{
215
+ if (!frameMap) return [];
216
+ const { scriptFrames, totalDurationInFrames } = frameMap;
217
+ if (0 === totalDurationInFrames) return [];
218
+ const markers = [];
219
+ for (const sf of scriptFrames){
220
+ if ('img' !== sf.type && 'insight' !== sf.type || 0 === sf.durationInFrames) continue;
221
+ const globalFrame = sf.startFrame;
222
+ const percent = globalFrame / totalDurationInFrames * 100;
223
+ if (percent > 1 && percent < 99) {
224
+ const parts = [
225
+ sf.title,
226
+ sf.subTitle
227
+ ].filter(Boolean);
228
+ markers.push({
229
+ percent,
230
+ title: parts.length > 0 ? parts.join(': ') : `Chapter ${markers.length + 1}`,
231
+ frame: globalFrame
232
+ });
233
+ }
707
234
  }
235
+ return markers;
708
236
  }, [
709
- canReplayNow
237
+ frameMap
710
238
  ]);
711
- let statusIconElement;
712
- let statusOnClick = ()=>{};
713
- if (animationProgress < 1) statusIconElement = /*#__PURE__*/ jsx(Spin, {
714
- indicator: /*#__PURE__*/ jsx(LoadingOutlined, {
715
- spin: true,
716
- color: "#333"
717
- }),
718
- size: "default"
239
+ if (!scripts || 0 === scripts.length || !frameMap) return /*#__PURE__*/ jsx("div", {
240
+ className: "player-container"
719
241
  });
720
- else {
721
- statusIconElement = /*#__PURE__*/ jsx(Spin, {
722
- indicator: /*#__PURE__*/ jsx(CaretRightOutlined, {
723
- color: "#333"
724
- }),
725
- size: "default"
726
- });
727
- statusOnClick = ()=>triggerReplay();
728
- }
729
- return /*#__PURE__*/ jsxs("div", {
242
+ const imgW = frameMap.imageWidth;
243
+ const imgH = frameMap.imageHeight;
244
+ const compositionWidth = imgW;
245
+ const compositionHeight = imgH;
246
+ const isPortraitCanvas = imgH > imgW;
247
+ const totalFrames = frameMap.totalDurationInFrames;
248
+ const seekPercent = totalFrames > 1 ? player.currentFrame / (totalFrames - 1) * 100 : 0;
249
+ return /*#__PURE__*/ jsx("div", {
730
250
  className: "player-container",
731
- children: [
732
- /*#__PURE__*/ jsx("div", {
733
- className: "canvas-container",
734
- ref: divContainerRef
735
- }),
736
- /*#__PURE__*/ jsx("div", {
737
- className: "player-timeline-wrapper",
738
- children: /*#__PURE__*/ jsx("div", {
739
- className: "player-timeline",
740
- children: /*#__PURE__*/ jsx("div", {
741
- className: "player-timeline-progress",
251
+ "data-fit-mode": null == props ? void 0 : props.fitMode,
252
+ children: /*#__PURE__*/ jsx("div", {
253
+ className: "canvas-container",
254
+ children: /*#__PURE__*/ jsxs("div", {
255
+ className: "player-wrapper",
256
+ ref: wrapperRef,
257
+ "data-portrait": isPortraitCanvas ? '' : void 0,
258
+ style: {
259
+ aspectRatio: `${compositionWidth}/${compositionHeight}`
260
+ },
261
+ onMouseMove: showControls,
262
+ onMouseEnter: onMouseEnter,
263
+ onMouseLeave: onMouseLeave,
264
+ children: [
265
+ /*#__PURE__*/ jsx("div", {
266
+ ref: renderLayerRef,
742
267
  style: {
743
- width: `${progressString}%`,
744
- transition: transitionStyle
745
- }
746
- })
747
- })
748
- }),
749
- /*#__PURE__*/ jsx("div", {
750
- className: "player-tools-wrapper",
751
- children: /*#__PURE__*/ jsx("div", {
752
- className: "player-tools",
753
- children: /*#__PURE__*/ jsxs("div", {
754
- className: "player-control",
755
- children: [
756
- /*#__PURE__*/ jsxs("div", {
757
- className: "status-text",
758
- children: [
759
- /*#__PURE__*/ jsx("div", {
760
- className: "title",
761
- children: titleText
762
- }),
763
- /*#__PURE__*/ jsx(Tooltip, {
764
- title: subTitleText,
765
- children: /*#__PURE__*/ jsx("div", {
766
- className: "subtitle",
767
- children: subTitleText
768
- })
769
- })
770
- ]
771
- }),
772
- isRecording ? null : /*#__PURE__*/ jsx("div", {
773
- className: "status-icon",
774
- onMouseEnter: ()=>setMouseOverStatusIcon(true),
775
- onMouseLeave: ()=>setMouseOverStatusIcon(false),
776
- onClick: statusOnClick,
777
- children: statusIconElement
778
- }),
779
- (null == props ? void 0 : props.reportFileContent) && (null == props ? void 0 : props.canDownloadReport) !== false ? /*#__PURE__*/ jsx(Tooltip, {
780
- title: "Download Report",
781
- children: /*#__PURE__*/ jsx("div", {
782
- className: "status-icon",
783
- onMouseEnter: ()=>setMouseOverStatusIcon(true),
784
- onMouseLeave: ()=>setMouseOverStatusIcon(false),
785
- onClick: ()=>downloadReport(props.reportFileContent),
786
- children: /*#__PURE__*/ jsx(DownloadOutlined, {
787
- color: "#333"
788
- })
789
- })
790
- }) : null,
791
- /*#__PURE__*/ jsx(Tooltip, {
792
- title: isRecording ? 'Generating...' : 'Export Video',
268
+ display: 'flex',
269
+ justifyContent: 'center',
270
+ alignItems: 'center',
271
+ width: '100%',
272
+ height: '100%',
273
+ overflow: 'hidden'
274
+ },
275
+ onClick: player.toggle,
276
+ children: (()=>{
277
+ const scale = containerSize.width > 0 && containerSize.height > 0 ? Math.min(containerSize.width / compositionWidth, containerSize.height / compositionHeight) : 1;
278
+ return /*#__PURE__*/ jsx("div", {
279
+ style: {
280
+ width: compositionWidth * scale,
281
+ height: compositionHeight * scale,
282
+ flexShrink: 0,
283
+ position: 'relative',
284
+ overflow: 'hidden'
285
+ },
793
286
  children: /*#__PURE__*/ jsx("div", {
794
- className: "status-icon",
795
- onClick: isRecording ? void 0 : handleExport,
796
287
  style: {
797
- opacity: isRecording ? 0.5 : 1,
798
- cursor: isRecording ? 'not-allowed' : 'pointer'
288
+ width: compositionWidth,
289
+ height: compositionHeight,
290
+ transformOrigin: '0 0',
291
+ transform: `scale(${scale})`
799
292
  },
800
- children: isRecording ? /*#__PURE__*/ jsx(Spin, {
801
- size: "default",
802
- percent: progressString
803
- }) : /*#__PURE__*/ jsx(ExportOutlined, {})
293
+ children: /*#__PURE__*/ jsx(StepsTimeline, {
294
+ frameMap: frameMap,
295
+ autoZoom: autoZoom,
296
+ frame: player.currentFrame,
297
+ width: compositionWidth,
298
+ height: compositionHeight,
299
+ fps: frameMap.fps
300
+ })
804
301
  })
302
+ });
303
+ })()
304
+ }),
305
+ subtitleEnabled && subtitle && /*#__PURE__*/ jsxs("div", {
306
+ className: "player-subtitle",
307
+ children: [
308
+ subtitle.title && /*#__PURE__*/ jsx("span", {
309
+ className: "player-subtitle-badge",
310
+ children: subtitle.title
805
311
  }),
806
- /*#__PURE__*/ jsx(Dropdown, {
807
- trigger: [
808
- 'hover',
809
- 'click'
810
- ],
811
- placement: "bottomRight",
812
- overlayStyle: {
813
- minWidth: '148px'
814
- },
815
- dropdownRender: ()=>/*#__PURE__*/ jsxs("div", {
816
- className: "player-settings-dropdown",
817
- children: [
818
- /*#__PURE__*/ jsxs("div", {
819
- className: "player-settings-item",
312
+ subtitle.subTitle && /*#__PURE__*/ jsx("span", {
313
+ className: "player-subtitle-text",
314
+ children: subtitle.subTitle
315
+ })
316
+ ]
317
+ }),
318
+ /*#__PURE__*/ jsxs("div", {
319
+ className: `control-bar ${controlsVisible ? '' : 'hidden'}`,
320
+ onClick: (e)=>e.stopPropagation(),
321
+ children: [
322
+ /*#__PURE__*/ jsx("div", {
323
+ className: "status-icon",
324
+ onClick: player.toggle,
325
+ children: player.playing ? /*#__PURE__*/ jsx(PauseOutlined, {}) : /*#__PURE__*/ jsx(CaretRightOutlined, {})
326
+ }),
327
+ /*#__PURE__*/ jsxs("span", {
328
+ className: "time-display",
329
+ children: [
330
+ formatTime(player.currentFrame, frameMap.fps),
331
+ " /",
332
+ ' ',
333
+ formatTime(totalFrames, frameMap.fps)
334
+ ]
335
+ }),
336
+ /*#__PURE__*/ jsxs("div", {
337
+ className: "seek-bar-track",
338
+ ref: seekBarRef,
339
+ onPointerDown: handleSeekPointerDown,
340
+ children: [
341
+ /*#__PURE__*/ jsx("div", {
342
+ className: "seek-bar-fill",
343
+ style: {
344
+ width: `${seekPercent}%`
345
+ }
346
+ }),
347
+ /*#__PURE__*/ jsx("div", {
348
+ className: "seek-bar-knob",
349
+ style: {
350
+ left: `${seekPercent}%`
351
+ }
352
+ }),
353
+ chapterMarkers.map((marker)=>/*#__PURE__*/ jsx(Tooltip, {
354
+ title: marker.title,
355
+ overlayClassName: "chapter-tooltip",
356
+ children: /*#__PURE__*/ jsx("div", {
357
+ className: "chapter-marker",
820
358
  style: {
821
- display: 'flex',
822
- alignItems: 'center',
823
- justifyContent: 'space-between',
824
- height: '32px',
825
- padding: '0 8px',
826
- borderRadius: '4px'
359
+ left: `${marker.percent}%`
827
360
  },
361
+ onClick: (e)=>{
362
+ e.stopPropagation();
363
+ player.seekTo(marker.frame);
364
+ }
365
+ })
366
+ }, marker.percent))
367
+ ]
368
+ }),
369
+ /*#__PURE__*/ jsxs("div", {
370
+ className: "player-custom-controls",
371
+ children: [
372
+ (null == props ? void 0 : props.reportFileContent) && (null == props ? void 0 : props.canDownloadReport) !== false ? /*#__PURE__*/ jsx(Tooltip, {
373
+ title: "Download Report",
374
+ children: /*#__PURE__*/ jsx("div", {
375
+ className: "status-icon",
376
+ onClick: ()=>downloadReport(props.reportFileContent),
377
+ children: /*#__PURE__*/ jsx(DownloadOutlined, {})
378
+ })
379
+ }) : null,
380
+ /*#__PURE__*/ jsx(Dropdown, {
381
+ trigger: [
382
+ 'hover',
383
+ 'click'
384
+ ],
385
+ placement: "topRight",
386
+ overlayStyle: {
387
+ minWidth: '148px'
388
+ },
389
+ dropdownRender: ()=>/*#__PURE__*/ jsxs("div", {
390
+ className: "player-settings-dropdown",
828
391
  children: [
829
392
  /*#__PURE__*/ jsxs("div", {
393
+ className: "player-settings-item",
830
394
  style: {
831
395
  display: 'flex',
832
396
  alignItems: 'center',
833
- gap: '4px'
397
+ gap: '4px',
398
+ height: '32px',
399
+ padding: '0 8px',
400
+ borderRadius: '4px',
401
+ cursor: isExporting ? 'not-allowed' : 'pointer',
402
+ opacity: isExporting ? 0.5 : 1
834
403
  },
404
+ onClick: isExporting ? void 0 : handleExportVideo,
835
405
  children: [
836
- /*#__PURE__*/ jsx(global_perspective, {
406
+ isExporting ? /*#__PURE__*/ jsx(Spin, {
407
+ size: "small"
408
+ }) : /*#__PURE__*/ jsx(ExportOutlined, {
837
409
  style: {
838
410
  width: '16px',
839
411
  height: '16px'
@@ -841,103 +413,169 @@ function Player(props) {
841
413
  }),
842
414
  /*#__PURE__*/ jsx("span", {
843
415
  style: {
844
- fontSize: '12px',
845
- marginRight: '16px'
416
+ fontSize: '12px'
846
417
  },
847
- children: "Focus on cursor"
418
+ children: isExporting ? `Exporting ${exportProgress}%` : 'Export video'
848
419
  })
849
420
  ]
850
421
  }),
851
- /*#__PURE__*/ jsx(Switch, {
852
- size: "small",
853
- checked: autoZoom,
854
- onChange: (checked)=>{
855
- setAutoZoom(checked);
856
- triggerReplay();
857
- }
858
- })
859
- ]
860
- }),
861
- /*#__PURE__*/ jsx("div", {
862
- className: "player-settings-divider"
863
- }),
864
- /*#__PURE__*/ jsxs("div", {
865
- style: {
866
- display: 'flex',
867
- alignItems: 'center',
868
- gap: '4px',
869
- height: '32px',
870
- padding: '0 8px'
871
- },
872
- children: [
873
- /*#__PURE__*/ jsx(ThunderboltOutlined, {
422
+ /*#__PURE__*/ jsx("div", {
423
+ className: "player-settings-divider"
424
+ }),
425
+ /*#__PURE__*/ jsxs("div", {
426
+ className: "player-settings-item",
874
427
  style: {
875
- width: '16px',
876
- height: '16px'
877
- }
428
+ display: 'flex',
429
+ alignItems: 'center',
430
+ justifyContent: 'space-between',
431
+ height: '32px',
432
+ padding: '0 8px',
433
+ borderRadius: '4px'
434
+ },
435
+ children: [
436
+ /*#__PURE__*/ jsxs("div", {
437
+ style: {
438
+ display: 'flex',
439
+ alignItems: 'center',
440
+ gap: '4px'
441
+ },
442
+ children: [
443
+ /*#__PURE__*/ jsx(global_perspective, {
444
+ style: {
445
+ width: '16px',
446
+ height: '16px'
447
+ }
448
+ }),
449
+ /*#__PURE__*/ jsx("span", {
450
+ style: {
451
+ fontSize: '12px',
452
+ marginRight: '16px'
453
+ },
454
+ children: "Focus on cursor"
455
+ })
456
+ ]
457
+ }),
458
+ /*#__PURE__*/ jsx(Switch, {
459
+ size: "small",
460
+ checked: autoZoom,
461
+ onChange: (checked)=>setAutoZoom(checked)
462
+ })
463
+ ]
464
+ }),
465
+ /*#__PURE__*/ jsxs("div", {
466
+ className: "player-settings-item",
467
+ style: {
468
+ display: 'flex',
469
+ alignItems: 'center',
470
+ justifyContent: 'space-between',
471
+ height: '32px',
472
+ padding: '0 8px',
473
+ borderRadius: '4px'
474
+ },
475
+ children: [
476
+ /*#__PURE__*/ jsxs("div", {
477
+ style: {
478
+ display: 'flex',
479
+ alignItems: 'center',
480
+ gap: '4px'
481
+ },
482
+ children: [
483
+ /*#__PURE__*/ jsx(FontSizeOutlined, {
484
+ style: {
485
+ width: '16px',
486
+ height: '16px'
487
+ }
488
+ }),
489
+ /*#__PURE__*/ jsx("span", {
490
+ style: {
491
+ fontSize: '12px',
492
+ marginRight: '16px'
493
+ },
494
+ children: "Subtitle"
495
+ })
496
+ ]
497
+ }),
498
+ /*#__PURE__*/ jsx(Switch, {
499
+ size: "small",
500
+ checked: subtitleEnabled,
501
+ onChange: (checked)=>setSubtitleEnabled(checked)
502
+ })
503
+ ]
504
+ }),
505
+ /*#__PURE__*/ jsx("div", {
506
+ className: "player-settings-divider"
878
507
  }),
879
- /*#__PURE__*/ jsx("span", {
508
+ /*#__PURE__*/ jsxs("div", {
880
509
  style: {
881
- fontSize: '12px'
510
+ display: 'flex',
511
+ alignItems: 'center',
512
+ gap: '4px',
513
+ height: '32px',
514
+ padding: '0 8px'
882
515
  },
883
- children: "Playback speed"
884
- })
516
+ children: [
517
+ /*#__PURE__*/ jsx(ThunderboltOutlined, {
518
+ style: {
519
+ width: '16px',
520
+ height: '16px'
521
+ }
522
+ }),
523
+ /*#__PURE__*/ jsx("span", {
524
+ style: {
525
+ fontSize: '12px'
526
+ },
527
+ children: "Playback speed"
528
+ })
529
+ ]
530
+ }),
531
+ [
532
+ 0.5,
533
+ 1,
534
+ 1.5,
535
+ 2
536
+ ].map((speed)=>/*#__PURE__*/ jsxs("div", {
537
+ onClick: ()=>setPlaybackSpeed(speed),
538
+ style: {
539
+ height: '32px',
540
+ lineHeight: '32px',
541
+ padding: '0 8px 0 24px',
542
+ fontSize: '12px',
543
+ cursor: 'pointer',
544
+ borderRadius: '4px'
545
+ },
546
+ className: `player-speed-option${playbackSpeed === speed ? ' active' : ''}`,
547
+ children: [
548
+ speed,
549
+ "x"
550
+ ]
551
+ }, speed))
885
552
  ]
886
553
  }),
887
- [
888
- 0.5,
889
- 1,
890
- 1.5,
891
- 2
892
- ].map((speed)=>/*#__PURE__*/ jsxs("div", {
893
- onClick: ()=>{
894
- setPlaybackSpeed(speed);
895
- triggerReplay();
896
- },
897
- style: {
898
- height: '32px',
899
- lineHeight: '32px',
900
- padding: '0 8px 0 24px',
901
- fontSize: '12px',
902
- cursor: 'pointer',
903
- borderRadius: '4px'
904
- },
905
- className: `player-speed-option${playbackSpeed === speed ? ' active' : ''}`,
906
- children: [
907
- speed,
908
- "x"
909
- ]
910
- }, speed))
911
- ]
554
+ menu: {
555
+ items: []
556
+ },
557
+ children: /*#__PURE__*/ jsx("div", {
558
+ className: "status-icon",
559
+ children: /*#__PURE__*/ jsx(player_setting, {
560
+ style: {
561
+ width: '16px',
562
+ height: '16px'
563
+ }
564
+ })
565
+ })
912
566
  }),
913
- menu: {
914
- items: []
915
- },
916
- children: /*#__PURE__*/ jsx("div", {
917
- className: "status-icon",
918
- onMouseEnter: ()=>setMouseOverSettingsIcon(true),
919
- onMouseLeave: ()=>setMouseOverSettingsIcon(false),
920
- style: {
921
- cursor: 'pointer',
922
- display: 'flex',
923
- alignItems: 'center',
924
- justifyContent: 'center',
925
- opacity: mouseOverSettingsIcon ? 1 : 0.7,
926
- transition: 'opacity 0.2s'
927
- },
928
- children: /*#__PURE__*/ jsx(player_setting, {
929
- style: {
930
- width: '16px',
931
- height: '16px'
932
- }
567
+ /*#__PURE__*/ jsx("div", {
568
+ className: "status-icon",
569
+ onClick: toggleFullscreen,
570
+ children: isFullscreen ? /*#__PURE__*/ jsx(CompressOutlined, {}) : /*#__PURE__*/ jsx(ExpandOutlined, {})
933
571
  })
934
- })
572
+ ]
935
573
  })
936
574
  ]
937
575
  })
938
- })
576
+ ]
939
577
  })
940
- ]
578
+ })
941
579
  });
942
580
  }
943
581
  export { Player };