ai-design-system 0.1.106 → 0.1.107
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/components/composites/ScenePlayer/ScenePlayer.tsx +107 -18
- package/dist/index.cjs +97 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +4 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +97 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -113,6 +113,7 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
113
113
|
|
|
114
114
|
const videoRef0 = React.useRef<HTMLVideoElement | null>(null);
|
|
115
115
|
const videoRef1 = React.useRef<HTMLVideoElement | null>(null);
|
|
116
|
+
const isTransitioningRef = React.useRef<boolean>(false);
|
|
116
117
|
|
|
117
118
|
const [isPlayingState, setIsPlayingState] = React.useState<boolean>(isPlayingProp ?? true);
|
|
118
119
|
const isPlaying = isPlayingProp !== undefined ? isPlayingProp : isPlayingState;
|
|
@@ -132,6 +133,15 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
132
133
|
|
|
133
134
|
const [isMuted, setIsMuted] = React.useState<boolean>(initialMuted);
|
|
134
135
|
|
|
136
|
+
const toggleMute = React.useCallback(() => {
|
|
137
|
+
setIsMuted((prev) => {
|
|
138
|
+
const next = !prev;
|
|
139
|
+
if (videoRef0.current) videoRef0.current.muted = next;
|
|
140
|
+
if (videoRef1.current) videoRef1.current.muted = next;
|
|
141
|
+
return next;
|
|
142
|
+
});
|
|
143
|
+
}, []);
|
|
144
|
+
|
|
135
145
|
const activeScene = scenes[currentSceneIndex] || scenes[0];
|
|
136
146
|
|
|
137
147
|
const goToScene = React.useCallback(
|
|
@@ -140,24 +150,51 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
140
150
|
const clampedIdx = (targetIdx + scenes.length) % scenes.length;
|
|
141
151
|
onSceneChange?.(clampedIdx, scenes[clampedIdx]);
|
|
142
152
|
setCurrentSceneIndex(clampedIdx);
|
|
153
|
+
isTransitioningRef.current = false;
|
|
143
154
|
|
|
144
155
|
if (scenes.length <= 1) {
|
|
145
156
|
setSlot0Index(clampedIdx);
|
|
146
157
|
const v0 = videoRef0.current;
|
|
147
158
|
if (v0) {
|
|
148
159
|
v0.currentTime = 0;
|
|
149
|
-
|
|
160
|
+
v0.muted = isMuted;
|
|
161
|
+
if (isPlaying) {
|
|
162
|
+
v0.play().catch((err) => {
|
|
163
|
+
if (err?.name === "NotAllowedError") {
|
|
164
|
+
v0.muted = true;
|
|
165
|
+
v0.play().catch(() => {});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
150
169
|
}
|
|
151
170
|
return;
|
|
152
171
|
}
|
|
153
172
|
|
|
154
173
|
const nextActiveSlot: 0 | 1 = activeSlot === 0 ? 1 : 0;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
targetVideo.currentTime = 0;
|
|
158
|
-
}
|
|
174
|
+
const nextVideo = nextActiveSlot === 0 ? videoRef0.current : videoRef1.current;
|
|
175
|
+
const currentVideo = activeSlot === 0 ? videoRef0.current : videoRef1.current;
|
|
159
176
|
const sceneAfterTarget = (clampedIdx + 1) % scenes.length;
|
|
160
177
|
|
|
178
|
+
// 1. Immediately start next video synchronously (0ms transition gap)
|
|
179
|
+
if (nextVideo) {
|
|
180
|
+
nextVideo.currentTime = 0;
|
|
181
|
+
nextVideo.muted = isMuted;
|
|
182
|
+
if (isPlaying) {
|
|
183
|
+
nextVideo.play().catch((err) => {
|
|
184
|
+
if (err?.name === "NotAllowedError") {
|
|
185
|
+
nextVideo.muted = true;
|
|
186
|
+
nextVideo.play().catch(() => {});
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 2. Pause previous video
|
|
193
|
+
if (currentVideo) {
|
|
194
|
+
currentVideo.pause();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 3. Swap slots and prepare idle slot to preload upcoming scene
|
|
161
198
|
if (nextActiveSlot === 0) {
|
|
162
199
|
setSlot0Index(clampedIdx);
|
|
163
200
|
setSlot1Index(sceneAfterTarget);
|
|
@@ -168,7 +205,7 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
168
205
|
|
|
169
206
|
setActiveSlot(nextActiveSlot);
|
|
170
207
|
},
|
|
171
|
-
[scenes, activeSlot, isPlaying, onSceneChange]
|
|
208
|
+
[scenes, activeSlot, isPlaying, isMuted, onSceneChange]
|
|
172
209
|
);
|
|
173
210
|
|
|
174
211
|
const handlePromptSubmit = React.useCallback(
|
|
@@ -197,10 +234,24 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
197
234
|
}, [scenes.length, currentSceneIndex, goToScene]);
|
|
198
235
|
|
|
199
236
|
const handleSceneEnded = React.useCallback(() => {
|
|
200
|
-
if (!scenes.length || !isPlaying) return;
|
|
237
|
+
if (!scenes.length || !isPlaying || isTransitioningRef.current) return;
|
|
238
|
+
isTransitioningRef.current = true;
|
|
201
239
|
handleNext();
|
|
202
240
|
}, [scenes.length, isPlaying, handleNext]);
|
|
203
241
|
|
|
242
|
+
const handleTimeUpdate = React.useCallback(
|
|
243
|
+
(e: React.SyntheticEvent<HTMLVideoElement>) => {
|
|
244
|
+
const video = e.currentTarget;
|
|
245
|
+
if (!scenes.length || !isPlaying || isTransitioningRef.current) return;
|
|
246
|
+
// Trigger next scene 60ms before end so decoder starts seamlessly with zero gap
|
|
247
|
+
if (video.duration > 0.1 && video.currentTime >= video.duration - 0.06) {
|
|
248
|
+
isTransitioningRef.current = true;
|
|
249
|
+
handleNext();
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
[scenes.length, isPlaying, handleNext]
|
|
253
|
+
);
|
|
254
|
+
|
|
204
255
|
const handleDownload = React.useCallback(async () => {
|
|
205
256
|
if (onDownload) {
|
|
206
257
|
onDownload();
|
|
@@ -240,7 +291,6 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
240
291
|
|
|
241
292
|
if (idleVideo) {
|
|
242
293
|
idleVideo.pause();
|
|
243
|
-
idleVideo.muted = true;
|
|
244
294
|
}
|
|
245
295
|
|
|
246
296
|
if (!activeVideo) return;
|
|
@@ -253,10 +303,9 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
253
303
|
}
|
|
254
304
|
const playPromise = activeVideo.play();
|
|
255
305
|
if (playPromise !== undefined) {
|
|
256
|
-
playPromise.catch(() => {
|
|
257
|
-
if (
|
|
306
|
+
playPromise.catch((err) => {
|
|
307
|
+
if (err?.name === "NotAllowedError") {
|
|
258
308
|
activeVideo.muted = true;
|
|
259
|
-
setIsMuted(true);
|
|
260
309
|
activeVideo.play().catch(() => {});
|
|
261
310
|
}
|
|
262
311
|
});
|
|
@@ -264,7 +313,45 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
264
313
|
} else {
|
|
265
314
|
activeVideo.pause();
|
|
266
315
|
}
|
|
267
|
-
}, [activeSlot, isPlaying, isMuted
|
|
316
|
+
}, [activeSlot, isPlaying, isMuted]);
|
|
317
|
+
|
|
318
|
+
// Ensure idle video element actively loads and buffers upcoming scene
|
|
319
|
+
React.useEffect(() => {
|
|
320
|
+
if (scenes.length <= 1) return;
|
|
321
|
+
const idleVideo = activeSlot === 0 ? videoRef1.current : videoRef0.current;
|
|
322
|
+
const idleIndex = activeSlot === 0 ? slot1Index : slot0Index;
|
|
323
|
+
const idleUrl = scenes[idleIndex]?.scene_clip_url;
|
|
324
|
+
|
|
325
|
+
if (idleVideo && idleUrl) {
|
|
326
|
+
if (idleVideo.src !== idleUrl) {
|
|
327
|
+
idleVideo.src = idleUrl;
|
|
328
|
+
}
|
|
329
|
+
idleVideo.preload = "auto";
|
|
330
|
+
idleVideo.load();
|
|
331
|
+
}
|
|
332
|
+
}, [activeSlot, slot0Index, slot1Index, scenes]);
|
|
333
|
+
|
|
334
|
+
// Prefetch upcoming clips into browser HTTP cache
|
|
335
|
+
React.useEffect(() => {
|
|
336
|
+
if (!scenes || scenes.length <= 2) return;
|
|
337
|
+
const toPrefetch = [
|
|
338
|
+
(currentSceneIndex + 2) % scenes.length,
|
|
339
|
+
(currentSceneIndex + 3) % scenes.length,
|
|
340
|
+
];
|
|
341
|
+
|
|
342
|
+
toPrefetch.forEach((idx) => {
|
|
343
|
+
const url = scenes[idx]?.scene_clip_url;
|
|
344
|
+
if (url) {
|
|
345
|
+
const link = document.createElement("link");
|
|
346
|
+
link.rel = "prefetch";
|
|
347
|
+
link.href = url;
|
|
348
|
+
document.head.appendChild(link);
|
|
349
|
+
setTimeout(() => {
|
|
350
|
+
if (link.parentNode) link.parentNode.removeChild(link);
|
|
351
|
+
}, 15000);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}, [currentSceneIndex, scenes]);
|
|
268
355
|
|
|
269
356
|
React.useEffect(() => {
|
|
270
357
|
if (!scenes || scenes.length === 0) return;
|
|
@@ -303,7 +390,7 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
303
390
|
handleBack?.();
|
|
304
391
|
} else if (e.key === "m" || e.key === "M") {
|
|
305
392
|
e.preventDefault();
|
|
306
|
-
|
|
393
|
+
toggleMute();
|
|
307
394
|
}
|
|
308
395
|
};
|
|
309
396
|
|
|
@@ -411,7 +498,7 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
411
498
|
size="icon"
|
|
412
499
|
data-testid="btn-toggle-mute"
|
|
413
500
|
aria-label={isMuted ? "Unmute audio" : "Mute audio"}
|
|
414
|
-
onClick={
|
|
501
|
+
onClick={toggleMute}
|
|
415
502
|
className="h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer"
|
|
416
503
|
>
|
|
417
504
|
<Icon name={isMuted ? "volume-x" : "volume-2"} className="w-4 h-4" />
|
|
@@ -439,11 +526,12 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
439
526
|
src={scenes[slot0Index]?.scene_clip_url}
|
|
440
527
|
preload="auto"
|
|
441
528
|
playsInline
|
|
442
|
-
muted={
|
|
529
|
+
muted={isMuted}
|
|
443
530
|
onEnded={activeSlot === 0 ? handleSceneEnded : undefined}
|
|
531
|
+
onTimeUpdate={activeSlot === 0 ? handleTimeUpdate : undefined}
|
|
444
532
|
data-testid="scene-video-0"
|
|
445
533
|
className={cn(
|
|
446
|
-
"w-full h-full object-contain bg-black absolute inset-0 transition-opacity duration-
|
|
534
|
+
"w-full h-full object-contain bg-black absolute inset-0 transition-opacity duration-75",
|
|
447
535
|
activeSlot === 0 ? "opacity-100 z-10" : "opacity-0 pointer-events-none z-0"
|
|
448
536
|
)}
|
|
449
537
|
/>
|
|
@@ -455,11 +543,12 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
455
543
|
src={scenes[slot1Index]?.scene_clip_url}
|
|
456
544
|
preload="auto"
|
|
457
545
|
playsInline
|
|
458
|
-
muted={
|
|
546
|
+
muted={isMuted}
|
|
459
547
|
onEnded={activeSlot === 1 ? handleSceneEnded : undefined}
|
|
548
|
+
onTimeUpdate={activeSlot === 1 ? handleTimeUpdate : undefined}
|
|
460
549
|
data-testid="scene-video-1"
|
|
461
550
|
className={cn(
|
|
462
|
-
"w-full h-full object-contain bg-black absolute inset-0 transition-opacity duration-
|
|
551
|
+
"w-full h-full object-contain bg-black absolute inset-0 transition-opacity duration-75",
|
|
463
552
|
activeSlot === 1 ? "opacity-100 z-10" : "opacity-0 pointer-events-none z-0"
|
|
464
553
|
)}
|
|
465
554
|
/>
|
package/dist/index.cjs
CHANGED
|
@@ -14716,6 +14716,7 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14716
14716
|
});
|
|
14717
14717
|
const videoRef0 = React3__namespace.useRef(null);
|
|
14718
14718
|
const videoRef1 = React3__namespace.useRef(null);
|
|
14719
|
+
const isTransitioningRef = React3__namespace.useRef(false);
|
|
14719
14720
|
const [isPlayingState, setIsPlayingState] = React3__namespace.useState(isPlayingProp != null ? isPlayingProp : true);
|
|
14720
14721
|
const isPlaying = isPlayingProp !== void 0 ? isPlayingProp : isPlayingState;
|
|
14721
14722
|
const setIsPlaying = React3__namespace.useCallback(
|
|
@@ -14730,6 +14731,14 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14730
14731
|
setIsPlaying((prev) => !prev);
|
|
14731
14732
|
}, [setIsPlaying]);
|
|
14732
14733
|
const [isMuted, setIsMuted] = React3__namespace.useState(initialMuted);
|
|
14734
|
+
const toggleMute = React3__namespace.useCallback(() => {
|
|
14735
|
+
setIsMuted((prev) => {
|
|
14736
|
+
const next = !prev;
|
|
14737
|
+
if (videoRef0.current) videoRef0.current.muted = next;
|
|
14738
|
+
if (videoRef1.current) videoRef1.current.muted = next;
|
|
14739
|
+
return next;
|
|
14740
|
+
});
|
|
14741
|
+
}, []);
|
|
14733
14742
|
const activeScene = scenes[currentSceneIndex] || scenes[0];
|
|
14734
14743
|
const goToScene = React3__namespace.useCallback(
|
|
14735
14744
|
(targetIdx) => {
|
|
@@ -14737,22 +14746,45 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14737
14746
|
const clampedIdx = (targetIdx + scenes.length) % scenes.length;
|
|
14738
14747
|
onSceneChange == null ? void 0 : onSceneChange(clampedIdx, scenes[clampedIdx]);
|
|
14739
14748
|
setCurrentSceneIndex(clampedIdx);
|
|
14749
|
+
isTransitioningRef.current = false;
|
|
14740
14750
|
if (scenes.length <= 1) {
|
|
14741
14751
|
setSlot0Index(clampedIdx);
|
|
14742
14752
|
const v0 = videoRef0.current;
|
|
14743
14753
|
if (v0) {
|
|
14744
14754
|
v0.currentTime = 0;
|
|
14745
|
-
|
|
14746
|
-
|
|
14755
|
+
v0.muted = isMuted;
|
|
14756
|
+
if (isPlaying) {
|
|
14757
|
+
v0.play().catch((err) => {
|
|
14758
|
+
if ((err == null ? void 0 : err.name) === "NotAllowedError") {
|
|
14759
|
+
v0.muted = true;
|
|
14760
|
+
v0.play().catch(() => {
|
|
14761
|
+
});
|
|
14762
|
+
}
|
|
14763
|
+
});
|
|
14764
|
+
}
|
|
14747
14765
|
}
|
|
14748
14766
|
return;
|
|
14749
14767
|
}
|
|
14750
14768
|
const nextActiveSlot = activeSlot === 0 ? 1 : 0;
|
|
14751
|
-
const
|
|
14752
|
-
|
|
14753
|
-
targetVideo.currentTime = 0;
|
|
14754
|
-
}
|
|
14769
|
+
const nextVideo = nextActiveSlot === 0 ? videoRef0.current : videoRef1.current;
|
|
14770
|
+
const currentVideo = activeSlot === 0 ? videoRef0.current : videoRef1.current;
|
|
14755
14771
|
const sceneAfterTarget = (clampedIdx + 1) % scenes.length;
|
|
14772
|
+
if (nextVideo) {
|
|
14773
|
+
nextVideo.currentTime = 0;
|
|
14774
|
+
nextVideo.muted = isMuted;
|
|
14775
|
+
if (isPlaying) {
|
|
14776
|
+
nextVideo.play().catch((err) => {
|
|
14777
|
+
if ((err == null ? void 0 : err.name) === "NotAllowedError") {
|
|
14778
|
+
nextVideo.muted = true;
|
|
14779
|
+
nextVideo.play().catch(() => {
|
|
14780
|
+
});
|
|
14781
|
+
}
|
|
14782
|
+
});
|
|
14783
|
+
}
|
|
14784
|
+
}
|
|
14785
|
+
if (currentVideo) {
|
|
14786
|
+
currentVideo.pause();
|
|
14787
|
+
}
|
|
14756
14788
|
if (nextActiveSlot === 0) {
|
|
14757
14789
|
setSlot0Index(clampedIdx);
|
|
14758
14790
|
setSlot1Index(sceneAfterTarget);
|
|
@@ -14762,7 +14794,7 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14762
14794
|
}
|
|
14763
14795
|
setActiveSlot(nextActiveSlot);
|
|
14764
14796
|
},
|
|
14765
|
-
[scenes, activeSlot, isPlaying, onSceneChange]
|
|
14797
|
+
[scenes, activeSlot, isPlaying, isMuted, onSceneChange]
|
|
14766
14798
|
);
|
|
14767
14799
|
const handlePromptSubmit = React3__namespace.useCallback(
|
|
14768
14800
|
(message, event) => {
|
|
@@ -14787,9 +14819,21 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14787
14819
|
goToScene(nextIdx);
|
|
14788
14820
|
}, [scenes.length, currentSceneIndex, goToScene]);
|
|
14789
14821
|
const handleSceneEnded = React3__namespace.useCallback(() => {
|
|
14790
|
-
if (!scenes.length || !isPlaying) return;
|
|
14822
|
+
if (!scenes.length || !isPlaying || isTransitioningRef.current) return;
|
|
14823
|
+
isTransitioningRef.current = true;
|
|
14791
14824
|
handleNext();
|
|
14792
14825
|
}, [scenes.length, isPlaying, handleNext]);
|
|
14826
|
+
const handleTimeUpdate = React3__namespace.useCallback(
|
|
14827
|
+
(e) => {
|
|
14828
|
+
const video = e.currentTarget;
|
|
14829
|
+
if (!scenes.length || !isPlaying || isTransitioningRef.current) return;
|
|
14830
|
+
if (video.duration > 0.1 && video.currentTime >= video.duration - 0.06) {
|
|
14831
|
+
isTransitioningRef.current = true;
|
|
14832
|
+
handleNext();
|
|
14833
|
+
}
|
|
14834
|
+
},
|
|
14835
|
+
[scenes.length, isPlaying, handleNext]
|
|
14836
|
+
);
|
|
14793
14837
|
const handleDownload = React3__namespace.useCallback(async () => {
|
|
14794
14838
|
var _a3, _b3;
|
|
14795
14839
|
if (onDownload) {
|
|
@@ -14826,7 +14870,6 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14826
14870
|
const idleVideo = activeSlot === 0 ? videoRef1.current : videoRef0.current;
|
|
14827
14871
|
if (idleVideo) {
|
|
14828
14872
|
idleVideo.pause();
|
|
14829
|
-
idleVideo.muted = true;
|
|
14830
14873
|
}
|
|
14831
14874
|
if (!activeVideo) return;
|
|
14832
14875
|
activeVideo.muted = isMuted;
|
|
@@ -14836,10 +14879,9 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14836
14879
|
}
|
|
14837
14880
|
const playPromise = activeVideo.play();
|
|
14838
14881
|
if (playPromise !== void 0) {
|
|
14839
|
-
playPromise.catch(() => {
|
|
14840
|
-
if (
|
|
14882
|
+
playPromise.catch((err) => {
|
|
14883
|
+
if ((err == null ? void 0 : err.name) === "NotAllowedError") {
|
|
14841
14884
|
activeVideo.muted = true;
|
|
14842
|
-
setIsMuted(true);
|
|
14843
14885
|
activeVideo.play().catch(() => {
|
|
14844
14886
|
});
|
|
14845
14887
|
}
|
|
@@ -14848,7 +14890,41 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14848
14890
|
} else {
|
|
14849
14891
|
activeVideo.pause();
|
|
14850
14892
|
}
|
|
14851
|
-
}, [activeSlot, isPlaying, isMuted
|
|
14893
|
+
}, [activeSlot, isPlaying, isMuted]);
|
|
14894
|
+
React3__namespace.useEffect(() => {
|
|
14895
|
+
var _a3;
|
|
14896
|
+
if (scenes.length <= 1) return;
|
|
14897
|
+
const idleVideo = activeSlot === 0 ? videoRef1.current : videoRef0.current;
|
|
14898
|
+
const idleIndex = activeSlot === 0 ? slot1Index : slot0Index;
|
|
14899
|
+
const idleUrl = (_a3 = scenes[idleIndex]) == null ? void 0 : _a3.scene_clip_url;
|
|
14900
|
+
if (idleVideo && idleUrl) {
|
|
14901
|
+
if (idleVideo.src !== idleUrl) {
|
|
14902
|
+
idleVideo.src = idleUrl;
|
|
14903
|
+
}
|
|
14904
|
+
idleVideo.preload = "auto";
|
|
14905
|
+
idleVideo.load();
|
|
14906
|
+
}
|
|
14907
|
+
}, [activeSlot, slot0Index, slot1Index, scenes]);
|
|
14908
|
+
React3__namespace.useEffect(() => {
|
|
14909
|
+
if (!scenes || scenes.length <= 2) return;
|
|
14910
|
+
const toPrefetch = [
|
|
14911
|
+
(currentSceneIndex + 2) % scenes.length,
|
|
14912
|
+
(currentSceneIndex + 3) % scenes.length
|
|
14913
|
+
];
|
|
14914
|
+
toPrefetch.forEach((idx) => {
|
|
14915
|
+
var _a3;
|
|
14916
|
+
const url = (_a3 = scenes[idx]) == null ? void 0 : _a3.scene_clip_url;
|
|
14917
|
+
if (url) {
|
|
14918
|
+
const link = document.createElement("link");
|
|
14919
|
+
link.rel = "prefetch";
|
|
14920
|
+
link.href = url;
|
|
14921
|
+
document.head.appendChild(link);
|
|
14922
|
+
setTimeout(() => {
|
|
14923
|
+
if (link.parentNode) link.parentNode.removeChild(link);
|
|
14924
|
+
}, 15e3);
|
|
14925
|
+
}
|
|
14926
|
+
});
|
|
14927
|
+
}, [currentSceneIndex, scenes]);
|
|
14852
14928
|
React3__namespace.useEffect(() => {
|
|
14853
14929
|
if (!scenes || scenes.length === 0) return;
|
|
14854
14930
|
if (currentSceneIndex >= scenes.length) {
|
|
@@ -14878,7 +14954,7 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14878
14954
|
handleBack == null ? void 0 : handleBack();
|
|
14879
14955
|
} else if (e.key === "m" || e.key === "M") {
|
|
14880
14956
|
e.preventDefault();
|
|
14881
|
-
|
|
14957
|
+
toggleMute();
|
|
14882
14958
|
}
|
|
14883
14959
|
};
|
|
14884
14960
|
window.addEventListener("keydown", handleKeyDown);
|
|
@@ -14983,7 +15059,7 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14983
15059
|
size: "icon",
|
|
14984
15060
|
"data-testid": "btn-toggle-mute",
|
|
14985
15061
|
"aria-label": isMuted ? "Unmute audio" : "Mute audio",
|
|
14986
|
-
onClick:
|
|
15062
|
+
onClick: toggleMute,
|
|
14987
15063
|
className: "h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer",
|
|
14988
15064
|
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: isMuted ? "volume-x" : "volume-2", className: "w-4 h-4" })
|
|
14989
15065
|
}
|
|
@@ -15010,11 +15086,12 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
15010
15086
|
src: (_b2 = scenes[slot0Index]) == null ? void 0 : _b2.scene_clip_url,
|
|
15011
15087
|
preload: "auto",
|
|
15012
15088
|
playsInline: true,
|
|
15013
|
-
muted:
|
|
15089
|
+
muted: isMuted,
|
|
15014
15090
|
onEnded: activeSlot === 0 ? handleSceneEnded : void 0,
|
|
15091
|
+
onTimeUpdate: activeSlot === 0 ? handleTimeUpdate : void 0,
|
|
15015
15092
|
"data-testid": "scene-video-0",
|
|
15016
15093
|
className: cn(
|
|
15017
|
-
"w-full h-full object-contain bg-black absolute inset-0 transition-opacity duration-
|
|
15094
|
+
"w-full h-full object-contain bg-black absolute inset-0 transition-opacity duration-75",
|
|
15018
15095
|
activeSlot === 0 ? "opacity-100 z-10" : "opacity-0 pointer-events-none z-0"
|
|
15019
15096
|
)
|
|
15020
15097
|
}
|
|
@@ -15026,11 +15103,12 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
15026
15103
|
src: (_c = scenes[slot1Index]) == null ? void 0 : _c.scene_clip_url,
|
|
15027
15104
|
preload: "auto",
|
|
15028
15105
|
playsInline: true,
|
|
15029
|
-
muted:
|
|
15106
|
+
muted: isMuted,
|
|
15030
15107
|
onEnded: activeSlot === 1 ? handleSceneEnded : void 0,
|
|
15108
|
+
onTimeUpdate: activeSlot === 1 ? handleTimeUpdate : void 0,
|
|
15031
15109
|
"data-testid": "scene-video-1",
|
|
15032
15110
|
className: cn(
|
|
15033
|
-
"w-full h-full object-contain bg-black absolute inset-0 transition-opacity duration-
|
|
15111
|
+
"w-full h-full object-contain bg-black absolute inset-0 transition-opacity duration-75",
|
|
15034
15112
|
activeSlot === 1 ? "opacity-100 z-10" : "opacity-0 pointer-events-none z-0"
|
|
15035
15113
|
)
|
|
15036
15114
|
}
|