@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamplace/components",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.26",
|
|
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",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@rn-primitives/slider": "^1.2.0",
|
|
30
30
|
"class-variance-authority": "^0.6.1",
|
|
31
31
|
"expo-keep-awake": "^14.0.0",
|
|
32
|
+
"expo-sqlite": "~15.2.12",
|
|
32
33
|
"expo-video": "^2.0.0",
|
|
33
34
|
"hls.js": "^1.5.17",
|
|
34
35
|
"lucide-react-native": "^0.514.0",
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
"react-native-svg": "^15.0.0",
|
|
41
42
|
"react-native-webrtc": "git+https://github.com/streamplace/react-native-webrtc.git#6b8472a771ac47f89217d327058a8a4124a6ae56",
|
|
42
43
|
"react-use-websocket": "^4.13.0",
|
|
43
|
-
"streamplace": "0.7.
|
|
44
|
+
"streamplace": "0.7.25",
|
|
44
45
|
"viem": "^2.21.44",
|
|
45
46
|
"zustand": "^5.0.5"
|
|
46
47
|
},
|
|
@@ -52,5 +53,5 @@
|
|
|
52
53
|
"start": "tsc --watch --preserveWatchOutput",
|
|
53
54
|
"prepare": "tsc"
|
|
54
55
|
},
|
|
55
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "4dcda25a5e66a2c1a9f412bee2e09c4dd528a939"
|
|
56
57
|
}
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
py,
|
|
28
28
|
w,
|
|
29
29
|
} from "../../lib/theme/atoms";
|
|
30
|
-
import { usePDSAgent } from "../../streamplace-store/xrpc";
|
|
31
30
|
import { Textarea } from "../ui/textarea";
|
|
32
31
|
import { RenderChatMessage } from "./chat-message";
|
|
33
32
|
import { EmojiData, EmojiSuggestions } from "./emoji-suggestions";
|
|
@@ -70,16 +69,6 @@ export function ChatBox({
|
|
|
70
69
|
const setReplyToMessage = useSetReplyToMessage();
|
|
71
70
|
const textAreaRef = useRef<TextInput>(null);
|
|
72
71
|
|
|
73
|
-
// are we logged in?
|
|
74
|
-
|
|
75
|
-
let agent = usePDSAgent();
|
|
76
|
-
|
|
77
|
-
if (!agent?.did) {
|
|
78
|
-
<View style={[layout.flex.row, layout.flex.alignCenter, gap.all[2]]}>
|
|
79
|
-
<Text>Log in to chat.</Text>
|
|
80
|
-
</View>;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
72
|
const authors = useMemo(() => {
|
|
84
73
|
if (!chat) return null;
|
|
85
74
|
return chat.reduce((acc, msg) => {
|
|
@@ -66,6 +66,9 @@ const segmentedObject = (
|
|
|
66
66
|
{obj.text}
|
|
67
67
|
</Text>
|
|
68
68
|
);
|
|
69
|
+
} else {
|
|
70
|
+
// render as normal text if we don't recognize the facet type
|
|
71
|
+
return <Text key={`unknown-facet-${index}`}>{obj.text}</Text>;
|
|
69
72
|
}
|
|
70
73
|
} else {
|
|
71
74
|
return <Text key={`text-${index}`}>{obj.text}</Text>;
|
|
@@ -105,9 +108,10 @@ export const RenderChatMessage = memo(
|
|
|
105
108
|
hour12: false,
|
|
106
109
|
});
|
|
107
110
|
}, []);
|
|
111
|
+
const replyTo = (item.replyTo as ChatMessageViewHydrated) || null;
|
|
108
112
|
return (
|
|
109
113
|
<>
|
|
110
|
-
{
|
|
114
|
+
{replyTo && showReply && (
|
|
111
115
|
<View
|
|
112
116
|
style={[
|
|
113
117
|
gap.all[2],
|
|
@@ -130,11 +134,11 @@ export const RenderChatMessage = memo(
|
|
|
130
134
|
>
|
|
131
135
|
<Text
|
|
132
136
|
style={{
|
|
133
|
-
color: getRgbColor(
|
|
137
|
+
color: getRgbColor(replyTo.chatProfile?.color),
|
|
134
138
|
fontWeight: "thin",
|
|
135
139
|
}}
|
|
136
140
|
>
|
|
137
|
-
@{(
|
|
141
|
+
@{(replyTo.author as any).handle}
|
|
138
142
|
</Text>{" "}
|
|
139
143
|
<Text
|
|
140
144
|
style={{
|
|
@@ -142,7 +146,7 @@ export const RenderChatMessage = memo(
|
|
|
142
146
|
fontStyle: "italic",
|
|
143
147
|
}}
|
|
144
148
|
>
|
|
145
|
-
{
|
|
149
|
+
{replyTo.record.text}
|
|
146
150
|
</Text>
|
|
147
151
|
</Text>
|
|
148
152
|
</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>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Play } from "lucide-react-native";
|
|
2
2
|
import { Pressable } from "react-native";
|
|
3
|
-
import { View, layout, usePlayerStore } from "../../..";
|
|
3
|
+
import { View, layout, usePlayerStore, useSetMuted } from "../../..";
|
|
4
4
|
import { h, p, w } from "../../../ui";
|
|
5
5
|
|
|
6
6
|
export function AutoplayButton() {
|
|
7
7
|
const autoplayFailed = usePlayerStore((x) => x.autoplayFailed);
|
|
8
8
|
const setAutoplayFailed = usePlayerStore((x) => x.setAutoplayFailed);
|
|
9
|
-
const setMuted =
|
|
9
|
+
const setMuted = useSetMuted();
|
|
10
10
|
const setMuteWasForced = usePlayerStore((x) => x.setMuteWasForced);
|
|
11
11
|
const setUserInteraction = usePlayerStore((x) => x.setUserInteraction);
|
|
12
12
|
const videoRef = usePlayerStore((x) => x.videoRef);
|
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
PlayerProtocol,
|
|
15
15
|
PlayerStatus,
|
|
16
16
|
Text,
|
|
17
|
+
useEffectiveVolume,
|
|
17
18
|
usePlayerStore as useIngestPlayerStore,
|
|
19
|
+
useMuted,
|
|
18
20
|
usePlayerStore,
|
|
19
21
|
useStreamplaceStore,
|
|
20
22
|
View,
|
|
@@ -71,8 +73,8 @@ export function NativeVideo(props?: {
|
|
|
71
73
|
const src = usePlayerStore((x) => x.src);
|
|
72
74
|
const { url } = srcToUrl({ src: src, selectedRendition }, protocol);
|
|
73
75
|
const setStatus = usePlayerStore((x) => x.setStatus);
|
|
74
|
-
const muted =
|
|
75
|
-
const volume =
|
|
76
|
+
const muted = useMuted();
|
|
77
|
+
const volume = useEffectiveVolume();
|
|
76
78
|
const setFullscreen = usePlayerStore((x) => x.setFullscreen);
|
|
77
79
|
const fullscreen = usePlayerStore((x) => x.fullscreen);
|
|
78
80
|
const playerEvent = usePlayerStore((x) => x.playerEvent);
|
|
@@ -211,8 +213,8 @@ export function NativeWHEP(props?: {
|
|
|
211
213
|
}, []);
|
|
212
214
|
|
|
213
215
|
const setStatus = usePlayerStore((x) => x.setStatus);
|
|
214
|
-
const muted =
|
|
215
|
-
const volume =
|
|
216
|
+
const muted = useMuted();
|
|
217
|
+
const volume = useEffectiveVolume();
|
|
216
218
|
|
|
217
219
|
useEffect(() => {
|
|
218
220
|
if (stuck && status === PlayerStatus.PLAYING) {
|
|
@@ -4,7 +4,10 @@ import {
|
|
|
4
4
|
IngestMediaSource,
|
|
5
5
|
PlayerProtocol,
|
|
6
6
|
PlayerStatus,
|
|
7
|
+
useEffectiveVolume,
|
|
8
|
+
useMuted,
|
|
7
9
|
usePlayerStore,
|
|
10
|
+
useSetMuted,
|
|
8
11
|
useStreamplaceStore,
|
|
9
12
|
} from "../..";
|
|
10
13
|
import { borderRadius, colors, mt } from "../../lib/theme/atoms";
|
|
@@ -135,11 +138,11 @@ const VideoElement = forwardRef<
|
|
|
135
138
|
const x = usePlayerStore((x) => x);
|
|
136
139
|
const url = useStreamplaceStore((x) => x.url);
|
|
137
140
|
const playerEvent = usePlayerStore((x) => x.playerEvent);
|
|
138
|
-
const setMuted = usePlayerStore((x) => x.setMuted);
|
|
139
141
|
const setMuteWasForced = usePlayerStore((x) => x.setMuteWasForced);
|
|
140
|
-
const muted = usePlayerStore((x) => x.muted);
|
|
141
142
|
const ingest = usePlayerStore((x) => x.ingestConnectionState !== null);
|
|
142
|
-
const volume =
|
|
143
|
+
const volume = useEffectiveVolume();
|
|
144
|
+
const muted = useMuted();
|
|
145
|
+
const setMuted = useSetMuted();
|
|
143
146
|
const setStatus = usePlayerStore((x) => x.setStatus);
|
|
144
147
|
const setUserInteraction = usePlayerStore((x) => x.setUserInteraction);
|
|
145
148
|
const setVideoRef = usePlayerStore((x) => x.setVideoRef);
|
|
@@ -7,6 +7,7 @@ export interface WebRTCDiagnostics {
|
|
|
7
7
|
rtcSessionDescription: boolean;
|
|
8
8
|
getUserMedia: boolean;
|
|
9
9
|
getDisplayMedia: boolean;
|
|
10
|
+
isHwH264Supported: boolean;
|
|
10
11
|
errors: string[];
|
|
11
12
|
warnings: string[];
|
|
12
13
|
}
|
|
@@ -19,6 +20,7 @@ export function useWebRTCDiagnostics(): WebRTCDiagnostics {
|
|
|
19
20
|
rtcSessionDescription: false,
|
|
20
21
|
getUserMedia: false,
|
|
21
22
|
getDisplayMedia: false,
|
|
23
|
+
isHwH264Supported: false,
|
|
22
24
|
errors: [],
|
|
23
25
|
warnings: [],
|
|
24
26
|
});
|
|
@@ -27,6 +29,23 @@ export function useWebRTCDiagnostics(): WebRTCDiagnostics {
|
|
|
27
29
|
const errors: string[] = [];
|
|
28
30
|
const warnings: string[] = [];
|
|
29
31
|
|
|
32
|
+
const checkH264Support = async (): Promise<boolean> => {
|
|
33
|
+
try {
|
|
34
|
+
const pc = new RTCPeerConnection();
|
|
35
|
+
const offer = await pc.createOffer();
|
|
36
|
+
pc.close();
|
|
37
|
+
|
|
38
|
+
if (offer.sdp) {
|
|
39
|
+
const h264Match = offer.sdp.search(/rtpmap:([0-9]+) H264/g);
|
|
40
|
+
return h264Match !== -1;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.warn("Failed to check H.264 support:", error);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
30
49
|
// Check if we're in a browser environment
|
|
31
50
|
if (typeof window === "undefined") {
|
|
32
51
|
errors.push("Running in non-browser environment");
|
|
@@ -37,6 +56,7 @@ export function useWebRTCDiagnostics(): WebRTCDiagnostics {
|
|
|
37
56
|
rtcSessionDescription: false,
|
|
38
57
|
getUserMedia: false,
|
|
39
58
|
getDisplayMedia: false,
|
|
59
|
+
isHwH264Supported: false,
|
|
40
60
|
errors,
|
|
41
61
|
warnings,
|
|
42
62
|
});
|
|
@@ -105,22 +125,45 @@ export function useWebRTCDiagnostics(): WebRTCDiagnostics {
|
|
|
105
125
|
|
|
106
126
|
const browserSupport = rtcPeerConnection && rtcSessionDescription;
|
|
107
127
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
128
|
+
// Check H.264 support asynchronously
|
|
129
|
+
if (rtcPeerConnection) {
|
|
130
|
+
checkH264Support().then((isHwH264Supported) => {
|
|
131
|
+
if (!isHwH264Supported) {
|
|
132
|
+
warnings.push(
|
|
133
|
+
"H.264 hardware acceleration is not supported\n In Firefox, try enabling 'media.webrtc.hw.h264.enabled' in about:config",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
setDiagnostics({
|
|
137
|
+
done: true,
|
|
138
|
+
browserSupport,
|
|
139
|
+
rtcPeerConnection,
|
|
140
|
+
rtcSessionDescription,
|
|
141
|
+
getUserMedia,
|
|
142
|
+
getDisplayMedia,
|
|
143
|
+
isHwH264Supported,
|
|
144
|
+
errors,
|
|
145
|
+
warnings,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
} else {
|
|
149
|
+
setDiagnostics({
|
|
150
|
+
done: true,
|
|
151
|
+
browserSupport,
|
|
152
|
+
rtcPeerConnection,
|
|
153
|
+
rtcSessionDescription,
|
|
154
|
+
getUserMedia,
|
|
155
|
+
getDisplayMedia,
|
|
156
|
+
isHwH264Supported: false,
|
|
157
|
+
errors,
|
|
158
|
+
warnings,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
118
161
|
}, []);
|
|
119
162
|
|
|
120
163
|
return diagnostics;
|
|
121
164
|
}
|
|
122
165
|
|
|
123
|
-
export function logWebRTCDiagnostics() {
|
|
166
|
+
export async function logWebRTCDiagnostics() {
|
|
124
167
|
console.group("WebRTC Diagnostics");
|
|
125
168
|
|
|
126
169
|
// Log browser support
|
|
@@ -133,17 +176,32 @@ export function logWebRTCDiagnostics() {
|
|
|
133
176
|
console.log("User Agent:", navigator.userAgent);
|
|
134
177
|
console.log("Protocol:", location.protocol);
|
|
135
178
|
console.log("Host:", location.hostname);
|
|
136
|
-
|
|
137
|
-
// Test basic WebRTC functionality
|
|
179
|
+
console.groupEnd();
|
|
138
180
|
if (window.RTCPeerConnection) {
|
|
139
181
|
try {
|
|
140
182
|
const pc = new RTCPeerConnection();
|
|
141
|
-
|
|
183
|
+
// Check H.264 support
|
|
184
|
+
try {
|
|
185
|
+
const offer = await pc.createOffer({ offerToReceiveVideo: true });
|
|
186
|
+
const isHwH264Supported = offer.sdp
|
|
187
|
+
? offer.sdp.search(/rtpmap:([0-9]+) H264/g) !== -1
|
|
188
|
+
: false;
|
|
189
|
+
console.group("WebRTC Peer Connection Test");
|
|
190
|
+
console.log("RTCPeerConnection creation: ✓ Success");
|
|
191
|
+
console.log(
|
|
192
|
+
"H.264 support:",
|
|
193
|
+
isHwH264Supported ? "✓ Supported" : "✗ Not supported",
|
|
194
|
+
);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.group("WebRTC Peer Connection Test");
|
|
197
|
+
console.error("H.264 check failed:", error);
|
|
198
|
+
}
|
|
199
|
+
|
|
142
200
|
pc.close();
|
|
143
201
|
} catch (error) {
|
|
202
|
+
console.group("WebRTC Peer Connection Test");
|
|
144
203
|
console.error("RTCPeerConnection creation: ✗ Failed", error);
|
|
145
204
|
}
|
|
146
205
|
}
|
|
147
|
-
|
|
148
206
|
console.groupEnd();
|
|
149
207
|
}
|
package/src/index.tsx
CHANGED
|
@@ -75,7 +75,21 @@ export const useCreateChatMessage = () => {
|
|
|
75
75
|
const rt = new RichText({ text: msg.text });
|
|
76
76
|
await rt.detectFacets(pdsAgent);
|
|
77
77
|
|
|
78
|
+
// filter out any facets that aren't in the allowed list
|
|
79
|
+
rt.facets = rt.facets?.filter((facet) => {
|
|
80
|
+
return (
|
|
81
|
+
// if all features are in the allowed list
|
|
82
|
+
facet.features.every((feature) =>
|
|
83
|
+
[
|
|
84
|
+
"app.bsky.richtext.facet#link",
|
|
85
|
+
"app.bsky.richtext.facet#mention",
|
|
86
|
+
].includes(feature.$type),
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
78
91
|
const record: PlaceStreamChatMessage.Record = {
|
|
92
|
+
$type: "place.stream.chat.message",
|
|
79
93
|
text: msg.text,
|
|
80
94
|
createdAt: new Date().toISOString(),
|
|
81
95
|
streamer: streamerProfile.did,
|
|
@@ -295,6 +309,7 @@ export const reduceChatIncremental = (
|
|
|
295
309
|
processedMessage = {
|
|
296
310
|
...message,
|
|
297
311
|
replyTo: {
|
|
312
|
+
$type: "place.stream.chat.defs#messageView",
|
|
298
313
|
cid: parentMsg.cid,
|
|
299
314
|
uri: parentMsg.uri,
|
|
300
315
|
author: parentMsg.author,
|
|
@@ -20,7 +20,7 @@ export const handleWebSocketMessages = (
|
|
|
20
20
|
state: LivestreamState,
|
|
21
21
|
messages: any[],
|
|
22
22
|
): LivestreamState => {
|
|
23
|
-
for (
|
|
23
|
+
for (let message of messages) {
|
|
24
24
|
if (PlaceStreamLivestream.isLivestreamView(message)) {
|
|
25
25
|
const newLivestream = message as LivestreamViewHydrated;
|
|
26
26
|
const oldLivestream = state.livestream;
|
|
@@ -41,11 +41,13 @@ export const handleWebSocketMessages = (
|
|
|
41
41
|
livestream: newLivestream,
|
|
42
42
|
};
|
|
43
43
|
} else if (PlaceStreamLivestream.isViewerCount(message)) {
|
|
44
|
+
message = message as PlaceStreamLivestream.ViewerCount;
|
|
44
45
|
state = {
|
|
45
46
|
...state,
|
|
46
47
|
viewers: message.count,
|
|
47
48
|
};
|
|
48
49
|
} else if (PlaceStreamChatDefs.isMessageView(message)) {
|
|
50
|
+
message = message as PlaceStreamChatDefs.MessageView;
|
|
49
51
|
// Explicitly map MessageView to MessageViewHydrated
|
|
50
52
|
const hydrated: ChatMessageViewHydrated = {
|
|
51
53
|
uri: message.uri,
|
|
@@ -74,6 +76,7 @@ export const handleWebSocketMessages = (
|
|
|
74
76
|
const block = message as PlaceStreamDefs.BlockView;
|
|
75
77
|
state = reduceChat(state, [], [block], []);
|
|
76
78
|
} else if (PlaceStreamDefs.isRenditions(message)) {
|
|
79
|
+
message = message as PlaceStreamDefs.Renditions;
|
|
77
80
|
state = {
|
|
78
81
|
...state,
|
|
79
82
|
renditions: message.renditions,
|
|
@@ -69,18 +69,6 @@ export interface PlayerState {
|
|
|
69
69
|
/** Function to set the ingestStarted timestamp */
|
|
70
70
|
setIngestStarted: (timestamp: number | null) => void;
|
|
71
71
|
|
|
72
|
-
/** Player muted state */
|
|
73
|
-
muted: boolean;
|
|
74
|
-
|
|
75
|
-
/** Function to set the muted state */
|
|
76
|
-
setMuted: (isMuted: boolean) => void;
|
|
77
|
-
|
|
78
|
-
/** Player volume level (0.0 to 1.0) */
|
|
79
|
-
volume: number;
|
|
80
|
-
|
|
81
|
-
/** Function to set the volume level */
|
|
82
|
-
setVolume: (volume: number) => void;
|
|
83
|
-
|
|
84
72
|
/** Player fullscreen state */
|
|
85
73
|
fullscreen: boolean;
|
|
86
74
|
|
|
@@ -52,14 +52,6 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
52
52
|
setIngestStarted: (timestamp: number | null) =>
|
|
53
53
|
set(() => ({ ingestStarted: timestamp })),
|
|
54
54
|
|
|
55
|
-
muted: false,
|
|
56
|
-
setMuted: (isMuted: boolean) =>
|
|
57
|
-
set(() => ({ muted: isMuted, muteWasForced: false })),
|
|
58
|
-
|
|
59
|
-
volume: 1.0,
|
|
60
|
-
setVolume: (volume: number) =>
|
|
61
|
-
set(() => ({ volume, muteWasForced: false })),
|
|
62
|
-
|
|
63
55
|
fullscreen: false,
|
|
64
56
|
setFullscreen: (isFullscreen: boolean) =>
|
|
65
57
|
set(() => ({ fullscreen: isFullscreen })),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type Cont = () => void;
|
|
2
|
+
|
|
3
|
+
export class Lock {
|
|
4
|
+
private readonly queue: Cont[] = [];
|
|
5
|
+
private acquired = false;
|
|
6
|
+
|
|
7
|
+
public async acquire(): Promise<void> {
|
|
8
|
+
if (!this.acquired) {
|
|
9
|
+
this.acquired = true;
|
|
10
|
+
} else {
|
|
11
|
+
return new Promise<void>((resolve, _) => {
|
|
12
|
+
this.queue.push(resolve);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public async release(): Promise<void> {
|
|
18
|
+
if (this.queue.length === 0 && this.acquired) {
|
|
19
|
+
this.acquired = false;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const continuation = this.queue.shift();
|
|
24
|
+
return new Promise((res: Cont) => {
|
|
25
|
+
continuation!();
|
|
26
|
+
res();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async critical<T>(task: () => Promise<T>) {
|
|
31
|
+
await this.acquire();
|
|
32
|
+
try {
|
|
33
|
+
return await task();
|
|
34
|
+
} finally {
|
|
35
|
+
await this.release();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Storage from "expo-sqlite/kv-store";
|
|
2
|
+
import { Lock } from "./lock";
|
|
3
|
+
import { AQStorage } from "./storage.shared";
|
|
4
|
+
|
|
5
|
+
// Needed because concurrent calls seem to return with a locked database
|
|
6
|
+
const lock = new Lock();
|
|
7
|
+
|
|
8
|
+
export default class NativeStorage implements AQStorage {
|
|
9
|
+
async getItem(key: string): Promise<string | null> {
|
|
10
|
+
return lock.critical(async () => {
|
|
11
|
+
try {
|
|
12
|
+
const value = await Storage.getItem(key);
|
|
13
|
+
return value ?? null;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
console.error(`error in NativeStorage.getItem: ${e}`);
|
|
16
|
+
throw e;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async setItem(key: string, value: string): Promise<void> {
|
|
22
|
+
return lock.critical(async () => {
|
|
23
|
+
try {
|
|
24
|
+
await Storage.setItem(key, value);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.error(`error in NativeStorage.setItem: ${e}`);
|
|
27
|
+
throw e;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async removeItem(key: string): Promise<void> {
|
|
33
|
+
return lock.critical(async () => {
|
|
34
|
+
try {
|
|
35
|
+
await Storage.removeItem(key);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error(`error in NativeStorage.removeItem: ${e}`);
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AQStorage } from "./storage.shared";
|
|
2
|
+
|
|
3
|
+
export default class WebStorage implements AQStorage {
|
|
4
|
+
async getItem(key: string): Promise<string | null> {
|
|
5
|
+
return localStorage.getItem(key);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async setItem(key: string, value: string): Promise<void> {
|
|
9
|
+
localStorage.setItem(key, value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async removeItem(key: string): Promise<void> {
|
|
13
|
+
localStorage.removeItem(key);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -11,8 +11,9 @@ export function StreamplaceProvider({
|
|
|
11
11
|
}: {
|
|
12
12
|
children: React.ReactNode;
|
|
13
13
|
url: string;
|
|
14
|
-
oauthSession?: SessionManager;
|
|
14
|
+
oauthSession?: SessionManager | null;
|
|
15
15
|
}) {
|
|
16
|
+
console.log("session in provider is", oauthSession);
|
|
16
17
|
// todo: handle url changes?
|
|
17
18
|
const store = useRef(makeStreamplaceStore({ url })).current;
|
|
18
19
|
|
|
@@ -248,6 +248,7 @@ export function useCreateStreamRecord() {
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
const record: PlaceStreamLivestream.Record = {
|
|
251
|
+
$type: "place.stream.livestream",
|
|
251
252
|
title: title,
|
|
252
253
|
url: finalUrl,
|
|
253
254
|
createdAt: new Date().toISOString(),
|
|
@@ -313,6 +314,7 @@ export function useUpdateStreamRecord(customUrl: string | null = null) {
|
|
|
313
314
|
}
|
|
314
315
|
|
|
315
316
|
const record: PlaceStreamLivestream.Record = {
|
|
317
|
+
$type: "place.stream.livestream",
|
|
316
318
|
title: title,
|
|
317
319
|
url: finalUrl,
|
|
318
320
|
createdAt: new Date().toISOString(),
|