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,24 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import type { Game } from "../types";
|
|
3
|
+
interface GameStreamContextType {
|
|
4
|
+
activeGames: Game[];
|
|
5
|
+
subscribeToGame: (gameId: string) => void;
|
|
6
|
+
unsubscribeFromGame: (gameId: string) => void;
|
|
7
|
+
getGame: (gameId: string) => Game | undefined;
|
|
8
|
+
updateGame: (game: Game) => void;
|
|
9
|
+
isConnected: boolean;
|
|
10
|
+
subscribedGameIds: string[];
|
|
11
|
+
}
|
|
12
|
+
interface GameStreamProviderProps {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
socketToken?: string;
|
|
15
|
+
environment?: 'development' | 'staging' | 'production';
|
|
16
|
+
websocketUrl?: string;
|
|
17
|
+
pusherConfig?: {
|
|
18
|
+
key: string;
|
|
19
|
+
cluster: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare const GameStreamProvider: React.FC<GameStreamProviderProps>;
|
|
23
|
+
export declare const useGameStreamContext: () => GameStreamContextType;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useState, useCallback } from "react";
|
|
2
|
+
import { GamePusherClient } from "../lib/pusher";
|
|
3
|
+
import { getWebSocketUrl } from "../config";
|
|
4
|
+
const GameStreamContext = createContext(undefined);
|
|
5
|
+
export const GameStreamProvider = ({ children, socketToken, environment = 'development', websocketUrl: customWebsocketUrl, pusherConfig, }) => {
|
|
6
|
+
const [activeGames, setActiveGames] = useState([]);
|
|
7
|
+
const [subscribedGameIds, setSubscribedGameIds] = useState([]);
|
|
8
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
9
|
+
const [websocket, setWebsocket] = useState(null);
|
|
10
|
+
const [pusherClient, setPusherClient] = useState(null);
|
|
11
|
+
// Determine the WebSocket URL to use
|
|
12
|
+
const websocketUrl = customWebsocketUrl || (socketToken ? getWebSocketUrl(environment, socketToken) : undefined);
|
|
13
|
+
// Initialize Pusher connection
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!pusherConfig)
|
|
16
|
+
return;
|
|
17
|
+
console.log("[GameStreamContext] Initializing Pusher");
|
|
18
|
+
const client = new GamePusherClient(pusherConfig);
|
|
19
|
+
client.connect();
|
|
20
|
+
setPusherClient(client);
|
|
21
|
+
setIsConnected(client.isConnected());
|
|
22
|
+
return () => {
|
|
23
|
+
console.log("[GameStreamContext] Cleaning up Pusher");
|
|
24
|
+
client.disconnect();
|
|
25
|
+
};
|
|
26
|
+
}, [pusherConfig]);
|
|
27
|
+
// Initialize WebSocket connection
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!websocketUrl || pusherConfig)
|
|
30
|
+
return; // Skip if using Pusher
|
|
31
|
+
console.log("[GameStreamContext] Connecting to WebSocket:", websocketUrl);
|
|
32
|
+
const ws = new WebSocket(websocketUrl);
|
|
33
|
+
ws.onopen = () => {
|
|
34
|
+
console.log("[GameStreamContext] WebSocket connected");
|
|
35
|
+
setIsConnected(true);
|
|
36
|
+
};
|
|
37
|
+
ws.onclose = () => {
|
|
38
|
+
console.log("[GameStreamContext] WebSocket disconnected");
|
|
39
|
+
setIsConnected(false);
|
|
40
|
+
};
|
|
41
|
+
ws.onerror = (error) => {
|
|
42
|
+
console.error("[GameStreamContext] WebSocket error:", error);
|
|
43
|
+
setIsConnected(false);
|
|
44
|
+
};
|
|
45
|
+
ws.onmessage = (event) => {
|
|
46
|
+
try {
|
|
47
|
+
const data = JSON.parse(event.data);
|
|
48
|
+
console.log("[GameStreamContext] Received message:", data);
|
|
49
|
+
// Handle different message types
|
|
50
|
+
if (data.type === "game_update") {
|
|
51
|
+
updateGame(data.game);
|
|
52
|
+
}
|
|
53
|
+
else if (data.type === "game_ended") {
|
|
54
|
+
removeGame(data.gameId);
|
|
55
|
+
}
|
|
56
|
+
else if (data.type === "score_update") {
|
|
57
|
+
updateGameScore(data.gameId, data.scores);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error("[GameStreamContext] Error parsing message:", error);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
setWebsocket(ws);
|
|
65
|
+
return () => {
|
|
66
|
+
console.log("[GameStreamContext] Cleaning up WebSocket");
|
|
67
|
+
ws.close();
|
|
68
|
+
};
|
|
69
|
+
}, [websocketUrl, pusherConfig]);
|
|
70
|
+
// Subscribe to a game
|
|
71
|
+
const subscribeToGame = useCallback((gameId) => {
|
|
72
|
+
console.log("[GameStreamContext] Subscribing to game:", gameId);
|
|
73
|
+
setSubscribedGameIds((prev) => {
|
|
74
|
+
if (prev.includes(gameId))
|
|
75
|
+
return prev;
|
|
76
|
+
return [...prev, gameId];
|
|
77
|
+
});
|
|
78
|
+
// Use Pusher if available
|
|
79
|
+
if (pusherClient) {
|
|
80
|
+
pusherClient.subscribeToGame(gameId, {
|
|
81
|
+
onGameUpdate: updateGame,
|
|
82
|
+
onScoreUpdate: (scores) => updateGameScore(gameId, scores),
|
|
83
|
+
onStatusUpdate: (status) => {
|
|
84
|
+
// Update game status
|
|
85
|
+
setActiveGames((prev) => prev.map((g) => (g.id === gameId ? Object.assign(Object.assign({}, g), { status: status }) : g)));
|
|
86
|
+
},
|
|
87
|
+
onGameEnded: () => removeGame(gameId),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Otherwise use WebSocket
|
|
91
|
+
else if (websocket && websocket.readyState === WebSocket.OPEN) {
|
|
92
|
+
websocket.send(JSON.stringify({
|
|
93
|
+
action: "subscribe",
|
|
94
|
+
gameId,
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
}, [websocket, pusherClient]);
|
|
98
|
+
// Unsubscribe from a game
|
|
99
|
+
const unsubscribeFromGame = useCallback((gameId) => {
|
|
100
|
+
console.log("[GameStreamContext] Unsubscribing from game:", gameId);
|
|
101
|
+
setSubscribedGameIds((prev) => prev.filter((id) => id !== gameId));
|
|
102
|
+
// Use Pusher if available
|
|
103
|
+
if (pusherClient) {
|
|
104
|
+
pusherClient.unsubscribeFromGame(gameId);
|
|
105
|
+
}
|
|
106
|
+
// Otherwise use WebSocket
|
|
107
|
+
else if (websocket && websocket.readyState === WebSocket.OPEN) {
|
|
108
|
+
websocket.send(JSON.stringify({
|
|
109
|
+
action: "unsubscribe",
|
|
110
|
+
gameId,
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
}, [websocket, pusherClient]);
|
|
114
|
+
// Get a specific game
|
|
115
|
+
const getGame = useCallback((gameId) => {
|
|
116
|
+
return activeGames.find((game) => game.id === gameId);
|
|
117
|
+
}, [activeGames]);
|
|
118
|
+
// Update game data
|
|
119
|
+
const updateGame = useCallback((game) => {
|
|
120
|
+
console.log("[GameStreamContext] Updating game:", game.id);
|
|
121
|
+
setActiveGames((prev) => {
|
|
122
|
+
const existingIndex = prev.findIndex((g) => g.id === game.id);
|
|
123
|
+
if (existingIndex >= 0) {
|
|
124
|
+
// Update existing game
|
|
125
|
+
const updated = [...prev];
|
|
126
|
+
updated[existingIndex] = game;
|
|
127
|
+
return updated;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// Add new game
|
|
131
|
+
return [...prev, game];
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}, []);
|
|
135
|
+
// Remove game (when ended)
|
|
136
|
+
const removeGame = useCallback((gameId) => {
|
|
137
|
+
console.log("[GameStreamContext] Removing game:", gameId);
|
|
138
|
+
setActiveGames((prev) => prev.filter((g) => g.id !== gameId));
|
|
139
|
+
}, []);
|
|
140
|
+
// Update game score only
|
|
141
|
+
const updateGameScore = useCallback((gameId, scores) => {
|
|
142
|
+
console.log("[GameStreamContext] Updating score for game:", gameId, scores);
|
|
143
|
+
setActiveGames((prev) => {
|
|
144
|
+
return prev.map((game) => {
|
|
145
|
+
if (game.id === gameId) {
|
|
146
|
+
return Object.assign(Object.assign({}, game), { teams: game.teams.map((team, index) => (Object.assign(Object.assign({}, team), { current_score: scores[index] || team.current_score }))) });
|
|
147
|
+
}
|
|
148
|
+
return game;
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}, []);
|
|
152
|
+
const value = {
|
|
153
|
+
activeGames,
|
|
154
|
+
subscribeToGame,
|
|
155
|
+
unsubscribeFromGame,
|
|
156
|
+
getGame,
|
|
157
|
+
updateGame,
|
|
158
|
+
isConnected,
|
|
159
|
+
subscribedGameIds,
|
|
160
|
+
};
|
|
161
|
+
return (React.createElement(GameStreamContext.Provider, { value: value }, children));
|
|
162
|
+
};
|
|
163
|
+
// Custom hook to use game stream context
|
|
164
|
+
export const useGameStreamContext = () => {
|
|
165
|
+
const context = useContext(GameStreamContext);
|
|
166
|
+
if (!context) {
|
|
167
|
+
throw new Error("useGameStreamContext must be used within GameStreamProvider");
|
|
168
|
+
}
|
|
169
|
+
return context;
|
|
170
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Example 1: Using GameProvider with WebSocket (Recommended)
|
|
4
|
+
* User only needs to pass their socket token!
|
|
5
|
+
*/
|
|
6
|
+
export declare const GameStreamWithSocketTokenExample: () => React.JSX.Element;
|
|
7
|
+
/**
|
|
8
|
+
* Example 2: Using custom WebSocket URL (Advanced)
|
|
9
|
+
*/
|
|
10
|
+
export declare const GameStreamWithCustomWebSocketExample: () => React.JSX.Element;
|
|
11
|
+
/**
|
|
12
|
+
* Example: Multiple games with subscriptions
|
|
13
|
+
*/
|
|
14
|
+
export declare const MultipleGamesExample: () => React.JSX.Element;
|
|
15
|
+
/**
|
|
16
|
+
* Example: Manual subscription control
|
|
17
|
+
*/
|
|
18
|
+
export declare const ManualSubscriptionExample: React.FC<{
|
|
19
|
+
gameId: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Example: Customizing overlay display
|
|
23
|
+
*/
|
|
24
|
+
export declare const CustomOverlayExample: React.FC<{
|
|
25
|
+
gameId: string;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { GamePlayer } from "../GamePlayer";
|
|
3
|
+
import { GameProvider } from "../GameContext";
|
|
4
|
+
import { useGameStream } from "../hooks/useGameStream";
|
|
5
|
+
/**
|
|
6
|
+
* Example 1: Using GameProvider with WebSocket (Recommended)
|
|
7
|
+
* User only needs to pass their socket token!
|
|
8
|
+
*/
|
|
9
|
+
export const GameStreamWithSocketTokenExample = () => {
|
|
10
|
+
return (React.createElement(GameProvider, { access_token: "your-api-access-token", socketToken: "your-socket-auth-token", environment: "production" // or 'staging' or 'development'
|
|
11
|
+
},
|
|
12
|
+
React.createElement(GamePlayerWithSubscription, { gameId: "game-123" })));
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Example 2: Using custom WebSocket URL (Advanced)
|
|
16
|
+
*/
|
|
17
|
+
export const GameStreamWithCustomWebSocketExample = () => {
|
|
18
|
+
return (React.createElement(GameProvider, { access_token: "your-api-access-token", websocketUrl: "wss://your-custom-websocket-server.com?token=abc123" },
|
|
19
|
+
React.createElement(GamePlayerWithSubscription, { gameId: "game-123" })));
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Component that subscribes to game updates
|
|
23
|
+
*/
|
|
24
|
+
const GamePlayerWithSubscription = ({ gameId }) => {
|
|
25
|
+
// Subscribe to game updates
|
|
26
|
+
const { game, isSubscribed, isConnected } = useGameStream({
|
|
27
|
+
gameId,
|
|
28
|
+
autoSubscribe: true,
|
|
29
|
+
});
|
|
30
|
+
return (React.createElement("div", null,
|
|
31
|
+
React.createElement("div", { className: "mb-4 flex items-center gap-2" },
|
|
32
|
+
React.createElement("div", { className: `h-3 w-3 rounded-full ${isConnected ? "bg-green-500" : "bg-red-500"}` }),
|
|
33
|
+
React.createElement("span", { className: "text-sm" }, isConnected ? "Connected" : "Disconnected"),
|
|
34
|
+
isSubscribed && (React.createElement("span", { className: "text-sm text-gray-500" },
|
|
35
|
+
"(Subscribed to ",
|
|
36
|
+
gameId,
|
|
37
|
+
")"))),
|
|
38
|
+
React.createElement(GamePlayer, { streamUrl: "http://your-stream-server.com/stream", game: game, showScoreOverlay: true, showNetworkIndicator: true, showGameTitle: true, showRound: true, width: "100%", height: "600px" }),
|
|
39
|
+
game && (React.createElement("div", { className: "mt-4 rounded-lg bg-gray-100 p-4" },
|
|
40
|
+
React.createElement("h3", { className: "mb-2 text-lg font-bold" }, game.game_name),
|
|
41
|
+
React.createElement("div", { className: "flex gap-4" }, game.teams.map((team) => (React.createElement("div", { key: team.id, className: "flex-1" },
|
|
42
|
+
React.createElement("div", { className: "font-semibold", style: { color: team.team_color } }, team.team_name),
|
|
43
|
+
React.createElement("div", { className: "text-2xl font-bold" }, team.current_score))))),
|
|
44
|
+
React.createElement("div", { className: "mt-2 text-sm text-gray-600" },
|
|
45
|
+
"Status: ",
|
|
46
|
+
game.status)))));
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Example: Multiple games with subscriptions
|
|
50
|
+
*/
|
|
51
|
+
export const MultipleGamesExample = () => {
|
|
52
|
+
const gameIds = ["game-1", "game-2", "game-3"];
|
|
53
|
+
return (React.createElement(GameProvider, { access_token: "your-api-access-token", socketToken: "your-socket-auth-token", environment: "production" },
|
|
54
|
+
React.createElement("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3" }, gameIds.map((gameId) => (React.createElement(GamePlayerWithSubscription, { key: gameId, gameId: gameId }))))));
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Example: Manual subscription control
|
|
58
|
+
*/
|
|
59
|
+
export const ManualSubscriptionExample = ({ gameId, }) => {
|
|
60
|
+
const { game, isSubscribed, subscribe, unsubscribe } = useGameStream({
|
|
61
|
+
gameId,
|
|
62
|
+
autoSubscribe: false, // Don't auto-subscribe
|
|
63
|
+
});
|
|
64
|
+
return (React.createElement("div", null,
|
|
65
|
+
React.createElement("div", { className: "mb-4 flex gap-2" },
|
|
66
|
+
React.createElement("button", { onClick: subscribe, disabled: isSubscribed, className: "rounded bg-blue-500 px-4 py-2 text-white disabled:bg-gray-300" }, "Subscribe"),
|
|
67
|
+
React.createElement("button", { onClick: unsubscribe, disabled: !isSubscribed, className: "rounded bg-red-500 px-4 py-2 text-white disabled:bg-gray-300" }, "Unsubscribe"),
|
|
68
|
+
React.createElement("span", { className: "flex items-center text-sm" }, isSubscribed ? "✓ Subscribed" : "○ Not subscribed")),
|
|
69
|
+
game && (React.createElement(GamePlayer, { streamUrl: "http://your-stream-server.com/stream", game: game, showScoreOverlay: true, gameTitle: "Custom Game Title", showGameTitle: true, showRound: true }))));
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Example: Customizing overlay display
|
|
73
|
+
*/
|
|
74
|
+
export const CustomOverlayExample = ({ gameId, }) => {
|
|
75
|
+
const { game } = useGameStream({
|
|
76
|
+
gameId,
|
|
77
|
+
autoSubscribe: true,
|
|
78
|
+
});
|
|
79
|
+
return (React.createElement("div", { className: "space-y-6" },
|
|
80
|
+
React.createElement("div", null,
|
|
81
|
+
React.createElement("h3", { className: "mb-2 font-bold" }, "With Custom Title and Round"),
|
|
82
|
+
React.createElement(GamePlayer, { game: game, streamUrl: "http://your-stream-server.com/stream", showScoreOverlay: true, gameTitle: "Championship Finals", showGameTitle: true, showRound: true, width: "100%", height: "400px" })),
|
|
83
|
+
React.createElement("div", null,
|
|
84
|
+
React.createElement("h3", { className: "mb-2 font-bold" }, "Round and Scores Only"),
|
|
85
|
+
React.createElement(GamePlayer, { game: game, streamUrl: "http://your-stream-server.com/stream", showScoreOverlay: true, showGameTitle: false, showRound: true, width: "100%", height: "400px" })),
|
|
86
|
+
React.createElement("div", null,
|
|
87
|
+
React.createElement("h3", { className: "mb-2 font-bold" }, "Scores Only"),
|
|
88
|
+
React.createElement(GamePlayer, { game: game, streamUrl: "http://your-stream-server.com/stream", showScoreOverlay: true, showGameTitle: false, showRound: false, width: "100%", height: "400px" })),
|
|
89
|
+
React.createElement("div", null,
|
|
90
|
+
React.createElement("h3", { className: "mb-2 font-bold" }, "No Overlay"),
|
|
91
|
+
React.createElement(GamePlayer, { game: game, streamUrl: "http://your-stream-server.com/stream", showScoreOverlay: false, width: "100%", height: "400px" }))));
|
|
92
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Auto-Subscribe Example
|
|
3
|
+
*
|
|
4
|
+
* This example shows the easiest way to use the Game SDK.
|
|
5
|
+
* Just pass the game object to GamePlayer - the SDK handles everything automatically!
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
declare function App(): React.JSX.Element;
|
|
9
|
+
export default App;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Auto-Subscribe Example
|
|
3
|
+
*
|
|
4
|
+
* This example shows the easiest way to use the Game SDK.
|
|
5
|
+
* Just pass the game object to GamePlayer - the SDK handles everything automatically!
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { GameProvider, GamePlayer, useGames } from '../index';
|
|
9
|
+
function App() {
|
|
10
|
+
return (React.createElement(GameProvider, { access_token: "your-access-token", socketToken: "your-socket-token", environment: "production" // or "development" or "staging"
|
|
11
|
+
},
|
|
12
|
+
React.createElement(GamePage, null)));
|
|
13
|
+
}
|
|
14
|
+
function GamePage() {
|
|
15
|
+
// Fetch games from API
|
|
16
|
+
const { games, loading } = useGames({
|
|
17
|
+
access_token: 'your-access-token',
|
|
18
|
+
api_url: 'https://api.example.com/v1/external/games'
|
|
19
|
+
});
|
|
20
|
+
if (loading)
|
|
21
|
+
return React.createElement("div", null, "Loading games...");
|
|
22
|
+
const game = games[0]; // Or select specific game
|
|
23
|
+
return (React.createElement("div", null,
|
|
24
|
+
React.createElement("h1", null,
|
|
25
|
+
"Live Game: ", game === null || game === void 0 ? void 0 :
|
|
26
|
+
game.game_name),
|
|
27
|
+
React.createElement(GamePlayer, { game: game, showScoreOverlay: true, showGameTitle: false, showRound: true, autoPlay: true, muted: true, width: "100%", height: "600px" })));
|
|
28
|
+
}
|
|
29
|
+
export default App;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useWebRTC } from "./useWebRTC";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Game } from "../types";
|
|
2
|
+
interface UseGameStreamOptions {
|
|
3
|
+
gameId?: string;
|
|
4
|
+
autoSubscribe?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface UseGameStreamReturn {
|
|
7
|
+
game: Game | undefined;
|
|
8
|
+
isSubscribed: boolean;
|
|
9
|
+
isConnected: boolean;
|
|
10
|
+
subscribe: () => void;
|
|
11
|
+
unsubscribe: () => void;
|
|
12
|
+
updateGame: (game: Game) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Hook to subscribe to a specific game's real-time updates
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* const { game, isSubscribed, subscribe } = useGameStream({
|
|
20
|
+
* gameId: "game-123",
|
|
21
|
+
* autoSubscribe: true
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Game data updates automatically via WebSocket
|
|
25
|
+
* console.log(game?.teams[0].current_score);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function useGameStream(options?: UseGameStreamOptions): UseGameStreamReturn;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useGameContext } from "../GameContext";
|
|
3
|
+
/**
|
|
4
|
+
* Hook to subscribe to a specific game's real-time updates
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* const { game, isSubscribed, subscribe } = useGameStream({
|
|
9
|
+
* gameId: "game-123",
|
|
10
|
+
* autoSubscribe: true
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* // Game data updates automatically via WebSocket
|
|
14
|
+
* console.log(game?.teams[0].current_score);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function useGameStream(options = {}) {
|
|
18
|
+
const { gameId, autoSubscribe = true } = options;
|
|
19
|
+
const { subscribeToGame, unsubscribeFromGame, getGame, updateGame: updateGameContext, isConnected, subscribedGameIds, activeGames, } = useGameContext();
|
|
20
|
+
const [game, setGame] = useState(undefined);
|
|
21
|
+
// Check if currently subscribed
|
|
22
|
+
const isSubscribed = gameId ? subscribedGameIds.includes(gameId) : false;
|
|
23
|
+
// Subscribe function
|
|
24
|
+
const subscribe = () => {
|
|
25
|
+
if (!gameId)
|
|
26
|
+
return;
|
|
27
|
+
subscribeToGame(gameId);
|
|
28
|
+
};
|
|
29
|
+
// Unsubscribe function
|
|
30
|
+
const unsubscribe = () => {
|
|
31
|
+
if (!gameId)
|
|
32
|
+
return;
|
|
33
|
+
unsubscribeFromGame(gameId);
|
|
34
|
+
};
|
|
35
|
+
// Auto-subscribe on mount if enabled, but wait for connection
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (autoSubscribe && gameId && isConnected) {
|
|
38
|
+
console.log('[useGameStream] Auto-subscribing to game:', gameId);
|
|
39
|
+
subscribe();
|
|
40
|
+
}
|
|
41
|
+
return () => {
|
|
42
|
+
if (autoSubscribe && gameId) {
|
|
43
|
+
unsubscribe();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}, [gameId, autoSubscribe, isConnected]);
|
|
47
|
+
// Update local game state when context changes
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!gameId)
|
|
50
|
+
return;
|
|
51
|
+
const currentGame = getGame(gameId);
|
|
52
|
+
console.log('[useGameStream] Context activeGames changed, updating local game state');
|
|
53
|
+
console.log('[useGameStream] Current game from context:', currentGame);
|
|
54
|
+
if (currentGame) {
|
|
55
|
+
setGame(currentGame);
|
|
56
|
+
}
|
|
57
|
+
}, [gameId, activeGames, getGame]);
|
|
58
|
+
// Listen for game updates - poll every second
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!gameId || !isSubscribed)
|
|
61
|
+
return;
|
|
62
|
+
const interval = setInterval(() => {
|
|
63
|
+
const currentGame = getGame(gameId);
|
|
64
|
+
if (currentGame) {
|
|
65
|
+
setGame(currentGame);
|
|
66
|
+
}
|
|
67
|
+
}, 1000);
|
|
68
|
+
return () => clearInterval(interval);
|
|
69
|
+
}, [gameId, isSubscribed, getGame]);
|
|
70
|
+
return {
|
|
71
|
+
game,
|
|
72
|
+
isSubscribed,
|
|
73
|
+
isConnected,
|
|
74
|
+
subscribe,
|
|
75
|
+
unsubscribe,
|
|
76
|
+
updateGame: updateGameContext,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface UseWebRTCOptions {
|
|
2
|
+
streamUrl?: string;
|
|
3
|
+
serverUrl?: string;
|
|
4
|
+
streamName?: string;
|
|
5
|
+
autoPlay?: boolean;
|
|
6
|
+
muted?: boolean;
|
|
7
|
+
onLoad?: () => void;
|
|
8
|
+
onError?: (error: Error) => void;
|
|
9
|
+
onConnectionStateChange?: (state: RTCPeerConnectionState) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface UseWebRTCReturn {
|
|
12
|
+
videoRef: React.RefObject<HTMLVideoElement>;
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
hasError: boolean;
|
|
15
|
+
errorMessage: string;
|
|
16
|
+
networkQuality: "good" | "fair" | "poor" | "bad";
|
|
17
|
+
latency: number;
|
|
18
|
+
bitrate: number;
|
|
19
|
+
retry: () => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function useWebRTC(options: UseWebRTCOptions): UseWebRTCReturn;
|