@livepeer-frameworks/player-react 0.0.4 → 0.1.1
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/README.md +16 -5
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/types/components/PlayerControls.d.ts +2 -0
- package/dist/types/components/StatsPanel.d.ts +2 -14
- package/dist/types/hooks/useMetaTrack.d.ts +1 -1
- package/dist/types/hooks/usePlayerController.d.ts +2 -0
- package/dist/types/hooks/useStreamState.d.ts +1 -1
- package/dist/types/hooks/useTelemetry.d.ts +1 -1
- package/dist/types/hooks/useViewerEndpoints.d.ts +2 -2
- package/dist/types/types.d.ts +1 -1
- package/dist/types/ui/button.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/DevModePanel.tsx +249 -170
- package/src/components/Icons.tsx +105 -25
- package/src/components/IdleScreen.tsx +262 -142
- package/src/components/LoadingScreen.tsx +171 -153
- package/src/components/LogoOverlay.tsx +3 -6
- package/src/components/Player.tsx +86 -74
- package/src/components/PlayerControls.tsx +351 -263
- package/src/components/PlayerErrorBoundary.tsx +6 -13
- package/src/components/SeekBar.tsx +96 -88
- package/src/components/SkipIndicator.tsx +2 -12
- package/src/components/SpeedIndicator.tsx +2 -11
- package/src/components/StatsPanel.tsx +65 -34
- package/src/components/StreamStateOverlay.tsx +105 -49
- package/src/components/SubtitleRenderer.tsx +29 -29
- package/src/components/ThumbnailOverlay.tsx +5 -6
- package/src/components/TitleOverlay.tsx +2 -8
- package/src/components/players/DashJsPlayer.tsx +13 -11
- package/src/components/players/HlsJsPlayer.tsx +13 -11
- package/src/components/players/MewsWsPlayer/index.tsx +13 -11
- package/src/components/players/MistPlayer.tsx +13 -11
- package/src/components/players/MistWebRTCPlayer/index.tsx +19 -10
- package/src/components/players/NativePlayer.tsx +10 -12
- package/src/components/players/VideoJsPlayer.tsx +13 -11
- package/src/context/PlayerContext.tsx +4 -8
- package/src/context/index.ts +3 -3
- package/src/hooks/useMetaTrack.ts +28 -28
- package/src/hooks/usePlaybackQuality.ts +3 -3
- package/src/hooks/usePlayerController.ts +186 -140
- package/src/hooks/usePlayerSelection.ts +6 -6
- package/src/hooks/useStreamState.ts +53 -58
- package/src/hooks/useTelemetry.ts +19 -4
- package/src/hooks/useViewerEndpoints.ts +40 -30
- package/src/index.tsx +36 -28
- package/src/types.ts +9 -9
- package/src/ui/badge.tsx +6 -5
- package/src/ui/button.tsx +9 -8
- package/src/ui/context-menu.tsx +42 -61
- package/src/ui/select.tsx +13 -7
- package/src/ui/slider.tsx +18 -29
|
@@ -29,16 +29,19 @@ const AnimatedBubble: React.FC<AnimatedBubbleProps> = ({ index }) => {
|
|
|
29
29
|
|
|
30
30
|
const animationCycle = () => {
|
|
31
31
|
setOpacity(0.15);
|
|
32
|
-
setTimeout(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
setPosition(getRandomPosition());
|
|
36
|
-
setSize(getRandomSize());
|
|
32
|
+
setTimeout(
|
|
33
|
+
() => {
|
|
34
|
+
setOpacity(0);
|
|
37
35
|
setTimeout(() => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
setPosition(getRandomPosition());
|
|
37
|
+
setSize(getRandomSize());
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
animationCycle();
|
|
40
|
+
}, 200);
|
|
41
|
+
}, 1500);
|
|
42
|
+
},
|
|
43
|
+
4000 + Math.random() * 3000
|
|
44
|
+
);
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
const timeout = setTimeout(animationCycle, index * 500);
|
|
@@ -58,19 +61,21 @@ const AnimatedBubble: React.FC<AnimatedBubbleProps> = ({ index }) => {
|
|
|
58
61
|
|
|
59
62
|
return (
|
|
60
63
|
<div
|
|
61
|
-
style={
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
style={
|
|
65
|
+
{
|
|
66
|
+
position: "absolute",
|
|
67
|
+
top: `${position.top}%`,
|
|
68
|
+
left: `${position.left}%`,
|
|
69
|
+
width: `${size}px`,
|
|
70
|
+
height: `${size}px`,
|
|
71
|
+
borderRadius: "50%",
|
|
72
|
+
background: bubbleColors[index % bubbleColors.length],
|
|
73
|
+
opacity,
|
|
74
|
+
transition: "opacity 1s ease-in-out",
|
|
75
|
+
pointerEvents: "none",
|
|
76
|
+
userSelect: "none",
|
|
77
|
+
} as React.CSSProperties
|
|
78
|
+
}
|
|
74
79
|
/>
|
|
75
80
|
);
|
|
76
81
|
};
|
|
@@ -138,11 +143,11 @@ const CenterLogo: React.FC<CenterLogoProps> = ({ containerRef, scale = 0.2, onHi
|
|
|
138
143
|
useEffect(() => {
|
|
139
144
|
if (containerRef.current) {
|
|
140
145
|
const container = containerRef.current;
|
|
141
|
-
container.addEventListener(
|
|
142
|
-
container.addEventListener(
|
|
146
|
+
container.addEventListener("mousemove", handleMouseMove);
|
|
147
|
+
container.addEventListener("mouseleave", handleMouseLeave);
|
|
143
148
|
return () => {
|
|
144
|
-
container.removeEventListener(
|
|
145
|
-
container.removeEventListener(
|
|
149
|
+
container.removeEventListener("mousemove", handleMouseMove);
|
|
150
|
+
container.removeEventListener("mouseleave", handleMouseLeave);
|
|
146
151
|
};
|
|
147
152
|
}
|
|
148
153
|
}, [logoSize, containerRef]);
|
|
@@ -169,7 +174,9 @@ const CenterLogo: React.FC<CenterLogoProps> = ({ containerRef, scale = 0.2, onHi
|
|
|
169
174
|
height: `${logoSize * 1.4}px`,
|
|
170
175
|
borderRadius: "50%",
|
|
171
176
|
background: "rgba(122, 162, 247, 0.15)",
|
|
172
|
-
animation: isHovered
|
|
177
|
+
animation: isHovered
|
|
178
|
+
? "logoPulse 1s ease-in-out infinite"
|
|
179
|
+
: "logoPulse 3s ease-in-out infinite",
|
|
173
180
|
transform: isHovered ? "scale(1.2)" : "scale(1)",
|
|
174
181
|
transition: "transform 0.3s ease-out",
|
|
175
182
|
pointerEvents: "none",
|
|
@@ -179,20 +186,22 @@ const CenterLogo: React.FC<CenterLogoProps> = ({ containerRef, scale = 0.2, onHi
|
|
|
179
186
|
src={logomarkAsset}
|
|
180
187
|
alt="FrameWorks Logo"
|
|
181
188
|
onClick={handleLogoClick}
|
|
182
|
-
style={
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
189
|
+
style={
|
|
190
|
+
{
|
|
191
|
+
width: `${logoSize}px`,
|
|
192
|
+
height: `${logoSize}px`,
|
|
193
|
+
position: "relative",
|
|
194
|
+
zIndex: 1,
|
|
195
|
+
filter: isHovered
|
|
196
|
+
? "drop-shadow(0 6px 12px rgba(36, 40, 59, 0.4)) brightness(1.1)"
|
|
197
|
+
: "drop-shadow(0 4px 8px rgba(36, 40, 59, 0.3))",
|
|
198
|
+
transform: isHovered ? "scale(1.1)" : "scale(1)",
|
|
199
|
+
transition: "all 0.3s ease-out",
|
|
200
|
+
cursor: isHovered ? "pointer" : "default",
|
|
201
|
+
userSelect: "none",
|
|
202
|
+
WebkitUserDrag: "none",
|
|
203
|
+
} as React.CSSProperties
|
|
204
|
+
}
|
|
196
205
|
/>
|
|
197
206
|
</div>
|
|
198
207
|
);
|
|
@@ -210,63 +219,107 @@ interface StatusOverlayProps {
|
|
|
210
219
|
onRetry?: () => void;
|
|
211
220
|
}
|
|
212
221
|
|
|
213
|
-
function getStatusLabel(status?: StreamStatus): string {
|
|
214
|
-
switch (status) {
|
|
215
|
-
case 'ONLINE': return 'ONLINE';
|
|
216
|
-
case 'OFFLINE': return 'OFFLINE';
|
|
217
|
-
case 'INITIALIZING': return 'STARTING';
|
|
218
|
-
case 'BOOTING': return 'STARTING';
|
|
219
|
-
case 'WAITING_FOR_DATA': return 'WAITING';
|
|
220
|
-
case 'SHUTTING_DOWN': return 'ENDING';
|
|
221
|
-
case 'ERROR': return 'ERROR';
|
|
222
|
-
case 'INVALID': return 'ERROR';
|
|
223
|
-
default: return 'CONNECTING';
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
222
|
function StatusIcon({ status }: { status?: StreamStatus }) {
|
|
228
223
|
const iconClass = "w-5 h-5";
|
|
229
224
|
|
|
230
225
|
// Spinner for loading states
|
|
231
|
-
if (
|
|
226
|
+
if (
|
|
227
|
+
status === "INITIALIZING" ||
|
|
228
|
+
status === "BOOTING" ||
|
|
229
|
+
status === "WAITING_FOR_DATA" ||
|
|
230
|
+
!status
|
|
231
|
+
) {
|
|
232
232
|
return (
|
|
233
|
-
<svg
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
<svg
|
|
234
|
+
className={`${iconClass} animate-spin`}
|
|
235
|
+
fill="none"
|
|
236
|
+
viewBox="0 0 24 24"
|
|
237
|
+
style={{ color: "hsl(var(--tn-yellow, 40 95% 64%))" }}
|
|
238
|
+
>
|
|
239
|
+
<circle
|
|
240
|
+
className="opacity-25"
|
|
241
|
+
cx="12"
|
|
242
|
+
cy="12"
|
|
243
|
+
r="10"
|
|
244
|
+
stroke="currentColor"
|
|
245
|
+
strokeWidth="4"
|
|
246
|
+
/>
|
|
247
|
+
<path
|
|
248
|
+
className="opacity-75"
|
|
249
|
+
fill="currentColor"
|
|
250
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
251
|
+
/>
|
|
236
252
|
</svg>
|
|
237
253
|
);
|
|
238
254
|
}
|
|
239
255
|
|
|
240
256
|
// Offline icon
|
|
241
|
-
if (status ===
|
|
257
|
+
if (status === "OFFLINE") {
|
|
242
258
|
return (
|
|
243
|
-
<svg
|
|
244
|
-
|
|
259
|
+
<svg
|
|
260
|
+
className={iconClass}
|
|
261
|
+
fill="none"
|
|
262
|
+
viewBox="0 0 24 24"
|
|
263
|
+
stroke="currentColor"
|
|
264
|
+
style={{ color: "hsl(var(--tn-red, 348 100% 72%))" }}
|
|
265
|
+
>
|
|
266
|
+
<path
|
|
267
|
+
strokeLinecap="round"
|
|
268
|
+
strokeLinejoin="round"
|
|
269
|
+
strokeWidth={2}
|
|
270
|
+
d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414"
|
|
271
|
+
/>
|
|
245
272
|
</svg>
|
|
246
273
|
);
|
|
247
274
|
}
|
|
248
275
|
|
|
249
276
|
// Error icon
|
|
250
|
-
if (status ===
|
|
277
|
+
if (status === "ERROR" || status === "INVALID") {
|
|
251
278
|
return (
|
|
252
|
-
<svg
|
|
253
|
-
|
|
279
|
+
<svg
|
|
280
|
+
className={iconClass}
|
|
281
|
+
fill="none"
|
|
282
|
+
viewBox="0 0 24 24"
|
|
283
|
+
stroke="currentColor"
|
|
284
|
+
style={{ color: "hsl(var(--tn-red, 348 100% 72%))" }}
|
|
285
|
+
>
|
|
286
|
+
<path
|
|
287
|
+
strokeLinecap="round"
|
|
288
|
+
strokeLinejoin="round"
|
|
289
|
+
strokeWidth={2}
|
|
290
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
291
|
+
/>
|
|
254
292
|
</svg>
|
|
255
293
|
);
|
|
256
294
|
}
|
|
257
295
|
|
|
258
296
|
// Default spinner
|
|
259
297
|
return (
|
|
260
|
-
<svg
|
|
298
|
+
<svg
|
|
299
|
+
className={`${iconClass} animate-spin`}
|
|
300
|
+
fill="none"
|
|
301
|
+
viewBox="0 0 24 24"
|
|
302
|
+
style={{ color: "hsl(var(--tn-cyan, 193 100% 75%))" }}
|
|
303
|
+
>
|
|
261
304
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
262
|
-
<path
|
|
305
|
+
<path
|
|
306
|
+
className="opacity-75"
|
|
307
|
+
fill="currentColor"
|
|
308
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
309
|
+
/>
|
|
263
310
|
</svg>
|
|
264
311
|
);
|
|
265
312
|
}
|
|
266
313
|
|
|
267
|
-
const StatusOverlay: React.FC<StatusOverlayProps> = ({
|
|
268
|
-
|
|
269
|
-
|
|
314
|
+
const StatusOverlay: React.FC<StatusOverlayProps> = ({
|
|
315
|
+
status,
|
|
316
|
+
message,
|
|
317
|
+
percentage,
|
|
318
|
+
error,
|
|
319
|
+
onRetry,
|
|
320
|
+
}) => {
|
|
321
|
+
const showRetry = (status === "ERROR" || status === "INVALID") && onRetry;
|
|
322
|
+
const showProgress = status === "INITIALIZING" && percentage !== undefined;
|
|
270
323
|
const displayMessage = error || message;
|
|
271
324
|
|
|
272
325
|
return (
|
|
@@ -366,50 +419,51 @@ interface Hitmarker {
|
|
|
366
419
|
const playHitmarkerSound = () => {
|
|
367
420
|
try {
|
|
368
421
|
// Embedded hitmarker sound as base64 data URL
|
|
369
|
-
const hitmarkerDataUrl =
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
422
|
+
const hitmarkerDataUrl =
|
|
423
|
+
"data:audio/mpeg;base64,SUQzBAAAAAAANFRDT04AAAAHAAADT3RoZXIAVFNTRQAAAA8AAANMYXZmNTcuODMuMTAwAAAAAAAAAAAA" +
|
|
424
|
+
"AAD/+1QAAAAAAAAAAAAAAAAAAAAA" +
|
|
425
|
+
"AAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAAAYAAAnAADs7Ozs7Ozs7Ozs7Ozs7OztiYmJiYmJiYmJi" +
|
|
426
|
+
"YmJiYmJiYomJiYmJiYmJiYmJiYmJiYmxsbGxsbGxsbGxsbGxsbGxsdjY2NjY2NjY2NjY2NjY2NjY////" +
|
|
427
|
+
"/////////////////wAAAABMYXZjNTcuMTAAAAAAAAAAAAAAAAAkAkAAAAAAAAAJwOuMZun/+5RkAA8S" +
|
|
428
|
+
"/F23AGAaAi0AF0AAAAAInXsEAIRXyQ8D4OQgjEhE3cO7ujuHF0XCOu4G7xKbi3Funu7u7p9dw7unu7u7" +
|
|
429
|
+
"p7u7u6fXcW7om7u7uiU3dxdT67u7p7uHdxelN3cW6fXcW7oXXd3eJTd3d0+u4t3iXdw4up70W4uiPruL" +
|
|
430
|
+
"DzMw8Pz79Y99JfkyfPv5/h9uTJoy79Y99Y97q3vyZPJk0ZfrL6x73Vn+J35dKKS/STQyQ8CAiCPNuRAO" +
|
|
431
|
+
"OqquAx+fzJeBKDAsgAMBuWcBsHKhjJTcCwIALyAvABbI0ZIcCmP8jHJe8gZAdVRp2TpnU/kUXV4iQuBA" +
|
|
432
|
+
"AkAQgisLPvwQ2Jz7wIkIpQ8QOl/KFy75w+2HpTFnRqXLQo0fzlSYRe5Ce9yZMEzRM4xesu95Mo8QQsoM" +
|
|
433
|
+
"H4gLg+fJqkmY3GZJE2kwGfMECJiAdIttoEa2yotfC7jsS2mjKgbzAfEMeiwZpGSUFCQwPKQiWXh0TnkN" +
|
|
434
|
+
"or5SmrKvwHlX2zFxKxPCzRL/+5RkIwADvUxLawwb0GdF6Y1hJlgNNJk+DSRwyQwI6AD2JCiBmhaff0dz" +
|
|
435
|
+
"CEBjgFABAcDNFc3YAEV4hQn0L/QvQnevom+n13eIjoTvABLrHg/L9RzdWXYonHbbbE2K0pX+gkL2g56R" +
|
|
436
|
+
"iwrbuWwhoABzQoMKOAIGAfE4UKk6BhSIJpECBq0CEYmZKYIiAJt72H24dNou7y/Ee7a/3v+MgySemSTY" +
|
|
437
|
+
"mnBAFwIAAGfCJ8/D9YfkwQEBcP38uA1d/EB1T5dZKEsgnuhwZirY5fIMRMdRn7U4OcN2m5NWeYdcPBwX" +
|
|
438
|
+
"DBOsJF1DBYks62pAURqz1hGoGHH/QIoRC80tYAJ8g4f3MPD51sywAbhAn/X9P/75tvZww3gZ3pYPDx/+" +
|
|
439
|
+
"ACO/7//ffHj/D/AAfATC4DYGFA3MRABo0lqWjBOl2yAda1C1BdhduXgm8FGnAQB/lDiEi6j9qw9EHigI" +
|
|
440
|
+
"IOLB6F1eIPd+T6Agc4//lMo6+k3tdttJY2gArU7cN07m2FLSm4gCjyz/+5RECwACwSRZawkdLFGi2mVh" +
|
|
441
|
+
"5h4LfFdPVPGACViTavaeMAAV0UkkEsDhxxJwqF04on002mZah8w9+5ItfSAoyZa1dchnPpLmAEKrVMRA" +
|
|
442
|
+
"//sD8w0WsB4xiw4JqaZMB45TdpIuXXUPf8Bpa35p/jQIAOAuZkmUeJoM5W6L2gqqO6rTuHjUTDnhy4Qi" +
|
|
443
|
+
"K348vtFysOizShoHbBpsPRYcSINCbiN4XOLPPAgq3dW2Ga7SlyiKXBV7W1RQl5BiiVGkwayJfEnPxgXk" +
|
|
444
|
+
"QeZxxzyhTuLO2XFUDDstoc6CkM1J8QZAjUN3bM8580cRygNfmPAELGjIH0Z/0A+8csyH/4eHvgAf8APg" +
|
|
445
|
+
"ABmZ98AARAADP////Dw8PHEmIpgGttpJQJsmZjq5nPQ8j5VqWW1evqdjP182PA6tHJZgkC5iSbEQkyJS" +
|
|
446
|
+
"z/BvP3eucLKN0+Wiza4feKKFBqiAEBAMXyYni5NZc16CDl/QY9j6BAcWSmQYcIcoMHYoQNBiIBgIBUAz" +
|
|
447
|
+
"QUMSnjj/+5RkCwADsFLffjEAAjrJe63JHACO6WtlnPMACKaCK1uMMADU5dI6JhW2cam98UlRmY4ihyKF" +
|
|
448
|
+
"rNsgpZd5PYgBALnYofKEt82De0GbW1DLibvFDK+bSeOm8qKdqUFZ7uiK8XMPHyqm3pTxUvcunUfxXEo9" +
|
|
449
|
+
"RNe5b/8vfCD3kzDN7vTtHyaIcntVDAYBAUBAAAAQBI2vguYNsHWm5AR3mZtZib8WAHFvz2Kf9//iYvlR" +
|
|
450
|
+
"B/+n///////////+UH7XoIDMoJAEAMtj8JshJPRwklVqNSpYnalfE+VzNCAISCoxVHEpIo/WrTiMvP7V" +
|
|
451
|
+
"TujOPnOglLbMLN/pq/d2Y4lRJIkSnPlUSJEjSKJqM41d88zWtMzP+fCOORmc9NeM+f1nnO//efM52/fG" +
|
|
452
|
+
"/ef385+5u+u1bRJkwU8FAkEItZpkRYeQYcAgZTEYlaZa2yROLeC0qdX73rZJJ/d2f6v6Or0u/+5FBYcn" +
|
|
453
|
+
"g0MlCiQTR9GUU5LScmSuSlH00IWqXA6jlw4BEcD/+5REEAAi3RtU+eYbGF1E+lk9g0YJzLUgh7BlQVGT" +
|
|
454
|
+
"ZJD0jKhhTNVilqrMzFRK+x/szcMKBWKep4NP1A0DR6RESkTp5Z1Q9Y8REgqMg1DpUBPleeqlRQcerBpM" +
|
|
455
|
+
"jiURHVD4XwAALhAgbxxlxYD5OFkG8oQRPB2EpsxSCNVlgcYUqoAyiVJmaARlkwplICfPoUy/zWEzM2pc" +
|
|
456
|
+
"NYzAQNJDSniEYecSEqxFEzQqEvUFGnvzwUfcRlpZ9T2LCR5QdDQDDhKICAjpJCagpRo9UQRPClZZlg6E" +
|
|
457
|
+
"p9DMTkTl+okuhRIVIzAQEf9L+Mx/DUjqmqN6kX7M36lS4zgLyJV3iV6j3xF8kJduJawVw1nndAlBaLLg" +
|
|
458
|
+
"JupwsTcLkxmJgFLgSzoCmHjSNGSqkGPCpnNqTXIwolf6qlVWN+q/su37HzgrES1pWGg3KnWh0FXCVniJ" +
|
|
459
|
+
"9K5b4iCrpLEuIcFTqwkVLFiqgaDqCCSMVWqxBAVCFOLVrVahm2ahUThUKJnmFCw15hD0Qhb/+5REEAhC" +
|
|
460
|
+
"YSRCSQEb4FOGaBUMI6JIRYC0QIB2SQsgGpgwDghgIlS6FU8VBXDoiBp5Y9gtkVnhEhYBdJFQ7kQ3w1yp" +
|
|
461
|
+
"0NB2CoNPEttZ1/aeDUAAA26FEghWgEKNVAVWkFAQEmMK2Uwk/qI0hqUb/4epVIZH1ai6szf6kzH1f2ar" +
|
|
462
|
+
"xYGS9FcOsN5UlJLQt///+oo0FRDTUQ0FBQr9f5LxXP+mEUfk0AIrf/5GRmQ0//mX//ZbLP5b5GrWSz+W" +
|
|
463
|
+
"SkZMrWyyyy2GRqyggVRyMv////////st//sn/yyVDI1l8mVgoYGDCOqiqIQBxmvxWCggTpZZZD//aWfy" +
|
|
464
|
+
"yWf/y/7KGDA0ssBggTof9k/+WS/8slQyMp/5Nfln8WAqGcUbULCrKxT9ISF+kKsxQWpMQU1FMy4xMDCq" +
|
|
465
|
+
"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" +
|
|
466
|
+
"qqqqqqqqqqqqqqqqqqqqqqqqqqo=";
|
|
413
467
|
|
|
414
468
|
const audio = new Audio(hitmarkerDataUrl);
|
|
415
469
|
audio.volume = 0.3;
|
|
@@ -439,8 +493,8 @@ const createSyntheticHitmarkerSound = () => {
|
|
|
439
493
|
oscillator2.frequency.setValueAtTime(3600, audioContext.currentTime);
|
|
440
494
|
oscillator2.frequency.exponentialRampToValueAtTime(1800, audioContext.currentTime + 0.04);
|
|
441
495
|
|
|
442
|
-
oscillator1.type =
|
|
443
|
-
oscillator2.type =
|
|
496
|
+
oscillator1.type = "triangle";
|
|
497
|
+
oscillator2.type = "sine";
|
|
444
498
|
|
|
445
499
|
gainNode1.gain.setValueAtTime(0, audioContext.currentTime);
|
|
446
500
|
gainNode1.gain.linearRampToValueAtTime(0.4, audioContext.currentTime + 0.002);
|
|
@@ -504,19 +558,19 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
504
558
|
y,
|
|
505
559
|
};
|
|
506
560
|
|
|
507
|
-
setHitmarkers(prev => [...prev, newHitmarker]);
|
|
561
|
+
setHitmarkers((prev) => [...prev, newHitmarker]);
|
|
508
562
|
playHitmarkerSound();
|
|
509
563
|
|
|
510
564
|
setTimeout(() => {
|
|
511
|
-
setHitmarkers(prev => prev.filter(h => h.id !== newHitmarker.id));
|
|
565
|
+
setHitmarkers((prev) => prev.filter((h) => h.id !== newHitmarker.id));
|
|
512
566
|
}, 600);
|
|
513
567
|
};
|
|
514
568
|
|
|
515
569
|
// Inject CSS animations
|
|
516
570
|
useEffect(() => {
|
|
517
|
-
const styleId =
|
|
571
|
+
const styleId = "idle-screen-animations";
|
|
518
572
|
if (!document.getElementById(styleId)) {
|
|
519
|
-
const style = document.createElement(
|
|
573
|
+
const style = document.createElement("style");
|
|
520
574
|
style.id = styleId;
|
|
521
575
|
style.textContent = `
|
|
522
576
|
@keyframes fadeInOut {
|
|
@@ -556,11 +610,12 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
556
610
|
<div
|
|
557
611
|
ref={containerRef}
|
|
558
612
|
className="fw-player-root"
|
|
559
|
-
style={
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
613
|
+
style={
|
|
614
|
+
{
|
|
615
|
+
position: "absolute",
|
|
616
|
+
inset: 0,
|
|
617
|
+
zIndex: 5,
|
|
618
|
+
background: `
|
|
564
619
|
linear-gradient(135deg,
|
|
565
620
|
hsl(var(--tn-bg-dark, 235 21% 11%)) 0%,
|
|
566
621
|
hsl(var(--tn-bg, 233 23% 17%)) 25%,
|
|
@@ -569,19 +624,20 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
569
624
|
hsl(var(--tn-bg-dark, 235 21% 11%)) 100%
|
|
570
625
|
)
|
|
571
626
|
`,
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
627
|
+
backgroundSize: "400% 400%",
|
|
628
|
+
animation: "gradientShift 16s ease-in-out infinite",
|
|
629
|
+
display: "flex",
|
|
630
|
+
flexDirection: "column",
|
|
631
|
+
alignItems: "center",
|
|
632
|
+
justifyContent: "center",
|
|
633
|
+
overflow: "hidden",
|
|
634
|
+
borderRadius: "0",
|
|
635
|
+
userSelect: "none",
|
|
636
|
+
} as React.CSSProperties
|
|
637
|
+
}
|
|
582
638
|
>
|
|
583
639
|
{/* Hitmarkers */}
|
|
584
|
-
{hitmarkers.map(hitmarker => (
|
|
640
|
+
{hitmarkers.map((hitmarker) => (
|
|
585
641
|
<div
|
|
586
642
|
key={hitmarker.id}
|
|
587
643
|
style={{
|
|
@@ -595,10 +651,62 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
595
651
|
height: "40px",
|
|
596
652
|
}}
|
|
597
653
|
>
|
|
598
|
-
<div
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
654
|
+
<div
|
|
655
|
+
style={{
|
|
656
|
+
position: "absolute",
|
|
657
|
+
top: "25%",
|
|
658
|
+
left: "25%",
|
|
659
|
+
width: "12px",
|
|
660
|
+
height: "3px",
|
|
661
|
+
backgroundColor: "#ffffff",
|
|
662
|
+
transform: "translate(-50%, -50%) rotate(45deg)",
|
|
663
|
+
animation: "hitmarkerFade45 0.6s ease-out forwards",
|
|
664
|
+
boxShadow: "0 0 8px rgba(255, 255, 255, 0.8)",
|
|
665
|
+
borderRadius: "1px",
|
|
666
|
+
}}
|
|
667
|
+
/>
|
|
668
|
+
<div
|
|
669
|
+
style={{
|
|
670
|
+
position: "absolute",
|
|
671
|
+
top: "25%",
|
|
672
|
+
left: "75%",
|
|
673
|
+
width: "12px",
|
|
674
|
+
height: "3px",
|
|
675
|
+
backgroundColor: "#ffffff",
|
|
676
|
+
transform: "translate(-50%, -50%) rotate(-45deg)",
|
|
677
|
+
animation: "hitmarkerFadeNeg45 0.6s ease-out forwards",
|
|
678
|
+
boxShadow: "0 0 8px rgba(255, 255, 255, 0.8)",
|
|
679
|
+
borderRadius: "1px",
|
|
680
|
+
}}
|
|
681
|
+
/>
|
|
682
|
+
<div
|
|
683
|
+
style={{
|
|
684
|
+
position: "absolute",
|
|
685
|
+
top: "75%",
|
|
686
|
+
left: "25%",
|
|
687
|
+
width: "12px",
|
|
688
|
+
height: "3px",
|
|
689
|
+
backgroundColor: "#ffffff",
|
|
690
|
+
transform: "translate(-50%, -50%) rotate(-45deg)",
|
|
691
|
+
animation: "hitmarkerFadeNeg45 0.6s ease-out forwards",
|
|
692
|
+
boxShadow: "0 0 8px rgba(255, 255, 255, 0.8)",
|
|
693
|
+
borderRadius: "1px",
|
|
694
|
+
}}
|
|
695
|
+
/>
|
|
696
|
+
<div
|
|
697
|
+
style={{
|
|
698
|
+
position: "absolute",
|
|
699
|
+
top: "75%",
|
|
700
|
+
left: "75%",
|
|
701
|
+
width: "12px",
|
|
702
|
+
height: "3px",
|
|
703
|
+
backgroundColor: "#ffffff",
|
|
704
|
+
transform: "translate(-50%, -50%) rotate(45deg)",
|
|
705
|
+
animation: "hitmarkerFade45 0.6s ease-out forwards",
|
|
706
|
+
boxShadow: "0 0 8px rgba(255, 255, 255, 0.8)",
|
|
707
|
+
borderRadius: "1px",
|
|
708
|
+
}}
|
|
709
|
+
/>
|
|
602
710
|
</div>
|
|
603
711
|
))}
|
|
604
712
|
|
|
@@ -612,7 +720,16 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
612
720
|
width: `${Math.random() * 4 + 2}px`,
|
|
613
721
|
height: `${Math.random() * 4 + 2}px`,
|
|
614
722
|
borderRadius: "50%",
|
|
615
|
-
background: [
|
|
723
|
+
background: [
|
|
724
|
+
"#7aa2f7",
|
|
725
|
+
"#bb9af7",
|
|
726
|
+
"#9ece6a",
|
|
727
|
+
"#73daca",
|
|
728
|
+
"#7dcfff",
|
|
729
|
+
"#f7768e",
|
|
730
|
+
"#e0af68",
|
|
731
|
+
"#2ac3de",
|
|
732
|
+
][index % 8],
|
|
616
733
|
opacity: 0,
|
|
617
734
|
animation: `floatUp ${8 + Math.random() * 4}s linear infinite`,
|
|
618
735
|
animationDelay: `${Math.random() * 8}s`,
|
|
@@ -627,7 +744,10 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
627
744
|
))}
|
|
628
745
|
|
|
629
746
|
{/* Center logo */}
|
|
630
|
-
<CenterLogo
|
|
747
|
+
<CenterLogo
|
|
748
|
+
containerRef={containerRef as React.RefObject<HTMLDivElement>}
|
|
749
|
+
onHitmarker={createHitmarker}
|
|
750
|
+
/>
|
|
631
751
|
|
|
632
752
|
{/* DVD Logo */}
|
|
633
753
|
<DvdLogo parentRef={containerRef as React.RefObject<HTMLDivElement>} scale={0.08} />
|