Live video on the AT Protocol
1import React, { useCallback, useMemo, useState } from "react";
2import { StoreApi } from "zustand";
3import { PlayerContext } from "./context";
4import { PlayerState } from "./player-state";
5import { makePlayerStore } from "./player-store";
6
7interface PlayerProviderProps {
8 children: React.ReactNode;
9 initialPlayers?: string[];
10 defaultId?: string;
11}
12
13function randomUUID(): string {
14 let dt = new Date().getTime();
15 var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
16 /[xy]/g,
17 function (c) {
18 var r = (dt + Math.random() * 16) % 16 | 0;
19 dt = Math.floor(dt / 16);
20 return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
21 },
22 );
23 return uuid;
24}
25
26export const PlayerProvider: React.FC<PlayerProviderProps> = ({
27 children,
28 initialPlayers = [],
29 defaultId = Math.random().toString(36).slice(8),
30}) => {
31 const [players, setPlayers] = useState<Record<string, StoreApi<PlayerState>>>(
32 () => {
33 // Initialize with any initial player IDs provided
34 const initialPlayerStores: Record<string, StoreApi<PlayerState>> = {};
35 for (const playerId of initialPlayers) {
36 initialPlayerStores[playerId] = makePlayerStore(playerId);
37 }
38
39 // Always create at least one player by default
40 if (initialPlayers.length === 0) {
41 initialPlayerStores[defaultId] = makePlayerStore(defaultId);
42 }
43
44 return initialPlayerStores;
45 },
46 );
47
48 const createPlayer = useCallback((id?: string) => {
49 const playerId = id || randomUUID();
50 const playerStore = makePlayerStore(playerId);
51
52 setPlayers((prev) => ({
53 ...prev,
54 [playerId]: playerStore,
55 }));
56
57 return playerId;
58 }, []);
59
60 const removePlayer = useCallback((id: string) => {
61 setPlayers((prev) => {
62 // Don't remove the last player
63 if (Object.keys(prev).length <= 1) {
64 console.warn("Cannot remove the last player");
65 return prev;
66 }
67
68 const newPlayers = { ...prev };
69 delete newPlayers[id];
70 return newPlayers;
71 });
72 }, []);
73
74 const contextValue = useMemo(
75 () => ({
76 players,
77 createPlayer,
78 removePlayer,
79 }),
80 [players, createPlayer, removePlayer],
81 );
82
83 return (
84 <PlayerContext.Provider value={contextValue}>
85 {children}
86 </PlayerContext.Provider>
87 );
88};
89
90// HOC to wrap components that need player context
91export function withPlayerProvider<P extends object>(
92 Component: React.ComponentType<P>,
93): React.FC<P & { initialPlayers?: string[] }> {
94 return function WithPlayerProvider(props: P & { initialPlayers?: string[] }) {
95 const { initialPlayers, ...componentProps } = props;
96 return (
97 <PlayerProvider initialPlayers={initialPlayers}>
98 <Component {...(componentProps as P)} />
99 </PlayerProvider>
100 );
101 };
102}