@streamplace/components 0.7.19 → 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.
- package/dist/components/chat/chat-box.js +5 -0
- 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/livestream-store/chat.js +23 -1
- package/dist/player-store/player-provider.js +2 -1
- package/dist/player-store/player-store.js +2 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/package.json +3 -3
- package/src/components/chat/chat-box.tsx +3 -0
- 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/livestream-store/chat.tsx +22 -0
- 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/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());
|
|
@@ -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%",
|
|
@@ -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");
|
|
@@ -90,6 +90,28 @@ const useCreateChatMessage = () => {
|
|
|
90
90
|
};
|
|
91
91
|
};
|
|
92
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;
|
|
93
115
|
const buildSortedChatList = (chatIndex, existingChatList, newMessages, removedKeys) => {
|
|
94
116
|
const sortedKeys = Object.keys(chatIndex).sort((a, b) => {
|
|
95
117
|
const aTime = parseInt(a.split("-")[0], 10);
|
|
@@ -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,
|
|
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.21",
|
|
4
4
|
"description": "Streamplace React (Native) Components",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "src/index.tsx",
|
|
@@ -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.21",
|
|
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": "e930332a9465e0ffd9a78a01ea39b134cd78e49e"
|
|
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"]>());
|
|
@@ -10,6 +10,7 @@ import { usePDSAgent } from "../../streamplace-store/xrpc";
|
|
|
10
10
|
|
|
11
11
|
import { Linking } from "react-native";
|
|
12
12
|
import { ChatMessageViewHydrated } from "streamplace";
|
|
13
|
+
import { useDeleteChatMessage } from "../../livestream-store";
|
|
13
14
|
import { useStreamplaceStore } from "../../streamplace-store";
|
|
14
15
|
import {
|
|
15
16
|
atoms,
|
|
@@ -172,11 +173,16 @@ export const ModView = forwardRef<ModViewRef, ModViewProps>(() => {
|
|
|
172
173
|
>
|
|
173
174
|
<Text color="primary">View user on {BSKY_FRONTEND_DOMAIN}</Text>
|
|
174
175
|
</DropdownMenuItem>
|
|
175
|
-
|
|
176
|
-
message={message}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
{message.author.did === agent?.did && (
|
|
177
|
+
<DeleteButton message={message} />
|
|
178
|
+
)}
|
|
179
|
+
{message.author.did !== agent?.did && (
|
|
180
|
+
<ReportButton
|
|
181
|
+
message={message}
|
|
182
|
+
setReportModalOpen={setReportModalOpen}
|
|
183
|
+
setReportSubject={setReportSubject}
|
|
184
|
+
/>
|
|
185
|
+
)}
|
|
180
186
|
</DropdownMenuGroup>
|
|
181
187
|
</>
|
|
182
188
|
)}
|
|
@@ -185,6 +191,34 @@ export const ModView = forwardRef<ModViewRef, ModViewProps>(() => {
|
|
|
185
191
|
);
|
|
186
192
|
});
|
|
187
193
|
|
|
194
|
+
export function DeleteButton({
|
|
195
|
+
message,
|
|
196
|
+
}: {
|
|
197
|
+
message: ChatMessageViewHydrated;
|
|
198
|
+
}) {
|
|
199
|
+
const deleteChatMessage = useDeleteChatMessage();
|
|
200
|
+
const [confirming, setConfirming] = useState(false);
|
|
201
|
+
const { onOpenChange } = useRootContext();
|
|
202
|
+
return (
|
|
203
|
+
<DropdownMenuItem
|
|
204
|
+
onPress={() => {
|
|
205
|
+
if (!message) return;
|
|
206
|
+
if (!confirming) {
|
|
207
|
+
setConfirming(true);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
deleteChatMessage(message.uri).then(() => {
|
|
211
|
+
onOpenChange?.(false);
|
|
212
|
+
});
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
<Text color="destructive">
|
|
216
|
+
{confirming ? "Are you sure?" : "Delete message"}
|
|
217
|
+
</Text>
|
|
218
|
+
</DropdownMenuItem>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
188
222
|
export function ReportButton({
|
|
189
223
|
message,
|
|
190
224
|
setReportModalOpen,
|
|
@@ -16,12 +16,14 @@ export function Fullscreen(props: {
|
|
|
16
16
|
const fullscreen = usePlayerStore((x) => x.fullscreen, playerId);
|
|
17
17
|
const setFullscreen = usePlayerStore((x) => x.setFullscreen, playerId);
|
|
18
18
|
const setSrc = usePlayerStore((x) => x.setSrc);
|
|
19
|
+
const setAutoplayFailed = usePlayerStore((x) => x.setAutoplayFailed);
|
|
19
20
|
|
|
20
21
|
const divRef = useRef<RNView>(null);
|
|
21
22
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
|
22
23
|
|
|
23
24
|
useEffect(() => {
|
|
24
25
|
setSrc(props.src);
|
|
26
|
+
setAutoplayFailed(false);
|
|
25
27
|
}, [props.src]);
|
|
26
28
|
|
|
27
29
|
useEffect(() => {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Play } from "lucide-react-native";
|
|
2
|
+
import { Pressable } from "react-native";
|
|
3
|
+
import { View, layout, usePlayerStore } from "../../..";
|
|
4
|
+
import { h, p, w } from "../../../ui";
|
|
5
|
+
|
|
6
|
+
export function AutoplayButton() {
|
|
7
|
+
const autoplayFailed = usePlayerStore((x) => x.autoplayFailed);
|
|
8
|
+
const setAutoplayFailed = usePlayerStore((x) => x.setAutoplayFailed);
|
|
9
|
+
const setMuted = usePlayerStore((x) => x.setMuted);
|
|
10
|
+
const setMuteWasForced = usePlayerStore((x) => x.setMuteWasForced);
|
|
11
|
+
const setUserInteraction = usePlayerStore((x) => x.setUserInteraction);
|
|
12
|
+
const videoRef = usePlayerStore((x) => x.videoRef);
|
|
13
|
+
|
|
14
|
+
const handlePlayButtonPress = () => {
|
|
15
|
+
if (videoRef && typeof videoRef === "object" && videoRef.current) {
|
|
16
|
+
videoRef.current
|
|
17
|
+
.play()
|
|
18
|
+
.then(() => {
|
|
19
|
+
setAutoplayFailed(false);
|
|
20
|
+
setUserInteraction();
|
|
21
|
+
})
|
|
22
|
+
.catch((err) => {
|
|
23
|
+
console.error("Manual play failed", err);
|
|
24
|
+
if (err.name === "NotAllowedError") {
|
|
25
|
+
setMuted(true);
|
|
26
|
+
videoRef.current!.muted = true;
|
|
27
|
+
videoRef
|
|
28
|
+
.current!.play()
|
|
29
|
+
.then(() => {
|
|
30
|
+
setAutoplayFailed(false);
|
|
31
|
+
setMuteWasForced(true);
|
|
32
|
+
setUserInteraction();
|
|
33
|
+
})
|
|
34
|
+
.catch((err) => {
|
|
35
|
+
console.error("Manual muted play also failed", err);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (!autoplayFailed) return null;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<View
|
|
46
|
+
style={[
|
|
47
|
+
layout.position.absolute,
|
|
48
|
+
layout.flex.center,
|
|
49
|
+
h.percent[100],
|
|
50
|
+
w.percent[100],
|
|
51
|
+
]}
|
|
52
|
+
>
|
|
53
|
+
<Pressable
|
|
54
|
+
onPress={handlePlayButtonPress}
|
|
55
|
+
style={[
|
|
56
|
+
{
|
|
57
|
+
flexDirection: "column",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
justifyContent: "center",
|
|
60
|
+
gap: 8,
|
|
61
|
+
},
|
|
62
|
+
]}
|
|
63
|
+
>
|
|
64
|
+
<View
|
|
65
|
+
style={[
|
|
66
|
+
p[4],
|
|
67
|
+
{
|
|
68
|
+
backgroundColor: "rgba(200,200,255, 0.1)",
|
|
69
|
+
borderRadius: 999,
|
|
70
|
+
borderWidth: 2,
|
|
71
|
+
borderColor: "rgba(200,200,255, 0.45)",
|
|
72
|
+
boxShadow: "0 0px 4px rgba(0, 0, 0, 1)",
|
|
73
|
+
shadowColor: "rgba(0, 0, 0, 1)",
|
|
74
|
+
},
|
|
75
|
+
]}
|
|
76
|
+
>
|
|
77
|
+
<Play
|
|
78
|
+
size="48"
|
|
79
|
+
color="rgba(120,120,120,0.3)"
|
|
80
|
+
fill="rgba(200,200,255,1)"
|
|
81
|
+
/>
|
|
82
|
+
</View>
|
|
83
|
+
</Pressable>
|
|
84
|
+
</View>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -154,6 +154,7 @@ const VideoElement = forwardRef<
|
|
|
154
154
|
playerEvent(url, now.toISOString(), evType, {});
|
|
155
155
|
};
|
|
156
156
|
const [firstAttempt, setFirstAttempt] = useState(true);
|
|
157
|
+
const setAutoplayFailed = usePlayerStore((x) => x.setAutoplayFailed);
|
|
157
158
|
|
|
158
159
|
const localVideoRef = props.videoRef ?? useRef<HTMLVideoElement | null>(null);
|
|
159
160
|
|
|
@@ -206,13 +207,22 @@ const VideoElement = forwardRef<
|
|
|
206
207
|
})
|
|
207
208
|
.catch((err) => {
|
|
208
209
|
console.error("Muted play also failed", err);
|
|
210
|
+
setAutoplayFailed(true);
|
|
209
211
|
});
|
|
210
212
|
}
|
|
213
|
+
} else {
|
|
214
|
+
// For other errors (not NotAllowedError), also show play button
|
|
215
|
+
setAutoplayFailed(true);
|
|
211
216
|
}
|
|
212
217
|
});
|
|
213
218
|
}
|
|
214
219
|
};
|
|
215
220
|
|
|
221
|
+
const handlePlaying = (e) => {
|
|
222
|
+
setAutoplayFailed(false);
|
|
223
|
+
event("playing")(e);
|
|
224
|
+
};
|
|
225
|
+
|
|
216
226
|
useEffect(() => {
|
|
217
227
|
return () => {
|
|
218
228
|
setStatus(PlayerStatus.START);
|
|
@@ -275,7 +285,7 @@ const VideoElement = forwardRef<
|
|
|
275
285
|
onLoadStart={event("loadstart")}
|
|
276
286
|
onPause={event("pause")}
|
|
277
287
|
onPlay={event("play")}
|
|
278
|
-
onPlaying={
|
|
288
|
+
onPlaying={handlePlaying}
|
|
279
289
|
onRateChange={event("ratechange")}
|
|
280
290
|
onSeeked={event("seeked")}
|
|
281
291
|
onSeeking={event("seeking")}
|
|
@@ -119,6 +119,28 @@ export const useCreateChatMessage = () => {
|
|
|
119
119
|
};
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
+
export const useDeleteChatMessage = () => {
|
|
123
|
+
const pdsAgent = usePDSAgent();
|
|
124
|
+
if (!pdsAgent) {
|
|
125
|
+
throw new Error("No PDS agent found");
|
|
126
|
+
}
|
|
127
|
+
const userDID = useDID();
|
|
128
|
+
if (!userDID) {
|
|
129
|
+
throw new Error("No user DID found");
|
|
130
|
+
}
|
|
131
|
+
return async (uri: string) => {
|
|
132
|
+
const rkey = uri.split("/").pop();
|
|
133
|
+
if (!rkey) {
|
|
134
|
+
throw new Error("No rkey found");
|
|
135
|
+
}
|
|
136
|
+
return await pdsAgent.com.atproto.repo.deleteRecord({
|
|
137
|
+
repo: userDID,
|
|
138
|
+
collection: "place.stream.chat.message",
|
|
139
|
+
rkey: rkey,
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
122
144
|
const buildSortedChatList = (
|
|
123
145
|
chatIndex: { [key: string]: ChatMessageViewHydrated },
|
|
124
146
|
existingChatList: ChatMessageViewHydrated[],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
1
2
|
import React, { useCallback, useMemo, useState } from "react";
|
|
2
3
|
import { StoreApi } from "zustand";
|
|
3
4
|
import { PlayerContext } from "./context";
|
|
@@ -33,7 +34,7 @@ export const PlayerProvider: React.FC<PlayerProviderProps> = ({
|
|
|
33
34
|
);
|
|
34
35
|
|
|
35
36
|
const createPlayer = useCallback((id?: string) => {
|
|
36
|
-
const playerId = id ||
|
|
37
|
+
const playerId = id || randomUUID();
|
|
37
38
|
const playerStore = makePlayerStore(playerId);
|
|
38
39
|
|
|
39
40
|
setPlayers((prev) => ({
|
|
@@ -142,6 +142,12 @@ export interface PlayerState {
|
|
|
142
142
|
/** Function to set the muteWasForced flag */
|
|
143
143
|
setMuteWasForced: (muteWasForced: boolean) => void;
|
|
144
144
|
|
|
145
|
+
/** Flag indicating if autoplay failed and needs user interaction */
|
|
146
|
+
autoplayFailed: boolean;
|
|
147
|
+
|
|
148
|
+
/** Function to set the autoplayFailed flag */
|
|
149
|
+
setAutoplayFailed: (autoplayFailed: boolean) => void;
|
|
150
|
+
|
|
145
151
|
/** Flag indicating if the player is embedded in another context */
|
|
146
152
|
embedded: boolean;
|
|
147
153
|
|
|
@@ -99,6 +99,10 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
99
99
|
setMuteWasForced: (muteWasForced: boolean) =>
|
|
100
100
|
set(() => ({ muteWasForced })),
|
|
101
101
|
|
|
102
|
+
autoplayFailed: false,
|
|
103
|
+
setAutoplayFailed: (autoplayFailed: boolean) =>
|
|
104
|
+
set(() => ({ autoplayFailed })),
|
|
105
|
+
|
|
102
106
|
embedded: false,
|
|
103
107
|
setEmbedded: (embedded: boolean) => set(() => ({ embedded })),
|
|
104
108
|
|