@streamplace/components 0.7.21 → 0.7.26
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 +0 -6
- package/dist/components/chat/chat-message.js +9 -4
- package/dist/components/chat/chat.js +14 -4
- package/dist/components/mobile-player/ui/autoplay-button.js +1 -1
- package/dist/components/mobile-player/video-async.native.js +4 -4
- package/dist/components/mobile-player/video.js +3 -3
- package/dist/components/mobile-player/webrtc-diagnostics.js +67 -13
- package/dist/index.js +4 -1
- package/dist/lib/system-messages.js +1 -0
- package/dist/livestream-store/chat.js +11 -0
- package/dist/livestream-store/stream-key.js +1 -0
- package/dist/livestream-store/websocket-consumer.js +4 -1
- package/dist/player-store/player-store.js +0 -4
- package/dist/storage/index.js +5 -0
- package/dist/storage/lock.js +40 -0
- package/dist/storage/storage.js +14 -0
- package/dist/storage/storage.native.js +44 -0
- package/dist/storage/storage.shared.js +2 -0
- package/dist/streamplace-provider/index.js +1 -0
- package/dist/streamplace-store/stream.js +2 -0
- package/dist/streamplace-store/streamplace-store.js +75 -2
- package/dist/streamplace-store/xrpc.js +10 -1
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/package.json +5 -4
- package/src/components/chat/chat-box.tsx +0 -11
- package/src/components/chat/chat-message.tsx +8 -4
- package/src/components/chat/chat.tsx +20 -4
- package/src/components/mobile-player/ui/autoplay-button.tsx +2 -2
- package/src/components/mobile-player/video-async.native.tsx +6 -4
- package/src/components/mobile-player/video.tsx +6 -3
- package/src/components/mobile-player/webrtc-diagnostics.tsx +73 -15
- package/src/index.tsx +4 -0
- package/src/lib/system-messages.ts +1 -0
- package/src/livestream-store/chat.tsx +15 -0
- package/src/livestream-store/stream-key.tsx +1 -0
- package/src/livestream-store/websocket-consumer.tsx +4 -1
- package/src/player-store/player-state.tsx +0 -12
- package/src/player-store/player-store.tsx +0 -8
- package/src/storage/index.tsx +3 -0
- package/src/storage/lock.tsx +38 -0
- package/src/storage/storage.native.tsx +42 -0
- package/src/storage/storage.shared.tsx +5 -0
- package/src/storage/storage.tsx +15 -0
- package/src/streamplace-provider/index.tsx +2 -1
- package/src/streamplace-store/stream.tsx +2 -0
- package/src/streamplace-store/streamplace-store.tsx +92 -2
- package/src/streamplace-store/xrpc.tsx +9 -2
- package/tsconfig.json +2 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -9,7 +9,6 @@ const react_2 = require("react");
|
|
|
9
9
|
const react_native_1 = require("react-native");
|
|
10
10
|
const __1 = require("../../");
|
|
11
11
|
const atoms_1 = require("../../lib/theme/atoms");
|
|
12
|
-
const xrpc_1 = require("../../streamplace-store/xrpc");
|
|
13
12
|
const textarea_1 = require("../ui/textarea");
|
|
14
13
|
const chat_message_1 = require("./chat-message");
|
|
15
14
|
const emoji_suggestions_1 = require("./emoji-suggestions");
|
|
@@ -33,11 +32,6 @@ function ChatBox({ isPopout, chatBoxStyle, emojiData, setIsChatVisible, }) {
|
|
|
33
32
|
const replyTo = (0, __1.useReplyToMessage)();
|
|
34
33
|
const setReplyToMessage = (0, __1.useSetReplyToMessage)();
|
|
35
34
|
const textAreaRef = (0, react_2.useRef)(null);
|
|
36
|
-
// are we logged in?
|
|
37
|
-
let agent = (0, xrpc_1.usePDSAgent)();
|
|
38
|
-
if (!agent?.did) {
|
|
39
|
-
(0, jsx_runtime_1.jsx)(__1.View, { style: [atoms_1.layout.flex.row, atoms_1.layout.flex.alignCenter, atoms_1.gap.all[2]], children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Log in to chat." }) });
|
|
40
|
-
}
|
|
41
35
|
const authors = (0, react_2.useMemo)(() => {
|
|
42
36
|
if (!chat)
|
|
43
37
|
return null;
|
|
@@ -28,6 +28,10 @@ const segmentedObject = (obj, index, userCache) => {
|
|
|
28
28
|
},
|
|
29
29
|
], onPress: () => react_native_1.Linking.openURL(`https://bsky.app/profile/${mtnftr.did || ""}`), children: obj.text }, `mention-${index}`));
|
|
30
30
|
}
|
|
31
|
+
else {
|
|
32
|
+
// render as normal text if we don't recognize the facet type
|
|
33
|
+
return (0, jsx_runtime_1.jsx)(text_1.Text, { children: obj.text }, `unknown-facet-${index}`);
|
|
34
|
+
}
|
|
31
35
|
}
|
|
32
36
|
else {
|
|
33
37
|
return (0, jsx_runtime_1.jsx)(text_1.Text, { children: obj.text }, `text-${index}`);
|
|
@@ -48,7 +52,8 @@ exports.RenderChatMessage = (0, react_1.memo)(function RenderChatMessage({ item,
|
|
|
48
52
|
hour12: false,
|
|
49
53
|
});
|
|
50
54
|
}, []);
|
|
51
|
-
|
|
55
|
+
const replyTo = item.replyTo || null;
|
|
56
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [replyTo && showReply && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
52
57
|
atoms_1.gap.all[2],
|
|
53
58
|
ui_1.layout.flex.row,
|
|
54
59
|
{ minWidth: 0, maxWidth: "100%" },
|
|
@@ -62,12 +67,12 @@ exports.RenderChatMessage = (0, react_1.memo)(function RenderChatMessage({ item,
|
|
|
62
67
|
atoms_1.mr[4],
|
|
63
68
|
{ minWidth: 0, overflow: "hidden" },
|
|
64
69
|
], children: [(0, jsx_runtime_1.jsxs)(text_1.Text, { style: {
|
|
65
|
-
color: getRgbColor(
|
|
70
|
+
color: getRgbColor(replyTo.chatProfile?.color),
|
|
66
71
|
fontWeight: "thin",
|
|
67
|
-
}, children: ["@",
|
|
72
|
+
}, children: ["@", replyTo.author.handle] }), " ", (0, jsx_runtime_1.jsx)(text_1.Text, { style: {
|
|
68
73
|
color: ui_1.colors.gray[300],
|
|
69
74
|
fontStyle: "italic",
|
|
70
|
-
}, children:
|
|
75
|
+
}, children: replyTo.record.text })] }) })), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
71
76
|
atoms_1.gap.all[2],
|
|
72
77
|
ui_1.layout.flex.row,
|
|
73
78
|
{ 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
|
}
|
|
@@ -9,7 +9,7 @@ const ui_1 = require("../../../ui");
|
|
|
9
9
|
function AutoplayButton() {
|
|
10
10
|
const autoplayFailed = (0, __1.usePlayerStore)((x) => x.autoplayFailed);
|
|
11
11
|
const setAutoplayFailed = (0, __1.usePlayerStore)((x) => x.setAutoplayFailed);
|
|
12
|
-
const setMuted = (0, __1.
|
|
12
|
+
const setMuted = (0, __1.useSetMuted)();
|
|
13
13
|
const setMuteWasForced = (0, __1.usePlayerStore)((x) => x.setMuteWasForced);
|
|
14
14
|
const setUserInteraction = (0, __1.usePlayerStore)((x) => x.setUserInteraction);
|
|
15
15
|
const videoRef = (0, __1.usePlayerStore)((x) => x.videoRef);
|
|
@@ -29,8 +29,8 @@ function NativeVideo(props) {
|
|
|
29
29
|
const src = (0, __1.usePlayerStore)((x) => x.src);
|
|
30
30
|
const { url } = (0, shared_1.srcToUrl)({ src: src, selectedRendition }, protocol);
|
|
31
31
|
const setStatus = (0, __1.usePlayerStore)((x) => x.setStatus);
|
|
32
|
-
const muted = (0, __1.
|
|
33
|
-
const volume = (0, __1.
|
|
32
|
+
const muted = (0, __1.useMuted)();
|
|
33
|
+
const volume = (0, __1.useEffectiveVolume)();
|
|
34
34
|
const setFullscreen = (0, __1.usePlayerStore)((x) => x.setFullscreen);
|
|
35
35
|
const fullscreen = (0, __1.usePlayerStore)((x) => x.fullscreen);
|
|
36
36
|
const playerEvent = (0, __1.usePlayerStore)((x) => x.playerEvent);
|
|
@@ -125,8 +125,8 @@ function NativeWHEP(props) {
|
|
|
125
125
|
setPlayerHeight(height);
|
|
126
126
|
}, []);
|
|
127
127
|
const setStatus = (0, __1.usePlayerStore)((x) => x.setStatus);
|
|
128
|
-
const muted = (0, __1.
|
|
129
|
-
const volume = (0, __1.
|
|
128
|
+
const muted = (0, __1.useMuted)();
|
|
129
|
+
const volume = (0, __1.useEffectiveVolume)();
|
|
130
130
|
(0, react_1.useEffect)(() => {
|
|
131
131
|
if (stuck && status === __1.PlayerStatus.PLAYING) {
|
|
132
132
|
console.log("setting status to stalled", status);
|
|
@@ -89,11 +89,11 @@ const VideoElement = (0, react_1.forwardRef)((props, ref) => {
|
|
|
89
89
|
const x = (0, __1.usePlayerStore)((x) => x);
|
|
90
90
|
const url = (0, __1.useStreamplaceStore)((x) => x.url);
|
|
91
91
|
const playerEvent = (0, __1.usePlayerStore)((x) => x.playerEvent);
|
|
92
|
-
const setMuted = (0, __1.usePlayerStore)((x) => x.setMuted);
|
|
93
92
|
const setMuteWasForced = (0, __1.usePlayerStore)((x) => x.setMuteWasForced);
|
|
94
|
-
const muted = (0, __1.usePlayerStore)((x) => x.muted);
|
|
95
93
|
const ingest = (0, __1.usePlayerStore)((x) => x.ingestConnectionState !== null);
|
|
96
|
-
const volume = (0, __1.
|
|
94
|
+
const volume = (0, __1.useEffectiveVolume)();
|
|
95
|
+
const muted = (0, __1.useMuted)();
|
|
96
|
+
const setMuted = (0, __1.useSetMuted)();
|
|
97
97
|
const setStatus = (0, __1.usePlayerStore)((x) => x.setStatus);
|
|
98
98
|
const setUserInteraction = (0, __1.usePlayerStore)((x) => x.setUserInteraction);
|
|
99
99
|
const setVideoRef = (0, __1.usePlayerStore)((x) => x.setVideoRef);
|
|
@@ -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
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Dashboard = exports.VideoRetry = exports.zero = exports.ui = exports.PlayerUI = exports.Player = exports.usePlayerContext = exports.withPlayerProvider = exports.PlayerProvider = void 0;
|
|
3
|
+
exports.storage = exports.Dashboard = exports.VideoRetry = exports.zero = exports.ui = exports.PlayerUI = exports.Player = exports.usePlayerContext = exports.withPlayerProvider = exports.PlayerProvider = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
// barrel file :)
|
|
6
6
|
tslib_1.__exportStar(require("./livestream-provider"), exports);
|
|
@@ -32,3 +32,6 @@ tslib_1.__exportStar(require("./components/share/sharesheet"), exports);
|
|
|
32
32
|
tslib_1.__exportStar(require("./components/keep-awake"), exports);
|
|
33
33
|
// Dashboard components
|
|
34
34
|
exports.Dashboard = tslib_1.__importStar(require("./components/dashboard"));
|
|
35
|
+
// Storage exports
|
|
36
|
+
var storage_1 = require("./storage");
|
|
37
|
+
Object.defineProperty(exports, "storage", { enumerable: true, get: function () { return tslib_1.__importDefault(storage_1).default; } });
|
|
@@ -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
|
};
|
|
@@ -49,7 +49,17 @@ const useCreateChatMessage = () => {
|
|
|
49
49
|
}
|
|
50
50
|
const rt = new api_1.RichText({ text: msg.text });
|
|
51
51
|
await rt.detectFacets(pdsAgent);
|
|
52
|
+
// filter out any facets that aren't in the allowed list
|
|
53
|
+
rt.facets = rt.facets?.filter((facet) => {
|
|
54
|
+
return (
|
|
55
|
+
// if all features are in the allowed list
|
|
56
|
+
facet.features.every((feature) => [
|
|
57
|
+
"app.bsky.richtext.facet#link",
|
|
58
|
+
"app.bsky.richtext.facet#mention",
|
|
59
|
+
].includes(feature.$type)));
|
|
60
|
+
});
|
|
52
61
|
const record = {
|
|
62
|
+
$type: "place.stream.chat.message",
|
|
53
63
|
text: msg.text,
|
|
54
64
|
createdAt: new Date().toISOString(),
|
|
55
65
|
streamer: streamerProfile.did,
|
|
@@ -226,6 +236,7 @@ const reduceChatIncremental = (state, newMessages, blocks, hideUris = []) => {
|
|
|
226
236
|
processedMessage = {
|
|
227
237
|
...message,
|
|
228
238
|
replyTo: {
|
|
239
|
+
$type: "place.stream.chat.defs#messageView",
|
|
229
240
|
cid: parentMsg.cid,
|
|
230
241
|
uri: parentMsg.uri,
|
|
231
242
|
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,
|
|
@@ -32,10 +32,6 @@ const makePlayerStore = (id) => {
|
|
|
32
32
|
setIngestAutoStart: (ingestAutoStart) => set(() => ({ ingestAutoStart })),
|
|
33
33
|
ingestStarted: null,
|
|
34
34
|
setIngestStarted: (timestamp) => set(() => ({ ingestStarted: timestamp })),
|
|
35
|
-
muted: false,
|
|
36
|
-
setMuted: (isMuted) => set(() => ({ muted: isMuted, muteWasForced: false })),
|
|
37
|
-
volume: 1.0,
|
|
38
|
-
setVolume: (volume) => set(() => ({ volume, muteWasForced: false })),
|
|
39
35
|
fullscreen: false,
|
|
40
36
|
setFullscreen: (isFullscreen) => set(() => ({ fullscreen: isFullscreen })),
|
|
41
37
|
status: player_state_1.PlayerStatus.START,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Lock = void 0;
|
|
4
|
+
class Lock {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.queue = [];
|
|
7
|
+
this.acquired = false;
|
|
8
|
+
}
|
|
9
|
+
async acquire() {
|
|
10
|
+
if (!this.acquired) {
|
|
11
|
+
this.acquired = true;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
return new Promise((resolve, _) => {
|
|
15
|
+
this.queue.push(resolve);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async release() {
|
|
20
|
+
if (this.queue.length === 0 && this.acquired) {
|
|
21
|
+
this.acquired = false;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const continuation = this.queue.shift();
|
|
25
|
+
return new Promise((res) => {
|
|
26
|
+
continuation();
|
|
27
|
+
res();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async critical(task) {
|
|
31
|
+
await this.acquire();
|
|
32
|
+
try {
|
|
33
|
+
return await task();
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await this.release();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.Lock = Lock;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class WebStorage {
|
|
4
|
+
async getItem(key) {
|
|
5
|
+
return localStorage.getItem(key);
|
|
6
|
+
}
|
|
7
|
+
async setItem(key, value) {
|
|
8
|
+
localStorage.setItem(key, value);
|
|
9
|
+
}
|
|
10
|
+
async removeItem(key) {
|
|
11
|
+
localStorage.removeItem(key);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.default = WebStorage;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const kv_store_1 = tslib_1.__importDefault(require("expo-sqlite/kv-store"));
|
|
5
|
+
const lock_1 = require("./lock");
|
|
6
|
+
// Needed because concurrent calls seem to return with a locked database
|
|
7
|
+
const lock = new lock_1.Lock();
|
|
8
|
+
class NativeStorage {
|
|
9
|
+
async getItem(key) {
|
|
10
|
+
return lock.critical(async () => {
|
|
11
|
+
try {
|
|
12
|
+
const value = await kv_store_1.default.getItem(key);
|
|
13
|
+
return value ?? null;
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
console.error(`error in NativeStorage.getItem: ${e}`);
|
|
17
|
+
throw e;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async setItem(key, value) {
|
|
22
|
+
return lock.critical(async () => {
|
|
23
|
+
try {
|
|
24
|
+
await kv_store_1.default.setItem(key, value);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
console.error(`error in NativeStorage.setItem: ${e}`);
|
|
28
|
+
throw e;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async removeItem(key) {
|
|
33
|
+
return lock.critical(async () => {
|
|
34
|
+
try {
|
|
35
|
+
await kv_store_1.default.removeItem(key);
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
console.error(`error in NativeStorage.removeItem: ${e}`);
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.default = NativeStorage;
|
|
@@ -8,6 +8,7 @@ const streamplace_store_1 = require("../streamplace-store/streamplace-store");
|
|
|
8
8
|
const context_1 = require("./context");
|
|
9
9
|
const poller_1 = tslib_1.__importDefault(require("./poller"));
|
|
10
10
|
function StreamplaceProvider({ children, url, oauthSession, }) {
|
|
11
|
+
console.log("session in provider is", oauthSession);
|
|
11
12
|
// todo: handle url changes?
|
|
12
13
|
const store = (0, react_1.useRef)((0, streamplace_store_1.makeStreamplaceStore)({ url })).current;
|
|
13
14
|
(0, react_1.useEffect)(() => {
|
|
@@ -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(),
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useSetHandle = exports.useHandle = exports.useDID = exports.useUrl = exports.makeStreamplaceStore = void 0;
|
|
3
|
+
exports.useEffectiveVolume = exports.useSetMuted = exports.useSetVolume = exports.useMuted = exports.useVolume = exports.useSetHandle = exports.useHandle = exports.useDID = exports.useUrl = exports.makeStreamplaceStore = void 0;
|
|
4
4
|
exports.getStreamplaceStoreFromContext = getStreamplaceStoreFromContext;
|
|
5
5
|
exports.useStreamplaceStore = useStreamplaceStore;
|
|
6
|
+
const tslib_1 = require("tslib");
|
|
6
7
|
const react_1 = require("react");
|
|
7
8
|
const zustand_1 = require("zustand");
|
|
9
|
+
const storage_1 = tslib_1.__importDefault(require("../storage"));
|
|
8
10
|
const context_1 = require("../streamplace-provider/context");
|
|
9
11
|
const makeStreamplaceStore = ({ url, }) => {
|
|
10
|
-
|
|
12
|
+
const VOLUME_STORAGE_KEY = "globalVolume";
|
|
13
|
+
const MUTED_STORAGE_KEY = "globalMuted";
|
|
14
|
+
const store = (0, zustand_1.createStore)()((set) => ({
|
|
11
15
|
url,
|
|
12
16
|
liveUsers: null,
|
|
13
17
|
setLiveUsers: (opts) => {
|
|
@@ -21,7 +25,60 @@ const makeStreamplaceStore = ({ url, }) => {
|
|
|
21
25
|
oauthSession: null,
|
|
22
26
|
handle: null,
|
|
23
27
|
chatProfile: null,
|
|
28
|
+
// Volume state - start with defaults
|
|
29
|
+
volume: 1.0,
|
|
30
|
+
muted: false,
|
|
31
|
+
setVolume: (volume) => {
|
|
32
|
+
// Ensure the value is finite and within bounds
|
|
33
|
+
if (!Number.isFinite(volume)) {
|
|
34
|
+
console.warn("Invalid volume value:", volume, "- using 1.0");
|
|
35
|
+
volume = 1.0;
|
|
36
|
+
}
|
|
37
|
+
const clampedVolume = Math.max(0, Math.min(1, volume));
|
|
38
|
+
set({ volume: clampedVolume });
|
|
39
|
+
// Auto-unmute if volume > 0
|
|
40
|
+
if (clampedVolume > 0) {
|
|
41
|
+
set({ muted: false });
|
|
42
|
+
storage_1.default.setItem(MUTED_STORAGE_KEY, "false").catch(console.error);
|
|
43
|
+
}
|
|
44
|
+
storage_1.default
|
|
45
|
+
.setItem(VOLUME_STORAGE_KEY, clampedVolume.toString())
|
|
46
|
+
.catch(console.error);
|
|
47
|
+
},
|
|
48
|
+
setMuted: (muted) => {
|
|
49
|
+
set({ muted });
|
|
50
|
+
storage_1.default.setItem(MUTED_STORAGE_KEY, muted.toString()).catch(console.error);
|
|
51
|
+
},
|
|
24
52
|
}));
|
|
53
|
+
// Load initial volume state from storage asynchronously
|
|
54
|
+
(async () => {
|
|
55
|
+
try {
|
|
56
|
+
const storedVolume = await storage_1.default.getItem(VOLUME_STORAGE_KEY);
|
|
57
|
+
const storedMuted = await storage_1.default.getItem(MUTED_STORAGE_KEY);
|
|
58
|
+
let initialVolume = 1.0;
|
|
59
|
+
let initialMuted = false;
|
|
60
|
+
if (storedVolume) {
|
|
61
|
+
const parsedVolume = parseFloat(storedVolume);
|
|
62
|
+
if (Number.isFinite(parsedVolume) &&
|
|
63
|
+
parsedVolume >= 0 &&
|
|
64
|
+
parsedVolume <= 1) {
|
|
65
|
+
initialVolume = parsedVolume;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (storedMuted) {
|
|
69
|
+
initialMuted = storedMuted === "true";
|
|
70
|
+
}
|
|
71
|
+
// Update the store with loaded values
|
|
72
|
+
store.setState({
|
|
73
|
+
volume: initialVolume,
|
|
74
|
+
muted: initialMuted,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
console.warn("Failed to load volume settings from storage:", e);
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
return store;
|
|
25
82
|
};
|
|
26
83
|
exports.makeStreamplaceStore = makeStreamplaceStore;
|
|
27
84
|
function getStreamplaceStoreFromContext() {
|
|
@@ -45,3 +102,19 @@ const useSetHandle = () => {
|
|
|
45
102
|
return (handle) => store.setState({ handle });
|
|
46
103
|
};
|
|
47
104
|
exports.useSetHandle = useSetHandle;
|
|
105
|
+
// Volume convenience hooks
|
|
106
|
+
const useVolume = () => useStreamplaceStore((x) => x.volume);
|
|
107
|
+
exports.useVolume = useVolume;
|
|
108
|
+
const useMuted = () => useStreamplaceStore((x) => x.muted);
|
|
109
|
+
exports.useMuted = useMuted;
|
|
110
|
+
const useSetVolume = () => useStreamplaceStore((x) => x.setVolume);
|
|
111
|
+
exports.useSetVolume = useSetVolume;
|
|
112
|
+
const useSetMuted = () => useStreamplaceStore((x) => x.setMuted);
|
|
113
|
+
exports.useSetMuted = useSetMuted;
|
|
114
|
+
// Composite hook for effective volume (0 if muted) - used by video components
|
|
115
|
+
const useEffectiveVolume = () => useStreamplaceStore((state) => {
|
|
116
|
+
const effectiveVolume = state.muted ? 0 : state.volume;
|
|
117
|
+
// Ensure we always return a finite number for HTMLMediaElement.volume
|
|
118
|
+
return Number.isFinite(effectiveVolume) ? effectiveVolume : 1.0;
|
|
119
|
+
});
|
|
120
|
+
exports.useEffectiveVolume = useEffectiveVolume;
|
|
@@ -6,9 +6,18 @@ const streamplace_1 = require("streamplace");
|
|
|
6
6
|
const _1 = require(".");
|
|
7
7
|
function usePDSAgent() {
|
|
8
8
|
const oauthSession = (0, _1.useStreamplaceStore)((state) => state.oauthSession);
|
|
9
|
+
// oauthsession is
|
|
10
|
+
// - undefined when loading
|
|
11
|
+
// - null when logged out, and
|
|
12
|
+
// - SessionManager when logged in
|
|
9
13
|
return (0, react_1.useMemo)(() => {
|
|
10
14
|
if (!oauthSession) {
|
|
11
|
-
|
|
15
|
+
if (oauthSession === undefined)
|
|
16
|
+
return null;
|
|
17
|
+
// TODO: change once we allow unauthed requests + profile indexing
|
|
18
|
+
// it's bluesky's AppView b/c otherwise we'd have goosewithpipe.jpg
|
|
19
|
+
// showing up everywhere
|
|
20
|
+
return new streamplace_1.StreamplaceAgent("https://public.api.bsky.app"); // nodeUrl);
|
|
12
21
|
}
|
|
13
22
|
return new streamplace_1.StreamplaceAgent(oauthSession);
|
|
14
23
|
}, [oauthSession]);
|
|
Binary file
|