@streamplace/components 0.6.37 → 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/package.json +8 -34
- package/LICENSE +0 -18
- package/README.md +0 -35
- package/dist/index.js +0 -6
- package/dist/livestream-provider/index.js +0 -20
- package/dist/livestream-provider/websocket.js +0 -41
- package/dist/livestream-store/chat.js +0 -162
- package/dist/livestream-store/context.js +0 -2
- package/dist/livestream-store/index.js +0 -3
- package/dist/livestream-store/livestream-state.js +0 -1
- package/dist/livestream-store/livestream-store.js +0 -39
- package/dist/livestream-store/websocket-consumer.js +0 -55
- package/dist/player-store/context.js +0 -2
- package/dist/player-store/index.js +0 -6
- package/dist/player-store/player-provider.js +0 -53
- package/dist/player-store/player-state.js +0 -22
- package/dist/player-store/player-store.js +0 -146
- package/dist/player-store/single-player-provider.js +0 -109
- package/dist/streamplace-provider/context.js +0 -2
- package/dist/streamplace-provider/index.js +0 -16
- package/dist/streamplace-provider/poller.js +0 -46
- package/dist/streamplace-provider/xrpc.js +0 -0
- package/dist/streamplace-store/index.js +0 -2
- package/dist/streamplace-store/streamplace-store.js +0 -37
- package/dist/streamplace-store/user.js +0 -47
- package/dist/streamplace-store/xrpc.js +0 -12
- 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/src/index.tsx +0 -6
- package/src/livestream-provider/index.tsx +0 -37
- package/src/livestream-provider/websocket.tsx +0 -47
- package/src/livestream-store/chat.tsx +0 -224
- package/src/livestream-store/context.tsx +0 -10
- package/src/livestream-store/index.tsx +0 -3
- package/src/livestream-store/livestream-state.tsx +0 -18
- package/src/livestream-store/livestream-store.tsx +0 -56
- package/src/livestream-store/websocket-consumer.tsx +0 -62
- package/src/player-store/context.tsx +0 -11
- package/src/player-store/index.tsx +0 -6
- package/src/player-store/player-provider.tsx +0 -90
- package/src/player-store/player-state.tsx +0 -159
- package/src/player-store/player-store.tsx +0 -217
- package/src/player-store/single-player-provider.tsx +0 -181
- package/src/streamplace-provider/context.tsx +0 -10
- package/src/streamplace-provider/index.tsx +0 -32
- package/src/streamplace-provider/poller.tsx +0 -55
- package/src/streamplace-provider/xrpc.tsx +0 -0
- package/src/streamplace-store/index.tsx +0 -2
- package/src/streamplace-store/streamplace-store.tsx +0 -89
- package/src/streamplace-store/user.tsx +0 -57
- package/src/streamplace-store/xrpc.tsx +0 -15
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import { RichText } from "@atproto/api";
|
|
2
|
-
import {
|
|
3
|
-
isLink,
|
|
4
|
-
isMention,
|
|
5
|
-
} from "@atproto/api/dist/client/types/app/bsky/richtext/facet";
|
|
6
|
-
import {
|
|
7
|
-
ChatMessageViewHydrated,
|
|
8
|
-
PlaceStreamChatMessage,
|
|
9
|
-
PlaceStreamDefs,
|
|
10
|
-
} from "streamplace";
|
|
11
|
-
import { useChatProfile, useDID, useHandle } from "../streamplace-store";
|
|
12
|
-
import { usePDSAgent } from "../streamplace-store/xrpc";
|
|
13
|
-
import { LivestreamState } from "./livestream-state";
|
|
14
|
-
import { getStoreFromContext, useLivestreamStore } from "./livestream-store";
|
|
15
|
-
|
|
16
|
-
export const useReplyToMessage = () =>
|
|
17
|
-
useLivestreamStore((state) => state.replyToMessage);
|
|
18
|
-
|
|
19
|
-
export const useSetReplyToMessage = () => {
|
|
20
|
-
const store = getStoreFromContext();
|
|
21
|
-
return (message: ChatMessageViewHydrated | null) => {
|
|
22
|
-
store.setState({ replyToMessage: message });
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export type NewChatMessage = {
|
|
27
|
-
text: string;
|
|
28
|
-
reply?: {
|
|
29
|
-
cid: string;
|
|
30
|
-
uri: string;
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const useCreateChatMessage = () => {
|
|
35
|
-
const pdsAgent = usePDSAgent();
|
|
36
|
-
const store = getStoreFromContext();
|
|
37
|
-
const userDID = useDID();
|
|
38
|
-
const userHandle = useHandle();
|
|
39
|
-
const chatProfile = useChatProfile();
|
|
40
|
-
|
|
41
|
-
return async (msg: NewChatMessage) => {
|
|
42
|
-
if (!pdsAgent || !userDID) {
|
|
43
|
-
throw new Error("No PDS agent or user DID found");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
let state = store.getState();
|
|
47
|
-
|
|
48
|
-
const streamerProfile = state.profile;
|
|
49
|
-
|
|
50
|
-
if (!streamerProfile) {
|
|
51
|
-
throw new Error("Profile not found");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const rt = new RichText({ text: msg.text });
|
|
55
|
-
rt.detectFacetsWithoutResolution();
|
|
56
|
-
|
|
57
|
-
const record: PlaceStreamChatMessage.Record = {
|
|
58
|
-
text: msg.text,
|
|
59
|
-
createdAt: new Date().toISOString(),
|
|
60
|
-
streamer: streamerProfile.did,
|
|
61
|
-
...(msg.reply
|
|
62
|
-
? {
|
|
63
|
-
reply: {
|
|
64
|
-
root: {
|
|
65
|
-
cid: msg.reply.cid,
|
|
66
|
-
uri: msg.reply.uri,
|
|
67
|
-
},
|
|
68
|
-
parent: {
|
|
69
|
-
cid: msg.reply.cid,
|
|
70
|
-
uri: msg.reply.uri,
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
}
|
|
74
|
-
: {}),
|
|
75
|
-
...(rt.facets && rt.facets.length > 0
|
|
76
|
-
? {
|
|
77
|
-
facets: rt.facets.map((facet) => ({
|
|
78
|
-
index: facet.index,
|
|
79
|
-
features: facet.features
|
|
80
|
-
.filter(
|
|
81
|
-
(feature) =>
|
|
82
|
-
feature.$type === "app.bsky.richtext.facet#link" ||
|
|
83
|
-
feature.$type === "app.bsky.richtext.facet#mention",
|
|
84
|
-
)
|
|
85
|
-
.map((feature) => {
|
|
86
|
-
if (isLink(feature)) {
|
|
87
|
-
return {
|
|
88
|
-
$type: "app.bsky.richtext.facet#link",
|
|
89
|
-
uri: feature.uri,
|
|
90
|
-
};
|
|
91
|
-
} else if (isMention(feature)) {
|
|
92
|
-
return {
|
|
93
|
-
$type: "app.bsky.richtext.facet#mention",
|
|
94
|
-
did: feature.did,
|
|
95
|
-
};
|
|
96
|
-
} else {
|
|
97
|
-
throw new Error("invalid code path");
|
|
98
|
-
}
|
|
99
|
-
}),
|
|
100
|
-
})),
|
|
101
|
-
}
|
|
102
|
-
: {}),
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const localChat: ChatMessageViewHydrated = {
|
|
106
|
-
uri: `local-${Date.now()}`,
|
|
107
|
-
cid: "",
|
|
108
|
-
author: {
|
|
109
|
-
did: userDID,
|
|
110
|
-
handle: userHandle || userDID,
|
|
111
|
-
},
|
|
112
|
-
record: record,
|
|
113
|
-
indexedAt: new Date().toISOString(),
|
|
114
|
-
chatProfile: chatProfile || undefined,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
state = reduceChat(state, [localChat], []);
|
|
118
|
-
store.setState(state);
|
|
119
|
-
|
|
120
|
-
await pdsAgent.com.atproto.repo.createRecord({
|
|
121
|
-
repo: userDID,
|
|
122
|
-
collection: "place.stream.chat.message",
|
|
123
|
-
record,
|
|
124
|
-
});
|
|
125
|
-
};
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const CHAT_LIMIT = 20;
|
|
129
|
-
|
|
130
|
-
export const reduceChat = (
|
|
131
|
-
state: LivestreamState,
|
|
132
|
-
messages: ChatMessageViewHydrated[],
|
|
133
|
-
blocks: PlaceStreamDefs.BlockView[],
|
|
134
|
-
): LivestreamState => {
|
|
135
|
-
state = { ...state } as LivestreamState;
|
|
136
|
-
let newChat: { [key: string]: ChatMessageViewHydrated } = {
|
|
137
|
-
...state.chatIndex,
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// Add new messages
|
|
141
|
-
for (let message of messages) {
|
|
142
|
-
const date = new Date(message.record.createdAt);
|
|
143
|
-
const key = `${date.getTime()}-${message.uri}`;
|
|
144
|
-
|
|
145
|
-
// Remove existing local message matching the server one
|
|
146
|
-
if (!message.uri.startsWith("local-")) {
|
|
147
|
-
const existingLocalMessageKey = Object.keys(newChat).find((k) => {
|
|
148
|
-
const msg = newChat[k];
|
|
149
|
-
return (
|
|
150
|
-
msg.uri.startsWith("local-") &&
|
|
151
|
-
msg.record.text === message.record.text &&
|
|
152
|
-
msg.author.did === message.author.did
|
|
153
|
-
);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
if (existingLocalMessageKey) {
|
|
157
|
-
delete newChat[existingLocalMessageKey];
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Handle reply information for local-first messages
|
|
162
|
-
if (message.record.reply) {
|
|
163
|
-
const reply = message.record.reply as {
|
|
164
|
-
parent?: { uri: string; cid: string };
|
|
165
|
-
root?: { uri: string; cid: string };
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const parentUri = reply?.parent?.uri || reply?.root?.uri;
|
|
169
|
-
|
|
170
|
-
if (parentUri) {
|
|
171
|
-
// First try to find the parent message in our chat
|
|
172
|
-
const parentMsgKey = Object.keys(newChat).find(
|
|
173
|
-
(k) => newChat[k].uri === parentUri,
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
if (parentMsgKey) {
|
|
177
|
-
// Found the parent message, add its info to our message
|
|
178
|
-
const parentMsg = newChat[parentMsgKey];
|
|
179
|
-
message = {
|
|
180
|
-
...message,
|
|
181
|
-
replyTo: {
|
|
182
|
-
cid: parentMsg.cid,
|
|
183
|
-
uri: parentMsg.uri,
|
|
184
|
-
author: parentMsg.author,
|
|
185
|
-
record: parentMsg.record,
|
|
186
|
-
chatProfile: parentMsg.chatProfile,
|
|
187
|
-
indexedAt: parentMsg.indexedAt,
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
newChat[key] = message;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
for (const block of blocks) {
|
|
198
|
-
for (const [k, v] of Object.entries(newChat)) {
|
|
199
|
-
if (v.author.did === block.record.subject) {
|
|
200
|
-
delete newChat[k];
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
let newChatList = Object.values(newChat).sort((a, b) =>
|
|
206
|
-
new Date(a.record.createdAt) > new Date(b.record.createdAt) ? 1 : -1,
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
newChatList = newChatList.slice(-CHAT_LIMIT);
|
|
210
|
-
|
|
211
|
-
newChat = newChatList.reduce(
|
|
212
|
-
(acc, msg) => {
|
|
213
|
-
acc[msg.uri] = msg;
|
|
214
|
-
return acc;
|
|
215
|
-
},
|
|
216
|
-
{} as { [key: string]: ChatMessageViewHydrated },
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
...state,
|
|
221
|
-
chatIndex: newChat,
|
|
222
|
-
chat: newChatList,
|
|
223
|
-
};
|
|
224
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { createContext } from "react";
|
|
2
|
-
import { LivestreamStore } from "../livestream-store/livestream-store";
|
|
3
|
-
|
|
4
|
-
type LivestreamContextType = {
|
|
5
|
-
store: LivestreamStore;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export const LivestreamContext = createContext<LivestreamContextType | null>(
|
|
9
|
-
null,
|
|
10
|
-
);
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { AppBskyActorDefs } from "@atproto/api";
|
|
2
|
-
import {
|
|
3
|
-
ChatMessageViewHydrated,
|
|
4
|
-
LivestreamViewHydrated,
|
|
5
|
-
PlaceStreamDefs,
|
|
6
|
-
PlaceStreamSegment,
|
|
7
|
-
} from "streamplace";
|
|
8
|
-
|
|
9
|
-
export interface LivestreamState {
|
|
10
|
-
profile: AppBskyActorDefs.ProfileViewBasic | null;
|
|
11
|
-
chatIndex: { [key: string]: ChatMessageViewHydrated };
|
|
12
|
-
chat: ChatMessageViewHydrated[];
|
|
13
|
-
livestream: LivestreamViewHydrated | null;
|
|
14
|
-
viewers: number | null;
|
|
15
|
-
segment: PlaceStreamSegment.Record | null;
|
|
16
|
-
renditions: PlaceStreamDefs.Rendition[];
|
|
17
|
-
replyToMessage: ChatMessageViewHydrated | null;
|
|
18
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { useContext } from "react";
|
|
2
|
-
import { createStore, StoreApi, useStore } from "zustand";
|
|
3
|
-
import { LivestreamContext } from "./context";
|
|
4
|
-
import { LivestreamState } from "./livestream-state";
|
|
5
|
-
import { handleWebSocketMessages } from "./websocket-consumer";
|
|
6
|
-
|
|
7
|
-
export type LivestreamStore = StoreApi<LivestreamState>;
|
|
8
|
-
|
|
9
|
-
export const makeLivestreamStore = (): StoreApi<LivestreamState> => {
|
|
10
|
-
return createStore<LivestreamState>()((set) => ({
|
|
11
|
-
profile: null,
|
|
12
|
-
chatIndex: {},
|
|
13
|
-
chat: [],
|
|
14
|
-
livestream: null,
|
|
15
|
-
viewers: null,
|
|
16
|
-
segment: null,
|
|
17
|
-
renditions: [],
|
|
18
|
-
replyToMessage: null,
|
|
19
|
-
}));
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export function getStoreFromContext(): LivestreamStore {
|
|
23
|
-
const context = useContext(LivestreamContext);
|
|
24
|
-
if (!context) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
"useLivestreamStore must be used within a LivestreamProvider",
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
return context.store;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function useLivestreamStore<U>(
|
|
33
|
-
selector: (state: LivestreamState) => U,
|
|
34
|
-
): U {
|
|
35
|
-
const store = getStoreFromContext();
|
|
36
|
-
return useStore(store, selector);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const useHandleWebsocketMessages = () => {
|
|
40
|
-
const store = getStoreFromContext();
|
|
41
|
-
return (messages: any[]) => {
|
|
42
|
-
store.setState((state) => handleWebSocketMessages(state, messages));
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const useChat = () => useLivestreamStore((x) => x.chat);
|
|
47
|
-
|
|
48
|
-
export const useProfile = () => useLivestreamStore((x) => x.profile);
|
|
49
|
-
|
|
50
|
-
export const useViewers = () => useLivestreamStore((x) => x.viewers);
|
|
51
|
-
|
|
52
|
-
export const useLivestream = () => useLivestreamStore((x) => x.livestream);
|
|
53
|
-
|
|
54
|
-
export const useSegment = () => useLivestreamStore((x) => x.segment);
|
|
55
|
-
|
|
56
|
-
export const useRenditions = () => useLivestreamStore((x) => x.renditions);
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { AppBskyActorDefs } from "@atproto/api";
|
|
2
|
-
import {
|
|
3
|
-
ChatMessageViewHydrated,
|
|
4
|
-
LivestreamViewHydrated,
|
|
5
|
-
PlaceStreamChatDefs,
|
|
6
|
-
PlaceStreamChatMessage,
|
|
7
|
-
PlaceStreamDefs,
|
|
8
|
-
PlaceStreamLivestream,
|
|
9
|
-
PlaceStreamSegment,
|
|
10
|
-
} from "streamplace";
|
|
11
|
-
import { reduceChat } from "./chat";
|
|
12
|
-
import { LivestreamState } from "./livestream-state";
|
|
13
|
-
|
|
14
|
-
export const handleWebSocketMessages = (
|
|
15
|
-
state: LivestreamState,
|
|
16
|
-
messages: any[],
|
|
17
|
-
): LivestreamState => {
|
|
18
|
-
for (const message of messages) {
|
|
19
|
-
if (PlaceStreamLivestream.isLivestreamView(message)) {
|
|
20
|
-
state = {
|
|
21
|
-
...state,
|
|
22
|
-
livestream: message as LivestreamViewHydrated,
|
|
23
|
-
};
|
|
24
|
-
} else if (PlaceStreamLivestream.isViewerCount(message)) {
|
|
25
|
-
state = {
|
|
26
|
-
...state,
|
|
27
|
-
viewers: message.count,
|
|
28
|
-
};
|
|
29
|
-
} else if (PlaceStreamChatDefs.isMessageView(message)) {
|
|
30
|
-
// Explicitly map MessageView to MessageViewHydrated
|
|
31
|
-
const hydrated: ChatMessageViewHydrated = {
|
|
32
|
-
uri: message.uri,
|
|
33
|
-
cid: message.cid,
|
|
34
|
-
author: message.author,
|
|
35
|
-
record: message.record as PlaceStreamChatMessage.Record,
|
|
36
|
-
indexedAt: message.indexedAt,
|
|
37
|
-
chatProfile: (message as any).chatProfile,
|
|
38
|
-
replyTo: (message as any).replyTo,
|
|
39
|
-
};
|
|
40
|
-
state = reduceChat(state, [hydrated], []);
|
|
41
|
-
} else if (PlaceStreamSegment.isRecord(message)) {
|
|
42
|
-
state = {
|
|
43
|
-
...state,
|
|
44
|
-
segment: message as PlaceStreamSegment.Record,
|
|
45
|
-
};
|
|
46
|
-
} else if (PlaceStreamDefs.isBlockView(message)) {
|
|
47
|
-
const block = message as PlaceStreamDefs.BlockView;
|
|
48
|
-
state = reduceChat(state, [], [block]);
|
|
49
|
-
} else if (PlaceStreamDefs.isRenditions(message)) {
|
|
50
|
-
state = {
|
|
51
|
-
...state,
|
|
52
|
-
renditions: message.renditions,
|
|
53
|
-
};
|
|
54
|
-
} else if (AppBskyActorDefs.isProfileViewBasic(message)) {
|
|
55
|
-
state = {
|
|
56
|
-
...state,
|
|
57
|
-
profile: message,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return reduceChat(state, [], []);
|
|
62
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { createContext } from "react";
|
|
2
|
-
import { StoreApi } from "zustand";
|
|
3
|
-
import { PlayerState } from "./player-state";
|
|
4
|
-
|
|
5
|
-
type PlayerContextType = {
|
|
6
|
-
players: Record<string, StoreApi<PlayerState>>;
|
|
7
|
-
createPlayer: (id?: string) => string;
|
|
8
|
-
removePlayer: (id: string) => void;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const PlayerContext = createContext<PlayerContextType | null>(null);
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useMemo, useState } from "react";
|
|
2
|
-
import { StoreApi } from "zustand";
|
|
3
|
-
import { PlayerContext } from "./context";
|
|
4
|
-
import { PlayerState } from "./player-state";
|
|
5
|
-
import { makePlayerStore } from "./player-store";
|
|
6
|
-
|
|
7
|
-
interface PlayerProviderProps {
|
|
8
|
-
children: React.ReactNode;
|
|
9
|
-
initialPlayers?: string[];
|
|
10
|
-
defaultId?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const PlayerProvider: React.FC<PlayerProviderProps> = ({
|
|
14
|
-
children,
|
|
15
|
-
initialPlayers = [],
|
|
16
|
-
defaultId = Math.random().toString(36).slice(8),
|
|
17
|
-
}) => {
|
|
18
|
-
const [players, setPlayers] = useState<Record<string, StoreApi<PlayerState>>>(
|
|
19
|
-
() => {
|
|
20
|
-
// Initialize with any initial player IDs provided
|
|
21
|
-
const initialPlayerStores: Record<string, StoreApi<PlayerState>> = {};
|
|
22
|
-
for (const playerId of initialPlayers) {
|
|
23
|
-
initialPlayerStores[playerId] = makePlayerStore(playerId);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Always create at least one player by default
|
|
27
|
-
if (initialPlayers.length === 0) {
|
|
28
|
-
initialPlayerStores[defaultId] = makePlayerStore(defaultId);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return initialPlayerStores;
|
|
32
|
-
},
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const createPlayer = useCallback((id?: string) => {
|
|
36
|
-
console.log("Creating new player");
|
|
37
|
-
const playerId = id || Math.random().toString(36).slice(8);
|
|
38
|
-
const playerStore = makePlayerStore(playerId);
|
|
39
|
-
|
|
40
|
-
setPlayers((prev) => ({
|
|
41
|
-
...prev,
|
|
42
|
-
[playerId]: playerStore,
|
|
43
|
-
}));
|
|
44
|
-
|
|
45
|
-
return playerId;
|
|
46
|
-
}, []);
|
|
47
|
-
|
|
48
|
-
const removePlayer = useCallback((id: string) => {
|
|
49
|
-
setPlayers((prev) => {
|
|
50
|
-
// Don't remove the last player
|
|
51
|
-
if (Object.keys(prev).length <= 1) {
|
|
52
|
-
console.warn("Cannot remove the last player");
|
|
53
|
-
return prev;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const newPlayers = { ...prev };
|
|
57
|
-
delete newPlayers[id];
|
|
58
|
-
return newPlayers;
|
|
59
|
-
});
|
|
60
|
-
}, []);
|
|
61
|
-
|
|
62
|
-
const contextValue = useMemo(
|
|
63
|
-
() => ({
|
|
64
|
-
players,
|
|
65
|
-
createPlayer,
|
|
66
|
-
removePlayer,
|
|
67
|
-
}),
|
|
68
|
-
[players, createPlayer, removePlayer],
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<PlayerContext.Provider value={contextValue}>
|
|
73
|
-
{children}
|
|
74
|
-
</PlayerContext.Provider>
|
|
75
|
-
);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// HOC to wrap components that need player context
|
|
79
|
-
export function withPlayerProvider<P extends object>(
|
|
80
|
-
Component: React.ComponentType<P>,
|
|
81
|
-
): React.FC<P & { initialPlayers?: string[] }> {
|
|
82
|
-
return function WithPlayerProvider(props: P & { initialPlayers?: string[] }) {
|
|
83
|
-
const { initialPlayers, ...componentProps } = props;
|
|
84
|
-
return (
|
|
85
|
-
<PlayerProvider initialPlayers={initialPlayers}>
|
|
86
|
-
<Component {...(componentProps as P)} />
|
|
87
|
-
</PlayerProvider>
|
|
88
|
-
);
|
|
89
|
-
};
|
|
90
|
-
}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
export enum PlayerProtocol {
|
|
2
|
-
WEBRTC = "webrtc",
|
|
3
|
-
HLS = "hls",
|
|
4
|
-
PROGRESSIVE_MP4 = "progressive-mp4",
|
|
5
|
-
PROGRESSIVE_WEBM = "progressive-webm",
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export enum PlayerStatus {
|
|
9
|
-
START = "start",
|
|
10
|
-
PLAYING = "playing",
|
|
11
|
-
STALLED = "stalled",
|
|
12
|
-
SUSPEND = "suspend",
|
|
13
|
-
WAITING = "waiting",
|
|
14
|
-
PAUSE = "pause",
|
|
15
|
-
MUTE = "mute",
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type PlayerStatusTracker = Partial<Record<PlayerStatus, number>>;
|
|
19
|
-
|
|
20
|
-
export enum IngestMediaSource {
|
|
21
|
-
USER = "user",
|
|
22
|
-
DISPLAY = "display",
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface PlayerState {
|
|
26
|
-
id: string;
|
|
27
|
-
selectedRendition: string;
|
|
28
|
-
setSelectedRendition: (rendition: string) => void;
|
|
29
|
-
protocol: PlayerProtocol;
|
|
30
|
-
setProtocol: (protocol: PlayerProtocol) => void;
|
|
31
|
-
|
|
32
|
-
/** Source */
|
|
33
|
-
src: string;
|
|
34
|
-
|
|
35
|
-
/** Function to set the source URL */
|
|
36
|
-
setSrc: (src: string) => void;
|
|
37
|
-
|
|
38
|
-
/** Flag indicating if ingest (stream input) is currently starting */
|
|
39
|
-
ingestStarting: boolean;
|
|
40
|
-
|
|
41
|
-
/** Function to set the ingestStarting flag */
|
|
42
|
-
setIngestStarting: (ingestStarting: boolean) => void;
|
|
43
|
-
|
|
44
|
-
/** Current connection state of ingest RTP/RTC peer connection */
|
|
45
|
-
ingestConnectionState: RTCPeerConnectionState | null;
|
|
46
|
-
|
|
47
|
-
/** Function to update the ingest connection state */
|
|
48
|
-
setIngestConnectionState: (state: RTCPeerConnectionState | null) => void;
|
|
49
|
-
|
|
50
|
-
ingestMediaSource?: IngestMediaSource;
|
|
51
|
-
setIngestMediaSource?: (source: IngestMediaSource) => void;
|
|
52
|
-
|
|
53
|
-
ingestAutoStart?: boolean;
|
|
54
|
-
setIngestAutoStart?: (autoStart: boolean) => void;
|
|
55
|
-
|
|
56
|
-
/** Timestamp (number) when ingest started, or null if not started */
|
|
57
|
-
ingestStarted: number | null;
|
|
58
|
-
|
|
59
|
-
/** Function to set the ingestStarted timestamp */
|
|
60
|
-
setIngestStarted: (timestamp: number | null) => void;
|
|
61
|
-
|
|
62
|
-
/** Player muted state */
|
|
63
|
-
muted: boolean;
|
|
64
|
-
|
|
65
|
-
/** Function to set the muted state */
|
|
66
|
-
setMuted: (isMuted: boolean) => void;
|
|
67
|
-
|
|
68
|
-
/** Player volume level (0.0 to 1.0) */
|
|
69
|
-
volume: number;
|
|
70
|
-
|
|
71
|
-
/** Function to set the volume level */
|
|
72
|
-
setVolume: (volume: number) => void;
|
|
73
|
-
|
|
74
|
-
/** Player fullscreen state */
|
|
75
|
-
fullscreen: boolean;
|
|
76
|
-
|
|
77
|
-
/** Function to set the fullscreen state */
|
|
78
|
-
setFullscreen: (isFullscreen: boolean) => void;
|
|
79
|
-
|
|
80
|
-
/** Current player status */
|
|
81
|
-
status: PlayerStatus;
|
|
82
|
-
|
|
83
|
-
/** Function to update the player status */
|
|
84
|
-
setStatus: (status: PlayerStatus) => void;
|
|
85
|
-
|
|
86
|
-
/** Current playback time in seconds */
|
|
87
|
-
playTime: number;
|
|
88
|
-
|
|
89
|
-
/** Function to set the current playback time */
|
|
90
|
-
setPlayTime: (playTime: number) => void;
|
|
91
|
-
|
|
92
|
-
/** Flag indicating if player is in offline state */
|
|
93
|
-
offline: boolean;
|
|
94
|
-
|
|
95
|
-
/** Function to set the offline state */
|
|
96
|
-
setOffline: (offline: boolean) => void;
|
|
97
|
-
/** Reference to the video element for direct manipulation (used for PiP) */
|
|
98
|
-
videoRef:
|
|
99
|
-
| React.MutableRefObject<HTMLVideoElement | null>
|
|
100
|
-
| ((instance: HTMLVideoElement | null) => void)
|
|
101
|
-
| null
|
|
102
|
-
| undefined;
|
|
103
|
-
|
|
104
|
-
/** Function to set the video reference */
|
|
105
|
-
setVideoRef: (
|
|
106
|
-
videoRef:
|
|
107
|
-
| React.MutableRefObject<HTMLVideoElement | null>
|
|
108
|
-
| ((instance: HTMLVideoElement | null) => void)
|
|
109
|
-
| null
|
|
110
|
-
| undefined,
|
|
111
|
-
) => void;
|
|
112
|
-
|
|
113
|
-
/** Flag indicating if player is in Picture-in-Picture mode */
|
|
114
|
-
pipMode: boolean;
|
|
115
|
-
|
|
116
|
-
/** Function to set the Picture-in-Picture mode */
|
|
117
|
-
setPipMode: (pipMode: boolean) => void;
|
|
118
|
-
|
|
119
|
-
/** Flag indicating if mute was forced by system (e.g., autoplay policy) */
|
|
120
|
-
muteWasForced: boolean;
|
|
121
|
-
|
|
122
|
-
/** Function to set the muteWasForced flag */
|
|
123
|
-
setMuteWasForced: (muteWasForced: boolean) => void;
|
|
124
|
-
|
|
125
|
-
/** Flag indicating if the player is embedded in another context */
|
|
126
|
-
embedded: boolean;
|
|
127
|
-
|
|
128
|
-
/** Function to set the embedded flag */
|
|
129
|
-
setEmbedded: (embedded: boolean) => void;
|
|
130
|
-
|
|
131
|
-
/** Flag indicating if player controls should be shown */
|
|
132
|
-
showControls: boolean;
|
|
133
|
-
controlsTimeout?: NodeJS.Timeout | undefined;
|
|
134
|
-
|
|
135
|
-
/** Function to set the showControls flag */
|
|
136
|
-
setShowControls: (showControls: boolean) => void;
|
|
137
|
-
|
|
138
|
-
telemetry: boolean;
|
|
139
|
-
setTelemetry: (telemetry: boolean) => void;
|
|
140
|
-
|
|
141
|
-
playerEvent: (
|
|
142
|
-
url: string,
|
|
143
|
-
time: string,
|
|
144
|
-
eventType: string,
|
|
145
|
-
meta: { [key: string]: any },
|
|
146
|
-
) => void;
|
|
147
|
-
|
|
148
|
-
clearControlsTimeout: () => void;
|
|
149
|
-
|
|
150
|
-
setUserInteraction: () => void;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export type PlayerEvent = {
|
|
154
|
-
id?: string;
|
|
155
|
-
time: string;
|
|
156
|
-
playerId: string;
|
|
157
|
-
eventType: string;
|
|
158
|
-
meta: { [key: string]: any };
|
|
159
|
-
};
|