@midscene/visualizer 1.7.7-beta-20260429033400.0 → 1.7.7
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/dist/es/component/player/index.css +27 -2
- package/dist/es/component/player/index.mjs +123 -50
- package/dist/es/component/player/playback-controls.mjs +4 -0
- package/dist/es/component/player/scenes/StepScene.mjs +16 -13
- package/dist/es/component/player/scenes/derive-frame-state.mjs +12 -2
- package/dist/es/component/player/scenes/export-branded-video.mjs +182 -47
- package/dist/es/component/player/scenes/pointer-layout.mjs +36 -0
- package/dist/lib/component/player/index.css +27 -2
- package/dist/lib/component/player/index.js +122 -49
- package/dist/lib/component/player/playback-controls.js +38 -0
- package/dist/lib/component/player/scenes/StepScene.js +15 -12
- package/dist/lib/component/player/scenes/derive-frame-state.js +16 -3
- package/dist/lib/component/player/scenes/export-branded-video.js +188 -47
- package/dist/lib/component/player/scenes/pointer-layout.js +88 -0
- package/dist/types/component/player/index.d.ts +1 -1
- package/dist/types/component/player/playback-controls.d.ts +1 -0
- package/dist/types/component/player/scenes/derive-frame-state.d.ts +2 -0
- package/dist/types/component/player/scenes/export-branded-video.d.ts +16 -0
- package/dist/types/component/player/scenes/pointer-layout.d.ts +20 -0
- package/package.json +5 -5
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { mouseLoading, mousePointer } from "../../../utils/index.mjs";
|
|
2
2
|
import { getCenterHighlightBox } from "../../../utils/highlight-element.mjs";
|
|
3
|
-
import { deriveFrameState } from "./derive-frame-state.mjs";
|
|
3
|
+
import { deriveFrameState, shouldRenderCursor } from "./derive-frame-state.mjs";
|
|
4
4
|
import { getPlaybackViewport } from "./playback-layout.mjs";
|
|
5
|
+
import { resolveExportPointerLayout, resolveSpinnerLayout } from "./pointer-layout.mjs";
|
|
5
6
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
6
7
|
try {
|
|
7
8
|
var info = gen[key](arg);
|
|
@@ -28,16 +29,64 @@ function _async_to_generator(fn) {
|
|
|
28
29
|
});
|
|
29
30
|
};
|
|
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
|
+
}
|
|
31
74
|
const W = 960;
|
|
32
75
|
const H = 540;
|
|
33
76
|
const POINTER_PHASE = 0.375;
|
|
34
77
|
const CROSSFADE_FRAMES = 10;
|
|
78
|
+
const EXPORT_STALL_GRACE_MS = 2000;
|
|
79
|
+
const EXPORT_STALL_GRACE_FRAMES = 10;
|
|
80
|
+
let activeExport = false;
|
|
35
81
|
function clamp(v, lo, hi) {
|
|
36
82
|
return Math.min(Math.max(v, lo), hi);
|
|
37
83
|
}
|
|
38
84
|
function lerp(a, b, t) {
|
|
39
85
|
return a + (b - a) * t;
|
|
40
86
|
}
|
|
87
|
+
function isExportRenderStalled(elapsedSinceLastFrameMs, frameDurationMs) {
|
|
88
|
+
return elapsedSinceLastFrameMs > Math.max(EXPORT_STALL_GRACE_MS, frameDurationMs * EXPORT_STALL_GRACE_FRAMES);
|
|
89
|
+
}
|
|
41
90
|
function resolveExportCamera(prevCamera, camera, imageWidth, progress, autoZoom) {
|
|
42
91
|
if (!autoZoom) return {
|
|
43
92
|
camLeft: 0,
|
|
@@ -59,60 +108,68 @@ function loadImage(src) {
|
|
|
59
108
|
img.src = src;
|
|
60
109
|
});
|
|
61
110
|
}
|
|
62
|
-
function
|
|
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();
|
|
63
126
|
for (const insight of insights)if (!(insight.alpha <= 0)) {
|
|
64
127
|
ctx.save();
|
|
65
128
|
ctx.globalAlpha *= insight.alpha;
|
|
66
129
|
if (insight.highlightElement) {
|
|
67
130
|
const highlightBox = getCenterHighlightBox(insight.highlightElement);
|
|
68
|
-
const
|
|
69
|
-
const ry = contentY + (highlightBox.top * cameraTransform.zoom + cameraTransform.ty * cameraTransform.zoom);
|
|
70
|
-
const highlightWidth = highlightBox.width * cameraTransform.zoom;
|
|
71
|
-
const highlightHeight = highlightBox.height * cameraTransform.zoom;
|
|
131
|
+
const projected = projectNativeRectToExportViewport(highlightBox, cameraTransform, viewport);
|
|
72
132
|
ctx.fillStyle = 'rgba(253, 89, 7, 0.4)';
|
|
73
|
-
ctx.fillRect(
|
|
133
|
+
ctx.fillRect(projected.left, projected.top, projected.width, projected.height);
|
|
74
134
|
ctx.strokeStyle = '#fd5907';
|
|
75
135
|
ctx.lineWidth = 1;
|
|
76
|
-
ctx.strokeRect(
|
|
136
|
+
ctx.strokeRect(projected.left, projected.top, projected.width, projected.height);
|
|
77
137
|
ctx.shadowColor = 'rgba(51, 51, 51, 0.4)';
|
|
78
138
|
ctx.shadowBlur = 2;
|
|
79
139
|
ctx.shadowOffsetX = 4;
|
|
80
140
|
ctx.shadowOffsetY = 4;
|
|
81
|
-
ctx.strokeRect(
|
|
141
|
+
ctx.strokeRect(projected.left, projected.top, projected.width, projected.height);
|
|
82
142
|
ctx.shadowBlur = 0;
|
|
83
143
|
ctx.shadowOffsetX = 0;
|
|
84
144
|
ctx.shadowOffsetY = 0;
|
|
85
145
|
}
|
|
86
146
|
if (insight.searchArea) {
|
|
87
|
-
const
|
|
88
|
-
const rx = bx + (r.left * cameraTransform.zoom + cameraTransform.tx * cameraTransform.zoom);
|
|
89
|
-
const ry = contentY + (r.top * cameraTransform.zoom + cameraTransform.ty * cameraTransform.zoom);
|
|
90
|
-
const rw = r.width * cameraTransform.zoom;
|
|
91
|
-
const rh = r.height * cameraTransform.zoom;
|
|
147
|
+
const projected = projectNativeRectToExportViewport(insight.searchArea, cameraTransform, viewport);
|
|
92
148
|
ctx.fillStyle = 'rgba(2, 131, 145, 0.4)';
|
|
93
|
-
ctx.fillRect(
|
|
149
|
+
ctx.fillRect(projected.left, projected.top, projected.width, projected.height);
|
|
94
150
|
ctx.strokeStyle = '#028391';
|
|
95
151
|
ctx.lineWidth = 1;
|
|
96
|
-
ctx.strokeRect(
|
|
152
|
+
ctx.strokeRect(projected.left, projected.top, projected.width, projected.height);
|
|
97
153
|
}
|
|
98
154
|
ctx.restore();
|
|
99
155
|
}
|
|
156
|
+
ctx.restore();
|
|
100
157
|
}
|
|
101
|
-
function drawSpinningPointer(ctx, img, x, y, elapsedMs) {
|
|
158
|
+
function drawSpinningPointer(ctx, img, x, y, layout, elapsedMs) {
|
|
102
159
|
const progress = (Math.sin(elapsedMs / 500 - Math.PI / 2) + 1) / 2;
|
|
103
160
|
const rotation = progress * Math.PI * 2;
|
|
104
161
|
ctx.save();
|
|
105
162
|
ctx.translate(x, y);
|
|
106
163
|
ctx.rotate(rotation);
|
|
107
|
-
ctx.drawImage(img, -
|
|
164
|
+
ctx.drawImage(img, -layout.centerOffsetX, -layout.centerOffsetY, layout.width, layout.height);
|
|
108
165
|
ctx.restore();
|
|
109
166
|
}
|
|
110
|
-
function drawSteps(ctx, stepsFrame, frameMap, imgCache,
|
|
167
|
+
function drawSteps(ctx, stepsFrame, frameMap, imgCache, pointerCache, spinnerImg, autoZoom) {
|
|
111
168
|
const { scriptFrames, imageWidth: baseW, imageHeight: baseH, fps } = frameMap;
|
|
112
169
|
const st = deriveFrameState(scriptFrames, stepsFrame, baseW, baseH, fps);
|
|
113
170
|
if (!st.img) return;
|
|
114
|
-
const { img, prevImg, imageWidth: imgW, imageHeight: imgH, camera, prevCamera, pointerMoved, imageChanged, rawProgress, frameInScript: fInScript, spinning, spinningElapsedMs, insights } = st;
|
|
115
|
-
const pT = pointerMoved ? Math.min(rawProgress / POINTER_PHASE, 1) : rawProgress;
|
|
171
|
+
const { img, prevImg, imageWidth: imgW, imageHeight: imgH, camera, prevCamera, pointerMoved, imageChanged, rawProgress, frameInScript: fInScript, spinning, spinningElapsedMs, currentPointerImg, pointerVisible, insights } = st;
|
|
172
|
+
const pT = autoZoom ? pointerMoved ? Math.min(rawProgress / POINTER_PHASE, 1) : rawProgress : 1;
|
|
116
173
|
const cT = pointerMoved ? rawProgress <= POINTER_PHASE ? 0 : Math.min((rawProgress - POINTER_PHASE) / (1 - POINTER_PHASE), 1) : rawProgress;
|
|
117
174
|
const { camLeft: camL, camTop: camT2, camWidth: camW } = resolveExportCamera(prevCamera, camera, imgW, cT, autoZoom);
|
|
118
175
|
const ptrX = lerp(prevCamera.pointerLeft, camera.pointerLeft, pT);
|
|
@@ -143,15 +200,42 @@ function drawSteps(ctx, stepsFrame, frameMap, imgCache, cursorImg, spinnerImg, a
|
|
|
143
200
|
zoom,
|
|
144
201
|
tx,
|
|
145
202
|
ty
|
|
146
|
-
},
|
|
203
|
+
}, {
|
|
204
|
+
offsetX,
|
|
205
|
+
offsetY,
|
|
206
|
+
contentWidth,
|
|
207
|
+
contentHeight,
|
|
208
|
+
imageWidth: imgW,
|
|
209
|
+
imageHeight: imgH
|
|
210
|
+
});
|
|
147
211
|
const camH = imgH / imgW * camW;
|
|
148
212
|
const sX = offsetX + (ptrX - camL) / camW * contentWidth;
|
|
149
213
|
const sY = offsetY + (ptrY - camT2) / camH * contentHeight;
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
214
|
+
const pointerLayout = resolveExportPointerLayout(imgW, contentWidth);
|
|
215
|
+
const spinnerLayout = resolveSpinnerLayout(pointerLayout);
|
|
216
|
+
var _pointerCache_get;
|
|
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);
|
|
153
226
|
}
|
|
154
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) {
|
|
155
239
|
return _async_to_generator(function*() {
|
|
156
240
|
const { totalDurationInFrames: total, fps } = frameMap;
|
|
157
241
|
var _options_autoZoom;
|
|
@@ -166,11 +250,19 @@ function exportBrandedVideo(frameMap, options, onProgress) {
|
|
|
166
250
|
imgCache.set(src, (yield loadImage(src)));
|
|
167
251
|
} catch (e) {}
|
|
168
252
|
})()));
|
|
169
|
-
|
|
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 (e) {}
|
|
264
|
+
})()));
|
|
170
265
|
let spinnerImg = null;
|
|
171
|
-
try {
|
|
172
|
-
cursorImg = yield loadImage(mousePointer);
|
|
173
|
-
} catch (e) {}
|
|
174
266
|
try {
|
|
175
267
|
spinnerImg = yield loadImage(mouseLoading);
|
|
176
268
|
} catch (e) {}
|
|
@@ -187,8 +279,41 @@ function exportBrandedVideo(frameMap, options, onProgress) {
|
|
|
187
279
|
if (e.data.size > 0) chunks.push(e.data);
|
|
188
280
|
};
|
|
189
281
|
return new Promise((resolve, reject)=>{
|
|
190
|
-
|
|
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
|
+
};
|
|
191
312
|
recorder.onstop = ()=>{
|
|
313
|
+
cleanup();
|
|
314
|
+
if (settled) return;
|
|
315
|
+
settled = true;
|
|
316
|
+
if (stoppedByError) return void reject(stoppedByError);
|
|
192
317
|
if (0 === chunks.length) return void reject(new Error('No video data'));
|
|
193
318
|
const blob = new Blob(chunks, {
|
|
194
319
|
type: 'video/webm'
|
|
@@ -198,28 +323,38 @@ function exportBrandedVideo(frameMap, options, onProgress) {
|
|
|
198
323
|
a.href = url;
|
|
199
324
|
a.download = 'midscene_replay.webm';
|
|
200
325
|
a.click();
|
|
201
|
-
stream.getTracks().forEach((track)=>track.stop());
|
|
202
326
|
setTimeout(()=>URL.revokeObjectURL(url), 1000);
|
|
203
327
|
resolve();
|
|
204
328
|
};
|
|
329
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
205
330
|
recorder.start();
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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);
|
|
220
351
|
};
|
|
221
|
-
requestAnimationFrame(
|
|
352
|
+
requestAnimationFrame((timestamp)=>{
|
|
353
|
+
lastFrameAt = timestamp;
|
|
354
|
+
nextFrameDueAt = timestamp;
|
|
355
|
+
renderFrame(timestamp);
|
|
356
|
+
});
|
|
222
357
|
});
|
|
223
358
|
})();
|
|
224
359
|
}
|
|
225
|
-
export { exportBrandedVideo, resolveExportCamera };
|
|
360
|
+
export { exportBrandedVideo, isExportRenderStalled, projectNativeRectToExportViewport, resolveExportCamera };
|
|
@@ -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 };
|
|
@@ -198,9 +198,11 @@
|
|
|
198
198
|
|
|
199
199
|
.player-container .player-custom-controls {
|
|
200
200
|
flex-direction: row;
|
|
201
|
+
justify-content: center;
|
|
201
202
|
align-items: center;
|
|
202
|
-
gap:
|
|
203
|
-
|
|
203
|
+
gap: 2px;
|
|
204
|
+
min-width: 58px;
|
|
205
|
+
margin-left: 4px;
|
|
204
206
|
display: flex;
|
|
205
207
|
}
|
|
206
208
|
|
|
@@ -208,6 +210,23 @@
|
|
|
208
210
|
color: #fff;
|
|
209
211
|
}
|
|
210
212
|
|
|
213
|
+
.player-container.player-container-empty {
|
|
214
|
+
justify-content: center;
|
|
215
|
+
align-items: center;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.player-container.player-container-empty .player-empty-state {
|
|
219
|
+
color: #6b7280;
|
|
220
|
+
flex-direction: column;
|
|
221
|
+
align-items: center;
|
|
222
|
+
gap: 12px;
|
|
223
|
+
display: flex;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.player-container.player-container-empty .player-empty-state .player-empty-text {
|
|
227
|
+
font-size: 14px;
|
|
228
|
+
}
|
|
229
|
+
|
|
211
230
|
.chapter-tooltip .ant-tooltip-inner {
|
|
212
231
|
-webkit-backdrop-filter: blur(8px);
|
|
213
232
|
backdrop-filter: blur(8px);
|
|
@@ -250,6 +269,12 @@
|
|
|
250
269
|
margin: 4px 0;
|
|
251
270
|
}
|
|
252
271
|
|
|
272
|
+
.player-export-label {
|
|
273
|
+
font-variant-numeric: tabular-nums;
|
|
274
|
+
font-feature-settings: "tnum";
|
|
275
|
+
font-size: 14px;
|
|
276
|
+
}
|
|
277
|
+
|
|
253
278
|
.player-speed-option:hover, .player-settings-item:hover {
|
|
254
279
|
background: rgba(0, 0, 0, .04);
|
|
255
280
|
}
|