@streamplace/components 0.7.1 → 0.7.3

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 (95) hide show
  1. package/dist/components/chat/chat-box.js +46 -43
  2. package/dist/components/chat/chat-message.js +36 -33
  3. package/dist/components/chat/chat.js +31 -27
  4. package/dist/components/chat/mention-suggestions.js +16 -13
  5. package/dist/components/chat/mod-view.js +20 -17
  6. package/dist/components/mobile-player/fullscreen.js +21 -17
  7. package/dist/components/mobile-player/fullscreen.native.js +39 -35
  8. package/dist/components/mobile-player/player.js +38 -32
  9. package/dist/components/mobile-player/props.js +2 -1
  10. package/dist/components/mobile-player/shared.js +16 -13
  11. package/dist/components/mobile-player/ui/countdown.js +23 -19
  12. package/dist/components/mobile-player/ui/index.js +10 -6
  13. package/dist/components/mobile-player/ui/input.js +16 -12
  14. package/dist/components/mobile-player/ui/loading.js +104 -0
  15. package/dist/components/mobile-player/ui/metrics.js +20 -16
  16. package/dist/components/mobile-player/ui/streamer-context-menu.js +6 -3
  17. package/dist/components/mobile-player/ui/viewer-context-menu.js +19 -16
  18. package/dist/components/mobile-player/ui/viewers.js +13 -9
  19. package/dist/components/mobile-player/use-webrtc.js +29 -24
  20. package/dist/components/mobile-player/video.js +109 -99
  21. package/dist/components/mobile-player/video.native.js +92 -85
  22. package/dist/components/mobile-player/webrtc-diagnostics.js +9 -5
  23. package/dist/components/mobile-player/webrtc-primitives.js +8 -6
  24. package/dist/components/mobile-player/webrtc-primitives.native.js +8 -1
  25. package/dist/components/ui/button.js +26 -23
  26. package/dist/components/ui/dialog.js +43 -40
  27. package/dist/components/ui/dropdown.js +121 -116
  28. package/dist/components/ui/icons.js +8 -5
  29. package/dist/components/ui/index.js +27 -19
  30. package/dist/components/ui/input.js +31 -28
  31. package/dist/components/ui/loader.js +9 -6
  32. package/dist/components/ui/primitives/button.js +33 -29
  33. package/dist/components/ui/primitives/input.js +44 -40
  34. package/dist/components/ui/primitives/modal.js +45 -41
  35. package/dist/components/ui/primitives/text.js +35 -29
  36. package/dist/components/ui/resizeable.js +66 -53
  37. package/dist/components/ui/text.js +50 -48
  38. package/dist/components/ui/textarea.js +13 -11
  39. package/dist/components/ui/toast.js +26 -23
  40. package/dist/components/ui/view.js +41 -39
  41. package/dist/hooks/index.js +12 -9
  42. package/dist/hooks/useAvatars.js +11 -8
  43. package/dist/hooks/useCameraToggle.js +7 -4
  44. package/dist/hooks/useKeyboard.js +13 -10
  45. package/dist/hooks/useKeyboardSlide.js +8 -5
  46. package/dist/hooks/useLivestreamInfo.js +17 -14
  47. package/dist/hooks/useOuterAndInnerDimensions.js +9 -6
  48. package/dist/hooks/usePlayerDimensions.js +9 -6
  49. package/dist/hooks/useSegmentDimensions.js +6 -3
  50. package/dist/hooks/useSegmentTiming.js +13 -10
  51. package/dist/index.js +24 -15
  52. package/dist/lib/facet.js +5 -1
  53. package/dist/lib/theme/atoms.js +153 -148
  54. package/dist/lib/theme/atoms.types.js +2 -1
  55. package/dist/lib/theme/index.js +31 -5
  56. package/dist/lib/theme/theme.js +91 -83
  57. package/dist/lib/theme/tokens.js +15 -12
  58. package/dist/lib/utils.js +22 -11
  59. package/dist/livestream-provider/index.js +19 -14
  60. package/dist/livestream-provider/websocket.js +14 -10
  61. package/dist/livestream-store/chat.js +26 -19
  62. package/dist/livestream-store/context.js +5 -2
  63. package/dist/livestream-store/index.js +7 -4
  64. package/dist/livestream-store/livestream-state.js +2 -1
  65. package/dist/livestream-store/livestream-store.js +31 -18
  66. package/dist/livestream-store/stream-key.js +22 -18
  67. package/dist/livestream-store/websocket-consumer.js +18 -14
  68. package/dist/player-store/context.js +5 -2
  69. package/dist/player-store/index.js +8 -5
  70. package/dist/player-store/player-provider.js +20 -15
  71. package/dist/player-store/player-state.js +9 -6
  72. package/dist/player-store/player-store.js +35 -21
  73. package/dist/player-store/single-player-provider.js +35 -23
  74. package/dist/streamplace-provider/context.js +5 -2
  75. package/dist/streamplace-provider/index.js +14 -10
  76. package/dist/streamplace-provider/poller.js +20 -17
  77. package/dist/streamplace-store/block.js +6 -3
  78. package/dist/streamplace-store/index.js +6 -3
  79. package/dist/streamplace-store/stream.js +58 -33
  80. package/dist/streamplace-store/streamplace-store.js +23 -13
  81. package/dist/streamplace-store/user.js +19 -14
  82. package/dist/streamplace-store/xrpc.js +10 -7
  83. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  84. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
  85. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/67b1eb60 +0 -0
  86. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/7c275f90 +0 -0
  87. package/package.json +5 -6
  88. package/src/components/mobile-player/ui/index.ts +1 -0
  89. package/src/components/mobile-player/ui/loading.tsx +154 -0
  90. package/src/components/ui/resizeable.tsx +26 -15
  91. package/src/player-store/player-state.tsx +4 -0
  92. package/src/player-store/player-store.tsx +5 -0
  93. package/src/streamplace-store/stream.tsx +66 -35
  94. package/tsconfig.json +2 -1
  95. package/tsconfig.tsbuildinfo +1 -1
@@ -1,24 +1,27 @@
1
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect } from "react";
3
- import { StreamplaceAgent } from "streamplace";
4
- import { useDID, useGetBskyProfile, useGetChatProfile, useStreamplaceStore, } from "../streamplace-store";
5
- import { usePDSAgent } from "../streamplace-store/xrpc";
6
- export default function Poller({ children }) {
7
- const url = useStreamplaceStore((state) => state.url);
8
- const setLiveUsers = useStreamplaceStore((state) => state.setLiveUsers);
9
- const did = useDID();
10
- const pdsAgent = usePDSAgent();
11
- const getChatProfile = useGetChatProfile();
12
- const getBskyProfile = useGetBskyProfile();
13
- const liveUserRefresh = useStreamplaceStore((state) => state.liveUsersRefresh);
14
- useEffect(() => {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = Poller;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const streamplace_1 = require("streamplace");
7
+ const streamplace_store_1 = require("../streamplace-store");
8
+ const xrpc_1 = require("../streamplace-store/xrpc");
9
+ function Poller({ children }) {
10
+ const url = (0, streamplace_store_1.useStreamplaceStore)((state) => state.url);
11
+ const setLiveUsers = (0, streamplace_store_1.useStreamplaceStore)((state) => state.setLiveUsers);
12
+ const did = (0, streamplace_store_1.useDID)();
13
+ const pdsAgent = (0, xrpc_1.usePDSAgent)();
14
+ const getChatProfile = (0, streamplace_store_1.useGetChatProfile)();
15
+ const getBskyProfile = (0, streamplace_store_1.useGetBskyProfile)();
16
+ const liveUserRefresh = (0, streamplace_store_1.useStreamplaceStore)((state) => state.liveUsersRefresh);
17
+ (0, react_1.useEffect)(() => {
15
18
  if (pdsAgent && did) {
16
19
  getChatProfile();
17
20
  getBskyProfile();
18
21
  }
19
22
  }, [pdsAgent, did]);
20
- useEffect(() => {
21
- const agent = new StreamplaceAgent(url);
23
+ (0, react_1.useEffect)(() => {
24
+ const agent = new streamplace_1.StreamplaceAgent(url);
22
25
  const go = async () => {
23
26
  setLiveUsers({
24
27
  liveUsersLoading: true,
@@ -42,5 +45,5 @@ export default function Poller({ children }) {
42
45
  const handle = setInterval(go, 3000);
43
46
  return () => clearInterval(handle);
44
47
  }, [url, liveUserRefresh]);
45
- return _jsx(_Fragment, { children: children });
48
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
46
49
  }
@@ -1,6 +1,9 @@
1
- import { usePDSAgent } from "./xrpc";
2
- export function useCreateBlockRecord() {
3
- let agent = usePDSAgent();
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCreateBlockRecord = useCreateBlockRecord;
4
+ const xrpc_1 = require("./xrpc");
5
+ function useCreateBlockRecord() {
6
+ let agent = (0, xrpc_1.usePDSAgent)();
4
7
  return async (subjectDID) => {
5
8
  if (!agent) {
6
9
  throw new Error("No PDS agent found");
@@ -1,3 +1,6 @@
1
- export * from "./stream";
2
- export * from "./streamplace-store";
3
- export * from "./user";
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./stream"), exports);
5
+ tslib_1.__exportStar(require("./streamplace-store"), exports);
6
+ tslib_1.__exportStar(require("./user"), exports);
@@ -1,29 +1,52 @@
1
- import { RichText } from "@atproto/api";
2
- import { useUrl } from "./streamplace-store";
3
- import { usePDSAgent } from "./xrpc";
4
- const uploadThumbnail = async (pdsAgent, customThumbnail) => {
5
- if (customThumbnail) {
6
- let tries = 0;
7
- try {
8
- let thumbnail = await pdsAgent.uploadBlob(customThumbnail);
9
- while (thumbnail.data.blob.size === 0 &&
10
- customThumbnail.size !== 0 &&
11
- tries < 3) {
12
- console.warn("Reuploading blob as blob sizes don't match! Blob size recieved is", thumbnail.data.blob.size, "and sent blob size is", customThumbnail.size);
13
- thumbnail = await pdsAgent.uploadBlob(customThumbnail);
14
- }
15
- if (tries === 3) {
16
- throw new Error("Could not successfully upload blob (tried thrice)");
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCreateStreamRecord = useCreateStreamRecord;
4
+ exports.useUpdateStreamRecord = useUpdateStreamRecord;
5
+ const api_1 = require("@atproto/api");
6
+ const streamplace_store_1 = require("./streamplace-store");
7
+ const xrpc_1 = require("./xrpc");
8
+ const react_1 = require("react");
9
+ const useUploadThumbnail = () => {
10
+ const abortRef = (0, react_1.useRef)(null);
11
+ (0, react_1.useEffect)(() => {
12
+ return () => {
13
+ // On unmount, abort any ongoing upload
14
+ abortRef.current?.abort();
15
+ };
16
+ }, []);
17
+ const uploadThumbnail = async (pdsAgent, customThumbnail) => {
18
+ if (!customThumbnail)
19
+ return undefined;
20
+ abortRef.current = new AbortController();
21
+ const { signal } = abortRef.current;
22
+ const maxTries = 3;
23
+ let lastError = null;
24
+ for (let tries = 0; tries < maxTries; tries++) {
25
+ try {
26
+ const thumbnail = await pdsAgent.uploadBlob(customThumbnail, {
27
+ signal,
28
+ });
29
+ if (thumbnail.success &&
30
+ thumbnail.data.blob.size === customThumbnail.size) {
31
+ console.log("Successfully uploaded thumbnail");
32
+ return thumbnail.data.blob;
33
+ }
34
+ else {
35
+ console.warn(`Blob size mismatch (attempt ${tries + 1}): received ${thumbnail.data.blob.size}, expected ${customThumbnail.size}`);
36
+ }
17
37
  }
18
- if (thumbnail.success) {
19
- console.log("Successfully uploaded thumbnail");
20
- return thumbnail.data.blob;
38
+ catch (e) {
39
+ if (signal.aborted) {
40
+ console.warn("Upload aborted");
41
+ return undefined;
42
+ }
43
+ lastError = e;
44
+ console.warn(`Error uploading thumbnail (attempt ${tries + 1}): ${e}`);
21
45
  }
22
46
  }
23
- catch (e) {
24
- throw new Error("Error uploading thumbnail: " + e);
25
- }
26
- }
47
+ throw new Error(`Could not successfully upload blob after ${maxTries} attempts. Last error: ${lastError}`);
48
+ };
49
+ return uploadThumbnail;
27
50
  };
28
51
  async function createNewPost(agent, record) {
29
52
  try {
@@ -35,15 +58,15 @@ async function createNewPost(agent, record) {
35
58
  throw error;
36
59
  }
37
60
  }
38
- function buildGoLivePost(text, url, profile, params, thumbnail) {
61
+ async function buildGoLivePost(text, url, profile, params, thumbnail, agent) {
39
62
  const now = new Date();
40
63
  const linkUrl = `${url.protocol}//${url.host}/${profile.handle}?${params.toString()}`;
41
64
  const prefix = `🔴 LIVE `;
42
65
  const textUrl = `${url.protocol}//${url.host}/${profile.handle}`;
43
66
  const suffix = ` ${text}`;
44
67
  const content = prefix + textUrl + suffix;
45
- const rt = new RichText({ text: content });
46
- rt.detectFacetsWithoutResolution();
68
+ const rt = new api_1.RichText({ text: content });
69
+ await rt.detectFacets(agent);
47
70
  const record = {
48
71
  $type: "app.bsky.feed.post",
49
72
  text: content,
@@ -65,9 +88,10 @@ function buildGoLivePost(text, url, profile, params, thumbnail) {
65
88
  };
66
89
  return record;
67
90
  }
68
- export function useCreateStreamRecord() {
69
- let agent = usePDSAgent();
70
- let url = useUrl();
91
+ function useCreateStreamRecord() {
92
+ let agent = (0, xrpc_1.usePDSAgent)();
93
+ let url = (0, streamplace_store_1.useUrl)();
94
+ const uploadThumbnail = useUploadThumbnail();
71
95
  return async (title, customThumbnail, submitPost = true) => {
72
96
  if (!agent) {
73
97
  throw new Error("No PDS agent found");
@@ -126,7 +150,7 @@ export function useCreateStreamRecord() {
126
150
  did: did,
127
151
  time: new Date().toISOString(),
128
152
  });
129
- let post = buildGoLivePost(title, u, profile.data, params, thumbnail);
153
+ let post = await buildGoLivePost(title, u, profile.data, params, thumbnail, agent);
130
154
  newPost = await createNewPost(agent, post);
131
155
  if (!newPost.uri || !newPost.cid) {
132
156
  throw new Error("Cannot read properties of undefined (reading 'uri' or 'cid')");
@@ -147,9 +171,10 @@ export function useCreateStreamRecord() {
147
171
  return record;
148
172
  };
149
173
  }
150
- export function useUpdateStreamRecord() {
151
- let agent = usePDSAgent();
152
- let url = useUrl();
174
+ function useUpdateStreamRecord() {
175
+ let agent = (0, xrpc_1.usePDSAgent)();
176
+ let url = (0, streamplace_store_1.useUrl)();
177
+ const uploadThumbnail = useUploadThumbnail();
153
178
  return async (title, livestream, customThumbnail) => {
154
179
  if (!agent) {
155
180
  throw new Error("No PDS agent found");
@@ -1,8 +1,13 @@
1
- import { useContext } from "react";
2
- import { createStore, useStore } from "zustand";
3
- import { StreamplaceContext } from "../streamplace-provider/context";
4
- export const makeStreamplaceStore = ({ url, }) => {
5
- return createStore()((set) => ({
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useSetHandle = exports.useHandle = exports.useDID = exports.useUrl = exports.makeStreamplaceStore = void 0;
4
+ exports.getStreamplaceStoreFromContext = getStreamplaceStoreFromContext;
5
+ exports.useStreamplaceStore = useStreamplaceStore;
6
+ const react_1 = require("react");
7
+ const zustand_1 = require("zustand");
8
+ const context_1 = require("../streamplace-provider/context");
9
+ const makeStreamplaceStore = ({ url, }) => {
10
+ return (0, zustand_1.createStore)()((set) => ({
6
11
  url,
7
12
  liveUsers: null,
8
13
  setLiveUsers: (opts) => {
@@ -18,20 +23,25 @@ export const makeStreamplaceStore = ({ url, }) => {
18
23
  chatProfile: null,
19
24
  }));
20
25
  };
21
- export function getStreamplaceStoreFromContext() {
22
- const context = useContext(StreamplaceContext);
26
+ exports.makeStreamplaceStore = makeStreamplaceStore;
27
+ function getStreamplaceStoreFromContext() {
28
+ const context = (0, react_1.useContext)(context_1.StreamplaceContext);
23
29
  if (!context) {
24
30
  throw new Error("useStreamplaceStore must be used within a StreamplaceProvider");
25
31
  }
26
32
  return context.store;
27
33
  }
28
- export function useStreamplaceStore(selector) {
29
- return useStore(getStreamplaceStoreFromContext(), selector);
34
+ function useStreamplaceStore(selector) {
35
+ return (0, zustand_1.useStore)(getStreamplaceStoreFromContext(), selector);
30
36
  }
31
- export const useUrl = () => useStreamplaceStore((x) => x.url);
32
- export const useDID = () => useStreamplaceStore((x) => x.oauthSession?.did);
33
- export const useHandle = () => useStreamplaceStore((x) => x.handle);
34
- export const useSetHandle = () => {
37
+ const useUrl = () => useStreamplaceStore((x) => x.url);
38
+ exports.useUrl = useUrl;
39
+ const useDID = () => useStreamplaceStore((x) => x.oauthSession?.did);
40
+ exports.useDID = useDID;
41
+ const useHandle = () => useStreamplaceStore((x) => x.handle);
42
+ exports.useHandle = useHandle;
43
+ const useSetHandle = () => {
35
44
  const store = getStreamplaceStoreFromContext();
36
45
  return (handle) => store.setState({ handle });
37
46
  };
47
+ exports.useSetHandle = useSetHandle;
@@ -1,10 +1,15 @@
1
- import { PlaceStreamChatProfile } from "streamplace";
2
- import { getStreamplaceStoreFromContext, useDID, useStreamplaceStore, } from "./streamplace-store";
3
- import { usePDSAgent } from "./xrpc";
4
- export function useGetChatProfile() {
5
- const did = useDID();
6
- const pdsAgent = usePDSAgent();
7
- const store = getStreamplaceStoreFromContext();
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useGetChatProfile = useGetChatProfile;
4
+ exports.useGetBskyProfile = useGetBskyProfile;
5
+ exports.useChatProfile = useChatProfile;
6
+ const streamplace_1 = require("streamplace");
7
+ const streamplace_store_1 = require("./streamplace-store");
8
+ const xrpc_1 = require("./xrpc");
9
+ function useGetChatProfile() {
10
+ const did = (0, streamplace_store_1.useDID)();
11
+ const pdsAgent = (0, xrpc_1.usePDSAgent)();
12
+ const store = (0, streamplace_store_1.getStreamplaceStoreFromContext)();
8
13
  return async () => {
9
14
  if (!did || !pdsAgent) {
10
15
  throw new Error("No DID or PDS agent");
@@ -17,7 +22,7 @@ export function useGetChatProfile() {
17
22
  if (!res.success) {
18
23
  throw new Error("Failed to get chat profile record");
19
24
  }
20
- if (PlaceStreamChatProfile.isRecord(res.data.value)) {
25
+ if (streamplace_1.PlaceStreamChatProfile.isRecord(res.data.value)) {
21
26
  store.setState({ chatProfile: res.data.value });
22
27
  }
23
28
  else {
@@ -25,10 +30,10 @@ export function useGetChatProfile() {
25
30
  }
26
31
  };
27
32
  }
28
- export function useGetBskyProfile() {
29
- const did = useDID();
30
- const pdsAgent = usePDSAgent();
31
- const store = getStreamplaceStoreFromContext();
33
+ function useGetBskyProfile() {
34
+ const did = (0, streamplace_store_1.useDID)();
35
+ const pdsAgent = (0, xrpc_1.usePDSAgent)();
36
+ const store = (0, streamplace_store_1.getStreamplaceStoreFromContext)();
32
37
  return async () => {
33
38
  if (!did || !pdsAgent) {
34
39
  throw new Error("No DID or PDS agent");
@@ -42,6 +47,6 @@ export function useGetBskyProfile() {
42
47
  store.setState({ handle: res.data.handle });
43
48
  };
44
49
  }
45
- export function useChatProfile() {
46
- return useStreamplaceStore((x) => x.chatProfile);
50
+ function useChatProfile() {
51
+ return (0, streamplace_store_1.useStreamplaceStore)((x) => x.chatProfile);
47
52
  }
@@ -1,12 +1,15 @@
1
- import { useMemo } from "react";
2
- import { StreamplaceAgent } from "streamplace";
3
- import { useStreamplaceStore } from ".";
4
- export function usePDSAgent() {
5
- const oauthSession = useStreamplaceStore((state) => state.oauthSession);
6
- return useMemo(() => {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePDSAgent = usePDSAgent;
4
+ const react_1 = require("react");
5
+ const streamplace_1 = require("streamplace");
6
+ const _1 = require(".");
7
+ function usePDSAgent() {
8
+ const oauthSession = (0, _1.useStreamplaceStore)((state) => state.oauthSession);
9
+ return (0, react_1.useMemo)(() => {
7
10
  if (!oauthSession) {
8
11
  return null;
9
12
  }
10
- return new StreamplaceAgent(oauthSession);
13
+ return new streamplace_1.StreamplaceAgent(oauthSession);
11
14
  }, [oauthSession]);
12
15
  }
package/package.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "name": "@streamplace/components",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Streamplace React (Native) Components",
5
- "type": "module",
6
5
  "main": "dist/index.js",
7
6
  "types": "src/index.tsx",
8
7
  "exports": {
9
8
  ".": {
10
9
  "@streamplace/dev": "./src/index.tsx",
11
- "types": "./dist/index.d.mjs",
12
- "default": "./dist/index.mjs"
10
+ "types": "./src/index.tsx",
11
+ "default": "./dist/index.js"
13
12
  }
14
13
  },
15
14
  "scripts": {
@@ -44,12 +43,12 @@
44
43
  "react-native-safe-area-context": "5.4.1",
45
44
  "react-native-webrtc": "git+https://github.com/streamplace/react-native-webrtc.git#6b8472a771ac47f89217d327058a8a4124a6ae56",
46
45
  "react-use-websocket": "^4.13.0",
47
- "streamplace": "0.7.1",
46
+ "streamplace": "0.7.2",
48
47
  "viem": "^2.21.44",
49
48
  "zustand": "^5.0.5"
50
49
  },
51
50
  "peerDependencies": {
52
51
  "react": "*"
53
52
  },
54
- "gitHead": "e092ac26f32426cfb14ec3cbda96265ad2fbae12"
53
+ "gitHead": "70367834f75a94f074bd81299c2e72c36b0dbbf0"
55
54
  }
@@ -1,5 +1,6 @@
1
1
  export * from "./countdown";
2
2
  export * from "./input";
3
+ export * from "./loading";
3
4
  export * from "./metrics";
4
5
  export * from "./streamer-context-menu";
5
6
  export * from "./viewer-context-menu";
@@ -0,0 +1,154 @@
1
+ import { useEffect, useState } from "react";
2
+ import Animated, {
3
+ runOnJS,
4
+ useAnimatedStyle,
5
+ useSharedValue,
6
+ withDelay,
7
+ withTiming,
8
+ } from "react-native-reanimated";
9
+ import { pt } from "../../../lib/theme/atoms";
10
+
11
+ type LoadingOverlayProps = {
12
+ visible: boolean;
13
+ width: number;
14
+ height: number;
15
+ subtitle?: string;
16
+ messages?: string[];
17
+ interval?: number; // in milliseconds
18
+ };
19
+
20
+ const defaultMessages = [
21
+ "Creating your stream",
22
+ "Uploading thumbnails",
23
+ "Getting things ready",
24
+ "Doing some magic",
25
+ "Preparing something special",
26
+ "Reticulating splines",
27
+ "Making it nice",
28
+ "Flipping some switches",
29
+ "Adding good vibes",
30
+ "Almost there",
31
+ "Summoning your Persona",
32
+ "Awakening our true selves",
33
+ "Fusion in progress",
34
+ "Equipping the right materia",
35
+ ];
36
+
37
+ export function LoadingOverlay({
38
+ visible,
39
+ width,
40
+ height,
41
+ subtitle,
42
+ messages = defaultMessages,
43
+ interval = 3000,
44
+ }: LoadingOverlayProps) {
45
+ const [currentIndex, setCurrentIndex] = useState(0);
46
+ const [shouldRender, setShouldRender] = useState(visible);
47
+
48
+ // Animation values
49
+ const translateY = useSharedValue(0);
50
+ const opacity = useSharedValue(1);
51
+
52
+ const wholeOpacity = useSharedValue(0);
53
+
54
+ // Handle fade-in and fade-out animations
55
+ useEffect(() => {
56
+ if (visible) {
57
+ setShouldRender(true); // Ensure the component is mounted
58
+ wholeOpacity.value = withTiming(1, { duration: 500 }); // Fade in
59
+ } else {
60
+ wholeOpacity.value = withTiming(0, { duration: 500 }, () => {
61
+ // Unmount after fade-out
62
+ runOnJS(setShouldRender)(false);
63
+ });
64
+ }
65
+ }, [visible]);
66
+
67
+ // Cycle messages on a timer
68
+ useEffect(() => {
69
+ if (!visible) {
70
+ setCurrentIndex(0);
71
+ return;
72
+ }
73
+
74
+ const timeout = setTimeout(() => {
75
+ setCurrentIndex((prev) => (prev + 1) % messages.length);
76
+ }, interval);
77
+
78
+ return () => clearTimeout(timeout);
79
+ }, [visible, currentIndex, interval, messages.length]);
80
+
81
+ // Trigger animation on each message change
82
+ useEffect(() => {
83
+ if (!visible) return;
84
+
85
+ const fadeDuration = Math.min(interval / 2, 250); // Simplified fade duration
86
+
87
+ // Reset animation values
88
+ translateY.value = 20;
89
+ opacity.value = 0;
90
+
91
+ // Sequential fade-in and fade-out
92
+ translateY.value = withTiming(0, { duration: fadeDuration });
93
+ opacity.value = withTiming(1, { duration: fadeDuration }, () => {
94
+ // add a delay for interval - (fadeDuration*2)
95
+
96
+ translateY.value = withDelay(
97
+ interval - fadeDuration * 2,
98
+ withTiming(-10, { duration: fadeDuration }),
99
+ );
100
+ opacity.value = withDelay(
101
+ interval - fadeDuration * 2,
102
+ withTiming(0, { duration: fadeDuration }),
103
+ );
104
+ });
105
+ }, [currentIndex, visible]);
106
+
107
+ const animatedStyle = useAnimatedStyle(() => {
108
+ return {
109
+ transform: [{ translateY: translateY.value }],
110
+ opacity: opacity.value,
111
+ };
112
+ });
113
+
114
+ const wholeAnimatedStyle = useAnimatedStyle(() => ({
115
+ opacity: wholeOpacity.value,
116
+ }));
117
+
118
+ if (!shouldRender) return null;
119
+
120
+ return (
121
+ <Animated.View
122
+ style={[
123
+ {
124
+ position: "absolute",
125
+ top: 0,
126
+ left: 0,
127
+ width,
128
+ height,
129
+ backgroundColor: "rgba(0,0,0,0.7)",
130
+ alignItems: "center",
131
+ justifyContent: "center",
132
+ zIndex: 1000,
133
+ },
134
+ wholeAnimatedStyle,
135
+ ]}
136
+ >
137
+ <Animated.Text
138
+ style={[
139
+ {
140
+ color: "white",
141
+ fontSize: 24,
142
+ fontWeight: "bold",
143
+ },
144
+ animatedStyle,
145
+ ]}
146
+ >
147
+ {messages[currentIndex]}
148
+ </Animated.Text>
149
+ <Animated.Text style={[pt[5], { color: "#a0a0a0" }]}>
150
+ {subtitle}
151
+ </Animated.Text>
152
+ </Animated.View>
153
+ );
154
+ }
@@ -15,7 +15,7 @@ import Animated, {
15
15
  } from "react-native-reanimated";
16
16
  import { useSafeAreaInsets } from "react-native-safe-area-context";
17
17
  import { useKeyboardSlide } from "../../hooks";
18
- import { bottom, layout, p, w, zIndex } from "../../lib/theme/atoms";
18
+ import { bottom, h, layout, p, w, zIndex } from "../../lib/theme/atoms";
19
19
  import { View } from "./view";
20
20
 
21
21
  const AnimatedView = Animated.createAnimatedComponent(View);
@@ -154,24 +154,35 @@ export function Resizable({
154
154
  style,
155
155
  ]}
156
156
  >
157
- <View style={[layout.flex.row, layout.flex.justifyCenter]}>
157
+ <View style={[layout.flex.row, layout.flex.justifyCenter, h[2]]}>
158
158
  <GestureDetector gesture={panGesture}>
159
159
  <View
160
- hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
161
- style={[
162
- w[32],
163
- {
164
- height: 6,
165
- transform: [{ translateY: -10 }],
166
- backgroundColor: "#eeeeee66",
167
- alignItems: "center",
168
- justifyContent: "center",
169
- borderRadius: 999,
170
- },
171
- ]}
172
- />
160
+ // Make the touch area much larger, but keep the visible handle small
161
+ style={{
162
+ height: 30, // Large touch area
163
+ width: 120, // Wide enough for thumbs
164
+ alignItems: "center",
165
+ justifyContent: "center",
166
+ //backgroundColor: "rgba(0,255,255,0.1)",
167
+ transform: [{ translateY: -30 }],
168
+ }}
169
+ >
170
+ <View
171
+ style={[
172
+ w[32],
173
+ {
174
+ height: 6,
175
+ backgroundColor: "#eeeeee66",
176
+ borderRadius: 999,
177
+
178
+ transform: [{ translateY: 5 }],
179
+ },
180
+ ]}
181
+ />
182
+ </View>
173
183
  </GestureDetector>
174
184
  </View>
185
+
175
186
  {children}
176
187
  </AnimatedView>
177
188
  </>
@@ -119,6 +119,10 @@ export interface PlayerState {
119
119
  | undefined,
120
120
  ) => void;
121
121
 
122
+ pipAction: (() => void) | undefined;
123
+ /** Function to set the Picture-in-Picture action */
124
+ setPipAction: (action: (() => void) | undefined) => void;
125
+
122
126
  /** Player element width (CSS value or number) */
123
127
  playerWidth?: string | number;
124
128
  /** Function to set the player width */
@@ -83,6 +83,11 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
83
83
  pipMode: false,
84
84
  setPipMode: (pipMode: boolean) => set(() => ({ pipMode })),
85
85
 
86
+ // Picture-in-Picture action function (set by player component)
87
+ pipAction: undefined,
88
+ setPipAction: (action: (() => void) | undefined) =>
89
+ set(() => ({ pipAction: action })),
90
+
86
91
  // Player element width/height setters for global sync
87
92
  playerWidth: undefined,
88
93
  setPlayerWidth: (playerWidth: number) => set(() => ({ playerWidth })),