@streamplace/components 0.7.25 → 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.
Files changed (37) hide show
  1. package/dist/components/chat/chat-box.js +0 -6
  2. package/dist/components/chat/chat-message.js +4 -0
  3. package/dist/components/mobile-player/ui/autoplay-button.js +1 -1
  4. package/dist/components/mobile-player/video-async.native.js +4 -4
  5. package/dist/components/mobile-player/video.js +3 -3
  6. package/dist/index.js +4 -1
  7. package/dist/livestream-store/chat.js +9 -0
  8. package/dist/player-store/player-store.js +0 -4
  9. package/dist/storage/index.js +5 -0
  10. package/dist/storage/lock.js +40 -0
  11. package/dist/storage/storage.js +14 -0
  12. package/dist/storage/storage.native.js +44 -0
  13. package/dist/storage/storage.shared.js +2 -0
  14. package/dist/streamplace-provider/index.js +1 -0
  15. package/dist/streamplace-store/streamplace-store.js +75 -2
  16. package/dist/streamplace-store/xrpc.js +10 -1
  17. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  18. package/package.json +3 -2
  19. package/src/components/chat/chat-box.tsx +0 -11
  20. package/src/components/chat/chat-message.tsx +3 -0
  21. package/src/components/mobile-player/ui/autoplay-button.tsx +2 -2
  22. package/src/components/mobile-player/video-async.native.tsx +6 -4
  23. package/src/components/mobile-player/video.tsx +6 -3
  24. package/src/index.tsx +4 -0
  25. package/src/livestream-store/chat.tsx +13 -0
  26. package/src/player-store/player-state.tsx +0 -12
  27. package/src/player-store/player-store.tsx +0 -8
  28. package/src/storage/index.tsx +3 -0
  29. package/src/storage/lock.tsx +38 -0
  30. package/src/storage/storage.native.tsx +42 -0
  31. package/src/storage/storage.shared.tsx +5 -0
  32. package/src/storage/storage.tsx +15 -0
  33. package/src/streamplace-provider/index.tsx +2 -1
  34. package/src/streamplace-store/streamplace-store.tsx +92 -2
  35. package/src/streamplace-store/xrpc.tsx +9 -2
  36. package/tsconfig.json +2 -1
  37. 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.usePlayerStore)((x) => x.setMuted);
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.usePlayerStore)((x) => x.muted);
33
- const volume = (0, __1.usePlayerStore)((x) => x.volume);
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.usePlayerStore)((x) => x.muted);
129
- const volume = (0, __1.usePlayerStore)((x) => x.volume);
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.usePlayerStore)((x) => x.volume);
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);
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; } });
@@ -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,
@@ -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,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const storage_1 = tslib_1.__importDefault(require("./storage"));
5
+ exports.default = new storage_1.default();
@@ -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;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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)(() => {
@@ -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
- return (0, zustand_1.createStore)()((set) => ({
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
- return null;
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]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamplace/components",
3
- "version": "0.7.25",
3
+ "version": "0.7.26",
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": "288afabcb270c01ae8012e2a5cd9d75d5e1aae28"
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>;
@@ -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 = usePlayerStore((x) => x.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 = usePlayerStore((x) => x.muted);
75
- const volume = usePlayerStore((x) => x.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 = usePlayerStore((x) => x.muted);
215
- const volume = usePlayerStore((x) => x.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 = usePlayerStore((x) => x.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);
package/src/index.tsx CHANGED
@@ -37,3 +37,7 @@ export * from "./components/keep-awake";
37
37
 
38
38
  // Dashboard components
39
39
  export * as Dashboard from "./components/dashboard";
40
+
41
+ // Storage exports
42
+ export { default as storage } from "./storage";
43
+ export type { AQStorage } from "./storage/storage.shared";
@@ -75,6 +75,19 @@ 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 = {
79
92
  $type: "place.stream.chat.message",
80
93
  text: msg.text,
@@ -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,3 @@
1
+ import Storage from "./storage";
2
+
3
+ export default new Storage();
@@ -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
+ }