@streamplace/components 0.7.19 → 0.7.25
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 +5 -0
- package/dist/components/chat/chat-message.js +5 -4
- package/dist/components/chat/chat.js +14 -4
- package/dist/components/chat/mod-view.js +19 -1
- package/dist/components/mobile-player/fullscreen.js +2 -0
- package/dist/components/mobile-player/ui/autoplay-button.js +68 -0
- package/dist/components/mobile-player/ui/index.js +1 -0
- package/dist/components/mobile-player/video.js +11 -1
- package/dist/components/mobile-player/webrtc-diagnostics.js +67 -13
- package/dist/lib/system-messages.js +1 -0
- package/dist/livestream-store/chat.js +25 -1
- package/dist/livestream-store/stream-key.js +1 -0
- package/dist/livestream-store/websocket-consumer.js +4 -1
- package/dist/player-store/player-provider.js +2 -1
- package/dist/player-store/player-store.js +2 -0
- package/dist/streamplace-store/stream.js +2 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/package.json +4 -4
- package/src/components/chat/chat-box.tsx +3 -0
- package/src/components/chat/chat-message.tsx +5 -4
- package/src/components/chat/chat.tsx +20 -4
- package/src/components/chat/mod-view.tsx +39 -5
- package/src/components/mobile-player/fullscreen.tsx +2 -0
- package/src/components/mobile-player/ui/autoplay-button.tsx +86 -0
- package/src/components/mobile-player/ui/index.ts +1 -0
- package/src/components/mobile-player/video.tsx +11 -1
- package/src/components/mobile-player/webrtc-diagnostics.tsx +73 -15
- package/src/lib/system-messages.ts +1 -0
- package/src/livestream-store/chat.tsx +24 -0
- package/src/livestream-store/stream-key.tsx +1 -0
- package/src/livestream-store/websocket-consumer.tsx +4 -1
- package/src/player-store/player-provider.tsx +2 -1
- package/src/player-store/player-state.tsx +6 -0
- package/src/player-store/player-store.tsx +4 -0
- package/src/streamplace-store/stream.tsx +2 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -42,6 +42,11 @@ function ChatBox({ isPopout, chatBoxStyle, emojiData, setIsChatVisible, }) {
|
|
|
42
42
|
if (!chat)
|
|
43
43
|
return null;
|
|
44
44
|
return chat.reduce((acc, msg) => {
|
|
45
|
+
// our fake system user "did"
|
|
46
|
+
if (msg.author.did === "did:sys:system")
|
|
47
|
+
return acc;
|
|
48
|
+
if (acc.has(msg.author.handle))
|
|
49
|
+
return acc;
|
|
45
50
|
acc.set(msg.author.handle, msg.chatProfile);
|
|
46
51
|
return acc;
|
|
47
52
|
}, new Map());
|
|
@@ -48,7 +48,8 @@ exports.RenderChatMessage = (0, react_1.memo)(function RenderChatMessage({ item,
|
|
|
48
48
|
hour12: false,
|
|
49
49
|
});
|
|
50
50
|
}, []);
|
|
51
|
-
|
|
51
|
+
const replyTo = item.replyTo || null;
|
|
52
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [replyTo && showReply && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
52
53
|
atoms_1.gap.all[2],
|
|
53
54
|
ui_1.layout.flex.row,
|
|
54
55
|
{ minWidth: 0, maxWidth: "100%" },
|
|
@@ -62,12 +63,12 @@ exports.RenderChatMessage = (0, react_1.memo)(function RenderChatMessage({ item,
|
|
|
62
63
|
atoms_1.mr[4],
|
|
63
64
|
{ minWidth: 0, overflow: "hidden" },
|
|
64
65
|
], children: [(0, jsx_runtime_1.jsxs)(text_1.Text, { style: {
|
|
65
|
-
color: getRgbColor(
|
|
66
|
+
color: getRgbColor(replyTo.chatProfile?.color),
|
|
66
67
|
fontWeight: "thin",
|
|
67
|
-
}, children: ["@",
|
|
68
|
+
}, children: ["@", replyTo.author.handle] }), " ", (0, jsx_runtime_1.jsx)(text_1.Text, { style: {
|
|
68
69
|
color: ui_1.colors.gray[300],
|
|
69
70
|
fontStyle: "italic",
|
|
70
|
-
}, children:
|
|
71
|
+
}, children: replyTo.record.text })] }) })), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
71
72
|
atoms_1.gap.all[2],
|
|
72
73
|
ui_1.layout.flex.row,
|
|
73
74
|
{ minWidth: 0, maxWidth: "100%" },
|
|
@@ -122,9 +122,7 @@ const ChatLine = (0, react_1.memo)(({ item, canModerate, }) => {
|
|
|
122
122
|
isHovered && atoms_1.bg.gray[950],
|
|
123
123
|
], onPointerEnter: handleHoverIn, onPointerLeave: handleHoverOut, children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [{ minWidth: 0, maxWidth: "100%" }], children: (0, jsx_runtime_1.jsx)(chat_message_1.RenderChatMessage, { item: item }) }), (0, jsx_runtime_1.jsx)(ActionsBar, { item: item, visible: isHovered, hoverTimeoutRef: hoverTimeoutRef })] }));
|
|
124
124
|
}
|
|
125
|
-
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(ReanimatedSwipeable_1.default, { containerStyle: [atoms_1.py[1]], friction: 2, enableTrackpadTwoFingerGesture: true, rightThreshold: 40, leftThreshold: 40, renderRightActions: react_native_1.Platform.OS === "android" ? undefined : RightAction, renderLeftActions: react_native_1.Platform.OS === "android" ? undefined : LeftAction, overshootFriction: 9, ref: (
|
|
126
|
-
swipeableRef.current = ref;
|
|
127
|
-
}, onSwipeableOpen: (r) => {
|
|
125
|
+
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(ReanimatedSwipeable_1.default, { containerStyle: [atoms_1.py[1]], friction: 2, enableTrackpadTwoFingerGesture: true, rightThreshold: 40, leftThreshold: 40, renderRightActions: react_native_1.Platform.OS === "android" ? undefined : RightAction, renderLeftActions: react_native_1.Platform.OS === "android" ? undefined : LeftAction, overshootFriction: 9, ref: swipeableRef, onSwipeableOpen: (r) => {
|
|
128
126
|
if (r === (react_native_1.Platform.OS === "android" ? "right" : "left")) {
|
|
129
127
|
setReply(item);
|
|
130
128
|
}
|
|
@@ -140,11 +138,23 @@ const ChatLine = (0, react_1.memo)(({ item, canModerate, }) => {
|
|
|
140
138
|
});
|
|
141
139
|
function Chat({ shownMessages = SHOWN_MSGS, style: propsStyle, canModerate = false, ...props }) {
|
|
142
140
|
const chat = (0, __1.useChat)();
|
|
141
|
+
const [isScrolledUp, setIsScrolledUp] = (0, react_1.useState)(false);
|
|
142
|
+
const handleScroll = (event) => {
|
|
143
|
+
const { contentOffset } = event.nativeEvent;
|
|
144
|
+
const scrolledUp = contentOffset.y > 20; // threshold
|
|
145
|
+
if (scrolledUp !== isScrolledUp) {
|
|
146
|
+
setIsScrolledUp(scrolledUp);
|
|
147
|
+
// Dismiss keyboard when scrolled up
|
|
148
|
+
if (scrolledUp && react_native_1.Platform.OS !== "web") {
|
|
149
|
+
react_native_1.Keyboard.dismiss();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
143
153
|
if (!chat)
|
|
144
154
|
return ((0, jsx_runtime_1.jsx)(__1.View, { style: [atoms_1.flex.shrink[1], { minWidth: 0, maxWidth: "100%" }], children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Loading chaat..." }) }));
|
|
145
155
|
return ((0, jsx_runtime_1.jsxs)(__1.View, { style: [atoms_1.flex.shrink[1], { minWidth: 0, maxWidth: "100%" }].concat(propsStyle || []), children: [(0, jsx_runtime_1.jsx)(react_native_gesture_handler_1.FlatList, { style: [
|
|
146
156
|
atoms_1.flex.grow[1],
|
|
147
157
|
atoms_1.flex.shrink[1],
|
|
148
158
|
{ minWidth: 0, maxWidth: "100%" },
|
|
149
|
-
], data: chat.slice(0, shownMessages), inverted: true, keyExtractor: keyExtractor, renderItem: ({ item, index }) => ((0, jsx_runtime_1.jsx)(ChatLine, { item: item, canModerate: canModerate })), removeClippedSubviews: true, maxToRenderPerBatch: 10, initialNumToRender: 10, updateCellsBatchingPeriod: 50 }), (0, jsx_runtime_1.jsx)(mod_view_1.ModView, {})] }));
|
|
159
|
+
], data: chat.slice(0, shownMessages), inverted: true, keyExtractor: keyExtractor, renderItem: ({ item, index }) => ((0, jsx_runtime_1.jsx)(ChatLine, { item: item, canModerate: canModerate })), removeClippedSubviews: true, maxToRenderPerBatch: 10, initialNumToRender: 10, updateCellsBatchingPeriod: 50, onScroll: handleScroll, scrollEventThrottle: 16 }), (0, jsx_runtime_1.jsx)(mod_view_1.ModView, {})] }));
|
|
150
160
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ModView = void 0;
|
|
4
|
+
exports.DeleteButton = DeleteButton;
|
|
4
5
|
exports.ReportButton = ReportButton;
|
|
5
6
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
7
|
const dropdown_menu_1 = require("@rn-primitives/dropdown-menu");
|
|
@@ -10,6 +11,7 @@ const player_store_1 = require("../../player-store");
|
|
|
10
11
|
const block_1 = require("../../streamplace-store/block");
|
|
11
12
|
const xrpc_1 = require("../../streamplace-store/xrpc");
|
|
12
13
|
const react_native_1 = require("react-native");
|
|
14
|
+
const livestream_store_1 = require("../../livestream-store");
|
|
13
15
|
const streamplace_store_1 = require("../../streamplace-store");
|
|
14
16
|
const ui_1 = require("../ui");
|
|
15
17
|
const BSKY_FRONTEND_DOMAIN = "bsky.app";
|
|
@@ -75,8 +77,24 @@ exports.ModView = (0, react_1.forwardRef)(() => {
|
|
|
75
77
|
? "Blocking..."
|
|
76
78
|
: `Block user @${message.author.handle} from this channel` })) })] })), (0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuGroup, { title: `User actions`, children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => {
|
|
77
79
|
react_native_1.Linking.openURL(`https://${BSKY_FRONTEND_DOMAIN}/profile/${message.author.handle}`);
|
|
78
|
-
}, children: (0, jsx_runtime_1.jsxs)(ui_1.Text, { color: "primary", children: ["View user on ", BSKY_FRONTEND_DOMAIN] }) }), (0, jsx_runtime_1.jsx)(ReportButton, { message: message, setReportModalOpen: setReportModalOpen, setReportSubject: setReportSubject })] })] })) })] }));
|
|
80
|
+
}, children: (0, jsx_runtime_1.jsxs)(ui_1.Text, { color: "primary", children: ["View user on ", BSKY_FRONTEND_DOMAIN] }) }), message.author.did === agent?.did && ((0, jsx_runtime_1.jsx)(DeleteButton, { message: message })), message.author.did !== agent?.did && ((0, jsx_runtime_1.jsx)(ReportButton, { message: message, setReportModalOpen: setReportModalOpen, setReportSubject: setReportSubject }))] })] })) })] }));
|
|
79
81
|
});
|
|
82
|
+
function DeleteButton({ message, }) {
|
|
83
|
+
const deleteChatMessage = (0, livestream_store_1.useDeleteChatMessage)();
|
|
84
|
+
const [confirming, setConfirming] = (0, react_1.useState)(false);
|
|
85
|
+
const { onOpenChange } = (0, dropdown_menu_1.useRootContext)();
|
|
86
|
+
return ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => {
|
|
87
|
+
if (!message)
|
|
88
|
+
return;
|
|
89
|
+
if (!confirming) {
|
|
90
|
+
setConfirming(true);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
deleteChatMessage(message.uri).then(() => {
|
|
94
|
+
onOpenChange?.(false);
|
|
95
|
+
});
|
|
96
|
+
}, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { color: "destructive", children: confirming ? "Are you sure?" : "Delete message" }) }));
|
|
97
|
+
}
|
|
80
98
|
function ReportButton({ message, setReportModalOpen, setReportSubject, }) {
|
|
81
99
|
const { onOpenChange } = (0, dropdown_menu_1.useRootContext)();
|
|
82
100
|
return ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => {
|
|
@@ -14,10 +14,12 @@ function Fullscreen(props) {
|
|
|
14
14
|
const fullscreen = (0, __1.usePlayerStore)((x) => x.fullscreen, playerId);
|
|
15
15
|
const setFullscreen = (0, __1.usePlayerStore)((x) => x.setFullscreen, playerId);
|
|
16
16
|
const setSrc = (0, __1.usePlayerStore)((x) => x.setSrc);
|
|
17
|
+
const setAutoplayFailed = (0, __1.usePlayerStore)((x) => x.setAutoplayFailed);
|
|
17
18
|
const divRef = (0, react_1.useRef)(null);
|
|
18
19
|
const videoRef = (0, react_1.useRef)(null);
|
|
19
20
|
(0, react_1.useEffect)(() => {
|
|
20
21
|
setSrc(props.src);
|
|
22
|
+
setAutoplayFailed(false);
|
|
21
23
|
}, [props.src]);
|
|
22
24
|
(0, react_1.useEffect)(() => {
|
|
23
25
|
if (!divRef.current) {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AutoplayButton = AutoplayButton;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const lucide_react_native_1 = require("lucide-react-native");
|
|
6
|
+
const react_native_1 = require("react-native");
|
|
7
|
+
const __1 = require("../../..");
|
|
8
|
+
const ui_1 = require("../../../ui");
|
|
9
|
+
function AutoplayButton() {
|
|
10
|
+
const autoplayFailed = (0, __1.usePlayerStore)((x) => x.autoplayFailed);
|
|
11
|
+
const setAutoplayFailed = (0, __1.usePlayerStore)((x) => x.setAutoplayFailed);
|
|
12
|
+
const setMuted = (0, __1.usePlayerStore)((x) => x.setMuted);
|
|
13
|
+
const setMuteWasForced = (0, __1.usePlayerStore)((x) => x.setMuteWasForced);
|
|
14
|
+
const setUserInteraction = (0, __1.usePlayerStore)((x) => x.setUserInteraction);
|
|
15
|
+
const videoRef = (0, __1.usePlayerStore)((x) => x.videoRef);
|
|
16
|
+
const handlePlayButtonPress = () => {
|
|
17
|
+
if (videoRef && typeof videoRef === "object" && videoRef.current) {
|
|
18
|
+
videoRef.current
|
|
19
|
+
.play()
|
|
20
|
+
.then(() => {
|
|
21
|
+
setAutoplayFailed(false);
|
|
22
|
+
setUserInteraction();
|
|
23
|
+
})
|
|
24
|
+
.catch((err) => {
|
|
25
|
+
console.error("Manual play failed", err);
|
|
26
|
+
if (err.name === "NotAllowedError") {
|
|
27
|
+
setMuted(true);
|
|
28
|
+
videoRef.current.muted = true;
|
|
29
|
+
videoRef
|
|
30
|
+
.current.play()
|
|
31
|
+
.then(() => {
|
|
32
|
+
setAutoplayFailed(false);
|
|
33
|
+
setMuteWasForced(true);
|
|
34
|
+
setUserInteraction();
|
|
35
|
+
})
|
|
36
|
+
.catch((err) => {
|
|
37
|
+
console.error("Manual muted play also failed", err);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
if (!autoplayFailed)
|
|
44
|
+
return null;
|
|
45
|
+
return ((0, jsx_runtime_1.jsx)(__1.View, { style: [
|
|
46
|
+
__1.layout.position.absolute,
|
|
47
|
+
__1.layout.flex.center,
|
|
48
|
+
ui_1.h.percent[100],
|
|
49
|
+
ui_1.w.percent[100],
|
|
50
|
+
], children: (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: handlePlayButtonPress, style: [
|
|
51
|
+
{
|
|
52
|
+
flexDirection: "column",
|
|
53
|
+
alignItems: "center",
|
|
54
|
+
justifyContent: "center",
|
|
55
|
+
gap: 8,
|
|
56
|
+
},
|
|
57
|
+
], children: (0, jsx_runtime_1.jsx)(__1.View, { style: [
|
|
58
|
+
ui_1.p[4],
|
|
59
|
+
{
|
|
60
|
+
backgroundColor: "rgba(200,200,255, 0.1)",
|
|
61
|
+
borderRadius: 999,
|
|
62
|
+
borderWidth: 2,
|
|
63
|
+
borderColor: "rgba(200,200,255, 0.45)",
|
|
64
|
+
boxShadow: "0 0px 4px rgba(0, 0, 0, 1)",
|
|
65
|
+
shadowColor: "rgba(0, 0, 0, 1)",
|
|
66
|
+
},
|
|
67
|
+
], children: (0, jsx_runtime_1.jsx)(lucide_react_native_1.Play, { size: "48", color: "rgba(120,120,120,0.3)", fill: "rgba(200,200,255,1)" }) }) }) }));
|
|
68
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./autoplay-button"), exports);
|
|
4
5
|
tslib_1.__exportStar(require("./countdown"), exports);
|
|
5
6
|
tslib_1.__exportStar(require("./input"), exports);
|
|
6
7
|
tslib_1.__exportStar(require("./metrics"), exports);
|
|
@@ -107,6 +107,7 @@ const VideoElement = (0, react_1.forwardRef)((props, ref) => {
|
|
|
107
107
|
playerEvent(url, now.toISOString(), evType, {});
|
|
108
108
|
};
|
|
109
109
|
const [firstAttempt, setFirstAttempt] = (0, react_1.useState)(true);
|
|
110
|
+
const setAutoplayFailed = (0, __1.usePlayerStore)((x) => x.setAutoplayFailed);
|
|
110
111
|
const localVideoRef = props.videoRef ?? (0, react_1.useRef)(null);
|
|
111
112
|
// setPipAction comes from Zustand store
|
|
112
113
|
(0, react_1.useEffect)(() => {
|
|
@@ -158,12 +159,21 @@ const VideoElement = (0, react_1.forwardRef)((props, ref) => {
|
|
|
158
159
|
})
|
|
159
160
|
.catch((err) => {
|
|
160
161
|
console.error("Muted play also failed", err);
|
|
162
|
+
setAutoplayFailed(true);
|
|
161
163
|
});
|
|
162
164
|
}
|
|
163
165
|
}
|
|
166
|
+
else {
|
|
167
|
+
// For other errors (not NotAllowedError), also show play button
|
|
168
|
+
setAutoplayFailed(true);
|
|
169
|
+
}
|
|
164
170
|
});
|
|
165
171
|
}
|
|
166
172
|
};
|
|
173
|
+
const handlePlaying = (e) => {
|
|
174
|
+
setAutoplayFailed(false);
|
|
175
|
+
event("playing")(e);
|
|
176
|
+
};
|
|
167
177
|
(0, react_1.useEffect)(() => {
|
|
168
178
|
return () => {
|
|
169
179
|
setStatus(__1.PlayerStatus.START);
|
|
@@ -198,7 +208,7 @@ const VideoElement = (0, react_1.forwardRef)((props, ref) => {
|
|
|
198
208
|
console.log("Sending", evType, "status to", url);
|
|
199
209
|
playerEvent(url, now.toISOString(), evType, {});
|
|
200
210
|
};
|
|
201
|
-
return ((0, jsx_runtime_1.jsx)("video", { autoPlay: true, playsInline: true, ref: handleVideoRef, controls: false, src: ingest ? undefined : props.url, muted: muted, crossOrigin: "anonymous", onMouseMove: setUserInteraction, onClick: setUserInteraction, onAbort: event("abort"), onCanPlay: eventLogger, onCanPlayThroughCapture: eventLogger, onCanPlayThrough: canPlayThrough, onEmptied: event("emptied"), onEncrypted: event("encrypted"), onEnded: event("ended"), onError: event("error"), onLoadedData: event("loadeddata"), onLoadedMetadata: event("loadedmetadata"), onLoadStart: event("loadstart"), onPause: event("pause"), onPlay: event("play"), onPlaying:
|
|
211
|
+
return ((0, jsx_runtime_1.jsx)("video", { autoPlay: true, playsInline: true, ref: handleVideoRef, controls: false, src: ingest ? undefined : props.url, muted: muted, crossOrigin: "anonymous", onMouseMove: setUserInteraction, onClick: setUserInteraction, onAbort: event("abort"), onCanPlay: eventLogger, onCanPlayThroughCapture: eventLogger, onCanPlayThrough: canPlayThrough, onEmptied: event("emptied"), onEncrypted: event("encrypted"), onEnded: event("ended"), onError: event("error"), onLoadedData: event("loadeddata"), onLoadedMetadata: event("loadedmetadata"), onLoadStart: event("loadstart"), onPause: event("pause"), onPlay: event("play"), onPlaying: handlePlaying, onRateChange: event("ratechange"), onSeeked: event("seeked"), onSeeking: event("seeking"), onStalled: event("stalled"), onSuspend: event("suspend"), onVolumeChange: event("volumechange"), onWaiting: event("waiting"), style: {
|
|
202
212
|
objectFit: props.objectFit || "contain",
|
|
203
213
|
backgroundColor: "transparent",
|
|
204
214
|
width: "100%",
|
|
@@ -11,12 +11,29 @@ function useWebRTCDiagnostics() {
|
|
|
11
11
|
rtcSessionDescription: false,
|
|
12
12
|
getUserMedia: false,
|
|
13
13
|
getDisplayMedia: false,
|
|
14
|
+
isHwH264Supported: false,
|
|
14
15
|
errors: [],
|
|
15
16
|
warnings: [],
|
|
16
17
|
});
|
|
17
18
|
(0, react_1.useEffect)(() => {
|
|
18
19
|
const errors = [];
|
|
19
20
|
const warnings = [];
|
|
21
|
+
const checkH264Support = async () => {
|
|
22
|
+
try {
|
|
23
|
+
const pc = new RTCPeerConnection();
|
|
24
|
+
const offer = await pc.createOffer();
|
|
25
|
+
pc.close();
|
|
26
|
+
if (offer.sdp) {
|
|
27
|
+
const h264Match = offer.sdp.search(/rtpmap:([0-9]+) H264/g);
|
|
28
|
+
return h264Match !== -1;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.warn("Failed to check H.264 support:", error);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
20
37
|
// Check if we're in a browser environment
|
|
21
38
|
if (typeof window === "undefined") {
|
|
22
39
|
errors.push("Running in non-browser environment");
|
|
@@ -27,6 +44,7 @@ function useWebRTCDiagnostics() {
|
|
|
27
44
|
rtcSessionDescription: false,
|
|
28
45
|
getUserMedia: false,
|
|
29
46
|
getDisplayMedia: false,
|
|
47
|
+
isHwH264Supported: false,
|
|
30
48
|
errors,
|
|
31
49
|
warnings,
|
|
32
50
|
});
|
|
@@ -71,20 +89,42 @@ function useWebRTCDiagnostics() {
|
|
|
71
89
|
warnings.push("Safari may have limited WebRTC codec support");
|
|
72
90
|
}
|
|
73
91
|
const browserSupport = rtcPeerConnection && rtcSessionDescription;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
// Check H.264 support asynchronously
|
|
93
|
+
if (rtcPeerConnection) {
|
|
94
|
+
checkH264Support().then((isHwH264Supported) => {
|
|
95
|
+
if (!isHwH264Supported) {
|
|
96
|
+
warnings.push("H.264 hardware acceleration is not supported\n In Firefox, try enabling 'media.webrtc.hw.h264.enabled' in about:config");
|
|
97
|
+
}
|
|
98
|
+
setDiagnostics({
|
|
99
|
+
done: true,
|
|
100
|
+
browserSupport,
|
|
101
|
+
rtcPeerConnection,
|
|
102
|
+
rtcSessionDescription,
|
|
103
|
+
getUserMedia,
|
|
104
|
+
getDisplayMedia,
|
|
105
|
+
isHwH264Supported,
|
|
106
|
+
errors,
|
|
107
|
+
warnings,
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
setDiagnostics({
|
|
113
|
+
done: true,
|
|
114
|
+
browserSupport,
|
|
115
|
+
rtcPeerConnection,
|
|
116
|
+
rtcSessionDescription,
|
|
117
|
+
getUserMedia,
|
|
118
|
+
getDisplayMedia,
|
|
119
|
+
isHwH264Supported: false,
|
|
120
|
+
errors,
|
|
121
|
+
warnings,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
84
124
|
}, []);
|
|
85
125
|
return diagnostics;
|
|
86
126
|
}
|
|
87
|
-
function logWebRTCDiagnostics() {
|
|
127
|
+
async function logWebRTCDiagnostics() {
|
|
88
128
|
console.group("WebRTC Diagnostics");
|
|
89
129
|
// Log browser support
|
|
90
130
|
console.log("RTCPeerConnection:", !!window.RTCPeerConnection);
|
|
@@ -95,14 +135,28 @@ function logWebRTCDiagnostics() {
|
|
|
95
135
|
console.log("User Agent:", navigator.userAgent);
|
|
96
136
|
console.log("Protocol:", location.protocol);
|
|
97
137
|
console.log("Host:", location.hostname);
|
|
98
|
-
|
|
138
|
+
console.groupEnd();
|
|
99
139
|
if (window.RTCPeerConnection) {
|
|
100
140
|
try {
|
|
101
141
|
const pc = new RTCPeerConnection();
|
|
102
|
-
|
|
142
|
+
// Check H.264 support
|
|
143
|
+
try {
|
|
144
|
+
const offer = await pc.createOffer({ offerToReceiveVideo: true });
|
|
145
|
+
const isHwH264Supported = offer.sdp
|
|
146
|
+
? offer.sdp.search(/rtpmap:([0-9]+) H264/g) !== -1
|
|
147
|
+
: false;
|
|
148
|
+
console.group("WebRTC Peer Connection Test");
|
|
149
|
+
console.log("RTCPeerConnection creation: ✓ Success");
|
|
150
|
+
console.log("H.264 support:", isHwH264Supported ? "✓ Supported" : "✗ Not supported");
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.group("WebRTC Peer Connection Test");
|
|
154
|
+
console.error("H.264 check failed:", error);
|
|
155
|
+
}
|
|
103
156
|
pc.close();
|
|
104
157
|
}
|
|
105
158
|
catch (error) {
|
|
159
|
+
console.group("WebRTC Peer Connection Test");
|
|
106
160
|
console.error("RTCPeerConnection creation: ✗ Failed", error);
|
|
107
161
|
}
|
|
108
162
|
}
|
|
@@ -32,6 +32,7 @@ const createSystemMessage = (type, text, metadata, date = new Date()) => {
|
|
|
32
32
|
indexedAt: now.toISOString(),
|
|
33
33
|
chatProfile: {
|
|
34
34
|
color: { red: 128, green: 128, blue: 128 }, // Gray color for system messages
|
|
35
|
+
$type: "place.stream.chat.profile",
|
|
35
36
|
},
|
|
36
37
|
};
|
|
37
38
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.reduceChat = exports.useReportChatMessage = exports.useSubmitReport = exports.reduceChatIncremental = exports.useCreateChatMessage = exports.useAddPendingHide = exports.usePendingHides = exports.useSetReplyToMessage = exports.useReplyToMessage = void 0;
|
|
3
|
+
exports.reduceChat = exports.useReportChatMessage = exports.useSubmitReport = exports.reduceChatIncremental = exports.useDeleteChatMessage = 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");
|
|
@@ -50,6 +50,7 @@ const useCreateChatMessage = () => {
|
|
|
50
50
|
const rt = new api_1.RichText({ text: msg.text });
|
|
51
51
|
await rt.detectFacets(pdsAgent);
|
|
52
52
|
const record = {
|
|
53
|
+
$type: "place.stream.chat.message",
|
|
53
54
|
text: msg.text,
|
|
54
55
|
createdAt: new Date().toISOString(),
|
|
55
56
|
streamer: streamerProfile.did,
|
|
@@ -90,6 +91,28 @@ const useCreateChatMessage = () => {
|
|
|
90
91
|
};
|
|
91
92
|
};
|
|
92
93
|
exports.useCreateChatMessage = useCreateChatMessage;
|
|
94
|
+
const useDeleteChatMessage = () => {
|
|
95
|
+
const pdsAgent = (0, xrpc_1.usePDSAgent)();
|
|
96
|
+
if (!pdsAgent) {
|
|
97
|
+
throw new Error("No PDS agent found");
|
|
98
|
+
}
|
|
99
|
+
const userDID = (0, streamplace_store_1.useDID)();
|
|
100
|
+
if (!userDID) {
|
|
101
|
+
throw new Error("No user DID found");
|
|
102
|
+
}
|
|
103
|
+
return async (uri) => {
|
|
104
|
+
const rkey = uri.split("/").pop();
|
|
105
|
+
if (!rkey) {
|
|
106
|
+
throw new Error("No rkey found");
|
|
107
|
+
}
|
|
108
|
+
return await pdsAgent.com.atproto.repo.deleteRecord({
|
|
109
|
+
repo: userDID,
|
|
110
|
+
collection: "place.stream.chat.message",
|
|
111
|
+
rkey: rkey,
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
exports.useDeleteChatMessage = useDeleteChatMessage;
|
|
93
116
|
const buildSortedChatList = (chatIndex, existingChatList, newMessages, removedKeys) => {
|
|
94
117
|
const sortedKeys = Object.keys(chatIndex).sort((a, b) => {
|
|
95
118
|
const aTime = parseInt(a.split("-")[0], 10);
|
|
@@ -204,6 +227,7 @@ const reduceChatIncremental = (state, newMessages, blocks, hideUris = []) => {
|
|
|
204
227
|
processedMessage = {
|
|
205
228
|
...message,
|
|
206
229
|
replyTo: {
|
|
230
|
+
$type: "place.stream.chat.defs#messageView",
|
|
207
231
|
cid: parentMsg.cid,
|
|
208
232
|
uri: parentMsg.uri,
|
|
209
233
|
author: parentMsg.author,
|
|
@@ -8,7 +8,7 @@ const chat_1 = require("./chat");
|
|
|
8
8
|
const problems_1 = require("./problems");
|
|
9
9
|
const MAX_RECENT_SEGMENTS = 10;
|
|
10
10
|
const handleWebSocketMessages = (state, messages) => {
|
|
11
|
-
for (
|
|
11
|
+
for (let message of messages) {
|
|
12
12
|
if (streamplace_1.PlaceStreamLivestream.isLivestreamView(message)) {
|
|
13
13
|
const newLivestream = message;
|
|
14
14
|
const oldLivestream = state.livestream;
|
|
@@ -27,12 +27,14 @@ const handleWebSocketMessages = (state, messages) => {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
else if (streamplace_1.PlaceStreamLivestream.isViewerCount(message)) {
|
|
30
|
+
message = message;
|
|
30
31
|
state = {
|
|
31
32
|
...state,
|
|
32
33
|
viewers: message.count,
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
else if (streamplace_1.PlaceStreamChatDefs.isMessageView(message)) {
|
|
37
|
+
message = message;
|
|
36
38
|
// Explicitly map MessageView to MessageViewHydrated
|
|
37
39
|
const hydrated = {
|
|
38
40
|
uri: message.uri,
|
|
@@ -64,6 +66,7 @@ const handleWebSocketMessages = (state, messages) => {
|
|
|
64
66
|
state = (0, chat_1.reduceChat)(state, [], [block], []);
|
|
65
67
|
}
|
|
66
68
|
else if (streamplace_1.PlaceStreamDefs.isRenditions(message)) {
|
|
69
|
+
message = message;
|
|
67
70
|
state = {
|
|
68
71
|
...state,
|
|
69
72
|
renditions: message.renditions,
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PlayerProvider = void 0;
|
|
4
4
|
exports.withPlayerProvider = withPlayerProvider;
|
|
5
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
6
7
|
const react_1 = require("react");
|
|
7
8
|
const context_1 = require("./context");
|
|
8
9
|
const player_store_1 = require("./player-store");
|
|
@@ -20,7 +21,7 @@ const PlayerProvider = ({ children, initialPlayers = [], defaultId = Math.random
|
|
|
20
21
|
return initialPlayerStores;
|
|
21
22
|
});
|
|
22
23
|
const createPlayer = (0, react_1.useCallback)((id) => {
|
|
23
|
-
const playerId = id ||
|
|
24
|
+
const playerId = id || (0, crypto_1.randomUUID)();
|
|
24
25
|
const playerStore = (0, player_store_1.makePlayerStore)(playerId);
|
|
25
26
|
setPlayers((prev) => ({
|
|
26
27
|
...prev,
|
|
@@ -58,6 +58,8 @@ const makePlayerStore = (id) => {
|
|
|
58
58
|
// * Will get set to 'false' if the user has interacted with the volume
|
|
59
59
|
muteWasForced: false,
|
|
60
60
|
setMuteWasForced: (muteWasForced) => set(() => ({ muteWasForced })),
|
|
61
|
+
autoplayFailed: false,
|
|
62
|
+
setAutoplayFailed: (autoplayFailed) => set(() => ({ autoplayFailed })),
|
|
61
63
|
embedded: false,
|
|
62
64
|
setEmbedded: (embedded) => set(() => ({ embedded })),
|
|
63
65
|
showControls: true,
|
|
@@ -179,6 +179,7 @@ function useCreateStreamRecord() {
|
|
|
179
179
|
platVersion = (0, browser_1.getBrowserName)(window.navigator.userAgent);
|
|
180
180
|
}
|
|
181
181
|
const record = {
|
|
182
|
+
$type: "place.stream.livestream",
|
|
182
183
|
title: title,
|
|
183
184
|
url: finalUrl,
|
|
184
185
|
createdAt: new Date().toISOString(),
|
|
@@ -230,6 +231,7 @@ function useUpdateStreamRecord(customUrl = null) {
|
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
233
|
const record = {
|
|
234
|
+
$type: "place.stream.livestream",
|
|
233
235
|
title: title,
|
|
234
236
|
url: finalUrl,
|
|
235
237
|
createdAt: new Date().toISOString(),
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamplace/components",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.25",
|
|
4
4
|
"description": "Streamplace React (Native) Components",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "src/index.tsx",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"tsup": "^8.5.0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@atproto/api": "^0.
|
|
23
|
+
"@atproto/api": "^0.16.7",
|
|
24
24
|
"@atproto/crypto": "^0.4.4",
|
|
25
25
|
"@emoji-mart/react": "^1.1.1",
|
|
26
26
|
"@gorhom/bottom-sheet": "^5.1.6",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"react-native-svg": "^15.0.0",
|
|
41
41
|
"react-native-webrtc": "git+https://github.com/streamplace/react-native-webrtc.git#6b8472a771ac47f89217d327058a8a4124a6ae56",
|
|
42
42
|
"react-use-websocket": "^4.13.0",
|
|
43
|
-
"streamplace": "0.7.
|
|
43
|
+
"streamplace": "0.7.25",
|
|
44
44
|
"viem": "^2.21.44",
|
|
45
45
|
"zustand": "^5.0.5"
|
|
46
46
|
},
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"start": "tsc --watch --preserveWatchOutput",
|
|
53
53
|
"prepare": "tsc"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "288afabcb270c01ae8012e2a5cd9d75d5e1aae28"
|
|
56
56
|
}
|
|
@@ -83,6 +83,9 @@ export function ChatBox({
|
|
|
83
83
|
const authors = useMemo(() => {
|
|
84
84
|
if (!chat) return null;
|
|
85
85
|
return chat.reduce((acc, msg) => {
|
|
86
|
+
// our fake system user "did"
|
|
87
|
+
if (msg.author.did === "did:sys:system") return acc;
|
|
88
|
+
if (acc.has(msg.author.handle)) return acc;
|
|
86
89
|
acc.set(msg.author.handle, msg.chatProfile);
|
|
87
90
|
return acc;
|
|
88
91
|
}, new Map<string, ChatMessageViewHydrated["chatProfile"]>());
|
|
@@ -105,9 +105,10 @@ export const RenderChatMessage = memo(
|
|
|
105
105
|
hour12: false,
|
|
106
106
|
});
|
|
107
107
|
}, []);
|
|
108
|
+
const replyTo = (item.replyTo as ChatMessageViewHydrated) || null;
|
|
108
109
|
return (
|
|
109
110
|
<>
|
|
110
|
-
{
|
|
111
|
+
{replyTo && showReply && (
|
|
111
112
|
<View
|
|
112
113
|
style={[
|
|
113
114
|
gap.all[2],
|
|
@@ -130,11 +131,11 @@ export const RenderChatMessage = memo(
|
|
|
130
131
|
>
|
|
131
132
|
<Text
|
|
132
133
|
style={{
|
|
133
|
-
color: getRgbColor(
|
|
134
|
+
color: getRgbColor(replyTo.chatProfile?.color),
|
|
134
135
|
fontWeight: "thin",
|
|
135
136
|
}}
|
|
136
137
|
>
|
|
137
|
-
@{(
|
|
138
|
+
@{(replyTo.author as any).handle}
|
|
138
139
|
</Text>{" "}
|
|
139
140
|
<Text
|
|
140
141
|
style={{
|
|
@@ -142,7 +143,7 @@ export const RenderChatMessage = memo(
|
|
|
142
143
|
fontStyle: "italic",
|
|
143
144
|
}}
|
|
144
145
|
>
|
|
145
|
-
{
|
|
146
|
+
{replyTo.record.text}
|
|
146
147
|
</Text>
|
|
147
148
|
</Text>
|
|
148
149
|
</View>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Ellipsis, Reply } from "lucide-react-native";
|
|
2
2
|
import { ComponentProps, memo, useEffect, useRef, useState } from "react";
|
|
3
|
-
import { Platform, Pressable } from "react-native";
|
|
3
|
+
import { Keyboard, Platform, Pressable } from "react-native";
|
|
4
4
|
import { FlatList } from "react-native-gesture-handler";
|
|
5
5
|
import Swipeable, {
|
|
6
6
|
SwipeableMethods,
|
|
@@ -223,9 +223,7 @@ const ChatLine = memo(
|
|
|
223
223
|
}
|
|
224
224
|
renderLeftActions={Platform.OS === "android" ? undefined : LeftAction}
|
|
225
225
|
overshootFriction={9}
|
|
226
|
-
ref={
|
|
227
|
-
swipeableRef.current = ref;
|
|
228
|
-
}}
|
|
226
|
+
ref={swipeableRef}
|
|
229
227
|
onSwipeableOpen={(r) => {
|
|
230
228
|
if (r === (Platform.OS === "android" ? "right" : "left")) {
|
|
231
229
|
setReply(item);
|
|
@@ -258,6 +256,22 @@ export function Chat({
|
|
|
258
256
|
canModerate?: boolean;
|
|
259
257
|
}) {
|
|
260
258
|
const chat = useChat();
|
|
259
|
+
const [isScrolledUp, setIsScrolledUp] = useState(false);
|
|
260
|
+
|
|
261
|
+
const handleScroll = (event: any) => {
|
|
262
|
+
const { contentOffset } = event.nativeEvent;
|
|
263
|
+
|
|
264
|
+
const scrolledUp = contentOffset.y > 20; // threshold
|
|
265
|
+
|
|
266
|
+
if (scrolledUp !== isScrolledUp) {
|
|
267
|
+
setIsScrolledUp(scrolledUp);
|
|
268
|
+
|
|
269
|
+
// Dismiss keyboard when scrolled up
|
|
270
|
+
if (scrolledUp && Platform.OS !== "web") {
|
|
271
|
+
Keyboard.dismiss();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
};
|
|
261
275
|
|
|
262
276
|
if (!chat)
|
|
263
277
|
return (
|
|
@@ -288,6 +302,8 @@ export function Chat({
|
|
|
288
302
|
maxToRenderPerBatch={10}
|
|
289
303
|
initialNumToRender={10}
|
|
290
304
|
updateCellsBatchingPeriod={50}
|
|
305
|
+
onScroll={handleScroll}
|
|
306
|
+
scrollEventThrottle={16}
|
|
291
307
|
/>
|
|
292
308
|
<ModView />
|
|
293
309
|
</View>
|