@streamplace/components 0.7.25 → 0.7.27
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 +4 -0
- package/dist/components/mobile-player/ui/autoplay-button.js +1 -1
- package/dist/components/mobile-player/use-webrtc.js +7 -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/video.native.js +10 -1
- package/dist/crypto-polyfill.js +0 -0
- package/dist/crypto-polyfill.native.js +24 -0
- package/dist/index.js +5 -1
- package/dist/livestream-store/chat.js +9 -2
- package/dist/livestream-store/stream-key.js +1 -1
- package/dist/player-store/player-provider.js +10 -2
- package/dist/player-store/player-store.js +0 -4
- package/dist/player-store/single-player-provider.js +1 -1
- 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 +1 -1
- 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 +3 -2
- package/src/components/chat/chat-box.tsx +0 -11
- package/src/components/chat/chat-message.tsx +3 -0
- package/src/components/mobile-player/ui/autoplay-button.tsx +2 -2
- package/src/components/mobile-player/use-webrtc.tsx +10 -1
- package/src/components/mobile-player/video-async.native.tsx +6 -4
- package/src/components/mobile-player/video.native.tsx +19 -4
- package/src/components/mobile-player/video.tsx +6 -3
- package/src/crypto-polyfill.native.tsx +24 -0
- package/src/crypto-polyfill.tsx +0 -0
- package/src/index.tsx +6 -0
- package/src/livestream-store/chat.tsx +13 -3
- package/src/livestream-store/stream-key.tsx +1 -1
- package/src/player-store/player-provider.tsx +13 -1
- package/src/player-store/player-state.tsx +0 -12
- package/src/player-store/player-store.tsx +0 -8
- package/src/player-store/single-player-provider.tsx +1 -1
- 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 +1 -1
- 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}`);
|
|
@@ -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);
|
|
@@ -178,6 +178,11 @@ function useWebRTCIngest({ endpoint, }) {
|
|
|
178
178
|
const ingestConnectionState = (0, __1.usePlayerStore)((x) => x.ingestConnectionState);
|
|
179
179
|
const setIngestConnectionState = (0, __1.usePlayerStore)((x) => x.setIngestConnectionState);
|
|
180
180
|
const storedKey = (0, __1.useStreamKey)();
|
|
181
|
+
(0, react_1.useEffect)(() => {
|
|
182
|
+
if (storedKey?.error) {
|
|
183
|
+
console.error("error creating stream key", storedKey.error);
|
|
184
|
+
}
|
|
185
|
+
}, [storedKey?.error]);
|
|
181
186
|
const [peerConnection, setPeerConnection] = (0, react_1.useState)(null);
|
|
182
187
|
const videoTransceiver = (0, react_1.useRef)(null);
|
|
183
188
|
const audioTransceiver = (0, react_1.useRef)(null);
|
|
@@ -211,7 +216,8 @@ function useWebRTCIngest({ endpoint, }) {
|
|
|
211
216
|
negotiateConnectionWithClientOffer(peerConnection, endpoint, storedKey.streamKey?.privateKey);
|
|
212
217
|
});
|
|
213
218
|
peerConnection.addEventListener("track", (ev) => {
|
|
214
|
-
console.log(ev);
|
|
219
|
+
console.log(`got peerconnection track with ${ev.track.kind}`, ev.track.id);
|
|
220
|
+
// console.log(ev);
|
|
215
221
|
});
|
|
216
222
|
setPeerConnection(peerConnection);
|
|
217
223
|
return () => {
|
|
@@ -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);
|
|
@@ -43,11 +43,20 @@ function VideoNative(props) {
|
|
|
43
43
|
importPromise = Promise.resolve().then(() => __importStar(require("./video-async.native")));
|
|
44
44
|
}
|
|
45
45
|
const [videoNativeModule, setVideoNativeModule] = (0, react_1.useState)(null);
|
|
46
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
46
47
|
(0, react_1.useEffect)(() => {
|
|
47
|
-
importPromise
|
|
48
|
+
importPromise
|
|
49
|
+
?.then((module) => {
|
|
48
50
|
setVideoNativeModule(module);
|
|
51
|
+
})
|
|
52
|
+
.catch((err) => {
|
|
53
|
+
setError(err.message);
|
|
49
54
|
});
|
|
50
55
|
}, []);
|
|
56
|
+
if (error) {
|
|
57
|
+
console.error(error);
|
|
58
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flex: 1, justifyContent: "center", alignItems: "center" }, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { children: error }) }));
|
|
59
|
+
}
|
|
51
60
|
if (!videoNativeModule) {
|
|
52
61
|
return (0, jsx_runtime_1.jsx)(react_native_1.View, {});
|
|
53
62
|
}
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// awkward use of require()? you betcha. but import() with Metro causes it to try and
|
|
2
|
+
// resolve the package at compile-time even if not installed, so here we are.
|
|
3
|
+
let rnqc = null;
|
|
4
|
+
let expoCrypto = null;
|
|
5
|
+
try {
|
|
6
|
+
rnqc = require("react-native-quick-crypto");
|
|
7
|
+
}
|
|
8
|
+
catch (err) { }
|
|
9
|
+
try {
|
|
10
|
+
expoCrypto = require("expo-crypto");
|
|
11
|
+
}
|
|
12
|
+
catch (err) { }
|
|
13
|
+
if (!rnqc && !expoCrypto) {
|
|
14
|
+
throw new Error("Livestreaming requires one of react-native-quick-crypto or expo-crypto");
|
|
15
|
+
}
|
|
16
|
+
else if (!rnqc && expoCrypto) {
|
|
17
|
+
// @atproto/crypto dependencies expect crypto.getRandomValues to be a function
|
|
18
|
+
if (typeof globalThis.crypto === "undefined") {
|
|
19
|
+
globalThis.crypto = {};
|
|
20
|
+
}
|
|
21
|
+
if (typeof globalThis.crypto.getRandomValues === "undefined") {
|
|
22
|
+
globalThis.crypto.getRandomValues = expoCrypto.getRandomValues;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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
|
+
require("./crypto-polyfill");
|
|
6
7
|
tslib_1.__exportStar(require("./livestream-provider"), exports);
|
|
7
8
|
tslib_1.__exportStar(require("./livestream-store"), exports);
|
|
8
9
|
tslib_1.__exportStar(require("./player-store"), exports);
|
|
@@ -32,3 +33,6 @@ tslib_1.__exportStar(require("./components/share/sharesheet"), exports);
|
|
|
32
33
|
tslib_1.__exportStar(require("./components/keep-awake"), exports);
|
|
33
34
|
// Dashboard components
|
|
34
35
|
exports.Dashboard = tslib_1.__importStar(require("./components/dashboard"));
|
|
36
|
+
// Storage exports
|
|
37
|
+
var storage_1 = require("./storage");
|
|
38
|
+
Object.defineProperty(exports, "storage", { enumerable: true, get: function () { return tslib_1.__importDefault(storage_1).default; } });
|
|
@@ -49,6 +49,15 @@ 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 = {
|
|
53
62
|
$type: "place.stream.chat.message",
|
|
54
63
|
text: msg.text,
|
|
@@ -151,11 +160,9 @@ const reduceChatIncremental = (state, newMessages, blocks, hideUris = []) => {
|
|
|
151
160
|
const newAuthors = { ...state.authors };
|
|
152
161
|
let hasChanges = false;
|
|
153
162
|
const removedKeys = new Set();
|
|
154
|
-
console.log("newMessages", newMessages);
|
|
155
163
|
for (const msg of newMessages) {
|
|
156
164
|
if (msg.deleted) {
|
|
157
165
|
hasChanges = true;
|
|
158
|
-
console.log("deleted", msg.uri);
|
|
159
166
|
removedKeys.add(msg.uri);
|
|
160
167
|
}
|
|
161
168
|
}
|
|
@@ -81,7 +81,7 @@ const useStreamKey = () => {
|
|
|
81
81
|
setStreamKey(JSON.stringify(newKey));
|
|
82
82
|
setKey(newKey);
|
|
83
83
|
};
|
|
84
|
-
generateKey();
|
|
84
|
+
generateKey().catch((err) => setError(err.message));
|
|
85
85
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
86
86
|
}, [key, setStreamKey]);
|
|
87
87
|
return { streamKey: key, error };
|
|
@@ -3,10 +3,18 @@ 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");
|
|
7
6
|
const react_1 = require("react");
|
|
8
7
|
const context_1 = require("./context");
|
|
9
8
|
const player_store_1 = require("./player-store");
|
|
9
|
+
function randomUUID() {
|
|
10
|
+
let dt = new Date().getTime();
|
|
11
|
+
var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
|
12
|
+
var r = (dt + Math.random() * 16) % 16 | 0;
|
|
13
|
+
dt = Math.floor(dt / 16);
|
|
14
|
+
return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
|
|
15
|
+
});
|
|
16
|
+
return uuid;
|
|
17
|
+
}
|
|
10
18
|
const PlayerProvider = ({ children, initialPlayers = [], defaultId = Math.random().toString(36).slice(8), }) => {
|
|
11
19
|
const [players, setPlayers] = (0, react_1.useState)(() => {
|
|
12
20
|
// Initialize with any initial player IDs provided
|
|
@@ -21,7 +29,7 @@ const PlayerProvider = ({ children, initialPlayers = [], defaultId = Math.random
|
|
|
21
29
|
return initialPlayerStores;
|
|
22
30
|
});
|
|
23
31
|
const createPlayer = (0, react_1.useCallback)((id) => {
|
|
24
|
-
const playerId = id ||
|
|
32
|
+
const playerId = id || randomUUID();
|
|
25
33
|
const playerStore = (0, player_store_1.makePlayerStore)(playerId);
|
|
26
34
|
setPlayers((prev) => ({
|
|
27
35
|
...prev,
|
|
@@ -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,
|
|
@@ -12,8 +12,8 @@ const tslib_1 = require("tslib");
|
|
|
12
12
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
13
13
|
const react_1 = tslib_1.__importStar(require("react"));
|
|
14
14
|
const zustand_1 = require("zustand");
|
|
15
|
-
const player_store_1 = require("../player-store");
|
|
16
15
|
const player_state_1 = require("./player-state");
|
|
16
|
+
const player_store_1 = require("./player-store");
|
|
17
17
|
const SinglePlayerContext = (0, react_1.createContext)(null);
|
|
18
18
|
/**
|
|
19
19
|
* Provider component for a single player that creates a scoped context
|
|
@@ -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)(() => {
|
|
@@ -111,7 +111,7 @@ function useCreateStreamRecord() {
|
|
|
111
111
|
}
|
|
112
112
|
// Use customUrl if provided, otherwise fall back to the store URL
|
|
113
113
|
const finalUrl = customUrl || url;
|
|
114
|
-
const u = new URL(
|
|
114
|
+
const u = new URL(url);
|
|
115
115
|
let thumbnail = undefined;
|
|
116
116
|
if (customThumbnail) {
|
|
117
117
|
try {
|
|
@@ -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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamplace/components",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.27",
|
|
4
4
|
"description": "Streamplace React (Native) Components",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "src/index.tsx",
|
|
@@ -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",
|
|
@@ -52,5 +53,5 @@
|
|
|
52
53
|
"start": "tsc --watch --preserveWatchOutput",
|
|
53
54
|
"prepare": "tsc"
|
|
54
55
|
},
|
|
55
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "c981451ba44cc3e2213e1734c31d35b1383b2132"
|
|
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>;
|
|
@@ -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);
|
|
@@ -205,6 +205,11 @@ export function useWebRTCIngest({
|
|
|
205
205
|
(x) => x.setIngestConnectionState,
|
|
206
206
|
);
|
|
207
207
|
const storedKey = useStreamKey();
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
if (storedKey?.error) {
|
|
210
|
+
console.error("error creating stream key", storedKey.error);
|
|
211
|
+
}
|
|
212
|
+
}, [storedKey?.error]);
|
|
208
213
|
const [peerConnection, setPeerConnection] =
|
|
209
214
|
useState<RTCPeerConnection | null>(null);
|
|
210
215
|
|
|
@@ -249,7 +254,11 @@ export function useWebRTCIngest({
|
|
|
249
254
|
});
|
|
250
255
|
|
|
251
256
|
peerConnection.addEventListener("track", (ev) => {
|
|
252
|
-
console.log(
|
|
257
|
+
console.log(
|
|
258
|
+
`got peerconnection track with ${ev.track.kind}`,
|
|
259
|
+
ev.track.id,
|
|
260
|
+
);
|
|
261
|
+
// console.log(ev);
|
|
253
262
|
});
|
|
254
263
|
|
|
255
264
|
setPeerConnection(peerConnection);
|
|
@@ -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) {
|