@livepeer-frameworks/player-react 0.1.0 → 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 +7 -9
- package/package.json +1 -1
- package/src/components/DevModePanel.tsx +244 -143
- package/src/components/Icons.tsx +105 -25
- package/src/components/IdleScreen.tsx +262 -128
- package/src/components/LoadingScreen.tsx +169 -151
- package/src/components/LogoOverlay.tsx +3 -6
- package/src/components/Player.tsx +84 -56
- package/src/components/PlayerControls.tsx +349 -256
- 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 +31 -22
- 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 +27 -27
- package/src/hooks/usePlaybackQuality.ts +3 -3
- package/src/hooks/usePlayerController.ts +186 -138
- package/src/hooks/usePlayerSelection.ts +6 -6
- package/src/hooks/useStreamState.ts +51 -56
- package/src/hooks/useTelemetry.ts +18 -3
- package/src/hooks/useViewerEndpoints.ts +34 -23
- package/src/index.tsx +36 -28
- package/src/types.ts +8 -8
- 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
|
);
|
|
@@ -214,45 +223,103 @@ function StatusIcon({ status }: { status?: StreamStatus }) {
|
|
|
214
223
|
const iconClass = "w-5 h-5";
|
|
215
224
|
|
|
216
225
|
// Spinner for loading states
|
|
217
|
-
if (
|
|
226
|
+
if (
|
|
227
|
+
status === "INITIALIZING" ||
|
|
228
|
+
status === "BOOTING" ||
|
|
229
|
+
status === "WAITING_FOR_DATA" ||
|
|
230
|
+
!status
|
|
231
|
+
) {
|
|
218
232
|
return (
|
|
219
|
-
<svg
|
|
220
|
-
|
|
221
|
-
|
|
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
|
+
/>
|
|
222
252
|
</svg>
|
|
223
253
|
);
|
|
224
254
|
}
|
|
225
255
|
|
|
226
256
|
// Offline icon
|
|
227
|
-
if (status ===
|
|
257
|
+
if (status === "OFFLINE") {
|
|
228
258
|
return (
|
|
229
|
-
<svg
|
|
230
|
-
|
|
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
|
+
/>
|
|
231
272
|
</svg>
|
|
232
273
|
);
|
|
233
274
|
}
|
|
234
275
|
|
|
235
276
|
// Error icon
|
|
236
|
-
if (status ===
|
|
277
|
+
if (status === "ERROR" || status === "INVALID") {
|
|
237
278
|
return (
|
|
238
|
-
<svg
|
|
239
|
-
|
|
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
|
+
/>
|
|
240
292
|
</svg>
|
|
241
293
|
);
|
|
242
294
|
}
|
|
243
295
|
|
|
244
296
|
// Default spinner
|
|
245
297
|
return (
|
|
246
|
-
<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
|
+
>
|
|
247
304
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
248
|
-
<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
|
+
/>
|
|
249
310
|
</svg>
|
|
250
311
|
);
|
|
251
312
|
}
|
|
252
313
|
|
|
253
|
-
const StatusOverlay: React.FC<StatusOverlayProps> = ({
|
|
254
|
-
|
|
255
|
-
|
|
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;
|
|
256
323
|
const displayMessage = error || message;
|
|
257
324
|
|
|
258
325
|
return (
|
|
@@ -352,50 +419,51 @@ interface Hitmarker {
|
|
|
352
419
|
const playHitmarkerSound = () => {
|
|
353
420
|
try {
|
|
354
421
|
// Embedded hitmarker sound as base64 data URL
|
|
355
|
-
const hitmarkerDataUrl =
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
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=";
|
|
399
467
|
|
|
400
468
|
const audio = new Audio(hitmarkerDataUrl);
|
|
401
469
|
audio.volume = 0.3;
|
|
@@ -425,8 +493,8 @@ const createSyntheticHitmarkerSound = () => {
|
|
|
425
493
|
oscillator2.frequency.setValueAtTime(3600, audioContext.currentTime);
|
|
426
494
|
oscillator2.frequency.exponentialRampToValueAtTime(1800, audioContext.currentTime + 0.04);
|
|
427
495
|
|
|
428
|
-
oscillator1.type =
|
|
429
|
-
oscillator2.type =
|
|
496
|
+
oscillator1.type = "triangle";
|
|
497
|
+
oscillator2.type = "sine";
|
|
430
498
|
|
|
431
499
|
gainNode1.gain.setValueAtTime(0, audioContext.currentTime);
|
|
432
500
|
gainNode1.gain.linearRampToValueAtTime(0.4, audioContext.currentTime + 0.002);
|
|
@@ -490,19 +558,19 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
490
558
|
y,
|
|
491
559
|
};
|
|
492
560
|
|
|
493
|
-
setHitmarkers(prev => [...prev, newHitmarker]);
|
|
561
|
+
setHitmarkers((prev) => [...prev, newHitmarker]);
|
|
494
562
|
playHitmarkerSound();
|
|
495
563
|
|
|
496
564
|
setTimeout(() => {
|
|
497
|
-
setHitmarkers(prev => prev.filter(h => h.id !== newHitmarker.id));
|
|
565
|
+
setHitmarkers((prev) => prev.filter((h) => h.id !== newHitmarker.id));
|
|
498
566
|
}, 600);
|
|
499
567
|
};
|
|
500
568
|
|
|
501
569
|
// Inject CSS animations
|
|
502
570
|
useEffect(() => {
|
|
503
|
-
const styleId =
|
|
571
|
+
const styleId = "idle-screen-animations";
|
|
504
572
|
if (!document.getElementById(styleId)) {
|
|
505
|
-
const style = document.createElement(
|
|
573
|
+
const style = document.createElement("style");
|
|
506
574
|
style.id = styleId;
|
|
507
575
|
style.textContent = `
|
|
508
576
|
@keyframes fadeInOut {
|
|
@@ -542,11 +610,12 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
542
610
|
<div
|
|
543
611
|
ref={containerRef}
|
|
544
612
|
className="fw-player-root"
|
|
545
|
-
style={
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
613
|
+
style={
|
|
614
|
+
{
|
|
615
|
+
position: "absolute",
|
|
616
|
+
inset: 0,
|
|
617
|
+
zIndex: 5,
|
|
618
|
+
background: `
|
|
550
619
|
linear-gradient(135deg,
|
|
551
620
|
hsl(var(--tn-bg-dark, 235 21% 11%)) 0%,
|
|
552
621
|
hsl(var(--tn-bg, 233 23% 17%)) 25%,
|
|
@@ -555,19 +624,20 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
555
624
|
hsl(var(--tn-bg-dark, 235 21% 11%)) 100%
|
|
556
625
|
)
|
|
557
626
|
`,
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
+
}
|
|
568
638
|
>
|
|
569
639
|
{/* Hitmarkers */}
|
|
570
|
-
{hitmarkers.map(hitmarker => (
|
|
640
|
+
{hitmarkers.map((hitmarker) => (
|
|
571
641
|
<div
|
|
572
642
|
key={hitmarker.id}
|
|
573
643
|
style={{
|
|
@@ -581,10 +651,62 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
581
651
|
height: "40px",
|
|
582
652
|
}}
|
|
583
653
|
>
|
|
584
|
-
<div
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
+
/>
|
|
588
710
|
</div>
|
|
589
711
|
))}
|
|
590
712
|
|
|
@@ -598,7 +720,16 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
598
720
|
width: `${Math.random() * 4 + 2}px`,
|
|
599
721
|
height: `${Math.random() * 4 + 2}px`,
|
|
600
722
|
borderRadius: "50%",
|
|
601
|
-
background: [
|
|
723
|
+
background: [
|
|
724
|
+
"#7aa2f7",
|
|
725
|
+
"#bb9af7",
|
|
726
|
+
"#9ece6a",
|
|
727
|
+
"#73daca",
|
|
728
|
+
"#7dcfff",
|
|
729
|
+
"#f7768e",
|
|
730
|
+
"#e0af68",
|
|
731
|
+
"#2ac3de",
|
|
732
|
+
][index % 8],
|
|
602
733
|
opacity: 0,
|
|
603
734
|
animation: `floatUp ${8 + Math.random() * 4}s linear infinite`,
|
|
604
735
|
animationDelay: `${Math.random() * 8}s`,
|
|
@@ -613,7 +744,10 @@ export const IdleScreen: React.FC<IdleScreenProps> = ({
|
|
|
613
744
|
))}
|
|
614
745
|
|
|
615
746
|
{/* Center logo */}
|
|
616
|
-
<CenterLogo
|
|
747
|
+
<CenterLogo
|
|
748
|
+
containerRef={containerRef as React.RefObject<HTMLDivElement>}
|
|
749
|
+
onHitmarker={createHitmarker}
|
|
750
|
+
/>
|
|
617
751
|
|
|
618
752
|
{/* DVD Logo */}
|
|
619
753
|
<DvdLogo parentRef={containerRef as React.RefObject<HTMLDivElement>} scale={0.08} />
|