@streamplace/components 0.0.1 → 0.7.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 +18 -0
- package/README.md +35 -0
- package/dist/components/chat/chat-box.js +109 -0
- package/dist/components/chat/chat-message.js +76 -0
- package/dist/components/chat/chat.js +56 -0
- package/dist/components/chat/mention-suggestions.js +39 -0
- package/dist/components/chat/mod-view.js +33 -0
- package/dist/components/mobile-player/fullscreen.js +69 -0
- package/dist/components/mobile-player/fullscreen.native.js +151 -0
- package/dist/components/mobile-player/player.js +103 -0
- package/dist/components/mobile-player/props.js +1 -0
- package/dist/components/mobile-player/shared.js +51 -0
- package/dist/components/mobile-player/ui/countdown.js +79 -0
- package/dist/components/mobile-player/ui/index.js +5 -0
- package/dist/components/mobile-player/ui/input.js +38 -0
- package/dist/components/mobile-player/ui/metrics.js +40 -0
- package/dist/components/mobile-player/ui/streamer-context-menu.js +4 -0
- package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -0
- package/dist/components/mobile-player/use-webrtc.js +232 -0
- package/dist/components/mobile-player/video.js +375 -0
- package/dist/components/mobile-player/video.native.js +238 -0
- package/dist/components/mobile-player/webrtc-diagnostics.js +106 -0
- package/dist/components/mobile-player/webrtc-primitives.js +25 -0
- package/dist/components/mobile-player/webrtc-primitives.native.js +1 -0
- package/dist/components/ui/button.js +220 -0
- package/dist/components/ui/dialog.js +203 -0
- package/dist/components/ui/dropdown.js +148 -0
- package/dist/components/ui/icons.js +22 -0
- package/dist/components/ui/index.js +22 -0
- package/dist/components/ui/input.js +202 -0
- package/dist/components/ui/loader.js +7 -0
- package/dist/components/ui/primitives/button.js +121 -0
- package/dist/components/ui/primitives/input.js +202 -0
- package/dist/components/ui/primitives/modal.js +203 -0
- package/dist/components/ui/primitives/text.js +286 -0
- package/dist/components/ui/resizeable.js +101 -0
- package/dist/components/ui/text.js +175 -0
- package/dist/components/ui/textarea.js +17 -0
- package/dist/components/ui/toast.js +129 -0
- package/dist/components/ui/view.js +250 -0
- package/dist/hooks/index.js +9 -0
- package/dist/hooks/useAvatars.js +32 -0
- package/dist/hooks/useCameraToggle.js +9 -0
- package/dist/hooks/useKeyboard.js +33 -0
- package/dist/hooks/useKeyboardSlide.js +11 -0
- package/dist/hooks/useLivestreamInfo.js +62 -0
- package/dist/hooks/useOuterAndInnerDimensions.js +27 -0
- package/dist/hooks/usePlayerDimensions.js +19 -0
- package/dist/hooks/useSegmentTiming.js +62 -0
- package/dist/index.js +16 -0
- package/dist/lib/facet.js +88 -0
- package/dist/lib/theme/atoms.js +620 -0
- package/dist/lib/theme/atoms.types.js +5 -0
- package/dist/lib/theme/index.js +9 -0
- package/dist/lib/theme/theme.js +248 -0
- package/dist/lib/theme/tokens.js +383 -0
- package/dist/lib/utils.js +94 -0
- package/dist/livestream-provider/index.js +25 -0
- package/dist/livestream-provider/websocket.js +41 -0
- package/dist/livestream-store/chat.js +186 -0
- package/dist/livestream-store/context.js +2 -0
- package/dist/livestream-store/index.js +4 -0
- package/dist/livestream-store/livestream-state.js +1 -0
- package/dist/livestream-store/livestream-store.js +42 -0
- package/dist/livestream-store/stream-key.js +115 -0
- package/dist/livestream-store/websocket-consumer.js +55 -0
- package/dist/player-store/context.js +2 -0
- package/dist/player-store/index.js +6 -0
- package/dist/player-store/player-provider.js +52 -0
- package/dist/player-store/player-state.js +22 -0
- package/dist/player-store/player-store.js +159 -0
- package/dist/player-store/single-player-provider.js +109 -0
- package/dist/streamplace-provider/context.js +2 -0
- package/dist/streamplace-provider/index.js +16 -0
- package/dist/streamplace-provider/poller.js +46 -0
- package/dist/streamplace-provider/xrpc.js +0 -0
- package/dist/streamplace-store/block.js +23 -0
- package/dist/streamplace-store/index.js +3 -0
- package/dist/streamplace-store/stream.js +193 -0
- package/dist/streamplace-store/streamplace-store.js +37 -0
- package/dist/streamplace-store/user.js +47 -0
- package/dist/streamplace-store/xrpc.js +12 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/67b1eb60 +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/7c275f90 +0 -0
- package/package.json +50 -8
- package/src/components/chat/chat-box.tsx +195 -0
- package/src/components/chat/chat-message.tsx +192 -0
- package/src/components/chat/chat.tsx +128 -0
- package/src/components/chat/mention-suggestions.tsx +71 -0
- package/src/components/chat/mod-view.tsx +118 -0
- package/src/components/mobile-player/fullscreen.native.tsx +193 -0
- package/src/components/mobile-player/fullscreen.tsx +79 -0
- package/src/components/mobile-player/player.tsx +134 -0
- package/src/components/mobile-player/props.tsx +11 -0
- package/src/components/mobile-player/shared.tsx +56 -0
- package/src/components/mobile-player/ui/countdown.tsx +119 -0
- package/src/components/mobile-player/ui/index.ts +5 -0
- package/src/components/mobile-player/ui/input.tsx +85 -0
- package/src/components/mobile-player/ui/metrics.tsx +69 -0
- package/src/components/mobile-player/ui/streamer-context-menu.tsx +3 -0
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +70 -0
- package/src/components/mobile-player/use-webrtc.tsx +282 -0
- package/src/components/mobile-player/video.native.tsx +360 -0
- package/src/components/mobile-player/video.tsx +557 -0
- package/src/components/mobile-player/webrtc-diagnostics.tsx +149 -0
- package/src/components/mobile-player/webrtc-primitives.native.tsx +6 -0
- package/src/components/mobile-player/webrtc-primitives.tsx +33 -0
- package/src/components/ui/button.tsx +309 -0
- package/src/components/ui/dialog.tsx +376 -0
- package/src/components/ui/dropdown.tsx +399 -0
- package/src/components/ui/icons.tsx +50 -0
- package/src/components/ui/index.ts +33 -0
- package/src/components/ui/input.tsx +350 -0
- package/src/components/ui/loader.tsx +9 -0
- package/src/components/ui/primitives/button.tsx +292 -0
- package/src/components/ui/primitives/input.tsx +422 -0
- package/src/components/ui/primitives/modal.tsx +421 -0
- package/src/components/ui/primitives/text.tsx +499 -0
- package/src/components/ui/resizeable.tsx +169 -0
- package/src/components/ui/text.tsx +330 -0
- package/src/components/ui/textarea.tsx +34 -0
- package/src/components/ui/toast.tsx +203 -0
- package/src/components/ui/view.tsx +344 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useAvatars.tsx +44 -0
- package/src/hooks/useCameraToggle.ts +12 -0
- package/src/hooks/useKeyboard.tsx +41 -0
- package/src/hooks/useKeyboardSlide.ts +12 -0
- package/src/hooks/useLivestreamInfo.ts +67 -0
- package/src/hooks/useOuterAndInnerDimensions.tsx +32 -0
- package/src/hooks/usePlayerDimensions.ts +23 -0
- package/src/hooks/useSegmentTiming.tsx +88 -0
- package/src/index.tsx +27 -0
- package/src/lib/facet.ts +131 -0
- package/src/lib/theme/atoms.ts +760 -0
- package/src/lib/theme/atoms.types.ts +258 -0
- package/src/lib/theme/index.ts +48 -0
- package/src/lib/theme/theme.tsx +436 -0
- package/src/lib/theme/tokens.ts +409 -0
- package/src/lib/utils.ts +132 -0
- package/src/livestream-provider/index.tsx +48 -0
- package/src/livestream-provider/websocket.tsx +47 -0
- package/src/livestream-store/chat.tsx +261 -0
- package/src/livestream-store/context.tsx +10 -0
- package/src/livestream-store/index.tsx +4 -0
- package/src/livestream-store/livestream-state.tsx +21 -0
- package/src/livestream-store/livestream-store.tsx +59 -0
- package/src/livestream-store/stream-key.tsx +124 -0
- package/src/livestream-store/websocket-consumer.tsx +62 -0
- package/src/player-store/context.tsx +11 -0
- package/src/player-store/index.tsx +6 -0
- package/src/player-store/player-provider.tsx +89 -0
- package/src/player-store/player-state.tsx +187 -0
- package/src/player-store/player-store.tsx +239 -0
- package/src/player-store/single-player-provider.tsx +181 -0
- package/src/streamplace-provider/context.tsx +10 -0
- package/src/streamplace-provider/index.tsx +32 -0
- package/src/streamplace-provider/poller.tsx +55 -0
- package/src/streamplace-provider/xrpc.tsx +0 -0
- package/src/streamplace-store/block.tsx +29 -0
- package/src/streamplace-store/index.tsx +3 -0
- package/src/streamplace-store/stream.tsx +262 -0
- package/src/streamplace-store/streamplace-store.tsx +89 -0
- package/src/streamplace-store/user.tsx +57 -0
- package/src/streamplace-store/xrpc.tsx +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { ChatMessageViewHydrated } from "streamplace";
|
|
2
|
+
|
|
3
|
+
export enum PlayerProtocol {
|
|
4
|
+
WEBRTC = "webrtc",
|
|
5
|
+
HLS = "hls",
|
|
6
|
+
PROGRESSIVE_MP4 = "progressive-mp4",
|
|
7
|
+
PROGRESSIVE_WEBM = "progressive-webm",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export enum PlayerStatus {
|
|
11
|
+
START = "start",
|
|
12
|
+
PLAYING = "playing",
|
|
13
|
+
STALLED = "stalled",
|
|
14
|
+
SUSPEND = "suspend",
|
|
15
|
+
WAITING = "waiting",
|
|
16
|
+
PAUSE = "pause",
|
|
17
|
+
MUTE = "mute",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type PlayerStatusTracker = Partial<Record<PlayerStatus, number>>;
|
|
21
|
+
|
|
22
|
+
export enum IngestMediaSource {
|
|
23
|
+
USER = "user",
|
|
24
|
+
DISPLAY = "display",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PlayerState {
|
|
28
|
+
id: string;
|
|
29
|
+
selectedRendition: string;
|
|
30
|
+
setSelectedRendition: (rendition: string) => void;
|
|
31
|
+
protocol: PlayerProtocol;
|
|
32
|
+
setProtocol: (protocol: PlayerProtocol) => void;
|
|
33
|
+
|
|
34
|
+
/** Source */
|
|
35
|
+
src: string;
|
|
36
|
+
|
|
37
|
+
/** Function to set the source URL */
|
|
38
|
+
setSrc: (src: string) => void;
|
|
39
|
+
|
|
40
|
+
/** Flag indicating if ingest (stream input) is currently starting */
|
|
41
|
+
ingestStarting: boolean;
|
|
42
|
+
|
|
43
|
+
/** Function to set the ingestStarting flag */
|
|
44
|
+
setIngestStarting: (ingestStarting: boolean) => void;
|
|
45
|
+
|
|
46
|
+
/** Flag indicating if ingest is live */
|
|
47
|
+
ingestLive: boolean;
|
|
48
|
+
setIngestLive: (ingestLive: boolean) => void;
|
|
49
|
+
|
|
50
|
+
/** Current connection state of ingest RTP/RTC peer connection */
|
|
51
|
+
ingestConnectionState: RTCPeerConnectionState | null;
|
|
52
|
+
|
|
53
|
+
/** Function to update the ingest connection state */
|
|
54
|
+
setIngestConnectionState: (state: RTCPeerConnectionState | null) => void;
|
|
55
|
+
|
|
56
|
+
ingestMediaSource?: IngestMediaSource;
|
|
57
|
+
setIngestMediaSource?: (source: IngestMediaSource) => void;
|
|
58
|
+
|
|
59
|
+
ingestCamera: "user" | "environment";
|
|
60
|
+
setIngestCamera: (camera: "user" | "environment") => void;
|
|
61
|
+
|
|
62
|
+
ingestAutoStart?: boolean;
|
|
63
|
+
setIngestAutoStart?: (autoStart: boolean) => void;
|
|
64
|
+
|
|
65
|
+
/** Timestamp (number) when ingest started, or null if not started */
|
|
66
|
+
ingestStarted: number | null;
|
|
67
|
+
|
|
68
|
+
/** Function to set the ingestStarted timestamp */
|
|
69
|
+
setIngestStarted: (timestamp: number | null) => void;
|
|
70
|
+
|
|
71
|
+
/** Player muted state */
|
|
72
|
+
muted: boolean;
|
|
73
|
+
|
|
74
|
+
/** Function to set the muted state */
|
|
75
|
+
setMuted: (isMuted: boolean) => void;
|
|
76
|
+
|
|
77
|
+
/** Player volume level (0.0 to 1.0) */
|
|
78
|
+
volume: number;
|
|
79
|
+
|
|
80
|
+
/** Function to set the volume level */
|
|
81
|
+
setVolume: (volume: number) => void;
|
|
82
|
+
|
|
83
|
+
/** Player fullscreen state */
|
|
84
|
+
fullscreen: boolean;
|
|
85
|
+
|
|
86
|
+
/** Function to set the fullscreen state */
|
|
87
|
+
setFullscreen: (isFullscreen: boolean) => void;
|
|
88
|
+
|
|
89
|
+
/** Current player status */
|
|
90
|
+
status: PlayerStatus;
|
|
91
|
+
|
|
92
|
+
/** Function to update the player status */
|
|
93
|
+
setStatus: (status: PlayerStatus) => void;
|
|
94
|
+
|
|
95
|
+
/** Current playback time in seconds */
|
|
96
|
+
playTime: number;
|
|
97
|
+
|
|
98
|
+
/** Function to set the current playback time */
|
|
99
|
+
setPlayTime: (playTime: number) => void;
|
|
100
|
+
|
|
101
|
+
/** Flag indicating if player is in offline state */
|
|
102
|
+
offline: boolean;
|
|
103
|
+
|
|
104
|
+
/** Function to set the offline state */
|
|
105
|
+
setOffline: (offline: boolean) => void;
|
|
106
|
+
/** Reference to the video element for direct manipulation (used for PiP) */
|
|
107
|
+
videoRef:
|
|
108
|
+
| React.MutableRefObject<HTMLVideoElement | null>
|
|
109
|
+
| ((instance: HTMLVideoElement | null) => void)
|
|
110
|
+
| null
|
|
111
|
+
| undefined;
|
|
112
|
+
|
|
113
|
+
/** Function to set the video reference */
|
|
114
|
+
setVideoRef: (
|
|
115
|
+
videoRef:
|
|
116
|
+
| React.MutableRefObject<HTMLVideoElement | null>
|
|
117
|
+
| ((instance: HTMLVideoElement | null) => void)
|
|
118
|
+
| null
|
|
119
|
+
| undefined,
|
|
120
|
+
) => void;
|
|
121
|
+
|
|
122
|
+
/** Player element width (CSS value or number) */
|
|
123
|
+
playerWidth?: string | number;
|
|
124
|
+
/** Function to set the player width */
|
|
125
|
+
setPlayerWidth: (width: number) => void;
|
|
126
|
+
|
|
127
|
+
/** Player element height (CSS value or number) */
|
|
128
|
+
playerHeight?: string | number;
|
|
129
|
+
/** Function to set the player height */
|
|
130
|
+
setPlayerHeight: (height: number) => void;
|
|
131
|
+
|
|
132
|
+
/** Flag indicating if player is in Picture-in-Picture mode */
|
|
133
|
+
pipMode: boolean;
|
|
134
|
+
|
|
135
|
+
/** Function to set the Picture-in-Picture mode */
|
|
136
|
+
setPipMode: (pipMode: boolean) => void;
|
|
137
|
+
|
|
138
|
+
/** Flag indicating if mute was forced by system (e.g., autoplay policy) */
|
|
139
|
+
muteWasForced: boolean;
|
|
140
|
+
|
|
141
|
+
/** Function to set the muteWasForced flag */
|
|
142
|
+
setMuteWasForced: (muteWasForced: boolean) => void;
|
|
143
|
+
|
|
144
|
+
/** Flag indicating if the player is embedded in another context */
|
|
145
|
+
embedded: boolean;
|
|
146
|
+
|
|
147
|
+
/** Function to set the embedded flag */
|
|
148
|
+
setEmbedded: (embedded: boolean) => void;
|
|
149
|
+
|
|
150
|
+
/** Flag indicating if player controls should be shown */
|
|
151
|
+
showControls: boolean;
|
|
152
|
+
controlsTimeout?: NodeJS.Timeout | undefined;
|
|
153
|
+
|
|
154
|
+
/** Function to set the showControls flag */
|
|
155
|
+
setShowControls: (showControls: boolean) => void;
|
|
156
|
+
|
|
157
|
+
telemetry: boolean;
|
|
158
|
+
setTelemetry: (telemetry: boolean) => void;
|
|
159
|
+
|
|
160
|
+
playerEvent: (
|
|
161
|
+
url: string,
|
|
162
|
+
time: string,
|
|
163
|
+
eventType: string,
|
|
164
|
+
meta: { [key: string]: any },
|
|
165
|
+
) => void;
|
|
166
|
+
|
|
167
|
+
clearControlsTimeout: () => void;
|
|
168
|
+
|
|
169
|
+
setUserInteraction: () => void;
|
|
170
|
+
|
|
171
|
+
showDebugInfo: boolean;
|
|
172
|
+
setShowDebugInfo: (showDebugInfo: boolean) => void;
|
|
173
|
+
|
|
174
|
+
/** Message to be moderated */
|
|
175
|
+
modMessage: ChatMessageViewHydrated | null;
|
|
176
|
+
|
|
177
|
+
/** Function to set the mod message */
|
|
178
|
+
setModMessage: (message: ChatMessageViewHydrated | null) => void;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type PlayerEvent = {
|
|
182
|
+
id?: string;
|
|
183
|
+
time: string;
|
|
184
|
+
playerId: string;
|
|
185
|
+
eventType: string;
|
|
186
|
+
meta: { [key: string]: any };
|
|
187
|
+
};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { ChatMessageViewHydrated } from "streamplace";
|
|
3
|
+
import { createStore, StoreApi, useStore } from "zustand";
|
|
4
|
+
import { PlayerContext } from "./context";
|
|
5
|
+
import {
|
|
6
|
+
IngestMediaSource,
|
|
7
|
+
PlayerEvent,
|
|
8
|
+
PlayerProtocol,
|
|
9
|
+
PlayerState,
|
|
10
|
+
PlayerStatus,
|
|
11
|
+
} from "./player-state";
|
|
12
|
+
|
|
13
|
+
export type PlayerStore = StoreApi<PlayerState>;
|
|
14
|
+
|
|
15
|
+
export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
16
|
+
return createStore<PlayerState>()((set) => ({
|
|
17
|
+
id: id || Math.random().toString(36).slice(8),
|
|
18
|
+
selectedRendition: "source",
|
|
19
|
+
setSelectedRendition: (rendition: string) =>
|
|
20
|
+
set((state) => ({ ...state, selectedRendition: rendition })),
|
|
21
|
+
protocol: PlayerProtocol.WEBRTC,
|
|
22
|
+
setProtocol: (protocol: PlayerProtocol) =>
|
|
23
|
+
set((state) => ({ ...state, protocol: protocol })),
|
|
24
|
+
|
|
25
|
+
src: "",
|
|
26
|
+
setSrc: (src: string) => set(() => ({ src })),
|
|
27
|
+
|
|
28
|
+
ingestStarting: false,
|
|
29
|
+
setIngestStarting: (ingestStarting: boolean) =>
|
|
30
|
+
set(() => ({ ingestStarting })),
|
|
31
|
+
|
|
32
|
+
ingestMediaSource: undefined,
|
|
33
|
+
setIngestMediaSource: (ingestMediaSource: IngestMediaSource | undefined) =>
|
|
34
|
+
set(() => ({ ingestMediaSource })),
|
|
35
|
+
|
|
36
|
+
ingestCamera: "user",
|
|
37
|
+
setIngestCamera: (ingestCamera: "user" | "environment") =>
|
|
38
|
+
set(() => ({ ingestCamera })),
|
|
39
|
+
|
|
40
|
+
ingestConnectionState: null,
|
|
41
|
+
setIngestConnectionState: (
|
|
42
|
+
ingestConnectionState: RTCPeerConnectionState | null,
|
|
43
|
+
) => set(() => ({ ingestConnectionState })),
|
|
44
|
+
|
|
45
|
+
ingestAutoStart: false,
|
|
46
|
+
setIngestAutoStart: (ingestAutoStart: boolean) =>
|
|
47
|
+
set(() => ({ ingestAutoStart })),
|
|
48
|
+
|
|
49
|
+
ingestStarted: null,
|
|
50
|
+
setIngestStarted: (timestamp: number | null) =>
|
|
51
|
+
set(() => ({ ingestStarted: timestamp })),
|
|
52
|
+
|
|
53
|
+
muted: false,
|
|
54
|
+
setMuted: (isMuted: boolean) =>
|
|
55
|
+
set(() => ({ muted: isMuted, muteWasForced: false })),
|
|
56
|
+
|
|
57
|
+
volume: 1.0,
|
|
58
|
+
setVolume: (volume: number) =>
|
|
59
|
+
set(() => ({ volume, muteWasForced: false })),
|
|
60
|
+
|
|
61
|
+
fullscreen: false,
|
|
62
|
+
setFullscreen: (isFullscreen: boolean) =>
|
|
63
|
+
set(() => ({ fullscreen: isFullscreen })),
|
|
64
|
+
|
|
65
|
+
status: PlayerStatus.START,
|
|
66
|
+
setStatus: (status: PlayerStatus) => set(() => ({ status })),
|
|
67
|
+
|
|
68
|
+
playTime: 0,
|
|
69
|
+
setPlayTime: (playTime: number) => set(() => ({ playTime })),
|
|
70
|
+
|
|
71
|
+
offline: false,
|
|
72
|
+
setOffline: (offline: boolean) => set(() => ({ offline })),
|
|
73
|
+
|
|
74
|
+
videoRef: undefined,
|
|
75
|
+
setVideoRef: (
|
|
76
|
+
videoRef:
|
|
77
|
+
| React.MutableRefObject<HTMLVideoElement | null>
|
|
78
|
+
| ((instance: HTMLVideoElement | null) => void)
|
|
79
|
+
| null
|
|
80
|
+
| undefined,
|
|
81
|
+
) => set(() => ({ videoRef })),
|
|
82
|
+
|
|
83
|
+
pipMode: false,
|
|
84
|
+
setPipMode: (pipMode: boolean) => set(() => ({ pipMode })),
|
|
85
|
+
|
|
86
|
+
// Player element width/height setters for global sync
|
|
87
|
+
playerWidth: undefined,
|
|
88
|
+
setPlayerWidth: (playerWidth: number) => set(() => ({ playerWidth })),
|
|
89
|
+
playerHeight: undefined,
|
|
90
|
+
setPlayerHeight: (playerHeight: number) => set(() => ({ playerHeight })),
|
|
91
|
+
|
|
92
|
+
// * Whether mute was forced by the browser or not for autoplay
|
|
93
|
+
// * Will get set to 'false' if the user has interacted with the volume
|
|
94
|
+
muteWasForced: false,
|
|
95
|
+
setMuteWasForced: (muteWasForced: boolean) =>
|
|
96
|
+
set(() => ({ muteWasForced })),
|
|
97
|
+
|
|
98
|
+
embedded: false,
|
|
99
|
+
setEmbedded: (embedded: boolean) => set(() => ({ embedded })),
|
|
100
|
+
|
|
101
|
+
showControls: true,
|
|
102
|
+
controlsTimeout: undefined,
|
|
103
|
+
setShowControls: (showControls: boolean) =>
|
|
104
|
+
set({ showControls, controlsTimeout: undefined }),
|
|
105
|
+
|
|
106
|
+
telemetry: true,
|
|
107
|
+
setTelemetry: (telemetry: boolean) => set(() => ({ telemetry })),
|
|
108
|
+
|
|
109
|
+
ingestLive: false,
|
|
110
|
+
setIngestLive: (ingestLive: boolean) => set(() => ({ ingestLive })),
|
|
111
|
+
|
|
112
|
+
playerEvent: async (
|
|
113
|
+
url: string,
|
|
114
|
+
time: string,
|
|
115
|
+
eventType: string,
|
|
116
|
+
meta: { [key: string]: any },
|
|
117
|
+
) =>
|
|
118
|
+
set((x) => {
|
|
119
|
+
const data: PlayerEvent = {
|
|
120
|
+
time: time,
|
|
121
|
+
playerId: x.id,
|
|
122
|
+
eventType: eventType,
|
|
123
|
+
meta: {
|
|
124
|
+
...meta,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
try {
|
|
128
|
+
// fetch url from sp provider
|
|
129
|
+
fetch(`${url}/api/player-event`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: JSON.stringify(data),
|
|
132
|
+
});
|
|
133
|
+
} catch (e) {
|
|
134
|
+
console.error("error sending player telemetry", e);
|
|
135
|
+
}
|
|
136
|
+
return {};
|
|
137
|
+
}),
|
|
138
|
+
|
|
139
|
+
// Clear the controls timeout, if it exists.
|
|
140
|
+
// Should be called on player unmount.
|
|
141
|
+
clearControlsTimeout: () =>
|
|
142
|
+
set((state) => {
|
|
143
|
+
if (state.controlsTimeout) {
|
|
144
|
+
clearTimeout(state.controlsTimeout);
|
|
145
|
+
}
|
|
146
|
+
return { controlsTimeout: undefined };
|
|
147
|
+
}),
|
|
148
|
+
|
|
149
|
+
setUserInteraction: () =>
|
|
150
|
+
set((p) => {
|
|
151
|
+
// controls timeout
|
|
152
|
+
if (p.controlsTimeout) {
|
|
153
|
+
clearTimeout(p.controlsTimeout);
|
|
154
|
+
}
|
|
155
|
+
let controlsTimeout = setTimeout(() => p.setShowControls(false), 1000);
|
|
156
|
+
return { showControls: true, controlsTimeout };
|
|
157
|
+
}),
|
|
158
|
+
|
|
159
|
+
showDebugInfo: false,
|
|
160
|
+
setShowDebugInfo: (showDebugInfo: boolean) =>
|
|
161
|
+
set(() => ({ showDebugInfo })),
|
|
162
|
+
|
|
163
|
+
modMessage: null,
|
|
164
|
+
setModMessage: (modMessage: ChatMessageViewHydrated | null) =>
|
|
165
|
+
set(() => ({ modMessage })),
|
|
166
|
+
}));
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export function usePlayerContext() {
|
|
170
|
+
const context = useContext(PlayerContext);
|
|
171
|
+
if (!context) {
|
|
172
|
+
throw new Error("usePlayerContext must be used within a PlayerProvider");
|
|
173
|
+
}
|
|
174
|
+
return context;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Get a specific player store by ID
|
|
178
|
+
export function getPlayerStoreById(id: string): PlayerStore {
|
|
179
|
+
const { players } = usePlayerContext();
|
|
180
|
+
const playerStore = players[id];
|
|
181
|
+
if (!playerStore) {
|
|
182
|
+
throw new Error(`No player found with ID: ${id}`);
|
|
183
|
+
}
|
|
184
|
+
return playerStore;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Will get the first player ID in the context
|
|
188
|
+
export function getFirstPlayerID(): string {
|
|
189
|
+
const { players } = usePlayerContext();
|
|
190
|
+
const playerIds = Object.keys(players);
|
|
191
|
+
if (playerIds.length === 0) {
|
|
192
|
+
throw new Error("No players found in context");
|
|
193
|
+
}
|
|
194
|
+
return playerIds[0];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function getPlayerStoreFromContext(): PlayerStore {
|
|
198
|
+
console.warn(
|
|
199
|
+
"getPlayerStoreFromContext is deprecated. Use getPlayerStoreById instead.",
|
|
200
|
+
);
|
|
201
|
+
const { players } = usePlayerContext();
|
|
202
|
+
const playerIds = Object.keys(players);
|
|
203
|
+
if (playerIds.length === 0) {
|
|
204
|
+
throw new Error("No players found in context");
|
|
205
|
+
}
|
|
206
|
+
return players[playerIds[0]];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Use a specific player store by ID
|
|
210
|
+
// If no ID is provided, it will use the first player in the context
|
|
211
|
+
export function usePlayerStore<U>(
|
|
212
|
+
selector: (state: PlayerState) => U,
|
|
213
|
+
playerId?: string,
|
|
214
|
+
): U {
|
|
215
|
+
if (!playerId) {
|
|
216
|
+
playerId = Object.keys(usePlayerContext().players)[0];
|
|
217
|
+
}
|
|
218
|
+
const store = getPlayerStoreById(playerId);
|
|
219
|
+
return useStore(store, selector);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* Convenience selectors/hooks */
|
|
223
|
+
export const usePlayerProtocol = (
|
|
224
|
+
playerId?: string,
|
|
225
|
+
): [PlayerProtocol, (protocol: PlayerProtocol) => void] =>
|
|
226
|
+
usePlayerStore((x) => [x.protocol, x.setProtocol], playerId);
|
|
227
|
+
|
|
228
|
+
export const intoPlayerProtocol = (protocol: string): PlayerProtocol => {
|
|
229
|
+
switch (protocol) {
|
|
230
|
+
case "hls":
|
|
231
|
+
return PlayerProtocol.HLS;
|
|
232
|
+
case "progressive-mp4":
|
|
233
|
+
return PlayerProtocol.PROGRESSIVE_MP4;
|
|
234
|
+
case "progressive-webm":
|
|
235
|
+
return PlayerProtocol.PROGRESSIVE_WEBM;
|
|
236
|
+
default:
|
|
237
|
+
return PlayerProtocol.WEBRTC;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo } from "react";
|
|
2
|
+
import { StoreApi, useStore } from "zustand";
|
|
3
|
+
import { usePlayerContext } from "../player-store";
|
|
4
|
+
import { PlayerProtocol, PlayerState } from "./player-state";
|
|
5
|
+
|
|
6
|
+
// Context for a single player
|
|
7
|
+
interface SinglePlayerContextType {
|
|
8
|
+
playerId: string;
|
|
9
|
+
playerStore: StoreApi<PlayerState>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const SinglePlayerContext = createContext<SinglePlayerContextType | null>(null);
|
|
13
|
+
|
|
14
|
+
interface SinglePlayerProviderProps {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
playerId?: string;
|
|
17
|
+
protocol?: PlayerProtocol;
|
|
18
|
+
rendition?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Provider component for a single player that creates a scoped context
|
|
23
|
+
* This allows components to access a specific player's state without passing IDs around
|
|
24
|
+
*/
|
|
25
|
+
export const SinglePlayerProvider: React.FC<SinglePlayerProviderProps> = ({
|
|
26
|
+
children,
|
|
27
|
+
playerId: providedPlayerId,
|
|
28
|
+
protocol = PlayerProtocol.WEBRTC,
|
|
29
|
+
rendition = "auto",
|
|
30
|
+
}) => {
|
|
31
|
+
const { players, createPlayer } = usePlayerContext();
|
|
32
|
+
|
|
33
|
+
// Create or get a player ID
|
|
34
|
+
const playerId = useMemo(() => {
|
|
35
|
+
// If a player ID is provided and exists, use it
|
|
36
|
+
if (providedPlayerId && players[providedPlayerId]) {
|
|
37
|
+
return providedPlayerId;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// If a player ID is provided but doesn't exist, create it
|
|
41
|
+
if (providedPlayerId) {
|
|
42
|
+
return createPlayer(providedPlayerId);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Otherwise create a new player
|
|
46
|
+
return createPlayer();
|
|
47
|
+
}, [providedPlayerId, players, createPlayer]);
|
|
48
|
+
|
|
49
|
+
// Get the player store
|
|
50
|
+
const playerStore = useMemo(() => {
|
|
51
|
+
return players[playerId];
|
|
52
|
+
}, [players, playerId]);
|
|
53
|
+
|
|
54
|
+
// Set initial protocol and rendition if provided
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
if (protocol) {
|
|
57
|
+
playerStore.setState((state) => ({
|
|
58
|
+
...state,
|
|
59
|
+
protocol,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (rendition) {
|
|
64
|
+
playerStore.setState((state) => ({
|
|
65
|
+
...state,
|
|
66
|
+
selectedRendition: rendition,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
}, [playerStore, protocol, rendition]);
|
|
70
|
+
|
|
71
|
+
// Create context value
|
|
72
|
+
const contextValue = useMemo(
|
|
73
|
+
() => ({
|
|
74
|
+
playerId,
|
|
75
|
+
playerStore,
|
|
76
|
+
}),
|
|
77
|
+
[playerId, playerStore],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<SinglePlayerContext.Provider value={contextValue}>
|
|
82
|
+
{children}
|
|
83
|
+
</SinglePlayerContext.Provider>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Hook to access the current single player context
|
|
89
|
+
*/
|
|
90
|
+
export function useSinglePlayerContext() {
|
|
91
|
+
const context = useContext(SinglePlayerContext);
|
|
92
|
+
if (!context) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
"useSinglePlayerContext must be used within a SinglePlayerProvider",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return context;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Hook to access the current player ID from the single player context
|
|
102
|
+
*/
|
|
103
|
+
export function useCurrentPlayerId(): string {
|
|
104
|
+
const { playerId } = useSinglePlayerContext();
|
|
105
|
+
return playerId;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Hook to access state from the current player without needing to specify the ID
|
|
110
|
+
*/
|
|
111
|
+
export function useCurrentPlayerStore<U>(
|
|
112
|
+
selector: (state: PlayerState) => U,
|
|
113
|
+
): U {
|
|
114
|
+
const { playerStore } = useSinglePlayerContext();
|
|
115
|
+
return useStore(playerStore, selector);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Hook to get the protocol of the current player
|
|
120
|
+
*/
|
|
121
|
+
export function useCurrentPlayerProtocol(): [
|
|
122
|
+
PlayerProtocol,
|
|
123
|
+
(protocol: PlayerProtocol) => void,
|
|
124
|
+
] {
|
|
125
|
+
return useCurrentPlayerStore(
|
|
126
|
+
(state) => [state.protocol, state.setProtocol] as const,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Hook to get the selected rendition of the current player
|
|
132
|
+
*/
|
|
133
|
+
export function useCurrentPlayerRendition(): [
|
|
134
|
+
string,
|
|
135
|
+
(rendition: string) => void,
|
|
136
|
+
] {
|
|
137
|
+
return useCurrentPlayerStore(
|
|
138
|
+
(state) => [state.selectedRendition, state.setSelectedRendition] as const,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Hook to get the ingest state of the current player
|
|
144
|
+
*/
|
|
145
|
+
export function useCurrentPlayerIngest(): {
|
|
146
|
+
starting: boolean;
|
|
147
|
+
setStarting: (starting: boolean) => void;
|
|
148
|
+
connectionState: RTCPeerConnectionState | null;
|
|
149
|
+
setConnectionState: (state: RTCPeerConnectionState | null) => void;
|
|
150
|
+
startedTimestamp: number | null;
|
|
151
|
+
setStartedTimestamp: (timestamp: number | null) => void;
|
|
152
|
+
} {
|
|
153
|
+
return useCurrentPlayerStore((state) => ({
|
|
154
|
+
starting: state.ingestStarting,
|
|
155
|
+
setStarting: state.setIngestStarting,
|
|
156
|
+
connectionState: state.ingestConnectionState,
|
|
157
|
+
setConnectionState: state.setIngestConnectionState,
|
|
158
|
+
startedTimestamp: state.ingestStarted,
|
|
159
|
+
setStartedTimestamp: state.setIngestStarted,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* HOC to wrap components with a SinglePlayerProvider
|
|
165
|
+
*/
|
|
166
|
+
export function withSinglePlayer<P extends object>(
|
|
167
|
+
Component: React.ComponentType<P>,
|
|
168
|
+
): React.FC<P & SinglePlayerProviderProps> {
|
|
169
|
+
return function WithSinglePlayer(props: P & SinglePlayerProviderProps) {
|
|
170
|
+
const { playerId, protocol, rendition, ...componentProps } = props;
|
|
171
|
+
return (
|
|
172
|
+
<SinglePlayerProvider
|
|
173
|
+
playerId={playerId}
|
|
174
|
+
protocol={protocol}
|
|
175
|
+
rendition={rendition}
|
|
176
|
+
>
|
|
177
|
+
<Component {...(componentProps as P)} />
|
|
178
|
+
</SinglePlayerProvider>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
import { StreamplaceStore } from "../streamplace-store/streamplace-store";
|
|
3
|
+
|
|
4
|
+
type StreamplaceContextType = {
|
|
5
|
+
store: StreamplaceStore;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const StreamplaceContext = createContext<StreamplaceContextType | null>(
|
|
9
|
+
null,
|
|
10
|
+
);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SessionManager } from "@atproto/api/dist/session-manager";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { makeStreamplaceStore } from "../streamplace-store/streamplace-store";
|
|
4
|
+
import { StreamplaceContext } from "./context";
|
|
5
|
+
import Poller from "./poller";
|
|
6
|
+
|
|
7
|
+
export function StreamplaceProvider({
|
|
8
|
+
children,
|
|
9
|
+
url,
|
|
10
|
+
oauthSession,
|
|
11
|
+
}: {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
url: string;
|
|
14
|
+
oauthSession?: SessionManager;
|
|
15
|
+
}) {
|
|
16
|
+
// todo: handle url changes?
|
|
17
|
+
const store = useRef(makeStreamplaceStore({ url })).current;
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
store.setState({ url });
|
|
21
|
+
}, [url]);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
store.setState({ oauthSession });
|
|
25
|
+
}, [oauthSession]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<StreamplaceContext.Provider value={{ store: store }}>
|
|
29
|
+
<Poller>{children}</Poller>
|
|
30
|
+
</StreamplaceContext.Provider>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import { StreamplaceAgent } from "streamplace";
|
|
3
|
+
import {
|
|
4
|
+
useDID,
|
|
5
|
+
useGetBskyProfile,
|
|
6
|
+
useGetChatProfile,
|
|
7
|
+
useStreamplaceStore,
|
|
8
|
+
} from "../streamplace-store";
|
|
9
|
+
import { usePDSAgent } from "../streamplace-store/xrpc";
|
|
10
|
+
|
|
11
|
+
export default function Poller({ children }: { children: React.ReactNode }) {
|
|
12
|
+
const url = useStreamplaceStore((state) => state.url);
|
|
13
|
+
const setLiveUsers = useStreamplaceStore((state) => state.setLiveUsers);
|
|
14
|
+
const did = useDID();
|
|
15
|
+
const pdsAgent = usePDSAgent();
|
|
16
|
+
const getChatProfile = useGetChatProfile();
|
|
17
|
+
const getBskyProfile = useGetBskyProfile();
|
|
18
|
+
const liveUserRefresh = useStreamplaceStore(
|
|
19
|
+
(state) => state.liveUsersRefresh,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (pdsAgent && did) {
|
|
24
|
+
getChatProfile();
|
|
25
|
+
getBskyProfile();
|
|
26
|
+
}
|
|
27
|
+
}, [pdsAgent, did]);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const agent = new StreamplaceAgent(url);
|
|
31
|
+
const go = async () => {
|
|
32
|
+
setLiveUsers({
|
|
33
|
+
liveUsersLoading: true,
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
const res = await agent.place.stream.live.getLiveUsers();
|
|
37
|
+
setLiveUsers({
|
|
38
|
+
liveUsers: res.data.streams || [],
|
|
39
|
+
liveUsersLoading: false,
|
|
40
|
+
liveUsersError: null,
|
|
41
|
+
});
|
|
42
|
+
} catch (e) {
|
|
43
|
+
setLiveUsers({
|
|
44
|
+
liveUsersLoading: false,
|
|
45
|
+
liveUsersError: e.message,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
go();
|
|
50
|
+
const handle = setInterval(go, 3000);
|
|
51
|
+
return () => clearInterval(handle);
|
|
52
|
+
}, [url, liveUserRefresh]);
|
|
53
|
+
|
|
54
|
+
return <>{children}</>;
|
|
55
|
+
}
|
|
File without changes
|