@streamplace/components 0.7.2 → 0.7.7
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 +212 -24
- package/dist/components/chat/chat-message.js +5 -5
- package/dist/components/chat/chat.js +83 -5
- package/dist/components/chat/emoji-suggestions.js +35 -0
- package/dist/components/chat/mod-view.js +59 -8
- package/dist/components/chat/system-message.js +19 -0
- package/dist/components/icons/bluesky-icon.js +9 -0
- package/dist/components/keep-awake.js +7 -0
- package/dist/components/keep-awake.native.js +16 -0
- package/dist/components/mobile-player/fullscreen.js +2 -1
- package/dist/components/mobile-player/fullscreen.native.js +3 -3
- package/dist/components/mobile-player/player.js +15 -30
- package/dist/components/mobile-player/ui/index.js +2 -0
- package/dist/components/mobile-player/ui/report-modal.js +90 -0
- package/dist/components/mobile-player/ui/streamer-loading-overlay.js +104 -0
- package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -1
- package/dist/components/mobile-player/ui/viewer-loading-overlay.js +49 -0
- package/dist/components/mobile-player/use-webrtc.js +7 -1
- package/dist/components/mobile-player/video-retry.js +29 -0
- package/dist/components/mobile-player/video.js +84 -9
- package/dist/components/mobile-player/video.native.js +24 -10
- package/dist/components/share/sharesheet.js +91 -0
- package/dist/components/ui/dialog.js +1 -1
- package/dist/components/ui/dropdown.js +6 -6
- package/dist/components/ui/index.js +2 -0
- package/dist/components/ui/primitives/modal.js +0 -1
- package/dist/components/ui/resizeable.js +20 -11
- package/dist/components/ui/slider.js +5 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/usePointerDevice.js +71 -0
- package/dist/index.js +10 -3
- package/dist/lib/system-messages.js +101 -0
- package/dist/livestream-store/chat.js +111 -18
- package/dist/livestream-store/livestream-store.js +3 -0
- package/dist/livestream-store/problems.js +76 -0
- package/dist/livestream-store/websocket-consumer.js +39 -4
- package/dist/player-store/player-store.js +33 -4
- package/dist/streamplace-store/block.js +51 -12
- package/dist/streamplace-store/stream.js +44 -23
- package/dist/ui/index.js +79 -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-92db9086-0 → v22.15.0-x64-efe9a9df-0}/67b1eb60 +0 -0
- package/node-compile-cache/{v22.15.0-x64-92db9086-0 → v22.15.0-x64-efe9a9df-0}/7c275f90 +0 -0
- package/package.json +6 -2
- package/src/components/chat/chat-box.tsx +295 -25
- package/src/components/chat/chat-message.tsx +6 -7
- package/src/components/chat/chat.tsx +192 -41
- package/src/components/chat/emoji-suggestions.tsx +94 -0
- package/src/components/chat/mod-view.tsx +119 -40
- package/src/components/chat/system-message.tsx +38 -0
- package/src/components/icons/bluesky-icon.tsx +9 -0
- package/src/components/keep-awake.native.tsx +13 -0
- package/src/components/keep-awake.tsx +3 -0
- package/src/components/mobile-player/fullscreen.native.tsx +12 -3
- package/src/components/mobile-player/fullscreen.tsx +10 -3
- package/src/components/mobile-player/player.tsx +28 -36
- package/src/components/mobile-player/props.tsx +1 -0
- package/src/components/mobile-player/ui/index.ts +2 -0
- package/src/components/mobile-player/ui/report-modal.tsx +195 -0
- package/src/components/mobile-player/ui/streamer-loading-overlay.tsx +154 -0
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +31 -3
- package/src/components/mobile-player/ui/viewer-loading-overlay.tsx +66 -0
- package/src/components/mobile-player/use-webrtc.tsx +10 -2
- package/src/components/mobile-player/video-retry.tsx +28 -0
- package/src/components/mobile-player/video.native.tsx +24 -10
- package/src/components/mobile-player/video.tsx +100 -21
- package/src/components/share/sharesheet.tsx +185 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/dropdown.tsx +13 -13
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/primitives/modal.tsx +0 -1
- package/src/components/ui/resizeable.tsx +26 -15
- package/src/components/ui/slider.tsx +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/usePointerDevice.ts +89 -0
- package/src/index.tsx +11 -2
- package/src/lib/system-messages.ts +135 -0
- package/src/livestream-store/chat.tsx +145 -17
- package/src/livestream-store/livestream-state.tsx +10 -0
- package/src/livestream-store/livestream-store.tsx +3 -0
- package/src/livestream-store/problems.tsx +96 -0
- package/src/livestream-store/websocket-consumer.tsx +44 -4
- package/src/player-store/player-state.tsx +25 -4
- package/src/player-store/player-store.tsx +43 -5
- package/src/streamplace-store/block.tsx +55 -13
- package/src/streamplace-store/stream.tsx +66 -35
- package/src/ui/index.ts +86 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/node-compile-cache/v22.15.0-x64-92db9086-0/37be0eec +0 -0
- package/node-compile-cache/v22.15.0-x64-92db9086-0/56540125 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.reduceChat = exports.reduceChatIncremental = exports.useCreateChatMessage = exports.useSetReplyToMessage = exports.useReplyToMessage = void 0;
|
|
3
|
+
exports.reduceChat = exports.useReportChatMessage = exports.useSubmitReport = exports.reduceChatIncremental = exports.useCreateChatMessage = exports.useAddPendingHide = exports.usePendingHides = exports.useSetReplyToMessage = exports.useReplyToMessage = void 0;
|
|
4
4
|
const api_1 = require("@atproto/api");
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const streamplace_store_1 = require("../streamplace-store");
|
|
@@ -15,6 +15,23 @@ const useSetReplyToMessage = () => {
|
|
|
15
15
|
}, [store]);
|
|
16
16
|
};
|
|
17
17
|
exports.useSetReplyToMessage = useSetReplyToMessage;
|
|
18
|
+
const usePendingHides = () => (0, livestream_store_1.useLivestreamStore)((state) => state.pendingHides);
|
|
19
|
+
exports.usePendingHides = usePendingHides;
|
|
20
|
+
const useAddPendingHide = () => {
|
|
21
|
+
const store = (0, livestream_store_1.getStoreFromContext)();
|
|
22
|
+
return (0, react_1.useCallback)((messageUri) => {
|
|
23
|
+
const state = store.getState();
|
|
24
|
+
if (!state.pendingHides.includes(messageUri)) {
|
|
25
|
+
const newPendingHides = [...state.pendingHides, messageUri];
|
|
26
|
+
const newState = (0, exports.reduceChat)(state, [], [], [messageUri]);
|
|
27
|
+
store.setState({
|
|
28
|
+
...newState,
|
|
29
|
+
pendingHides: newPendingHides,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}, [store]);
|
|
33
|
+
};
|
|
34
|
+
exports.useAddPendingHide = useAddPendingHide;
|
|
18
35
|
const useCreateChatMessage = () => {
|
|
19
36
|
const pdsAgent = (0, xrpc_1.usePDSAgent)();
|
|
20
37
|
const store = (0, livestream_store_1.getStoreFromContext)();
|
|
@@ -63,7 +80,7 @@ const useCreateChatMessage = () => {
|
|
|
63
80
|
indexedAt: new Date().toISOString(),
|
|
64
81
|
chatProfile: chatProfile || undefined,
|
|
65
82
|
};
|
|
66
|
-
state = (0, exports.reduceChat)(state, [localChat], []);
|
|
83
|
+
state = (0, exports.reduceChat)(state, [localChat], [], []);
|
|
67
84
|
store.setState(state);
|
|
68
85
|
await pdsAgent.com.atproto.repo.createRecord({
|
|
69
86
|
repo: userDID,
|
|
@@ -79,7 +96,9 @@ const buildSortedChatList = (chatIndex, existingChatList, newMessages, removedKe
|
|
|
79
96
|
const bTime = parseInt(b.split("-")[0], 10);
|
|
80
97
|
return bTime - aTime;
|
|
81
98
|
});
|
|
82
|
-
return sortedKeys
|
|
99
|
+
return sortedKeys
|
|
100
|
+
.map((key) => chatIndex[key])
|
|
101
|
+
.filter((msg) => !removedKeys.has(msg.uri));
|
|
83
102
|
};
|
|
84
103
|
const profileIsDifferent = (newProfile, oldProfile) => {
|
|
85
104
|
if (!oldProfile) {
|
|
@@ -99,14 +118,25 @@ const profileIsDifferent = (newProfile, oldProfile) => {
|
|
|
99
118
|
const { red: oldRed, green: oldGreen, blue: oldBlue } = oldProfile.color;
|
|
100
119
|
return newRed !== oldRed || newGreen !== oldGreen || newBlue !== oldBlue;
|
|
101
120
|
};
|
|
102
|
-
const reduceChatIncremental = (state, newMessages, blocks) => {
|
|
103
|
-
if (newMessages.length === 0 &&
|
|
121
|
+
const reduceChatIncremental = (state, newMessages, blocks, hideUris = []) => {
|
|
122
|
+
if (newMessages.length === 0 &&
|
|
123
|
+
blocks.length === 0 &&
|
|
124
|
+
hideUris.length === 0) {
|
|
104
125
|
return state;
|
|
105
126
|
}
|
|
106
127
|
const newChatIndex = { ...state.chatIndex };
|
|
107
128
|
const newAuthors = { ...state.authors };
|
|
108
129
|
let hasChanges = false;
|
|
109
130
|
const removedKeys = new Set();
|
|
131
|
+
console.log("newMessages", newMessages);
|
|
132
|
+
for (const msg of newMessages) {
|
|
133
|
+
if (msg.deleted) {
|
|
134
|
+
hasChanges = true;
|
|
135
|
+
console.log("deleted", msg.uri);
|
|
136
|
+
removedKeys.add(msg.uri);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
newMessages = newMessages.filter((msg) => msg.deleted !== true);
|
|
110
140
|
// handle blocks
|
|
111
141
|
if (blocks.length > 0) {
|
|
112
142
|
const blockedDIDs = new Set(blocks.map((block) => block.record.subject));
|
|
@@ -118,13 +148,26 @@ const reduceChatIncremental = (state, newMessages, blocks) => {
|
|
|
118
148
|
}
|
|
119
149
|
}
|
|
120
150
|
}
|
|
151
|
+
if (hideUris.length > 0) {
|
|
152
|
+
for (const [key, message] of Object.entries(newChatIndex)) {
|
|
153
|
+
if (hideUris.includes(message.uri)) {
|
|
154
|
+
delete newChatIndex[key];
|
|
155
|
+
removedKeys.add(key);
|
|
156
|
+
hasChanges = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
121
160
|
const messagesToAdd = [];
|
|
122
161
|
for (const message of newMessages) {
|
|
162
|
+
// don't worry about messages that will be hidden
|
|
163
|
+
if (state.pendingHides.includes(message.uri)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
123
166
|
const date = new Date(message.record.createdAt);
|
|
124
167
|
const key = `${date.getTime()}-${message.uri}`;
|
|
125
168
|
// only change the ref if the profile is different to avoid re-renders elsewhere
|
|
126
|
-
if (profileIsDifferent(message.chatProfile, newAuthors[message.author.
|
|
127
|
-
newAuthors[message.author.
|
|
169
|
+
if (profileIsDifferent(message.chatProfile, newAuthors[message.author.did])) {
|
|
170
|
+
newAuthors[message.author.did] = message.chatProfile;
|
|
128
171
|
}
|
|
129
172
|
// skip messages we already have
|
|
130
173
|
if (newChatIndex[key] && newChatIndex[key].uri === message.uri) {
|
|
@@ -156,17 +199,20 @@ const reduceChatIncremental = (state, newMessages, blocks) => {
|
|
|
156
199
|
const parentMsgKey = Object.keys(newChatIndex).find((k) => newChatIndex[k].uri === parentUri);
|
|
157
200
|
if (parentMsgKey) {
|
|
158
201
|
const parentMsg = newChatIndex[parentMsgKey];
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
202
|
+
// Don't allow replies to system messages
|
|
203
|
+
if (parentMsg.author.did !== "did:sys:system") {
|
|
204
|
+
processedMessage = {
|
|
205
|
+
...message,
|
|
206
|
+
replyTo: {
|
|
207
|
+
cid: parentMsg.cid,
|
|
208
|
+
uri: parentMsg.uri,
|
|
209
|
+
author: parentMsg.author,
|
|
210
|
+
record: parentMsg.record,
|
|
211
|
+
chatProfile: parentMsg.chatProfile,
|
|
212
|
+
indexedAt: parentMsg.indexedAt,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
170
216
|
}
|
|
171
217
|
}
|
|
172
218
|
}
|
|
@@ -183,11 +229,58 @@ const reduceChatIncremental = (state, newMessages, blocks) => {
|
|
|
183
229
|
}
|
|
184
230
|
// Build the new sorted chat list efficiently
|
|
185
231
|
const newChatList = buildSortedChatList(newChatIndex, state.chat, messagesToAdd, removedKeys);
|
|
232
|
+
// Clean up pendingHides - remove URIs that we've now processed
|
|
233
|
+
let newPendingHides = state.pendingHides;
|
|
234
|
+
if (hideUris.length > 0) {
|
|
235
|
+
newPendingHides = state.pendingHides.filter((uri) => !hideUris.includes(uri));
|
|
236
|
+
}
|
|
186
237
|
return {
|
|
187
238
|
...state,
|
|
239
|
+
authors: newAuthors,
|
|
188
240
|
chatIndex: newChatIndex,
|
|
189
241
|
chat: newChatList,
|
|
242
|
+
pendingHides: newPendingHides,
|
|
190
243
|
};
|
|
191
244
|
};
|
|
192
245
|
exports.reduceChatIncremental = reduceChatIncremental;
|
|
246
|
+
const useSubmitReport = () => {
|
|
247
|
+
const pdsAgent = (0, xrpc_1.usePDSAgent)();
|
|
248
|
+
const userDID = (0, streamplace_store_1.useDID)();
|
|
249
|
+
return (0, react_1.useCallback)(async (subject, reasonType, reason,
|
|
250
|
+
// no clue about this
|
|
251
|
+
moderationSvcDid = "did:web:stream.place") => {
|
|
252
|
+
if (!pdsAgent || !userDID) {
|
|
253
|
+
throw new Error("No PDS agent or user DID found");
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
const response = await pdsAgent.com.atproto.moderation.createReport({
|
|
257
|
+
reasonType,
|
|
258
|
+
reason,
|
|
259
|
+
subject: subject,
|
|
260
|
+
}, {
|
|
261
|
+
headers: {
|
|
262
|
+
// "atproto-proxy": `${userDID}#atproto_labeler`,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
return response;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error("Failed to submit report:", error);
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}, [pdsAgent, userDID]);
|
|
272
|
+
};
|
|
273
|
+
exports.useSubmitReport = useSubmitReport;
|
|
274
|
+
const useReportChatMessage = () => {
|
|
275
|
+
const submitReport = (0, exports.useSubmitReport)();
|
|
276
|
+
return (0, react_1.useCallback)(async (message, reasonType, reason) => {
|
|
277
|
+
const reportSubject = {
|
|
278
|
+
$type: "com.atproto.repo.strongRef",
|
|
279
|
+
uri: message.uri,
|
|
280
|
+
cid: message.cid,
|
|
281
|
+
};
|
|
282
|
+
return await submitReport(reportSubject, reasonType, reason);
|
|
283
|
+
}, [submitReport]);
|
|
284
|
+
};
|
|
285
|
+
exports.useReportChatMessage = useReportChatMessage;
|
|
193
286
|
exports.reduceChat = exports.reduceChatIncremental;
|
|
@@ -14,12 +14,15 @@ const makeLivestreamStore = () => {
|
|
|
14
14
|
chat: [],
|
|
15
15
|
livestream: null,
|
|
16
16
|
viewers: null,
|
|
17
|
+
pendingHides: [],
|
|
17
18
|
segment: null,
|
|
18
19
|
renditions: [],
|
|
19
20
|
replyToMessage: null,
|
|
20
21
|
streamKey: null,
|
|
21
22
|
setStreamKey: (sk) => set({ streamKey: sk }),
|
|
22
23
|
authors: {},
|
|
24
|
+
recentSegments: [],
|
|
25
|
+
problems: [],
|
|
23
26
|
}));
|
|
24
27
|
};
|
|
25
28
|
exports.makeLivestreamStore = makeLivestreamStore;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findProblems = void 0;
|
|
4
|
+
const VARIANCE_THRESHOLD = 0.5;
|
|
5
|
+
const DURATION_THRESHOLD = 5000000000; // 5s in ns
|
|
6
|
+
const detectVariableSegmentLength = (segments) => {
|
|
7
|
+
if (segments.length < 3) {
|
|
8
|
+
// Need at least 3 segments to detect variability
|
|
9
|
+
return { variable: false, duration: false };
|
|
10
|
+
}
|
|
11
|
+
const durations = segments
|
|
12
|
+
.map((segment) => segment.duration)
|
|
13
|
+
.filter((duration) => duration !== undefined && duration > 0);
|
|
14
|
+
if (durations.length < 3) {
|
|
15
|
+
return { variable: false, duration: false };
|
|
16
|
+
}
|
|
17
|
+
// Calculate mean
|
|
18
|
+
const mean = durations.reduce((sum, duration) => sum + duration, 0) /
|
|
19
|
+
durations.length;
|
|
20
|
+
// Calculate standard deviation
|
|
21
|
+
const variance = durations.reduce((sum, duration) => {
|
|
22
|
+
const diff = duration - mean;
|
|
23
|
+
return sum + diff * diff;
|
|
24
|
+
}, 0) / durations.length;
|
|
25
|
+
const stdDev = Math.sqrt(variance);
|
|
26
|
+
// Calculate coefficient of variation (CV)
|
|
27
|
+
const cv = stdDev / mean;
|
|
28
|
+
// CV > 0.5 indicates high variability
|
|
29
|
+
// This threshold can be adjusted based on testing
|
|
30
|
+
return {
|
|
31
|
+
variable: cv > VARIANCE_THRESHOLD,
|
|
32
|
+
duration: mean > DURATION_THRESHOLD,
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const findProblems = (segments) => {
|
|
36
|
+
const problems = [];
|
|
37
|
+
let hasBFrames = false;
|
|
38
|
+
for (const segment of segments) {
|
|
39
|
+
const video = segment.video?.[0];
|
|
40
|
+
if (!video) {
|
|
41
|
+
// i mean yes this is a problem but it can't happen yet
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (video.bframes === true) {
|
|
45
|
+
hasBFrames = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (hasBFrames) {
|
|
50
|
+
problems.push({
|
|
51
|
+
code: "bframes",
|
|
52
|
+
message: "Your stream contains B-Frames, which are not supported in Streamplace. Your stream will stutter.",
|
|
53
|
+
severity: "error",
|
|
54
|
+
link: "https://stream.place/docs/guides/start-streaming/obs/#obs-configuration",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const { variable, duration } = detectVariableSegmentLength(segments);
|
|
58
|
+
if (variable) {
|
|
59
|
+
problems.push({
|
|
60
|
+
code: "variable_segment_length",
|
|
61
|
+
message: "Your stream contains variable segment lengths, which may cause playback issues.",
|
|
62
|
+
severity: "warning",
|
|
63
|
+
link: "https://stream.place/docs/guides/start-streaming/obs/#obs-configuration",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (duration) {
|
|
67
|
+
problems.push({
|
|
68
|
+
code: "long_segments",
|
|
69
|
+
message: "Your stream contains long segments (>5s). This will work fine, but increases the delay of the livestream.",
|
|
70
|
+
severity: "warning",
|
|
71
|
+
link: "https://stream.place/docs/guides/start-streaming/obs/#obs-configuration",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return problems;
|
|
75
|
+
};
|
|
76
|
+
exports.findProblems = findProblems;
|
|
@@ -3,13 +3,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.handleWebSocketMessages = void 0;
|
|
4
4
|
const api_1 = require("@atproto/api");
|
|
5
5
|
const streamplace_1 = require("streamplace");
|
|
6
|
+
const system_messages_1 = require("../lib/system-messages");
|
|
6
7
|
const chat_1 = require("./chat");
|
|
8
|
+
const problems_1 = require("./problems");
|
|
9
|
+
const MAX_RECENT_SEGMENTS = 10;
|
|
7
10
|
const handleWebSocketMessages = (state, messages) => {
|
|
8
11
|
for (const message of messages) {
|
|
9
12
|
if (streamplace_1.PlaceStreamLivestream.isLivestreamView(message)) {
|
|
13
|
+
const newLivestream = message;
|
|
14
|
+
const oldLivestream = state.livestream;
|
|
15
|
+
// check if this is actually new
|
|
16
|
+
if (!oldLivestream || oldLivestream.uri !== newLivestream.uri) {
|
|
17
|
+
const streamTitle = newLivestream.record.title || "something cool!";
|
|
18
|
+
const systemMessage = system_messages_1.SystemMessages.streamStart(streamTitle);
|
|
19
|
+
// set proper times
|
|
20
|
+
systemMessage.indexedAt = newLivestream.indexedAt;
|
|
21
|
+
systemMessage.record.createdAt = newLivestream.record.createdAt;
|
|
22
|
+
state = (0, chat_1.reduceChat)(state, [systemMessage], []);
|
|
23
|
+
}
|
|
10
24
|
state = {
|
|
11
25
|
...state,
|
|
12
|
-
livestream:
|
|
26
|
+
livestream: newLivestream,
|
|
13
27
|
};
|
|
14
28
|
}
|
|
15
29
|
else if (streamplace_1.PlaceStreamLivestream.isViewerCount(message)) {
|
|
@@ -28,18 +42,26 @@ const handleWebSocketMessages = (state, messages) => {
|
|
|
28
42
|
indexedAt: message.indexedAt,
|
|
29
43
|
chatProfile: message.chatProfile,
|
|
30
44
|
replyTo: message.replyTo,
|
|
45
|
+
deleted: message.deleted,
|
|
31
46
|
};
|
|
32
|
-
state = (0, chat_1.reduceChat)(state, [hydrated], []);
|
|
47
|
+
state = (0, chat_1.reduceChat)(state, [hydrated], [], []);
|
|
33
48
|
}
|
|
34
49
|
else if (streamplace_1.PlaceStreamSegment.isRecord(message)) {
|
|
50
|
+
const newRecentSegments = [...state.recentSegments];
|
|
51
|
+
newRecentSegments.unshift(message);
|
|
52
|
+
if (newRecentSegments.length > MAX_RECENT_SEGMENTS) {
|
|
53
|
+
newRecentSegments.pop();
|
|
54
|
+
}
|
|
35
55
|
state = {
|
|
36
56
|
...state,
|
|
37
57
|
segment: message,
|
|
58
|
+
recentSegments: newRecentSegments,
|
|
59
|
+
problems: (0, problems_1.findProblems)(newRecentSegments),
|
|
38
60
|
};
|
|
39
61
|
}
|
|
40
62
|
else if (streamplace_1.PlaceStreamDefs.isBlockView(message)) {
|
|
41
63
|
const block = message;
|
|
42
|
-
state = (0, chat_1.reduceChat)(state, [], [block]);
|
|
64
|
+
state = (0, chat_1.reduceChat)(state, [], [block], []);
|
|
43
65
|
}
|
|
44
66
|
else if (streamplace_1.PlaceStreamDefs.isRenditions(message)) {
|
|
45
67
|
state = {
|
|
@@ -53,7 +75,20 @@ const handleWebSocketMessages = (state, messages) => {
|
|
|
53
75
|
profile: message,
|
|
54
76
|
};
|
|
55
77
|
}
|
|
78
|
+
else if (streamplace_1.PlaceStreamChatGate.isRecord(message)) {
|
|
79
|
+
const hideRecord = message;
|
|
80
|
+
const hiddenMessageUri = hideRecord.hiddenMessage;
|
|
81
|
+
const newPendingHides = [...state.pendingHides];
|
|
82
|
+
if (!newPendingHides.includes(hiddenMessageUri)) {
|
|
83
|
+
newPendingHides.push(hiddenMessageUri);
|
|
84
|
+
}
|
|
85
|
+
state = {
|
|
86
|
+
...state,
|
|
87
|
+
pendingHides: newPendingHides,
|
|
88
|
+
};
|
|
89
|
+
state = (0, chat_1.reduceChat)(state, [], [], [hiddenMessageUri]);
|
|
90
|
+
}
|
|
56
91
|
}
|
|
57
|
-
return (0, chat_1.reduceChat)(state, [], []);
|
|
92
|
+
return (0, chat_1.reduceChat)(state, [], [], []);
|
|
58
93
|
};
|
|
59
94
|
exports.handleWebSocketMessages = handleWebSocketMessages;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.intoPlayerProtocol = exports.usePlayerProtocol = exports.makePlayerStore = void 0;
|
|
3
|
+
exports.useOffline = exports.intoPlayerProtocol = exports.usePlayerProtocol = exports.makePlayerStore = void 0;
|
|
4
4
|
exports.usePlayerContext = usePlayerContext;
|
|
5
5
|
exports.getPlayerStoreById = getPlayerStoreById;
|
|
6
6
|
exports.getFirstPlayerID = getFirstPlayerID;
|
|
@@ -8,6 +8,7 @@ exports.getPlayerStoreFromContext = getPlayerStoreFromContext;
|
|
|
8
8
|
exports.usePlayerStore = usePlayerStore;
|
|
9
9
|
const react_1 = require("react");
|
|
10
10
|
const zustand_1 = require("zustand");
|
|
11
|
+
const livestream_store_1 = require("../livestream-store");
|
|
11
12
|
const context_1 = require("./context");
|
|
12
13
|
const player_state_1 = require("./player-state");
|
|
13
14
|
const makePlayerStore = (id) => {
|
|
@@ -41,12 +42,13 @@ const makePlayerStore = (id) => {
|
|
|
41
42
|
setStatus: (status) => set(() => ({ status })),
|
|
42
43
|
playTime: 0,
|
|
43
44
|
setPlayTime: (playTime) => set(() => ({ playTime })),
|
|
44
|
-
offline: false,
|
|
45
|
-
setOffline: (offline) => set(() => ({ offline })),
|
|
46
45
|
videoRef: undefined,
|
|
47
46
|
setVideoRef: (videoRef) => set(() => ({ videoRef })),
|
|
48
47
|
pipMode: false,
|
|
49
48
|
setPipMode: (pipMode) => set(() => ({ pipMode })),
|
|
49
|
+
// Picture-in-Picture action function (set by player component)
|
|
50
|
+
pipAction: undefined,
|
|
51
|
+
setPipAction: (action) => set(() => ({ pipAction: action })),
|
|
50
52
|
// Player element width/height setters for global sync
|
|
51
53
|
playerWidth: undefined,
|
|
52
54
|
setPlayerWidth: (playerWidth) => set(() => ({ playerWidth })),
|
|
@@ -65,6 +67,8 @@ const makePlayerStore = (id) => {
|
|
|
65
67
|
setTelemetry: (telemetry) => set(() => ({ telemetry })),
|
|
66
68
|
ingestLive: false,
|
|
67
69
|
setIngestLive: (ingestLive) => set(() => ({ ingestLive })),
|
|
70
|
+
reportingURL: null,
|
|
71
|
+
setReportingURL: (reportingURL) => set(() => ({ reportingURL })),
|
|
68
72
|
playerEvent: async (url, time, eventType, meta) => set((x) => {
|
|
69
73
|
const data = {
|
|
70
74
|
time: time,
|
|
@@ -76,7 +80,8 @@ const makePlayerStore = (id) => {
|
|
|
76
80
|
};
|
|
77
81
|
try {
|
|
78
82
|
// fetch url from sp provider
|
|
79
|
-
|
|
83
|
+
const reportingURL = x.reportingURL ?? `${url}/api/player-event`;
|
|
84
|
+
fetch(reportingURL, {
|
|
80
85
|
method: "POST",
|
|
81
86
|
body: JSON.stringify(data),
|
|
82
87
|
});
|
|
@@ -106,6 +111,10 @@ const makePlayerStore = (id) => {
|
|
|
106
111
|
setShowDebugInfo: (showDebugInfo) => set(() => ({ showDebugInfo })),
|
|
107
112
|
modMessage: null,
|
|
108
113
|
setModMessage: (modMessage) => set(() => ({ modMessage })),
|
|
114
|
+
reportModalOpen: false,
|
|
115
|
+
setReportModalOpen: (reportModalOpen) => set(() => ({ reportModalOpen })),
|
|
116
|
+
reportSubject: null,
|
|
117
|
+
setReportSubject: (subject) => set(() => ({ reportSubject: subject })),
|
|
109
118
|
}));
|
|
110
119
|
};
|
|
111
120
|
exports.makePlayerStore = makePlayerStore;
|
|
@@ -168,3 +177,23 @@ const intoPlayerProtocol = (protocol) => {
|
|
|
168
177
|
}
|
|
169
178
|
};
|
|
170
179
|
exports.intoPlayerProtocol = intoPlayerProtocol;
|
|
180
|
+
// returns true if the livestream has been offline for more than 10 seconds and we're not playing
|
|
181
|
+
const useOffline = () => {
|
|
182
|
+
const status = usePlayerStore((x) => x.status);
|
|
183
|
+
const segment = (0, livestream_store_1.useLivestreamStore)((x) => x.segment);
|
|
184
|
+
const [now, setNow] = (0, react_1.useState)(Date.now());
|
|
185
|
+
(0, react_1.useEffect)(() => {
|
|
186
|
+
const interval = setInterval(() => {
|
|
187
|
+
setNow(Date.now());
|
|
188
|
+
}, 500);
|
|
189
|
+
return () => clearInterval(interval);
|
|
190
|
+
}, []);
|
|
191
|
+
if (status === player_state_1.PlayerStatus.PLAYING) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
if (!segment?.startTime) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return now - Date.parse(segment.startTime) > 10000;
|
|
198
|
+
};
|
|
199
|
+
exports.useOffline = useOffline;
|
|
@@ -1,26 +1,65 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useCreateBlockRecord = useCreateBlockRecord;
|
|
4
|
+
exports.useCreateHideChatRecord = useCreateHideChatRecord;
|
|
5
|
+
const react_1 = require("react");
|
|
4
6
|
const xrpc_1 = require("./xrpc");
|
|
5
7
|
function useCreateBlockRecord() {
|
|
6
8
|
let agent = (0, xrpc_1.usePDSAgent)();
|
|
7
|
-
|
|
9
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
10
|
+
const createBlock = async (subjectDID) => {
|
|
8
11
|
if (!agent) {
|
|
9
12
|
throw new Error("No PDS agent found");
|
|
10
13
|
}
|
|
11
14
|
if (!agent.did) {
|
|
12
15
|
throw new Error("No user DID found, assuming not logged in");
|
|
13
16
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
setIsLoading(true);
|
|
18
|
+
try {
|
|
19
|
+
const record = {
|
|
20
|
+
$type: "app.bsky.graph.block",
|
|
21
|
+
subject: subjectDID,
|
|
22
|
+
createdAt: new Date().toISOString(),
|
|
23
|
+
};
|
|
24
|
+
const result = await agent.com.atproto.repo.createRecord({
|
|
25
|
+
repo: agent.did,
|
|
26
|
+
collection: "app.bsky.graph.block",
|
|
27
|
+
record,
|
|
28
|
+
});
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
setIsLoading(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return { createBlock, isLoading };
|
|
36
|
+
}
|
|
37
|
+
function useCreateHideChatRecord() {
|
|
38
|
+
let agent = (0, xrpc_1.usePDSAgent)();
|
|
39
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
40
|
+
const createHideChat = async (chatMessageUri) => {
|
|
41
|
+
if (!agent) {
|
|
42
|
+
throw new Error("No PDS agent found");
|
|
43
|
+
}
|
|
44
|
+
if (!agent.did) {
|
|
45
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
46
|
+
}
|
|
47
|
+
setIsLoading(true);
|
|
48
|
+
try {
|
|
49
|
+
const record = {
|
|
50
|
+
$type: "place.stream.chat.gate",
|
|
51
|
+
hiddenMessage: chatMessageUri,
|
|
52
|
+
};
|
|
53
|
+
const result = await agent.com.atproto.repo.createRecord({
|
|
54
|
+
repo: agent.did,
|
|
55
|
+
collection: "place.stream.chat.gate",
|
|
56
|
+
record,
|
|
57
|
+
});
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
setIsLoading(false);
|
|
62
|
+
}
|
|
25
63
|
};
|
|
64
|
+
return { createHideChat, isLoading };
|
|
26
65
|
}
|
|
@@ -5,29 +5,48 @@ exports.useUpdateStreamRecord = useUpdateStreamRecord;
|
|
|
5
5
|
const api_1 = require("@atproto/api");
|
|
6
6
|
const streamplace_store_1 = require("./streamplace-store");
|
|
7
7
|
const xrpc_1 = require("./xrpc");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
const react_1 = require("react");
|
|
9
|
+
const useUploadThumbnail = () => {
|
|
10
|
+
const abortRef = (0, react_1.useRef)(null);
|
|
11
|
+
(0, react_1.useEffect)(() => {
|
|
12
|
+
return () => {
|
|
13
|
+
// On unmount, abort any ongoing upload
|
|
14
|
+
abortRef.current?.abort();
|
|
15
|
+
};
|
|
16
|
+
}, []);
|
|
17
|
+
const uploadThumbnail = async (pdsAgent, customThumbnail) => {
|
|
18
|
+
if (!customThumbnail)
|
|
19
|
+
return undefined;
|
|
20
|
+
abortRef.current = new AbortController();
|
|
21
|
+
const { signal } = abortRef.current;
|
|
22
|
+
const maxTries = 3;
|
|
23
|
+
let lastError = null;
|
|
24
|
+
for (let tries = 0; tries < maxTries; tries++) {
|
|
25
|
+
try {
|
|
26
|
+
const thumbnail = await pdsAgent.uploadBlob(customThumbnail, {
|
|
27
|
+
signal,
|
|
28
|
+
});
|
|
29
|
+
if (thumbnail.success &&
|
|
30
|
+
thumbnail.data.blob.size === customThumbnail.size) {
|
|
31
|
+
console.log("Successfully uploaded thumbnail");
|
|
32
|
+
return thumbnail.data.blob;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.warn(`Blob size mismatch (attempt ${tries + 1}): received ${thumbnail.data.blob.size}, expected ${customThumbnail.size}`);
|
|
36
|
+
}
|
|
21
37
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
catch (e) {
|
|
39
|
+
if (signal.aborted) {
|
|
40
|
+
console.warn("Upload aborted");
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
lastError = e;
|
|
44
|
+
console.warn(`Error uploading thumbnail (attempt ${tries + 1}): ${e}`);
|
|
25
45
|
}
|
|
26
46
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
47
|
+
throw new Error(`Could not successfully upload blob after ${maxTries} attempts. Last error: ${lastError}`);
|
|
48
|
+
};
|
|
49
|
+
return uploadThumbnail;
|
|
31
50
|
};
|
|
32
51
|
async function createNewPost(agent, record) {
|
|
33
52
|
try {
|
|
@@ -39,7 +58,7 @@ async function createNewPost(agent, record) {
|
|
|
39
58
|
throw error;
|
|
40
59
|
}
|
|
41
60
|
}
|
|
42
|
-
function buildGoLivePost(text, url, profile, params, thumbnail) {
|
|
61
|
+
async function buildGoLivePost(text, url, profile, params, thumbnail, agent) {
|
|
43
62
|
const now = new Date();
|
|
44
63
|
const linkUrl = `${url.protocol}//${url.host}/${profile.handle}?${params.toString()}`;
|
|
45
64
|
const prefix = `🔴 LIVE `;
|
|
@@ -47,7 +66,7 @@ function buildGoLivePost(text, url, profile, params, thumbnail) {
|
|
|
47
66
|
const suffix = ` ${text}`;
|
|
48
67
|
const content = prefix + textUrl + suffix;
|
|
49
68
|
const rt = new api_1.RichText({ text: content });
|
|
50
|
-
rt.
|
|
69
|
+
await rt.detectFacets(agent);
|
|
51
70
|
const record = {
|
|
52
71
|
$type: "app.bsky.feed.post",
|
|
53
72
|
text: content,
|
|
@@ -72,6 +91,7 @@ function buildGoLivePost(text, url, profile, params, thumbnail) {
|
|
|
72
91
|
function useCreateStreamRecord() {
|
|
73
92
|
let agent = (0, xrpc_1.usePDSAgent)();
|
|
74
93
|
let url = (0, streamplace_store_1.useUrl)();
|
|
94
|
+
const uploadThumbnail = useUploadThumbnail();
|
|
75
95
|
return async (title, customThumbnail, submitPost = true) => {
|
|
76
96
|
if (!agent) {
|
|
77
97
|
throw new Error("No PDS agent found");
|
|
@@ -130,7 +150,7 @@ function useCreateStreamRecord() {
|
|
|
130
150
|
did: did,
|
|
131
151
|
time: new Date().toISOString(),
|
|
132
152
|
});
|
|
133
|
-
let post = buildGoLivePost(title, u, profile.data, params, thumbnail);
|
|
153
|
+
let post = await buildGoLivePost(title, u, profile.data, params, thumbnail, agent);
|
|
134
154
|
newPost = await createNewPost(agent, post);
|
|
135
155
|
if (!newPost.uri || !newPost.cid) {
|
|
136
156
|
throw new Error("Cannot read properties of undefined (reading 'uri' or 'cid')");
|
|
@@ -154,6 +174,7 @@ function useCreateStreamRecord() {
|
|
|
154
174
|
function useUpdateStreamRecord() {
|
|
155
175
|
let agent = (0, xrpc_1.usePDSAgent)();
|
|
156
176
|
let url = (0, streamplace_store_1.useUrl)();
|
|
177
|
+
const uploadThumbnail = useUploadThumbnail();
|
|
157
178
|
return async (title, livestream, customThumbnail) => {
|
|
158
179
|
if (!agent) {
|
|
159
180
|
throw new Error("No PDS agent found");
|