kodenique-game-sdk 1.0.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.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/GameContext.d.ts +4 -0
- package/dist/GameContext.js +327 -0
- package/dist/GameDebug.d.ts +5 -0
- package/dist/GameDebug.js +172 -0
- package/dist/GamePlayer.d.ts +26 -0
- package/dist/GamePlayer.js +54 -0
- package/dist/SimplePlayer.d.ts +11 -0
- package/dist/SimplePlayer.js +41 -0
- package/dist/components/GamePlayerOverlays.d.ts +29 -0
- package/dist/components/GamePlayerOverlays.js +467 -0
- package/dist/components/GamePlayerVideo.d.ts +7 -0
- package/dist/components/GamePlayerVideo.js +86 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/config.d.ts +50 -0
- package/dist/config.js +46 -0
- package/dist/contexts/GameStreamContext.d.ts +24 -0
- package/dist/contexts/GameStreamContext.js +170 -0
- package/dist/examples/GameStreamExample.d.ts +26 -0
- package/dist/examples/GameStreamExample.js +92 -0
- package/dist/examples/SimpleAutoSubscribe.d.ts +9 -0
- package/dist/examples/SimpleAutoSubscribe.js +29 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useGameStream.d.ts +29 -0
- package/dist/hooks/useGameStream.js +78 -0
- package/dist/hooks/useWebRTC.d.ts +21 -0
- package/dist/hooks/useWebRTC.js +555 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/lib/pusher.d.ts +50 -0
- package/dist/lib/pusher.js +137 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.js +1 -0
- package/dist/useGames.d.ts +2 -0
- package/dist/useGames.js +73 -0
- package/package.json +66 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export const NetworkIndicator = ({ networkQuality, latency, bitrate, }) => {
|
|
3
|
+
const qualityLabel = {
|
|
4
|
+
good: "Excellent",
|
|
5
|
+
fair: "Fair",
|
|
6
|
+
poor: "Poor",
|
|
7
|
+
bad: "Bad",
|
|
8
|
+
}[networkQuality];
|
|
9
|
+
return (React.createElement("div", { style: {
|
|
10
|
+
position: "absolute",
|
|
11
|
+
top: "15px",
|
|
12
|
+
right: "15px",
|
|
13
|
+
display: "flex",
|
|
14
|
+
alignItems: "center",
|
|
15
|
+
gap: "8px",
|
|
16
|
+
background: "rgba(0, 0, 0, 0.7)",
|
|
17
|
+
padding: "8px 12px",
|
|
18
|
+
borderRadius: "20px",
|
|
19
|
+
fontSize: "12px",
|
|
20
|
+
color: "#fff",
|
|
21
|
+
zIndex: 5,
|
|
22
|
+
} },
|
|
23
|
+
React.createElement("div", { style: {
|
|
24
|
+
display: "flex",
|
|
25
|
+
alignItems: "flex-end",
|
|
26
|
+
gap: "2px",
|
|
27
|
+
height: "16px",
|
|
28
|
+
} }, [4, 8, 12, 16].map((height, i) => (React.createElement("div", { key: i, style: {
|
|
29
|
+
width: "4px",
|
|
30
|
+
height: `${height}px`,
|
|
31
|
+
background: networkQuality === "good"
|
|
32
|
+
? "#4ade80"
|
|
33
|
+
: networkQuality === "fair" && i < 3
|
|
34
|
+
? "#fbbf24"
|
|
35
|
+
: networkQuality === "poor" && i < 2
|
|
36
|
+
? "#ff6b6b"
|
|
37
|
+
: networkQuality === "bad" && i < 1
|
|
38
|
+
? "#ff6b6b"
|
|
39
|
+
: "#666",
|
|
40
|
+
borderRadius: "1px",
|
|
41
|
+
transition: "background 0.3s",
|
|
42
|
+
} })))),
|
|
43
|
+
React.createElement("span", null, qualityLabel),
|
|
44
|
+
latency > 0 && React.createElement("span", null,
|
|
45
|
+
"~",
|
|
46
|
+
latency,
|
|
47
|
+
"ms"),
|
|
48
|
+
bitrate > 0 && React.createElement("span", null,
|
|
49
|
+
bitrate,
|
|
50
|
+
"kbps")));
|
|
51
|
+
};
|
|
52
|
+
export const ErrorOverlay = ({ errorMessage, onRetry, }) => {
|
|
53
|
+
return (React.createElement("div", { style: {
|
|
54
|
+
position: "absolute",
|
|
55
|
+
top: 0,
|
|
56
|
+
left: 0,
|
|
57
|
+
right: 0,
|
|
58
|
+
bottom: 0,
|
|
59
|
+
display: "flex",
|
|
60
|
+
flexDirection: "column",
|
|
61
|
+
alignItems: "center",
|
|
62
|
+
justifyContent: "center",
|
|
63
|
+
backgroundColor: "rgba(0, 0, 0, 0.9)",
|
|
64
|
+
color: "#fff",
|
|
65
|
+
zIndex: 10,
|
|
66
|
+
cursor: "pointer",
|
|
67
|
+
}, onClick: onRetry },
|
|
68
|
+
React.createElement("div", { style: { fontSize: "48px", marginBottom: "16px" } }, "\uD83D\uDCE1"),
|
|
69
|
+
React.createElement("div", { style: {
|
|
70
|
+
fontSize: "20px",
|
|
71
|
+
fontWeight: "bold",
|
|
72
|
+
marginBottom: "8px",
|
|
73
|
+
} }, "Connection Issue"),
|
|
74
|
+
React.createElement("div", { style: { fontSize: "14px", opacity: 0.7, marginBottom: "20px" } }, errorMessage || "Unable to connect to stream"),
|
|
75
|
+
React.createElement("div", { style: {
|
|
76
|
+
padding: "10px 20px",
|
|
77
|
+
backgroundColor: "#00d4ff",
|
|
78
|
+
color: "#000",
|
|
79
|
+
borderRadius: "5px",
|
|
80
|
+
fontWeight: "bold",
|
|
81
|
+
fontSize: "16px",
|
|
82
|
+
} }, "Click to Retry")));
|
|
83
|
+
};
|
|
84
|
+
export const LoadingOverlay = () => {
|
|
85
|
+
return (React.createElement("div", { style: {
|
|
86
|
+
position: "absolute",
|
|
87
|
+
top: 0,
|
|
88
|
+
left: 0,
|
|
89
|
+
right: 0,
|
|
90
|
+
bottom: 0,
|
|
91
|
+
display: "flex",
|
|
92
|
+
flexDirection: "column",
|
|
93
|
+
alignItems: "center",
|
|
94
|
+
justifyContent: "center",
|
|
95
|
+
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
|
96
|
+
color: "#fff",
|
|
97
|
+
zIndex: 10,
|
|
98
|
+
} },
|
|
99
|
+
React.createElement("div", { style: {
|
|
100
|
+
width: "60px",
|
|
101
|
+
height: "60px",
|
|
102
|
+
border: "4px solid rgba(255, 255, 255, 0.2)",
|
|
103
|
+
borderTopColor: "#00d4ff",
|
|
104
|
+
borderRadius: "50%",
|
|
105
|
+
animation: "spin 1s linear infinite",
|
|
106
|
+
} }),
|
|
107
|
+
React.createElement("div", { style: { marginTop: "15px", fontSize: "18px" } }, "Connecting..."),
|
|
108
|
+
React.createElement("div", { style: { marginTop: "5px", fontSize: "14px", opacity: 0.7 } }, "Establishing WebRTC connection"),
|
|
109
|
+
React.createElement("style", null, `
|
|
110
|
+
@keyframes spin {
|
|
111
|
+
to { transform: rotate(360deg); }
|
|
112
|
+
}
|
|
113
|
+
`)));
|
|
114
|
+
};
|
|
115
|
+
export const RoundWinnerOverlay = ({ round, game, onComplete, }) => {
|
|
116
|
+
const [show, setShow] = React.useState(true);
|
|
117
|
+
React.useEffect(() => {
|
|
118
|
+
// Auto-hide after 5 seconds
|
|
119
|
+
const timer = setTimeout(() => {
|
|
120
|
+
setShow(false);
|
|
121
|
+
setTimeout(onComplete, 500); // Wait for fade out
|
|
122
|
+
}, 5000);
|
|
123
|
+
return () => clearTimeout(timer);
|
|
124
|
+
}, [onComplete]);
|
|
125
|
+
if (!show)
|
|
126
|
+
return null;
|
|
127
|
+
// Determine winner
|
|
128
|
+
const isDraw = round.result === "draw";
|
|
129
|
+
const winnerTeam = game.teams.find(t => t.id === round.winner_team_id);
|
|
130
|
+
const winnerName = (winnerTeam === null || winnerTeam === void 0 ? void 0 : winnerTeam.team_name) || "Unknown";
|
|
131
|
+
const winnerColor = (winnerTeam === null || winnerTeam === void 0 ? void 0 : winnerTeam.team_color) || "#FFF";
|
|
132
|
+
return (React.createElement("div", { style: {
|
|
133
|
+
position: "absolute",
|
|
134
|
+
top: 0,
|
|
135
|
+
left: 0,
|
|
136
|
+
right: 0,
|
|
137
|
+
bottom: 0,
|
|
138
|
+
display: "flex",
|
|
139
|
+
flexDirection: "column",
|
|
140
|
+
alignItems: "center",
|
|
141
|
+
justifyContent: "center",
|
|
142
|
+
backgroundColor: "rgba(0, 0, 0, 0.9)",
|
|
143
|
+
zIndex: 100,
|
|
144
|
+
animation: "fadeIn 0.5s ease-in",
|
|
145
|
+
} },
|
|
146
|
+
React.createElement("div", { style: {
|
|
147
|
+
fontSize: "80px",
|
|
148
|
+
marginBottom: "20px",
|
|
149
|
+
animation: "bounce 1s ease infinite",
|
|
150
|
+
} }, isDraw ? "π€" : "π"),
|
|
151
|
+
React.createElement("div", { style: {
|
|
152
|
+
fontSize: "24px",
|
|
153
|
+
fontWeight: "600",
|
|
154
|
+
color: "#94a3b8",
|
|
155
|
+
marginBottom: "10px",
|
|
156
|
+
textTransform: "uppercase",
|
|
157
|
+
letterSpacing: "2px",
|
|
158
|
+
} },
|
|
159
|
+
"Round ",
|
|
160
|
+
round.round_number,
|
|
161
|
+
" Finished"),
|
|
162
|
+
React.createElement("div", { style: {
|
|
163
|
+
fontSize: "48px",
|
|
164
|
+
fontWeight: "bold",
|
|
165
|
+
color: isDraw ? "#fbbf24" : winnerColor,
|
|
166
|
+
marginBottom: "20px",
|
|
167
|
+
textShadow: `0 4px 20px ${isDraw ? "#fbbf24" : winnerColor}80`,
|
|
168
|
+
animation: "pulse 2s ease infinite",
|
|
169
|
+
} }, isDraw ? "DRAW!" : `${winnerName} WINS!`),
|
|
170
|
+
React.createElement("div", { style: {
|
|
171
|
+
fontSize: "32px",
|
|
172
|
+
fontWeight: "600",
|
|
173
|
+
color: "#fff",
|
|
174
|
+
display: "flex",
|
|
175
|
+
gap: "20px",
|
|
176
|
+
alignItems: "center",
|
|
177
|
+
} },
|
|
178
|
+
React.createElement("span", { style: { color: game.teams[0].team_color } },
|
|
179
|
+
game.teams[0].team_name,
|
|
180
|
+
": ",
|
|
181
|
+
round.red_team_score),
|
|
182
|
+
React.createElement("span", { style: { opacity: 0.5 } }, "-"),
|
|
183
|
+
React.createElement("span", { style: { color: game.teams[1].team_color } },
|
|
184
|
+
game.teams[1].team_name,
|
|
185
|
+
": ",
|
|
186
|
+
round.blue_team_score)),
|
|
187
|
+
!isDraw && (React.createElement("div", { style: {
|
|
188
|
+
position: "absolute",
|
|
189
|
+
top: "50%",
|
|
190
|
+
left: "50%",
|
|
191
|
+
transform: "translate(-50%, -50%)",
|
|
192
|
+
pointerEvents: "none",
|
|
193
|
+
} }, [...Array(20)].map((_, i) => (React.createElement("div", { key: i, style: {
|
|
194
|
+
position: "absolute",
|
|
195
|
+
width: "10px",
|
|
196
|
+
height: "10px",
|
|
197
|
+
backgroundColor: ["#fbbf24", "#f59e0b", winnerColor, "#4ade80"][i % 4],
|
|
198
|
+
borderRadius: "50%",
|
|
199
|
+
animation: `confetti${i % 4} 3s ease-out infinite`,
|
|
200
|
+
left: `${Math.random() * 100 - 50}px`,
|
|
201
|
+
top: `${Math.random() * 100 - 50}px`,
|
|
202
|
+
} }))))),
|
|
203
|
+
React.createElement("style", null, `
|
|
204
|
+
@keyframes fadeIn {
|
|
205
|
+
from { opacity: 0; }
|
|
206
|
+
to { opacity: 1; }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@keyframes bounce {
|
|
210
|
+
0%, 100% { transform: translateY(0); }
|
|
211
|
+
50% { transform: translateY(-20px); }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@keyframes pulse {
|
|
215
|
+
0%, 100% {
|
|
216
|
+
opacity: 1;
|
|
217
|
+
transform: scale(1);
|
|
218
|
+
}
|
|
219
|
+
50% {
|
|
220
|
+
opacity: 0.8;
|
|
221
|
+
transform: scale(1.05);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@keyframes confetti0 {
|
|
226
|
+
0% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
|
|
227
|
+
100% { transform: translate(${Math.random() * 400 - 200}px, ${Math.random() * 400 + 200}px) rotate(720deg); opacity: 0; }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@keyframes confetti1 {
|
|
231
|
+
0% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
|
|
232
|
+
100% { transform: translate(${Math.random() * 400 - 200}px, ${Math.random() * 400 + 200}px) rotate(360deg); opacity: 0; }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@keyframes confetti2 {
|
|
236
|
+
0% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
|
|
237
|
+
100% { transform: translate(${Math.random() * 400 - 200}px, ${Math.random() * 400 + 200}px) rotate(540deg); opacity: 0; }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@keyframes confetti3 {
|
|
241
|
+
0% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
|
|
242
|
+
100% { transform: translate(${Math.random() * 400 - 200}px, ${Math.random() * 400 + 200}px) rotate(900deg); opacity: 0; }
|
|
243
|
+
}
|
|
244
|
+
`)));
|
|
245
|
+
};
|
|
246
|
+
export const ScoreOverlay = ({ game, gameTitle, showGameTitle = true, showRound = true, showWinnerAnimation = true, // Default: show animations
|
|
247
|
+
}) => {
|
|
248
|
+
const [showWinner, setShowWinner] = React.useState(false);
|
|
249
|
+
const [finishedRound, setFinishedRound] = React.useState(null);
|
|
250
|
+
const prevRoundsRef = React.useRef(game.rounds);
|
|
251
|
+
const isInitialMount = React.useRef(true);
|
|
252
|
+
const shownAnimationRoundIds = React.useRef(new Set());
|
|
253
|
+
// Detect when a round becomes finished (only show for NEW finishes, not on initial load!)
|
|
254
|
+
React.useEffect(() => {
|
|
255
|
+
// Skip animation detection if disabled
|
|
256
|
+
if (!showWinnerAnimation) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const prevRounds = prevRoundsRef.current;
|
|
260
|
+
const currentRounds = game.rounds;
|
|
261
|
+
// Skip on initial mount - mark all existing finished rounds as already shown
|
|
262
|
+
if (isInitialMount.current) {
|
|
263
|
+
isInitialMount.current = false;
|
|
264
|
+
prevRoundsRef.current = currentRounds;
|
|
265
|
+
// Mark all already-finished rounds so we don't show animations for them
|
|
266
|
+
if (currentRounds) {
|
|
267
|
+
currentRounds.forEach((round) => {
|
|
268
|
+
if (round.status === "finished") {
|
|
269
|
+
shownAnimationRoundIds.current.add(round.id);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (currentRounds && prevRounds) {
|
|
276
|
+
// Check if a new round was created - if so, hide any active animation
|
|
277
|
+
const newRoundCreated = currentRounds.find((round) => {
|
|
278
|
+
const prevRound = prevRounds.find((r) => r.id === round.id);
|
|
279
|
+
return !prevRound;
|
|
280
|
+
});
|
|
281
|
+
if (newRoundCreated) {
|
|
282
|
+
setShowWinner(false);
|
|
283
|
+
setFinishedRound(null);
|
|
284
|
+
if (newRoundCreated.status === "finished") {
|
|
285
|
+
shownAnimationRoundIds.current.add(newRoundCreated.id);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Find newly finished round
|
|
289
|
+
const newlyFinished = currentRounds.find((round) => {
|
|
290
|
+
const prevRound = prevRounds.find((r) => r.id === round.id);
|
|
291
|
+
if (!prevRound)
|
|
292
|
+
return false;
|
|
293
|
+
const wsAction = round._wsAction;
|
|
294
|
+
const prevWsAction = prevRound._wsAction;
|
|
295
|
+
// Show animation when round transitions to finished status
|
|
296
|
+
const isFinishAction = wsAction === "finished" || wsAction === "winner_set";
|
|
297
|
+
const wasNotFinishActionBefore = prevWsAction !== "finished" && prevWsAction !== "winner_set";
|
|
298
|
+
const isActuallyFinished = round.status === "finished";
|
|
299
|
+
const wasNotFinishedBefore = prevRound.status !== "finished";
|
|
300
|
+
const hasResult = round.result !== null;
|
|
301
|
+
const notShownYet = !shownAnimationRoundIds.current.has(round.id);
|
|
302
|
+
// Show animation if:
|
|
303
|
+
// 1. Action is explicitly "finished" or "winner_set", OR
|
|
304
|
+
// 2. Status changed to "finished" (for direct updates from backend)
|
|
305
|
+
const shouldShow = (isFinishAction && wasNotFinishActionBefore) ||
|
|
306
|
+
(isActuallyFinished && wasNotFinishedBefore);
|
|
307
|
+
return shouldShow && hasResult && notShownYet;
|
|
308
|
+
});
|
|
309
|
+
if (newlyFinished) {
|
|
310
|
+
shownAnimationRoundIds.current.add(newlyFinished.id);
|
|
311
|
+
setFinishedRound(newlyFinished);
|
|
312
|
+
setShowWinner(true);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
prevRoundsRef.current = currentRounds;
|
|
316
|
+
}, [game.rounds, showWinnerAnimation]);
|
|
317
|
+
// Get current round - use the highest round number regardless of status
|
|
318
|
+
const activeRound = game.rounds && game.rounds.length > 0
|
|
319
|
+
? game.rounds.reduce((highest, current) => current.round_number > highest.round_number ? current : highest)
|
|
320
|
+
: undefined;
|
|
321
|
+
const currentRoundNumber = activeRound === null || activeRound === void 0 ? void 0 : activeRound.round_number;
|
|
322
|
+
// Use custom title or game name
|
|
323
|
+
const displayTitle = gameTitle || game.game_name;
|
|
324
|
+
// Get team names for "Red vs Blue" display
|
|
325
|
+
const teamNames = game.teams.length === 2
|
|
326
|
+
? `${game.teams[0].team_name} vs ${game.teams[1].team_name}`
|
|
327
|
+
: game.teams.map((t) => t.team_name).join(" vs ");
|
|
328
|
+
return (React.createElement(React.Fragment, null,
|
|
329
|
+
showWinnerAnimation && showWinner && finishedRound && (React.createElement(RoundWinnerOverlay, { round: finishedRound, game: game, onComplete: () => setShowWinner(false) })),
|
|
330
|
+
React.createElement("div", { style: {
|
|
331
|
+
position: "absolute",
|
|
332
|
+
top: "20px",
|
|
333
|
+
left: "50%",
|
|
334
|
+
transform: "translateX(-50%)",
|
|
335
|
+
display: "flex",
|
|
336
|
+
flexDirection: "column",
|
|
337
|
+
alignItems: "center",
|
|
338
|
+
backgroundColor: "rgba(0, 0, 0, 0.85)",
|
|
339
|
+
padding: "16px 28px",
|
|
340
|
+
borderRadius: "12px",
|
|
341
|
+
zIndex: 5,
|
|
342
|
+
backdropFilter: "blur(10px)",
|
|
343
|
+
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
344
|
+
minWidth: "400px",
|
|
345
|
+
} },
|
|
346
|
+
(showGameTitle || showRound) && (React.createElement("div", { style: {
|
|
347
|
+
display: "flex",
|
|
348
|
+
alignItems: "center",
|
|
349
|
+
gap: "12px",
|
|
350
|
+
marginBottom: "12px",
|
|
351
|
+
width: "100%",
|
|
352
|
+
justifyContent: "center",
|
|
353
|
+
} },
|
|
354
|
+
showGameTitle && (React.createElement("div", { style: {
|
|
355
|
+
fontSize: "18px",
|
|
356
|
+
fontWeight: "bold",
|
|
357
|
+
color: "#fff",
|
|
358
|
+
letterSpacing: "0.5px",
|
|
359
|
+
} }, displayTitle)),
|
|
360
|
+
showGameTitle && showRound && currentRoundNumber && (React.createElement("div", { style: {
|
|
361
|
+
fontSize: "16px",
|
|
362
|
+
fontWeight: "bold",
|
|
363
|
+
color: "#fff",
|
|
364
|
+
opacity: 0.4,
|
|
365
|
+
} }, "-")),
|
|
366
|
+
showRound && currentRoundNumber && (React.createElement("div", { style: {
|
|
367
|
+
fontSize: "16px",
|
|
368
|
+
fontWeight: "600",
|
|
369
|
+
color: "#00d4ff",
|
|
370
|
+
backgroundColor: "rgba(0, 212, 255, 0.15)",
|
|
371
|
+
padding: "4px 12px",
|
|
372
|
+
borderRadius: "6px",
|
|
373
|
+
textTransform: "uppercase",
|
|
374
|
+
letterSpacing: "0.5px",
|
|
375
|
+
} },
|
|
376
|
+
"Round ",
|
|
377
|
+
currentRoundNumber)),
|
|
378
|
+
showRound && (activeRound === null || activeRound === void 0 ? void 0 : activeRound.status) === "finished" && activeRound.result && (React.createElement("div", { style: {
|
|
379
|
+
fontSize: "12px",
|
|
380
|
+
fontWeight: "700",
|
|
381
|
+
color: activeRound.result === "draw" ? "#fbbf24" : "#4ade80",
|
|
382
|
+
backgroundColor: activeRound.result === "draw" ? "rgba(251, 191, 36, 0.2)" : "rgba(74, 222, 128, 0.2)",
|
|
383
|
+
padding: "4px 10px",
|
|
384
|
+
borderRadius: "6px",
|
|
385
|
+
textTransform: "uppercase",
|
|
386
|
+
display: "flex",
|
|
387
|
+
alignItems: "center",
|
|
388
|
+
gap: "4px",
|
|
389
|
+
} }, activeRound.result === "draw" ? "π€ DRAW" :
|
|
390
|
+
activeRound.result === "red_win" ? "π RED WINS" :
|
|
391
|
+
activeRound.result === "blue_win" ? "π BLUE WINS" : "")),
|
|
392
|
+
game.status === "live" && (React.createElement("div", { style: {
|
|
393
|
+
fontSize: "11px",
|
|
394
|
+
fontWeight: "700",
|
|
395
|
+
color: "#4ade80",
|
|
396
|
+
backgroundColor: "rgba(74, 222, 128, 0.2)",
|
|
397
|
+
padding: "4px 8px",
|
|
398
|
+
borderRadius: "6px",
|
|
399
|
+
textTransform: "uppercase",
|
|
400
|
+
display: "flex",
|
|
401
|
+
alignItems: "center",
|
|
402
|
+
gap: "4px",
|
|
403
|
+
} },
|
|
404
|
+
React.createElement("div", { style: {
|
|
405
|
+
width: "6px",
|
|
406
|
+
height: "6px",
|
|
407
|
+
borderRadius: "50%",
|
|
408
|
+
backgroundColor: "#4ade80",
|
|
409
|
+
animation: "pulse 2s ease-in-out infinite",
|
|
410
|
+
} }),
|
|
411
|
+
"LIVE")))),
|
|
412
|
+
React.createElement("div", { style: {
|
|
413
|
+
fontSize: "14px",
|
|
414
|
+
fontWeight: "600",
|
|
415
|
+
color: "#94a3b8",
|
|
416
|
+
textTransform: "uppercase",
|
|
417
|
+
letterSpacing: "1px",
|
|
418
|
+
marginBottom: "14px",
|
|
419
|
+
} }, teamNames),
|
|
420
|
+
React.createElement("div", { style: {
|
|
421
|
+
display: "flex",
|
|
422
|
+
gap: "24px",
|
|
423
|
+
alignItems: "center",
|
|
424
|
+
justifyContent: "center",
|
|
425
|
+
} }, game.teams.map((team, index) => {
|
|
426
|
+
// Use active round scores if available, otherwise fall back to team scores
|
|
427
|
+
const score = activeRound
|
|
428
|
+
? index === 0
|
|
429
|
+
? activeRound.red_team_score
|
|
430
|
+
: activeRound.blue_team_score
|
|
431
|
+
: team.current_score;
|
|
432
|
+
return (React.createElement(React.Fragment, { key: team.id },
|
|
433
|
+
index > 0 && (React.createElement("div", { style: {
|
|
434
|
+
fontSize: "24px",
|
|
435
|
+
fontWeight: "bold",
|
|
436
|
+
color: "#fff",
|
|
437
|
+
opacity: 0.3,
|
|
438
|
+
} }, ":")),
|
|
439
|
+
React.createElement("div", { style: {
|
|
440
|
+
display: "flex",
|
|
441
|
+
alignItems: "center",
|
|
442
|
+
gap: "12px",
|
|
443
|
+
} },
|
|
444
|
+
team.logo_url && (React.createElement("img", { src: team.logo_url, alt: team.team_name, style: {
|
|
445
|
+
width: "40px",
|
|
446
|
+
height: "40px",
|
|
447
|
+
borderRadius: "50%",
|
|
448
|
+
objectFit: "cover",
|
|
449
|
+
border: `3px solid ${team.team_color}`,
|
|
450
|
+
boxShadow: `0 0 12px ${team.team_color}40`,
|
|
451
|
+
} })),
|
|
452
|
+
React.createElement("div", { style: {
|
|
453
|
+
fontSize: "32px",
|
|
454
|
+
fontWeight: "bold",
|
|
455
|
+
color: "#fff",
|
|
456
|
+
textShadow: `0 2px 8px ${team.team_color}40`,
|
|
457
|
+
minWidth: "50px",
|
|
458
|
+
textAlign: "center",
|
|
459
|
+
} }, score))));
|
|
460
|
+
})),
|
|
461
|
+
React.createElement("style", null, `
|
|
462
|
+
@keyframes pulse {
|
|
463
|
+
0%, 100% { opacity: 1; }
|
|
464
|
+
50% { opacity: 0.4; }
|
|
465
|
+
}
|
|
466
|
+
`))));
|
|
467
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export const GamePlayerVideo = ({ videoRef, controls, }) => {
|
|
3
|
+
const handleVideoEvent = (eventName, extraInfo) => {
|
|
4
|
+
const video = videoRef.current;
|
|
5
|
+
if (!video)
|
|
6
|
+
return;
|
|
7
|
+
const info = extraInfo || {
|
|
8
|
+
readyState: video.readyState,
|
|
9
|
+
paused: video.paused,
|
|
10
|
+
};
|
|
11
|
+
console.log(`[GamePlayer] ${eventName}`, info);
|
|
12
|
+
};
|
|
13
|
+
return (React.createElement("video", { ref: videoRef, autoPlay: true, muted: true, playsInline: true, controls: controls, preload: "auto", style: {
|
|
14
|
+
width: "100%",
|
|
15
|
+
height: "100%",
|
|
16
|
+
objectFit: "contain",
|
|
17
|
+
display: "block",
|
|
18
|
+
backgroundColor: "#000",
|
|
19
|
+
}, onLoadedMetadata: () => {
|
|
20
|
+
const video = videoRef.current;
|
|
21
|
+
if (video) {
|
|
22
|
+
handleVideoEvent("π Video metadata loaded", {
|
|
23
|
+
dimensions: `${video.videoWidth}x${video.videoHeight}`,
|
|
24
|
+
});
|
|
25
|
+
if (video.paused) {
|
|
26
|
+
video.play().catch(() => { });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}, onLoadedData: () => {
|
|
30
|
+
var _a;
|
|
31
|
+
handleVideoEvent("π¦ Video data loaded - readyState:", {
|
|
32
|
+
readyState: (_a = videoRef.current) === null || _a === void 0 ? void 0 : _a.readyState,
|
|
33
|
+
});
|
|
34
|
+
const video = videoRef.current;
|
|
35
|
+
if (video && video.paused) {
|
|
36
|
+
video.play().catch(() => { });
|
|
37
|
+
}
|
|
38
|
+
}, onCanPlay: () => {
|
|
39
|
+
var _a;
|
|
40
|
+
handleVideoEvent("π’ Video can play! ReadyState:", {
|
|
41
|
+
readyState: (_a = videoRef.current) === null || _a === void 0 ? void 0 : _a.readyState,
|
|
42
|
+
});
|
|
43
|
+
const video = videoRef.current;
|
|
44
|
+
if (video && video.paused) {
|
|
45
|
+
video.play().catch(() => { });
|
|
46
|
+
}
|
|
47
|
+
}, onCanPlayThrough: () => {
|
|
48
|
+
var _a;
|
|
49
|
+
handleVideoEvent("π’π’ Video can play through! ReadyState:", {
|
|
50
|
+
readyState: (_a = videoRef.current) === null || _a === void 0 ? void 0 : _a.readyState,
|
|
51
|
+
});
|
|
52
|
+
const video = videoRef.current;
|
|
53
|
+
if (video && video.paused) {
|
|
54
|
+
video.play().catch(() => { });
|
|
55
|
+
}
|
|
56
|
+
}, onPlaying: () => {
|
|
57
|
+
handleVideoEvent("π Video is playing!");
|
|
58
|
+
}, onWaiting: () => {
|
|
59
|
+
handleVideoEvent("β³ Video is waiting for data...");
|
|
60
|
+
}, onStalled: () => {
|
|
61
|
+
handleVideoEvent("β οΈ Video stalled - may need buffering");
|
|
62
|
+
}, onSuspend: () => {
|
|
63
|
+
handleVideoEvent("βΈοΈ Video data loading suspended");
|
|
64
|
+
}, onProgress: () => {
|
|
65
|
+
const video = videoRef.current;
|
|
66
|
+
if (video && video.buffered.length > 0) {
|
|
67
|
+
handleVideoEvent("π Buffering progress:", {
|
|
68
|
+
buffered: `${video.buffered.end(0)} seconds`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}, onClick: () => {
|
|
72
|
+
console.log("[GamePlayer] Video clicked!");
|
|
73
|
+
const video = videoRef.current;
|
|
74
|
+
if (video) {
|
|
75
|
+
if (video.paused) {
|
|
76
|
+
console.log("[GamePlayer] Video is paused, playing...");
|
|
77
|
+
video
|
|
78
|
+
.play()
|
|
79
|
+
.catch((e) => console.log("[GamePlayer] Play failed on click:", e));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log("[GamePlayer] Video is already playing");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} }));
|
|
86
|
+
};
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const API_CONFIG: {
|
|
2
|
+
development: {
|
|
3
|
+
api_url: string;
|
|
4
|
+
websocket_url: string;
|
|
5
|
+
streaming: {
|
|
6
|
+
serverUrl: string;
|
|
7
|
+
serverPort: number;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
staging: {
|
|
11
|
+
api_url: string;
|
|
12
|
+
websocket_url: string;
|
|
13
|
+
streaming: {
|
|
14
|
+
serverUrl: string;
|
|
15
|
+
serverPort: number;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
production: {
|
|
19
|
+
api_url: string;
|
|
20
|
+
websocket_url: string;
|
|
21
|
+
streaming: {
|
|
22
|
+
serverUrl: string;
|
|
23
|
+
serverPort: number;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export declare function getConfig(env?: "development" | "staging" | "production"): {
|
|
28
|
+
api_url: string;
|
|
29
|
+
websocket_url: string;
|
|
30
|
+
streaming: {
|
|
31
|
+
serverUrl: string;
|
|
32
|
+
serverPort: number;
|
|
33
|
+
};
|
|
34
|
+
} | {
|
|
35
|
+
api_url: string;
|
|
36
|
+
websocket_url: string;
|
|
37
|
+
streaming: {
|
|
38
|
+
serverUrl: string;
|
|
39
|
+
serverPort: number;
|
|
40
|
+
};
|
|
41
|
+
} | {
|
|
42
|
+
api_url: string;
|
|
43
|
+
websocket_url: string;
|
|
44
|
+
streaming: {
|
|
45
|
+
serverUrl: string;
|
|
46
|
+
serverPort: number;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export declare function getWebRTCUrl(serverUrl: string, port: number, streamName: string): string;
|
|
50
|
+
export declare function getWebSocketUrl(env?: "development" | "staging" | "production", token?: string): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// API Configuration for Game SDK
|
|
2
|
+
export const API_CONFIG = {
|
|
3
|
+
// Development (local)
|
|
4
|
+
development: {
|
|
5
|
+
api_url: "http://localhost:5001/v1/external/games",
|
|
6
|
+
websocket_url: "ws.kodenique.com",
|
|
7
|
+
streaming: {
|
|
8
|
+
serverUrl: "localhost",
|
|
9
|
+
serverPort: 1985,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
// Staging
|
|
13
|
+
staging: {
|
|
14
|
+
api_url: "https://api-stg.wspo.club/v1/external/games",
|
|
15
|
+
websocket_url: "ws.kodenique.com",
|
|
16
|
+
streaming: {
|
|
17
|
+
serverUrl: "143.198.90.133",
|
|
18
|
+
serverPort: 1985,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
// Production
|
|
22
|
+
production: {
|
|
23
|
+
api_url: "https://api.wspo.club/v1/external/games",
|
|
24
|
+
websocket_url: "ws.kodenique.com",
|
|
25
|
+
streaming: {
|
|
26
|
+
serverUrl: "143.198.90.133",
|
|
27
|
+
serverPort: 1985,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
// Helper to get current environment config
|
|
32
|
+
export function getConfig(env = "development") {
|
|
33
|
+
return API_CONFIG[env];
|
|
34
|
+
}
|
|
35
|
+
// Helper to construct WebRTC URL
|
|
36
|
+
export function getWebRTCUrl(serverUrl, port, streamName) {
|
|
37
|
+
return `http://${serverUrl}:${port}/rtc/v1/whep/?app=live&stream=${streamName}`;
|
|
38
|
+
}
|
|
39
|
+
// Helper to construct WebSocket URL with token
|
|
40
|
+
export function getWebSocketUrl(env = "development", token) {
|
|
41
|
+
const baseUrl = API_CONFIG[env].websocket_url;
|
|
42
|
+
if (token) {
|
|
43
|
+
return `${baseUrl}?token=${token}`;
|
|
44
|
+
}
|
|
45
|
+
return baseUrl;
|
|
46
|
+
}
|