@streamplace/components 0.7.18 → 0.7.21

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.
Files changed (122) hide show
  1. package/dist/assets/emoji-data.json +19371 -0
  2. package/dist/components/chat/chat-box.js +319 -0
  3. package/dist/components/chat/chat-message.js +87 -0
  4. package/dist/components/chat/chat.js +150 -0
  5. package/dist/components/chat/emoji-suggestions.js +35 -0
  6. package/dist/components/chat/mention-suggestions.js +42 -0
  7. package/dist/components/chat/mod-view.js +112 -0
  8. package/dist/components/chat/system-message.js +19 -0
  9. package/dist/components/dashboard/chat-panel.js +38 -0
  10. package/dist/components/dashboard/header.js +80 -0
  11. package/dist/components/dashboard/index.js +14 -0
  12. package/dist/components/dashboard/information-widget.js +234 -0
  13. package/dist/components/dashboard/mod-actions.js +71 -0
  14. package/dist/components/dashboard/problems.js +74 -0
  15. package/dist/components/icons/bluesky-icon.js +9 -0
  16. package/dist/components/keep-awake.js +7 -0
  17. package/dist/components/keep-awake.native.js +16 -0
  18. package/dist/components/mobile-player/fullscreen.js +76 -0
  19. package/dist/components/mobile-player/fullscreen.native.js +141 -0
  20. package/dist/components/mobile-player/player.js +94 -0
  21. package/dist/components/mobile-player/props.js +2 -0
  22. package/dist/components/mobile-player/shared.js +54 -0
  23. package/dist/components/mobile-player/ui/autoplay-button.js +68 -0
  24. package/dist/components/mobile-player/ui/countdown.js +83 -0
  25. package/dist/components/mobile-player/ui/index.js +12 -0
  26. package/dist/components/mobile-player/ui/input.js +42 -0
  27. package/dist/components/mobile-player/ui/metrics.js +44 -0
  28. package/dist/components/mobile-player/ui/report-modal.js +90 -0
  29. package/dist/components/mobile-player/ui/streamer-context-menu.js +7 -0
  30. package/dist/components/mobile-player/ui/streamer-loading-overlay.js +104 -0
  31. package/dist/components/mobile-player/ui/viewer-context-menu.js +51 -0
  32. package/dist/components/mobile-player/ui/viewer-loading-overlay.js +49 -0
  33. package/dist/components/mobile-player/ui/viewers.js +23 -0
  34. package/dist/components/mobile-player/use-webrtc.js +243 -0
  35. package/dist/components/mobile-player/video-async.native.js +276 -0
  36. package/dist/components/mobile-player/video-retry.js +29 -0
  37. package/dist/components/mobile-player/video.js +475 -0
  38. package/dist/components/mobile-player/video.native.js +56 -0
  39. package/dist/components/mobile-player/webrtc-diagnostics.js +110 -0
  40. package/dist/components/mobile-player/webrtc-primitives.js +27 -0
  41. package/dist/components/mobile-player/webrtc-primitives.native.js +8 -0
  42. package/dist/components/share/sharesheet.js +91 -0
  43. package/dist/components/ui/button.js +223 -0
  44. package/dist/components/ui/dialog.js +206 -0
  45. package/dist/components/ui/dropdown.js +172 -0
  46. package/dist/components/ui/icons.js +25 -0
  47. package/dist/components/ui/index.js +34 -0
  48. package/dist/components/ui/info-box.js +31 -0
  49. package/dist/components/ui/info-row.js +23 -0
  50. package/dist/components/ui/input.js +205 -0
  51. package/dist/components/ui/loader.js +10 -0
  52. package/dist/components/ui/primitives/button.js +125 -0
  53. package/dist/components/ui/primitives/input.js +206 -0
  54. package/dist/components/ui/primitives/modal.js +206 -0
  55. package/dist/components/ui/primitives/text.js +292 -0
  56. package/dist/components/ui/resizeable.js +121 -0
  57. package/dist/components/ui/slider.js +5 -0
  58. package/dist/components/ui/text.js +177 -0
  59. package/dist/components/ui/textarea.js +19 -0
  60. package/dist/components/ui/toast.js +175 -0
  61. package/dist/components/ui/view.js +252 -0
  62. package/dist/hooks/index.js +14 -0
  63. package/dist/hooks/useAvatars.js +35 -0
  64. package/dist/hooks/useCameraToggle.js +12 -0
  65. package/dist/hooks/useKeyboard.js +36 -0
  66. package/dist/hooks/useKeyboardSlide.js +14 -0
  67. package/dist/hooks/useLivestreamInfo.js +69 -0
  68. package/dist/hooks/useOuterAndInnerDimensions.js +30 -0
  69. package/dist/hooks/usePlayerDimensions.js +22 -0
  70. package/dist/hooks/usePointerDevice.js +71 -0
  71. package/dist/hooks/useSegmentDimensions.js +17 -0
  72. package/dist/hooks/useSegmentTiming.js +65 -0
  73. package/dist/index.js +34 -0
  74. package/dist/lib/browser.js +35 -0
  75. package/dist/lib/facet.js +92 -0
  76. package/dist/lib/system-messages.js +101 -0
  77. package/dist/lib/theme/atoms.js +646 -0
  78. package/dist/lib/theme/atoms.types.js +6 -0
  79. package/dist/lib/theme/index.js +35 -0
  80. package/dist/lib/theme/theme.js +256 -0
  81. package/dist/lib/theme/tokens.js +659 -0
  82. package/dist/lib/utils.js +105 -0
  83. package/dist/livestream-provider/index.js +30 -0
  84. package/dist/livestream-provider/websocket.js +45 -0
  85. package/dist/livestream-store/chat.js +308 -0
  86. package/dist/livestream-store/context.js +5 -0
  87. package/dist/livestream-store/index.js +7 -0
  88. package/dist/livestream-store/livestream-state.js +2 -0
  89. package/dist/livestream-store/livestream-store.js +58 -0
  90. package/dist/livestream-store/problems.js +76 -0
  91. package/dist/livestream-store/stream-key.js +88 -0
  92. package/dist/livestream-store/websocket-consumer.js +94 -0
  93. package/dist/player-store/context.js +5 -0
  94. package/dist/player-store/index.js +9 -0
  95. package/dist/player-store/player-provider.js +58 -0
  96. package/dist/player-store/player-state.js +25 -0
  97. package/dist/player-store/player-store.js +201 -0
  98. package/dist/player-store/single-player-provider.js +121 -0
  99. package/dist/streamplace-provider/context.js +5 -0
  100. package/dist/streamplace-provider/index.js +20 -0
  101. package/dist/streamplace-provider/poller.js +49 -0
  102. package/dist/streamplace-provider/xrpc.js +0 -0
  103. package/dist/streamplace-store/block.js +65 -0
  104. package/dist/streamplace-store/index.js +6 -0
  105. package/dist/streamplace-store/stream.js +247 -0
  106. package/dist/streamplace-store/streamplace-store.js +47 -0
  107. package/dist/streamplace-store/user.js +52 -0
  108. package/dist/streamplace-store/xrpc.js +15 -0
  109. package/dist/ui/index.js +79 -0
  110. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  111. package/package.json +5 -4
  112. package/src/components/chat/chat-box.tsx +3 -0
  113. package/src/components/chat/mod-view.tsx +39 -5
  114. package/src/components/mobile-player/fullscreen.tsx +2 -0
  115. package/src/components/mobile-player/ui/autoplay-button.tsx +86 -0
  116. package/src/components/mobile-player/ui/index.ts +1 -0
  117. package/src/components/mobile-player/video.tsx +11 -1
  118. package/src/livestream-store/chat.tsx +22 -0
  119. package/src/player-store/player-provider.tsx +2 -1
  120. package/src/player-store/player-state.tsx +6 -0
  121. package/src/player-store/player-store.tsx +4 -0
  122. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeStyles = mergeStyles;
4
+ exports.createStyleMerger = createStyleMerger;
5
+ exports.conditionalStyle = conditionalStyle;
6
+ exports.responsiveValue = responsiveValue;
7
+ exports.platformStyle = platformStyle;
8
+ exports.hexToRgba = hexToRgba;
9
+ exports.debounce = debounce;
10
+ exports.throttle = throttle;
11
+ exports.forwardProps = forwardProps;
12
+ const react_native_1 = require("react-native");
13
+ /**
14
+ * Merges React Native styles similar to how cn() merges CSS classes
15
+ * Handles arrays, objects, and falsy values
16
+ */
17
+ function mergeStyles(...styles) {
18
+ const validStyles = styles.filter(Boolean).flat();
19
+ return react_native_1.StyleSheet.flatten(validStyles) || {};
20
+ }
21
+ /**
22
+ * Creates a style merger function that includes base styles
23
+ * Useful for component variants
24
+ */
25
+ function createStyleMerger(baseStyle) {
26
+ return (...styles) => {
27
+ return mergeStyles(baseStyle, ...styles);
28
+ };
29
+ }
30
+ /**
31
+ * Conditionally applies styles based on boolean conditions
32
+ */
33
+ function conditionalStyle(condition, trueStyle, falseStyle) {
34
+ return condition ? trueStyle : falseStyle;
35
+ }
36
+ /**
37
+ * Creates responsive values based on screen dimensions
38
+ */
39
+ function responsiveValue(values, screenWidth) {
40
+ if (screenWidth >= 1280 && values.xl !== undefined)
41
+ return values.xl;
42
+ if (screenWidth >= 1024 && values.lg !== undefined)
43
+ return values.lg;
44
+ if (screenWidth >= 768 && values.md !== undefined)
45
+ return values.md;
46
+ if (screenWidth >= 640 && values.sm !== undefined)
47
+ return values.sm;
48
+ return values.default;
49
+ }
50
+ /**
51
+ * Creates platform-specific styles
52
+ */
53
+ function platformStyle(styles) {
54
+ const Platform = require("react-native").Platform;
55
+ if (Platform.OS === "ios" && styles.ios)
56
+ return styles.ios;
57
+ if (Platform.OS === "android" && styles.android)
58
+ return styles.android;
59
+ if (Platform.OS === "web" && styles.web)
60
+ return styles.web;
61
+ return styles.default || {};
62
+ }
63
+ /**
64
+ * Converts hex color to rgba
65
+ */
66
+ function hexToRgba(hex, alpha = 1) {
67
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
68
+ if (!result)
69
+ return hex;
70
+ const r = parseInt(result[1], 16);
71
+ const g = parseInt(result[2], 16);
72
+ const b = parseInt(result[3], 16);
73
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
74
+ }
75
+ /**
76
+ * Creates a debounced function for performance
77
+ */
78
+ function debounce(func, delay) {
79
+ let timeoutId;
80
+ return (...args) => {
81
+ clearTimeout(timeoutId);
82
+ timeoutId = setTimeout(() => func(...args), delay);
83
+ };
84
+ }
85
+ /**
86
+ * Creates a throttled function for performance
87
+ */
88
+ function throttle(func, delay) {
89
+ let lastCall = 0;
90
+ return (...args) => {
91
+ const now = Date.now();
92
+ if (now - lastCall >= delay) {
93
+ lastCall = now;
94
+ func(...args);
95
+ }
96
+ };
97
+ }
98
+ /**
99
+ * Type-safe component prop forwarding
100
+ */
101
+ function forwardProps(props, omit) {
102
+ const result = { ...props };
103
+ omit.forEach((key) => delete result[key]);
104
+ return result;
105
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LivestreamProvider = LivestreamProvider;
4
+ exports.WebsocketWatcher = WebsocketWatcher;
5
+ exports.LivestreamPoller = LivestreamPoller;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const livestream_store_1 = require("../livestream-store");
9
+ const websocket_1 = require("./websocket");
10
+ function LivestreamProvider({ children, src, }) {
11
+ const context = (0, react_1.useContext)(livestream_store_1.LivestreamContext);
12
+ const store = (0, react_1.useRef)((0, livestream_store_1.makeLivestreamStore)()).current;
13
+ if (context) {
14
+ // this is ok, there's use cases for having one in another
15
+ // like having a player component that's independently usable
16
+ // but can also be embedded within an entire livestream page
17
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
18
+ }
19
+ window.livestreamStore = store;
20
+ return ((0, jsx_runtime_1.jsx)(livestream_store_1.LivestreamContext.Provider, { value: { store: store }, children: (0, jsx_runtime_1.jsx)(LivestreamPoller, { src: src, children: children }) }));
21
+ }
22
+ function WebsocketWatcher({ src }) {
23
+ (0, websocket_1.useLivestreamWebsocket)(src);
24
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {});
25
+ }
26
+ function LivestreamPoller({ children, src, }) {
27
+ // Websocket watcher is a sibling instead of a parent to avoid
28
+ // re-rendering when the websocket does stuff
29
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(WebsocketWatcher, { src: src }), children] }));
30
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useLivestreamWebsocket = useLivestreamWebsocket;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = require("react");
6
+ const react_use_websocket_1 = tslib_1.__importDefault(require("react-use-websocket"));
7
+ const livestream_store_1 = require("../livestream-store");
8
+ const streamplace_store_1 = require("../streamplace-store");
9
+ function useLivestreamWebsocket(src) {
10
+ const url = (0, streamplace_store_1.useUrl)();
11
+ const handleWebSocketMessages = (0, livestream_store_1.useHandleWebsocketMessages)();
12
+ let wsUrl = url.replace(/^http\:/, "ws:");
13
+ wsUrl = wsUrl.replace(/^https\:/, "wss:");
14
+ const ref = (0, react_1.useRef)([]);
15
+ const handle = (0, react_1.useRef)(null);
16
+ const { readyState } = (0, react_use_websocket_1.default)(`${wsUrl}/api/websocket/${src}`, {
17
+ reconnectInterval: 1000,
18
+ shouldReconnect: () => true,
19
+ onOpen: () => {
20
+ ref.current = [];
21
+ },
22
+ onError: (e) => {
23
+ console.log("onError", e);
24
+ },
25
+ // spamming the redux store with messages causes a zillion re-renders,
26
+ // so we batch them up a bit
27
+ onMessage: (msg) => {
28
+ try {
29
+ const data = JSON.parse(msg.data);
30
+ ref.current.push(data);
31
+ if (handle.current) {
32
+ return;
33
+ }
34
+ handle.current = setTimeout(() => {
35
+ handleWebSocketMessages(ref.current);
36
+ ref.current = [];
37
+ handle.current = null;
38
+ }, 250);
39
+ }
40
+ catch (e) {
41
+ console.log("onMessage parse error", e);
42
+ }
43
+ },
44
+ });
45
+ }
@@ -0,0 +1,308 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reduceChat = exports.useReportChatMessage = exports.useSubmitReport = exports.reduceChatIncremental = exports.useDeleteChatMessage = exports.useCreateChatMessage = exports.useAddPendingHide = exports.usePendingHides = exports.useSetReplyToMessage = exports.useReplyToMessage = void 0;
4
+ const api_1 = require("@atproto/api");
5
+ const react_1 = require("react");
6
+ const streamplace_store_1 = require("../streamplace-store");
7
+ const xrpc_1 = require("../streamplace-store/xrpc");
8
+ const livestream_store_1 = require("./livestream-store");
9
+ const useReplyToMessage = () => (0, livestream_store_1.useLivestreamStore)((state) => state.replyToMessage);
10
+ exports.useReplyToMessage = useReplyToMessage;
11
+ const useSetReplyToMessage = () => {
12
+ const store = (0, livestream_store_1.getStoreFromContext)();
13
+ return (0, react_1.useCallback)((message) => {
14
+ store.setState({ replyToMessage: message });
15
+ }, [store]);
16
+ };
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;
35
+ const useCreateChatMessage = () => {
36
+ const pdsAgent = (0, xrpc_1.usePDSAgent)();
37
+ const store = (0, livestream_store_1.getStoreFromContext)();
38
+ const userDID = (0, streamplace_store_1.useDID)();
39
+ const userHandle = (0, streamplace_store_1.useHandle)();
40
+ const chatProfile = (0, streamplace_store_1.useChatProfile)();
41
+ return async (msg) => {
42
+ if (!pdsAgent || !userDID) {
43
+ throw new Error("No PDS agent or user DID found");
44
+ }
45
+ let state = store.getState();
46
+ const streamerProfile = state.profile;
47
+ if (!streamerProfile) {
48
+ throw new Error("Profile not found");
49
+ }
50
+ const rt = new api_1.RichText({ text: msg.text });
51
+ await rt.detectFacets(pdsAgent);
52
+ const record = {
53
+ text: msg.text,
54
+ createdAt: new Date().toISOString(),
55
+ streamer: streamerProfile.did,
56
+ facets: rt.facets,
57
+ ...(msg.reply
58
+ ? {
59
+ reply: {
60
+ root: {
61
+ cid: msg.reply.cid,
62
+ uri: msg.reply.uri,
63
+ },
64
+ parent: {
65
+ cid: msg.reply.cid,
66
+ uri: msg.reply.uri,
67
+ },
68
+ },
69
+ }
70
+ : {}),
71
+ };
72
+ const localChat = {
73
+ uri: `local-${Date.now()}`,
74
+ cid: "",
75
+ author: {
76
+ did: userDID,
77
+ handle: userHandle || userDID,
78
+ },
79
+ record: record,
80
+ indexedAt: new Date().toISOString(),
81
+ chatProfile: chatProfile || undefined,
82
+ };
83
+ state = (0, exports.reduceChat)(state, [localChat], [], []);
84
+ store.setState(state);
85
+ await pdsAgent.com.atproto.repo.createRecord({
86
+ repo: userDID,
87
+ collection: "place.stream.chat.message",
88
+ record,
89
+ });
90
+ };
91
+ };
92
+ exports.useCreateChatMessage = useCreateChatMessage;
93
+ const useDeleteChatMessage = () => {
94
+ const pdsAgent = (0, xrpc_1.usePDSAgent)();
95
+ if (!pdsAgent) {
96
+ throw new Error("No PDS agent found");
97
+ }
98
+ const userDID = (0, streamplace_store_1.useDID)();
99
+ if (!userDID) {
100
+ throw new Error("No user DID found");
101
+ }
102
+ return async (uri) => {
103
+ const rkey = uri.split("/").pop();
104
+ if (!rkey) {
105
+ throw new Error("No rkey found");
106
+ }
107
+ return await pdsAgent.com.atproto.repo.deleteRecord({
108
+ repo: userDID,
109
+ collection: "place.stream.chat.message",
110
+ rkey: rkey,
111
+ });
112
+ };
113
+ };
114
+ exports.useDeleteChatMessage = useDeleteChatMessage;
115
+ const buildSortedChatList = (chatIndex, existingChatList, newMessages, removedKeys) => {
116
+ const sortedKeys = Object.keys(chatIndex).sort((a, b) => {
117
+ const aTime = parseInt(a.split("-")[0], 10);
118
+ const bTime = parseInt(b.split("-")[0], 10);
119
+ return bTime - aTime;
120
+ });
121
+ return sortedKeys
122
+ .map((key) => chatIndex[key])
123
+ .filter((msg) => !removedKeys.has(msg.uri));
124
+ };
125
+ const profileIsDifferent = (newProfile, oldProfile) => {
126
+ if (!oldProfile) {
127
+ return true;
128
+ }
129
+ if (!newProfile) {
130
+ return false;
131
+ }
132
+ if (!oldProfile.color) {
133
+ return true;
134
+ }
135
+ if (!newProfile.color) {
136
+ // idk. shouldn't happen.
137
+ return false;
138
+ }
139
+ const { red: newRed, green: newGreen, blue: newBlue } = newProfile.color;
140
+ const { red: oldRed, green: oldGreen, blue: oldBlue } = oldProfile.color;
141
+ return newRed !== oldRed || newGreen !== oldGreen || newBlue !== oldBlue;
142
+ };
143
+ const reduceChatIncremental = (state, newMessages, blocks, hideUris = []) => {
144
+ if (newMessages.length === 0 &&
145
+ blocks.length === 0 &&
146
+ hideUris.length === 0) {
147
+ return state;
148
+ }
149
+ const newChatIndex = { ...state.chatIndex };
150
+ const newAuthors = { ...state.authors };
151
+ let hasChanges = false;
152
+ const removedKeys = new Set();
153
+ console.log("newMessages", newMessages);
154
+ for (const msg of newMessages) {
155
+ if (msg.deleted) {
156
+ hasChanges = true;
157
+ console.log("deleted", msg.uri);
158
+ removedKeys.add(msg.uri);
159
+ }
160
+ }
161
+ newMessages = newMessages.filter((msg) => msg.deleted !== true);
162
+ // handle blocks
163
+ if (blocks.length > 0) {
164
+ const blockedDIDs = new Set(blocks.map((block) => block.record.subject));
165
+ for (const [key, message] of Object.entries(newChatIndex)) {
166
+ if (blockedDIDs.has(message.author.did)) {
167
+ delete newChatIndex[key];
168
+ removedKeys.add(key);
169
+ hasChanges = true;
170
+ }
171
+ }
172
+ }
173
+ if (hideUris.length > 0) {
174
+ for (const [key, message] of Object.entries(newChatIndex)) {
175
+ if (hideUris.includes(message.uri)) {
176
+ delete newChatIndex[key];
177
+ removedKeys.add(key);
178
+ hasChanges = true;
179
+ }
180
+ }
181
+ }
182
+ const messagesToAdd = [];
183
+ for (const message of newMessages) {
184
+ // don't worry about messages that will be hidden
185
+ if (state.pendingHides.includes(message.uri)) {
186
+ continue;
187
+ }
188
+ const date = new Date(message.record.createdAt);
189
+ const key = `${date.getTime()}-${message.uri}`;
190
+ // only change the ref if the profile is different to avoid re-renders elsewhere
191
+ if (profileIsDifferent(message.chatProfile, newAuthors[message.author.did])) {
192
+ newAuthors[message.author.did] = message.chatProfile;
193
+ }
194
+ // skip messages we already have
195
+ if (newChatIndex[key] && newChatIndex[key].uri === message.uri) {
196
+ continue;
197
+ }
198
+ // if we have a local message, replace it with the new one
199
+ if (!message.uri.startsWith("local-")) {
200
+ const existingLocalKey = Object.keys(newChatIndex).find((k) => {
201
+ const msg = newChatIndex[k];
202
+ return (msg.uri.startsWith("local-") &&
203
+ msg.record.text === message.record.text &&
204
+ msg.author.did === message.author.did &&
205
+ Math.abs(new Date(msg.record.createdAt).getTime() - date.getTime()) <
206
+ 10000 // Within 10 seconds
207
+ );
208
+ });
209
+ if (existingLocalKey) {
210
+ delete newChatIndex[existingLocalKey];
211
+ removedKeys.add(existingLocalKey);
212
+ hasChanges = true;
213
+ }
214
+ }
215
+ // add reply info
216
+ let processedMessage = message;
217
+ if (message.record.reply) {
218
+ const reply = message.record.reply;
219
+ const parentUri = reply?.parent?.uri || reply?.root?.uri;
220
+ if (parentUri) {
221
+ const parentMsgKey = Object.keys(newChatIndex).find((k) => newChatIndex[k].uri === parentUri);
222
+ if (parentMsgKey) {
223
+ const parentMsg = newChatIndex[parentMsgKey];
224
+ // Don't allow replies to system messages
225
+ if (parentMsg.author.did !== "did:sys:system") {
226
+ processedMessage = {
227
+ ...message,
228
+ replyTo: {
229
+ cid: parentMsg.cid,
230
+ uri: parentMsg.uri,
231
+ author: parentMsg.author,
232
+ record: parentMsg.record,
233
+ chatProfile: parentMsg.chatProfile,
234
+ indexedAt: parentMsg.indexedAt,
235
+ },
236
+ };
237
+ }
238
+ }
239
+ }
240
+ }
241
+ messagesToAdd.push({ key, message: processedMessage });
242
+ hasChanges = true;
243
+ }
244
+ // Add new messages to index
245
+ for (const { key, message } of messagesToAdd) {
246
+ newChatIndex[key] = message;
247
+ }
248
+ // only rebuild if we have changes
249
+ if (!hasChanges) {
250
+ return state;
251
+ }
252
+ // Build the new sorted chat list efficiently
253
+ const newChatList = buildSortedChatList(newChatIndex, state.chat, messagesToAdd, removedKeys);
254
+ // Clean up pendingHides - remove URIs that we've now processed
255
+ let newPendingHides = state.pendingHides;
256
+ if (hideUris.length > 0) {
257
+ newPendingHides = state.pendingHides.filter((uri) => !hideUris.includes(uri));
258
+ }
259
+ return {
260
+ ...state,
261
+ authors: newAuthors,
262
+ chatIndex: newChatIndex,
263
+ chat: newChatList,
264
+ pendingHides: newPendingHides,
265
+ };
266
+ };
267
+ exports.reduceChatIncremental = reduceChatIncremental;
268
+ const useSubmitReport = () => {
269
+ const pdsAgent = (0, xrpc_1.usePDSAgent)();
270
+ const userDID = (0, streamplace_store_1.useDID)();
271
+ return (0, react_1.useCallback)(async (subject, reasonType, reason,
272
+ // no clue about this
273
+ moderationSvcDid = "did:web:stream.place") => {
274
+ if (!pdsAgent || !userDID) {
275
+ throw new Error("No PDS agent or user DID found");
276
+ }
277
+ try {
278
+ const response = await pdsAgent.com.atproto.moderation.createReport({
279
+ reasonType,
280
+ reason,
281
+ subject: subject,
282
+ }, {
283
+ headers: {
284
+ // "atproto-proxy": `${userDID}#atproto_labeler`,
285
+ },
286
+ });
287
+ return response;
288
+ }
289
+ catch (error) {
290
+ console.error("Failed to submit report:", error);
291
+ throw error;
292
+ }
293
+ }, [pdsAgent, userDID]);
294
+ };
295
+ exports.useSubmitReport = useSubmitReport;
296
+ const useReportChatMessage = () => {
297
+ const submitReport = (0, exports.useSubmitReport)();
298
+ return (0, react_1.useCallback)(async (message, reasonType, reason) => {
299
+ const reportSubject = {
300
+ $type: "com.atproto.repo.strongRef",
301
+ uri: message.uri,
302
+ cid: message.cid,
303
+ };
304
+ return await submitReport(reportSubject, reasonType, reason);
305
+ }, [submitReport]);
306
+ };
307
+ exports.useReportChatMessage = useReportChatMessage;
308
+ exports.reduceChat = exports.reduceChatIncremental;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LivestreamContext = void 0;
4
+ const react_1 = require("react");
5
+ exports.LivestreamContext = (0, react_1.createContext)(null);
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./chat"), exports);
5
+ tslib_1.__exportStar(require("./context"), exports);
6
+ tslib_1.__exportStar(require("./livestream-store"), exports);
7
+ tslib_1.__exportStar(require("./stream-key"), exports);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useRenditions = exports.useSegment = exports.useLivestream = exports.useViewers = exports.useProfile = exports.useChat = exports.useHandleWebsocketMessages = exports.makeLivestreamStore = void 0;
4
+ exports.getStoreFromContext = getStoreFromContext;
5
+ exports.useLivestreamStore = useLivestreamStore;
6
+ const react_1 = require("react");
7
+ const zustand_1 = require("zustand");
8
+ const context_1 = require("./context");
9
+ const websocket_consumer_1 = require("./websocket-consumer");
10
+ const makeLivestreamStore = () => {
11
+ return (0, zustand_1.createStore)()((set) => ({
12
+ profile: null,
13
+ chatIndex: {},
14
+ chat: [],
15
+ livestream: null,
16
+ viewers: null,
17
+ pendingHides: [],
18
+ segment: null,
19
+ renditions: [],
20
+ replyToMessage: null,
21
+ streamKey: null,
22
+ setStreamKey: (sk) => set({ streamKey: sk }),
23
+ authors: {},
24
+ recentSegments: [],
25
+ problems: [],
26
+ }));
27
+ };
28
+ exports.makeLivestreamStore = makeLivestreamStore;
29
+ function getStoreFromContext() {
30
+ const context = (0, react_1.useContext)(context_1.LivestreamContext);
31
+ if (!context) {
32
+ throw new Error("useLivestreamStore must be used within a LivestreamProvider");
33
+ }
34
+ return context.store;
35
+ }
36
+ function useLivestreamStore(selector) {
37
+ const store = getStoreFromContext();
38
+ return (0, zustand_1.useStore)(store, selector);
39
+ }
40
+ const useHandleWebsocketMessages = () => {
41
+ const store = getStoreFromContext();
42
+ return (messages) => {
43
+ store.setState((state) => (0, websocket_consumer_1.handleWebSocketMessages)(state, messages));
44
+ };
45
+ };
46
+ exports.useHandleWebsocketMessages = useHandleWebsocketMessages;
47
+ const useChat = () => useLivestreamStore((x) => x.chat);
48
+ exports.useChat = useChat;
49
+ const useProfile = () => useLivestreamStore((x) => x.profile);
50
+ exports.useProfile = useProfile;
51
+ const useViewers = () => useLivestreamStore((x) => x.viewers);
52
+ exports.useViewers = useViewers;
53
+ const useLivestream = () => useLivestreamStore((x) => x.livestream);
54
+ exports.useLivestream = useLivestream;
55
+ const useSegment = () => useLivestreamStore((x) => x.segment);
56
+ exports.useSegment = useSegment;
57
+ const useRenditions = () => useLivestreamStore((x) => x.renditions);
58
+ exports.useRenditions = useRenditions;
@@ -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;