@streamplace/components 0.6.37 → 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/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 +10 -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 +8 -3
- package/dist/livestream-store/chat.js +89 -65
- package/dist/livestream-store/index.js +1 -0
- package/dist/livestream-store/livestream-store.js +3 -0
- package/dist/livestream-store/stream-key.js +115 -0
- package/dist/player-store/player-provider.js +0 -1
- package/dist/player-store/player-store.js +13 -0
- package/dist/streamplace-store/block.js +23 -0
- package/dist/streamplace-store/index.js +1 -0
- package/dist/streamplace-store/stream.js +193 -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 +20 -4
- 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 +21 -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 +13 -2
- package/src/livestream-store/chat.tsx +115 -78
- package/src/livestream-store/index.tsx +1 -0
- package/src/livestream-store/livestream-state.tsx +3 -0
- package/src/livestream-store/livestream-store.tsx +3 -0
- package/src/livestream-store/stream-key.tsx +124 -0
- package/src/player-store/player-provider.tsx +0 -1
- package/src/player-store/player-state.tsx +28 -0
- package/src/player-store/player-store.tsx +22 -0
- package/src/streamplace-store/block.tsx +29 -0
- package/src/streamplace-store/index.tsx +1 -0
- package/src/streamplace-store/stream.tsx +262 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { RichText } from "@atproto/api";
|
|
2
|
-
import {
|
|
3
|
-
isLink,
|
|
4
|
-
isMention,
|
|
5
|
-
} from "@atproto/api/dist/client/types/app/bsky/richtext/facet";
|
|
2
|
+
import { useCallback } from "react";
|
|
6
3
|
import {
|
|
7
4
|
ChatMessageViewHydrated,
|
|
8
5
|
PlaceStreamChatMessage,
|
|
@@ -18,9 +15,12 @@ export const useReplyToMessage = () =>
|
|
|
18
15
|
|
|
19
16
|
export const useSetReplyToMessage = () => {
|
|
20
17
|
const store = getStoreFromContext();
|
|
21
|
-
return (
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
return useCallback(
|
|
19
|
+
(message: ChatMessageViewHydrated | null) => {
|
|
20
|
+
store.setState({ replyToMessage: message });
|
|
21
|
+
},
|
|
22
|
+
[store],
|
|
23
|
+
);
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export type NewChatMessage = {
|
|
@@ -52,12 +52,13 @@ export const useCreateChatMessage = () => {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const rt = new RichText({ text: msg.text });
|
|
55
|
-
rt.
|
|
55
|
+
await rt.detectFacets(pdsAgent);
|
|
56
56
|
|
|
57
57
|
const record: PlaceStreamChatMessage.Record = {
|
|
58
58
|
text: msg.text,
|
|
59
59
|
createdAt: new Date().toISOString(),
|
|
60
60
|
streamer: streamerProfile.did,
|
|
61
|
+
facets: rt.facets as PlaceStreamChatMessage.Record["facets"],
|
|
61
62
|
...(msg.reply
|
|
62
63
|
? {
|
|
63
64
|
reply: {
|
|
@@ -72,34 +73,6 @@ export const useCreateChatMessage = () => {
|
|
|
72
73
|
},
|
|
73
74
|
}
|
|
74
75
|
: {}),
|
|
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
76
|
};
|
|
104
77
|
|
|
105
78
|
const localChat: ChatMessageViewHydrated = {
|
|
@@ -125,40 +98,108 @@ export const useCreateChatMessage = () => {
|
|
|
125
98
|
};
|
|
126
99
|
};
|
|
127
100
|
|
|
128
|
-
const
|
|
101
|
+
const buildSortedChatList = (
|
|
102
|
+
chatIndex: { [key: string]: ChatMessageViewHydrated },
|
|
103
|
+
existingChatList: ChatMessageViewHydrated[],
|
|
104
|
+
newMessages: { key: string; message: ChatMessageViewHydrated }[],
|
|
105
|
+
removedKeys: Set<string>,
|
|
106
|
+
): ChatMessageViewHydrated[] => {
|
|
107
|
+
const sortedKeys = Object.keys(chatIndex).sort((a, b) => {
|
|
108
|
+
const aTime = parseInt(a.split("-")[0], 10);
|
|
109
|
+
const bTime = parseInt(b.split("-")[0], 10);
|
|
110
|
+
return bTime - aTime;
|
|
111
|
+
});
|
|
112
|
+
return sortedKeys.map((key) => chatIndex[key]);
|
|
113
|
+
};
|
|
129
114
|
|
|
130
|
-
|
|
115
|
+
const profileIsDifferent = (
|
|
116
|
+
newProfile: ChatMessageViewHydrated["chatProfile"],
|
|
117
|
+
oldProfile: ChatMessageViewHydrated["chatProfile"],
|
|
118
|
+
) => {
|
|
119
|
+
if (!oldProfile) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (!newProfile) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (!oldProfile.color) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (!newProfile.color) {
|
|
129
|
+
// idk. shouldn't happen.
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const { red: newRed, green: newGreen, blue: newBlue } = newProfile.color;
|
|
133
|
+
const { red: oldRed, green: oldGreen, blue: oldBlue } = oldProfile.color;
|
|
134
|
+
return newRed !== oldRed || newGreen !== oldGreen || newBlue !== oldBlue;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const reduceChatIncremental = (
|
|
131
138
|
state: LivestreamState,
|
|
132
|
-
|
|
139
|
+
newMessages: ChatMessageViewHydrated[],
|
|
133
140
|
blocks: PlaceStreamDefs.BlockView[],
|
|
134
141
|
): LivestreamState => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
if (newMessages.length === 0 && blocks.length === 0) {
|
|
143
|
+
return state;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const newChatIndex = { ...state.chatIndex };
|
|
147
|
+
const newAuthors = { ...state.authors };
|
|
148
|
+
let hasChanges = false;
|
|
149
|
+
const removedKeys = new Set<string>();
|
|
139
150
|
|
|
140
|
-
//
|
|
141
|
-
|
|
151
|
+
// handle blocks
|
|
152
|
+
if (blocks.length > 0) {
|
|
153
|
+
const blockedDIDs = new Set(blocks.map((block) => block.record.subject));
|
|
154
|
+
for (const [key, message] of Object.entries(newChatIndex)) {
|
|
155
|
+
if (blockedDIDs.has(message.author.did)) {
|
|
156
|
+
delete newChatIndex[key];
|
|
157
|
+
removedKeys.add(key);
|
|
158
|
+
hasChanges = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const messagesToAdd: { key: string; message: ChatMessageViewHydrated }[] = [];
|
|
164
|
+
|
|
165
|
+
for (const message of newMessages) {
|
|
142
166
|
const date = new Date(message.record.createdAt);
|
|
143
167
|
const key = `${date.getTime()}-${message.uri}`;
|
|
144
168
|
|
|
145
|
-
//
|
|
169
|
+
// only change the ref if the profile is different to avoid re-renders elsewhere
|
|
170
|
+
if (
|
|
171
|
+
profileIsDifferent(message.chatProfile, newAuthors[message.author.handle])
|
|
172
|
+
) {
|
|
173
|
+
newAuthors[message.author.handle] = message.chatProfile;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// skip messages we already have
|
|
177
|
+
if (newChatIndex[key] && newChatIndex[key].uri === message.uri) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// if we have a local message, replace it with the new one
|
|
146
182
|
if (!message.uri.startsWith("local-")) {
|
|
147
|
-
const
|
|
148
|
-
const msg =
|
|
183
|
+
const existingLocalKey = Object.keys(newChatIndex).find((k) => {
|
|
184
|
+
const msg = newChatIndex[k];
|
|
149
185
|
return (
|
|
150
186
|
msg.uri.startsWith("local-") &&
|
|
151
187
|
msg.record.text === message.record.text &&
|
|
152
|
-
msg.author.did === message.author.did
|
|
188
|
+
msg.author.did === message.author.did &&
|
|
189
|
+
Math.abs(new Date(msg.record.createdAt).getTime() - date.getTime()) <
|
|
190
|
+
10000 // Within 10 seconds
|
|
153
191
|
);
|
|
154
192
|
});
|
|
155
193
|
|
|
156
|
-
if (
|
|
157
|
-
delete
|
|
194
|
+
if (existingLocalKey) {
|
|
195
|
+
delete newChatIndex[existingLocalKey];
|
|
196
|
+
removedKeys.add(existingLocalKey);
|
|
197
|
+
hasChanges = true;
|
|
158
198
|
}
|
|
159
199
|
}
|
|
160
200
|
|
|
161
|
-
//
|
|
201
|
+
// add reply info
|
|
202
|
+
let processedMessage = message;
|
|
162
203
|
if (message.record.reply) {
|
|
163
204
|
const reply = message.record.reply as {
|
|
164
205
|
parent?: { uri: string; cid: string };
|
|
@@ -166,17 +207,14 @@ export const reduceChat = (
|
|
|
166
207
|
};
|
|
167
208
|
|
|
168
209
|
const parentUri = reply?.parent?.uri || reply?.root?.uri;
|
|
169
|
-
|
|
170
210
|
if (parentUri) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
(k) => newChat[k].uri === parentUri,
|
|
211
|
+
const parentMsgKey = Object.keys(newChatIndex).find(
|
|
212
|
+
(k) => newChatIndex[k].uri === parentUri,
|
|
174
213
|
);
|
|
175
214
|
|
|
176
215
|
if (parentMsgKey) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
message = {
|
|
216
|
+
const parentMsg = newChatIndex[parentMsgKey];
|
|
217
|
+
processedMessage = {
|
|
180
218
|
...message,
|
|
181
219
|
replyTo: {
|
|
182
220
|
cid: parentMsg.cid,
|
|
@@ -191,34 +229,33 @@ export const reduceChat = (
|
|
|
191
229
|
}
|
|
192
230
|
}
|
|
193
231
|
|
|
194
|
-
|
|
232
|
+
messagesToAdd.push({ key, message: processedMessage });
|
|
233
|
+
hasChanges = true;
|
|
195
234
|
}
|
|
196
235
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
delete newChat[k];
|
|
201
|
-
}
|
|
202
|
-
}
|
|
236
|
+
// Add new messages to index
|
|
237
|
+
for (const { key, message } of messagesToAdd) {
|
|
238
|
+
newChatIndex[key] = message;
|
|
203
239
|
}
|
|
204
240
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
newChatList = newChatList.slice(-CHAT_LIMIT);
|
|
241
|
+
// only rebuild if we have changes
|
|
242
|
+
if (!hasChanges) {
|
|
243
|
+
return state;
|
|
244
|
+
}
|
|
210
245
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
246
|
+
// Build the new sorted chat list efficiently
|
|
247
|
+
const newChatList = buildSortedChatList(
|
|
248
|
+
newChatIndex,
|
|
249
|
+
state.chat,
|
|
250
|
+
messagesToAdd,
|
|
251
|
+
removedKeys,
|
|
217
252
|
);
|
|
218
253
|
|
|
219
254
|
return {
|
|
220
255
|
...state,
|
|
221
|
-
chatIndex:
|
|
256
|
+
chatIndex: newChatIndex,
|
|
222
257
|
chat: newChatList,
|
|
223
258
|
};
|
|
224
259
|
};
|
|
260
|
+
|
|
261
|
+
export const reduceChat = reduceChatIncremental;
|
|
@@ -10,9 +10,12 @@ export interface LivestreamState {
|
|
|
10
10
|
profile: AppBskyActorDefs.ProfileViewBasic | null;
|
|
11
11
|
chatIndex: { [key: string]: ChatMessageViewHydrated };
|
|
12
12
|
chat: ChatMessageViewHydrated[];
|
|
13
|
+
authors: { [key: string]: ChatMessageViewHydrated["chatProfile"] };
|
|
13
14
|
livestream: LivestreamViewHydrated | null;
|
|
14
15
|
viewers: number | null;
|
|
15
16
|
segment: PlaceStreamSegment.Record | null;
|
|
16
17
|
renditions: PlaceStreamDefs.Rendition[];
|
|
17
18
|
replyToMessage: ChatMessageViewHydrated | null;
|
|
19
|
+
streamKey: string | null;
|
|
20
|
+
setStreamKey: (key: string | null) => void;
|
|
18
21
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { bytesToMultibase, Secp256k1Keypair } from "@atproto/crypto";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Platform } from "react-native";
|
|
4
|
+
import { PlaceStreamKey } from "streamplace";
|
|
5
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
6
|
+
import { usePDSAgent } from "../streamplace-store/xrpc";
|
|
7
|
+
import { useLivestreamStore } from "./livestream-store";
|
|
8
|
+
|
|
9
|
+
function getBrowserName(userAgent: string) {
|
|
10
|
+
// The order matters here, and this may report false positives for unlisted browsers.
|
|
11
|
+
|
|
12
|
+
if (userAgent.includes("Firefox")) {
|
|
13
|
+
// "Mozilla/5.0 (X11; Linux i686; rv:104.0) Gecko/20100101 Firefox/104.0"
|
|
14
|
+
return "Mozilla Firefox";
|
|
15
|
+
} else if (userAgent.includes("SamsungBrowser")) {
|
|
16
|
+
// "Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G955F Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.4 Chrome/67.0.3396.87 Mobile Safari/537.36"
|
|
17
|
+
return "Samsung Internet";
|
|
18
|
+
} else if (userAgent.includes("Opera") || userAgent.includes("OPR")) {
|
|
19
|
+
// "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_5_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 OPR/90.0.4480.54"
|
|
20
|
+
return "Opera";
|
|
21
|
+
} else if (userAgent.includes("Edge")) {
|
|
22
|
+
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299"
|
|
23
|
+
return "Microsoft Edge (Legacy)";
|
|
24
|
+
} else if (userAgent.includes("Edg")) {
|
|
25
|
+
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 Edg/104.0.1293.70"
|
|
26
|
+
return "Microsoft Edge (Chromium)";
|
|
27
|
+
} else if (userAgent.includes("Chrome")) {
|
|
28
|
+
// "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
|
|
29
|
+
return "Google Chrome or Chromium";
|
|
30
|
+
} else if (userAgent.includes("Safari")) {
|
|
31
|
+
// "Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Mobile/15E148 Safari/604.1"
|
|
32
|
+
return "Apple Safari";
|
|
33
|
+
}
|
|
34
|
+
return "unknown";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const useStreamKey = (): {
|
|
38
|
+
streamKey: {
|
|
39
|
+
privateKey: string;
|
|
40
|
+
did: string;
|
|
41
|
+
address: string;
|
|
42
|
+
} | null;
|
|
43
|
+
error: string | null;
|
|
44
|
+
} => {
|
|
45
|
+
const pdsAgent = usePDSAgent();
|
|
46
|
+
const streamKey = useLivestreamStore((state) => state.streamKey);
|
|
47
|
+
const setStreamKey = useLivestreamStore((state) => state.setStreamKey);
|
|
48
|
+
const [key, setKey] = useState<any>(streamKey ? JSON.parse(streamKey) : null);
|
|
49
|
+
const [error, setError] = useState<string | null>(null);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (key) return; // already have key
|
|
53
|
+
|
|
54
|
+
const generateKey = async () => {
|
|
55
|
+
if (!pdsAgent) {
|
|
56
|
+
setError("PDS Agent is not available");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let did = pdsAgent.did;
|
|
60
|
+
if (!did) {
|
|
61
|
+
setError("PDS Agent did is not available (not logged in?)");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const keypair = await Secp256k1Keypair.create({ exportable: true });
|
|
66
|
+
const exportedKey = await keypair.export();
|
|
67
|
+
const didBytes = new TextEncoder().encode(did);
|
|
68
|
+
const combinedKey = new Uint8Array([...exportedKey, ...didBytes]);
|
|
69
|
+
const multibaseKey = bytesToMultibase(combinedKey, "base58btc");
|
|
70
|
+
const hexKey = Array.from(exportedKey)
|
|
71
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
72
|
+
.join("");
|
|
73
|
+
const account = privateKeyToAccount(`0x${hexKey}`);
|
|
74
|
+
const newKey = {
|
|
75
|
+
privateKey: multibaseKey,
|
|
76
|
+
did: keypair.did(),
|
|
77
|
+
address: account.address.toLowerCase(),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
let platform: string = Platform.OS;
|
|
81
|
+
if (
|
|
82
|
+
Platform.OS === "web" &&
|
|
83
|
+
typeof window !== "undefined" &&
|
|
84
|
+
window.navigator
|
|
85
|
+
) {
|
|
86
|
+
if (window.navigator.userAgent.includes("streamplace-desktop")) {
|
|
87
|
+
platform = "Desktop";
|
|
88
|
+
} else {
|
|
89
|
+
platform = getBrowserName(window.navigator.userAgent);
|
|
90
|
+
if (platform !== "unknown") {
|
|
91
|
+
platform = platform;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else if (platform === "android") {
|
|
95
|
+
platform = "Android";
|
|
96
|
+
} else if (platform === "ios") {
|
|
97
|
+
platform = "iOS";
|
|
98
|
+
} else if (platform === "macos") {
|
|
99
|
+
platform = "macOS";
|
|
100
|
+
} else if (platform === "windows") {
|
|
101
|
+
platform = "Windows";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const record: PlaceStreamKey.Record = {
|
|
105
|
+
signingKey: keypair.did(),
|
|
106
|
+
createdAt: new Date().toISOString(),
|
|
107
|
+
createdBy: "Streamplace on " + platform,
|
|
108
|
+
};
|
|
109
|
+
await pdsAgent.com.atproto.repo.createRecord({
|
|
110
|
+
repo: did,
|
|
111
|
+
collection: "place.stream.key",
|
|
112
|
+
record,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
setStreamKey(JSON.stringify(newKey));
|
|
116
|
+
setKey(newKey);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
generateKey();
|
|
120
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
121
|
+
}, [key, setStreamKey]);
|
|
122
|
+
|
|
123
|
+
return { streamKey: key, error };
|
|
124
|
+
};
|
|
@@ -33,7 +33,6 @@ export const PlayerProvider: React.FC<PlayerProviderProps> = ({
|
|
|
33
33
|
);
|
|
34
34
|
|
|
35
35
|
const createPlayer = useCallback((id?: string) => {
|
|
36
|
-
console.log("Creating new player");
|
|
37
36
|
const playerId = id || Math.random().toString(36).slice(8);
|
|
38
37
|
const playerStore = makePlayerStore(playerId);
|
|
39
38
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ChatMessageViewHydrated } from "streamplace";
|
|
2
|
+
|
|
1
3
|
export enum PlayerProtocol {
|
|
2
4
|
WEBRTC = "webrtc",
|
|
3
5
|
HLS = "hls",
|
|
@@ -41,6 +43,10 @@ export interface PlayerState {
|
|
|
41
43
|
/** Function to set the ingestStarting flag */
|
|
42
44
|
setIngestStarting: (ingestStarting: boolean) => void;
|
|
43
45
|
|
|
46
|
+
/** Flag indicating if ingest is live */
|
|
47
|
+
ingestLive: boolean;
|
|
48
|
+
setIngestLive: (ingestLive: boolean) => void;
|
|
49
|
+
|
|
44
50
|
/** Current connection state of ingest RTP/RTC peer connection */
|
|
45
51
|
ingestConnectionState: RTCPeerConnectionState | null;
|
|
46
52
|
|
|
@@ -50,6 +56,9 @@ export interface PlayerState {
|
|
|
50
56
|
ingestMediaSource?: IngestMediaSource;
|
|
51
57
|
setIngestMediaSource?: (source: IngestMediaSource) => void;
|
|
52
58
|
|
|
59
|
+
ingestCamera: "user" | "environment";
|
|
60
|
+
setIngestCamera: (camera: "user" | "environment") => void;
|
|
61
|
+
|
|
53
62
|
ingestAutoStart?: boolean;
|
|
54
63
|
setIngestAutoStart?: (autoStart: boolean) => void;
|
|
55
64
|
|
|
@@ -110,6 +119,16 @@ export interface PlayerState {
|
|
|
110
119
|
| undefined,
|
|
111
120
|
) => void;
|
|
112
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
|
+
|
|
113
132
|
/** Flag indicating if player is in Picture-in-Picture mode */
|
|
114
133
|
pipMode: boolean;
|
|
115
134
|
|
|
@@ -148,6 +167,15 @@ export interface PlayerState {
|
|
|
148
167
|
clearControlsTimeout: () => void;
|
|
149
168
|
|
|
150
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;
|
|
151
179
|
}
|
|
152
180
|
|
|
153
181
|
export type PlayerEvent = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useContext } from "react";
|
|
2
|
+
import { ChatMessageViewHydrated } from "streamplace";
|
|
2
3
|
import { createStore, StoreApi, useStore } from "zustand";
|
|
3
4
|
import { PlayerContext } from "./context";
|
|
4
5
|
import {
|
|
@@ -32,6 +33,10 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
32
33
|
setIngestMediaSource: (ingestMediaSource: IngestMediaSource | undefined) =>
|
|
33
34
|
set(() => ({ ingestMediaSource })),
|
|
34
35
|
|
|
36
|
+
ingestCamera: "user",
|
|
37
|
+
setIngestCamera: (ingestCamera: "user" | "environment") =>
|
|
38
|
+
set(() => ({ ingestCamera })),
|
|
39
|
+
|
|
35
40
|
ingestConnectionState: null,
|
|
36
41
|
setIngestConnectionState: (
|
|
37
42
|
ingestConnectionState: RTCPeerConnectionState | null,
|
|
@@ -78,6 +83,12 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
78
83
|
pipMode: false,
|
|
79
84
|
setPipMode: (pipMode: boolean) => set(() => ({ pipMode })),
|
|
80
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
|
+
|
|
81
92
|
// * Whether mute was forced by the browser or not for autoplay
|
|
82
93
|
// * Will get set to 'false' if the user has interacted with the volume
|
|
83
94
|
muteWasForced: false,
|
|
@@ -95,6 +106,9 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
95
106
|
telemetry: true,
|
|
96
107
|
setTelemetry: (telemetry: boolean) => set(() => ({ telemetry })),
|
|
97
108
|
|
|
109
|
+
ingestLive: false,
|
|
110
|
+
setIngestLive: (ingestLive: boolean) => set(() => ({ ingestLive })),
|
|
111
|
+
|
|
98
112
|
playerEvent: async (
|
|
99
113
|
url: string,
|
|
100
114
|
time: string,
|
|
@@ -141,6 +155,14 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
141
155
|
let controlsTimeout = setTimeout(() => p.setShowControls(false), 1000);
|
|
142
156
|
return { showControls: true, controlsTimeout };
|
|
143
157
|
}),
|
|
158
|
+
|
|
159
|
+
showDebugInfo: false,
|
|
160
|
+
setShowDebugInfo: (showDebugInfo: boolean) =>
|
|
161
|
+
set(() => ({ showDebugInfo })),
|
|
162
|
+
|
|
163
|
+
modMessage: null,
|
|
164
|
+
setModMessage: (modMessage: ChatMessageViewHydrated | null) =>
|
|
165
|
+
set(() => ({ modMessage })),
|
|
144
166
|
}));
|
|
145
167
|
};
|
|
146
168
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AppBskyGraphBlock } from "@atproto/api";
|
|
2
|
+
import { usePDSAgent } from "./xrpc";
|
|
3
|
+
|
|
4
|
+
export function useCreateBlockRecord() {
|
|
5
|
+
let agent = usePDSAgent();
|
|
6
|
+
|
|
7
|
+
return async (subjectDID: string) => {
|
|
8
|
+
if (!agent) {
|
|
9
|
+
throw new Error("No PDS agent found");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (!agent.did) {
|
|
13
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const record: AppBskyGraphBlock.Record = {
|
|
17
|
+
$type: "app.bsky.graph.block",
|
|
18
|
+
subject: subjectDID,
|
|
19
|
+
createdAt: new Date().toISOString(),
|
|
20
|
+
};
|
|
21
|
+
return await agent.com.atproto.repo.createRecord({
|
|
22
|
+
repo: agent.did,
|
|
23
|
+
collection: "app.bsky.graph.block",
|
|
24
|
+
record,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return record;
|
|
28
|
+
};
|
|
29
|
+
}
|