@streamplace/components 0.7.2 → 0.7.7

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 (91) hide show
  1. package/dist/components/chat/chat-box.js +212 -24
  2. package/dist/components/chat/chat-message.js +5 -5
  3. package/dist/components/chat/chat.js +83 -5
  4. package/dist/components/chat/emoji-suggestions.js +35 -0
  5. package/dist/components/chat/mod-view.js +59 -8
  6. package/dist/components/chat/system-message.js +19 -0
  7. package/dist/components/icons/bluesky-icon.js +9 -0
  8. package/dist/components/keep-awake.js +7 -0
  9. package/dist/components/keep-awake.native.js +16 -0
  10. package/dist/components/mobile-player/fullscreen.js +2 -1
  11. package/dist/components/mobile-player/fullscreen.native.js +3 -3
  12. package/dist/components/mobile-player/player.js +15 -30
  13. package/dist/components/mobile-player/ui/index.js +2 -0
  14. package/dist/components/mobile-player/ui/report-modal.js +90 -0
  15. package/dist/components/mobile-player/ui/streamer-loading-overlay.js +104 -0
  16. package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -1
  17. package/dist/components/mobile-player/ui/viewer-loading-overlay.js +49 -0
  18. package/dist/components/mobile-player/use-webrtc.js +7 -1
  19. package/dist/components/mobile-player/video-retry.js +29 -0
  20. package/dist/components/mobile-player/video.js +84 -9
  21. package/dist/components/mobile-player/video.native.js +24 -10
  22. package/dist/components/share/sharesheet.js +91 -0
  23. package/dist/components/ui/dialog.js +1 -1
  24. package/dist/components/ui/dropdown.js +6 -6
  25. package/dist/components/ui/index.js +2 -0
  26. package/dist/components/ui/primitives/modal.js +0 -1
  27. package/dist/components/ui/resizeable.js +20 -11
  28. package/dist/components/ui/slider.js +5 -0
  29. package/dist/hooks/index.js +1 -0
  30. package/dist/hooks/usePointerDevice.js +71 -0
  31. package/dist/index.js +10 -3
  32. package/dist/lib/system-messages.js +101 -0
  33. package/dist/livestream-store/chat.js +111 -18
  34. package/dist/livestream-store/livestream-store.js +3 -0
  35. package/dist/livestream-store/problems.js +76 -0
  36. package/dist/livestream-store/websocket-consumer.js +39 -4
  37. package/dist/player-store/player-store.js +33 -4
  38. package/dist/streamplace-store/block.js +51 -12
  39. package/dist/streamplace-store/stream.js +44 -23
  40. package/dist/ui/index.js +79 -0
  41. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  42. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
  43. package/node-compile-cache/{v22.15.0-x64-92db9086-0 → v22.15.0-x64-efe9a9df-0}/67b1eb60 +0 -0
  44. package/node-compile-cache/{v22.15.0-x64-92db9086-0 → v22.15.0-x64-efe9a9df-0}/7c275f90 +0 -0
  45. package/package.json +6 -2
  46. package/src/components/chat/chat-box.tsx +295 -25
  47. package/src/components/chat/chat-message.tsx +6 -7
  48. package/src/components/chat/chat.tsx +192 -41
  49. package/src/components/chat/emoji-suggestions.tsx +94 -0
  50. package/src/components/chat/mod-view.tsx +119 -40
  51. package/src/components/chat/system-message.tsx +38 -0
  52. package/src/components/icons/bluesky-icon.tsx +9 -0
  53. package/src/components/keep-awake.native.tsx +13 -0
  54. package/src/components/keep-awake.tsx +3 -0
  55. package/src/components/mobile-player/fullscreen.native.tsx +12 -3
  56. package/src/components/mobile-player/fullscreen.tsx +10 -3
  57. package/src/components/mobile-player/player.tsx +28 -36
  58. package/src/components/mobile-player/props.tsx +1 -0
  59. package/src/components/mobile-player/ui/index.ts +2 -0
  60. package/src/components/mobile-player/ui/report-modal.tsx +195 -0
  61. package/src/components/mobile-player/ui/streamer-loading-overlay.tsx +154 -0
  62. package/src/components/mobile-player/ui/viewer-context-menu.tsx +31 -3
  63. package/src/components/mobile-player/ui/viewer-loading-overlay.tsx +66 -0
  64. package/src/components/mobile-player/use-webrtc.tsx +10 -2
  65. package/src/components/mobile-player/video-retry.tsx +28 -0
  66. package/src/components/mobile-player/video.native.tsx +24 -10
  67. package/src/components/mobile-player/video.tsx +100 -21
  68. package/src/components/share/sharesheet.tsx +185 -0
  69. package/src/components/ui/dialog.tsx +1 -1
  70. package/src/components/ui/dropdown.tsx +13 -13
  71. package/src/components/ui/index.ts +2 -0
  72. package/src/components/ui/primitives/modal.tsx +0 -1
  73. package/src/components/ui/resizeable.tsx +26 -15
  74. package/src/components/ui/slider.tsx +1 -0
  75. package/src/hooks/index.ts +1 -0
  76. package/src/hooks/usePointerDevice.ts +89 -0
  77. package/src/index.tsx +11 -2
  78. package/src/lib/system-messages.ts +135 -0
  79. package/src/livestream-store/chat.tsx +145 -17
  80. package/src/livestream-store/livestream-state.tsx +10 -0
  81. package/src/livestream-store/livestream-store.tsx +3 -0
  82. package/src/livestream-store/problems.tsx +96 -0
  83. package/src/livestream-store/websocket-consumer.tsx +44 -4
  84. package/src/player-store/player-state.tsx +25 -4
  85. package/src/player-store/player-store.tsx +43 -5
  86. package/src/streamplace-store/block.tsx +55 -13
  87. package/src/streamplace-store/stream.tsx +66 -35
  88. package/src/ui/index.ts +86 -0
  89. package/tsconfig.tsbuildinfo +1 -1
  90. package/node-compile-cache/v22.15.0-x64-92db9086-0/37be0eec +0 -0
  91. package/node-compile-cache/v22.15.0-x64-92db9086-0/56540125 +0 -0
@@ -51,6 +51,15 @@ function NativeVideo() {
51
51
  };
52
52
  }, [setStatus]);
53
53
  const player = (0, expo_video_1.useVideoPlayer)(url, (player) => {
54
+ player.addListener("playingChange", (newIsPlaying) => {
55
+ console.log("playingChange", newIsPlaying);
56
+ if (newIsPlaying) {
57
+ setStatus(__1.PlayerStatus.PLAYING);
58
+ }
59
+ else {
60
+ setStatus(__1.PlayerStatus.WAITING);
61
+ }
62
+ });
54
63
  player.loop = true;
55
64
  player.muted = muted;
56
65
  player.play();
@@ -72,10 +81,12 @@ function NativeVideo() {
72
81
  ].map((evType) => {
73
82
  return player.addListener(evType, (...args) => {
74
83
  const now = new Date();
84
+ console.log("video native event", evType);
75
85
  playerEvent(spurl, now.toISOString(), evType, { args: args });
76
86
  });
77
87
  });
78
88
  subs.push(player.addListener("playingChange", (newIsPlaying) => {
89
+ console.log("playingChange", newIsPlaying);
79
90
  if (newIsPlaying) {
80
91
  setStatus(__1.PlayerStatus.PLAYING);
81
92
  }
@@ -100,6 +111,7 @@ function NativeWHEP() {
100
111
  const src = (0, __1.usePlayerStore)((x) => x.src);
101
112
  const { url } = (0, shared_1.srcToUrl)({ src: src, selectedRendition }, __1.PlayerProtocol.WEBRTC);
102
113
  const [stream, stuck] = (0, use_webrtc_1.default)(url);
114
+ const status = (0, __1.usePlayerStore)((x) => x.status);
103
115
  const setPlayerWidth = (0, __1.usePlayerStore)((x) => x.setPlayerWidth);
104
116
  const setPlayerHeight = (0, __1.usePlayerStore)((x) => x.setPlayerHeight);
105
117
  // PiP support: wire up videoRef (no direct ref for RTCView)
@@ -116,21 +128,23 @@ function NativeWHEP() {
116
128
  const muted = (0, __1.usePlayerStore)((x) => x.muted);
117
129
  const volume = (0, __1.usePlayerStore)((x) => x.volume);
118
130
  (0, react_1.useEffect)(() => {
119
- if (stuck) {
131
+ if (stuck && status === __1.PlayerStatus.PLAYING) {
132
+ console.log("setting status to stalled", status);
120
133
  setStatus(__1.PlayerStatus.STALLED);
121
134
  }
122
- else {
135
+ if (!stuck && status === __1.PlayerStatus.STALLED) {
136
+ console.log("setting status to playing", status);
123
137
  setStatus(__1.PlayerStatus.PLAYING);
124
138
  }
125
- }, [stuck, setStatus]);
139
+ }, [stuck, status]);
126
140
  const mediaStream = stream;
127
- (0, react_1.useEffect)(() => {
128
- if (!mediaStream) {
129
- setStatus(__1.PlayerStatus.WAITING);
130
- return;
131
- }
132
- setStatus(__1.PlayerStatus.PLAYING);
133
- }, [mediaStream, setStatus]);
141
+ // useEffect(() => {
142
+ // if (!mediaStream) {
143
+ // setStatus(PlayerStatus.WAITING);
144
+ // return;
145
+ // }
146
+ // setStatus(PlayerStatus.PLAYING);
147
+ // }, [mediaStream, setStatus]);
134
148
  (0, react_1.useEffect)(() => {
135
149
  if (!mediaStream) {
136
150
  return;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ShareSheet = ShareSheet;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const lucide_react_native_1 = require("lucide-react-native");
6
+ const react_1 = require("react");
7
+ const react_native_1 = require("react-native");
8
+ const theme_1 = require("../../lib/theme");
9
+ const livestream_store_1 = require("../../livestream-store");
10
+ const streamplace_store_1 = require("../../streamplace-store");
11
+ const bluesky_icon_1 = require("../icons/bluesky-icon");
12
+ const ui_1 = require("../ui");
13
+ function ShareSheet({ onShare } = {}) {
14
+ const profile = (0, livestream_store_1.useLivestreamStore)((x) => x.profile);
15
+ const [isCopying, setIsCopying] = (0, react_1.useState)(false);
16
+ const url = (0, streamplace_store_1.useUrl)();
17
+ // Get the current stream URL
18
+ const getStreamUrl = (0, react_1.useCallback)(() => {
19
+ return url + (profile ? `/@${profile.handle}` : "");
20
+ }, [profile]);
21
+ // Get the embed URL
22
+ const getEmbedUrl = (0, react_1.useCallback)(() => {
23
+ return url + (profile ? `/embed/${profile.handle}` : "");
24
+ }, [profile]);
25
+ // Get embed code
26
+ const getEmbedCode = (0, react_1.useCallback)(() => {
27
+ const embedUrl = getEmbedUrl();
28
+ return `<iframe src="${embedUrl}" width="640" height="360" frameborder="0" allowfullscreen></iframe>`;
29
+ }, [getEmbedUrl]);
30
+ // Copy to clipboard handler
31
+ const copyToClipboard = (0, react_1.useCallback)(async (text, label) => {
32
+ setIsCopying(true);
33
+ try {
34
+ if (react_native_1.Platform.OS === "web") {
35
+ await navigator.clipboard.writeText(text);
36
+ }
37
+ else {
38
+ react_native_1.Clipboard.setString(text);
39
+ }
40
+ onShare?.(`copy_${label.toLowerCase().replace(/\s+/g, "_")}`, true);
41
+ }
42
+ catch (error) {
43
+ onShare?.(`copy_${label.toLowerCase().replace(/\s+/g, "_")}`, false);
44
+ }
45
+ finally {
46
+ setIsCopying(false);
47
+ }
48
+ }, [onShare]);
49
+ // Share to Bluesky
50
+ const shareToBluesky = (0, react_1.useCallback)(() => {
51
+ const streamUrl = getStreamUrl();
52
+ const text = profile
53
+ ? `Check out @${profile.handle} live on Streamplace! ${streamUrl}`
54
+ : `Check out this stream on Streamplace! ${streamUrl}`;
55
+ const blueskyUrl = `https://bsky.app/intent/compose?text=${encodeURIComponent(text)}`;
56
+ react_native_1.Linking.openURL(blueskyUrl);
57
+ onShare?.("share_bluesky", true);
58
+ }, [profile, getStreamUrl, onShare]);
59
+ // Share to Twitter/X
60
+ const shareToTwitter = (0, react_1.useCallback)(() => {
61
+ const streamUrl = getStreamUrl();
62
+ const text = profile
63
+ ? `Check out @${profile.handle} live on Streamplace!`
64
+ : `Check out this stream on Streamplace!`;
65
+ const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(streamUrl)}`;
66
+ react_native_1.Linking.openURL(twitterUrl);
67
+ onShare?.("share_twitter", true);
68
+ }, [profile, getStreamUrl, onShare]);
69
+ // Native share (mobile)
70
+ const nativeShare = (0, react_1.useCallback)(async () => {
71
+ const streamUrl = getStreamUrl();
72
+ const text = profile
73
+ ? `Check out @${profile.handle} live on Streamplace!`
74
+ : `Check out this stream on Streamplace!`;
75
+ if (react_native_1.Platform.OS === "web" && navigator.share) {
76
+ try {
77
+ await navigator.share({
78
+ title: "Streamplace",
79
+ text: text,
80
+ url: streamUrl,
81
+ });
82
+ onShare?.("share_native", true);
83
+ }
84
+ catch (error) {
85
+ // User cancelled or error occurred
86
+ onShare?.("share_native", false);
87
+ }
88
+ }
89
+ }, [profile, getStreamUrl, onShare]);
90
+ return ((0, jsx_runtime_1.jsxs)(ui_1.DropdownMenu, { children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuTrigger, { children: (0, jsx_runtime_1.jsx)(lucide_react_native_1.Share2, { color: theme_1.colors.gray[200] }) }), (0, jsx_runtime_1.jsxs)(ui_1.ResponsiveDropdownMenuContent, { children: [(0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuGroup, { title: "Share", children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: shareToBluesky, closeOnPress: true, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [(0, jsx_runtime_1.jsx)(bluesky_icon_1.BlueskyIcon, { size: 20, color: theme_1.colors.gray[400] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Share to Bluesky" })] }) }), react_native_1.Platform.OS !== "web" || (navigator && navigator.share) ? ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: nativeShare, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Share2, { size: 20, color: theme_1.colors.gray[400] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "More Options..." })] }) })) : null] }), (0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuGroup, { title: "Copy", children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => copyToClipboard(getStreamUrl(), "Stream link"), disabled: isCopying, closeOnPress: true, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Link2, { size: 20, color: theme_1.colors.gray[400] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Copy Link" })] }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuSeparator, {}), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => copyToClipboard(getEmbedCode(), "Embed code"), disabled: isCopying, closeOnPress: true, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Code, { size: 20, color: theme_1.colors.gray[400] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Copy Embed Code" })] }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuSeparator, {}), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { closeOnPress: true, onPress: () => copyToClipboard(getEmbedUrl(), "Embed URL"), disabled: isCopying, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Copy, { size: 20, color: theme_1.colors.gray[400] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Copy Embed URL" })] }) })] })] })] }));
91
+ }
@@ -103,7 +103,7 @@ exports.DialogFooter = (0, react_1.forwardRef)(({ children, direction = "row", j
103
103
  exports.DialogFooter.displayName = "DialogFooter";
104
104
  // Dialog Close Icon component (Lucide X)
105
105
  const DialogCloseIcon = () => {
106
- return (0, jsx_runtime_1.jsx)(ThemedX, { size: "md", variant: "muted" });
106
+ return (0, jsx_runtime_1.jsx)(ThemedX, { size: "md", variant: "default" });
107
107
  };
108
108
  // Create theme-aware styles
109
109
  function createStyles(theme) {
@@ -23,7 +23,7 @@ exports.DropdownMenuBottomSheet = (0, react_1.forwardRef)(function DropdownMenuB
23
23
  const sheetRef = (0, react_1.useRef)(null);
24
24
  return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Portal, { hostName: portalHost, children: (0, jsx_runtime_1.jsx)(bottom_sheet_1.default, { ref: sheetRef,
25
25
  // why the heck is this 1-indexed
26
- index: open ? 3 : -1, snapPoints: snapPoints, enablePanDownToClose: true, onClose: () => onOpenChange?.(false), style: [overlayStyle], backgroundStyle: [atoms_1.bg.black, atoms_1.a.radius.all.md, atoms_1.a.shadows.md, atoms_1.p[1]], handleIndicatorStyle: [
26
+ index: open ? 3 : -1, snapPoints: snapPoints, enablePanDownToClose: true, enableDynamicSizing: true, enableContentPanningGesture: false, backdropComponent: ({ style }) => ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [style, react_native_1.StyleSheet.absoluteFill], onPress: () => onOpenChange?.(false) })), onClose: () => onOpenChange?.(false), style: [overlayStyle], backgroundStyle: [atoms_1.bg.black, atoms_1.a.radius.all.md, atoms_1.a.shadows.md, atoms_1.p[1]], handleIndicatorStyle: [
27
27
  atoms_1.a.sizes.width[12],
28
28
  atoms_1.a.sizes.height[1],
29
29
  atoms_1.bg.gray[500],
@@ -129,7 +129,7 @@ exports.DropdownMenuLabel = (0, react_1.forwardRef)(({ inset, ...props }, ref) =
129
129
  ], ...props }));
130
130
  });
131
131
  exports.DropdownMenuSeparator = (0, react_1.forwardRef)((props, ref) => {
132
- return ((0, jsx_runtime_1.jsx)(react_native_1.View, { ref: ref, style: [atoms_1.mx[2], atoms_1.h[0.5] || { height: 0.5 }, atoms_1.bg.gray[800]], ...props }));
132
+ return (0, jsx_runtime_1.jsx)(react_native_1.View, { ref: ref, style: [{ height: 0.5 }, atoms_1.bg.gray[800]], ...props });
133
133
  });
134
134
  function DropdownMenuShortcut(props) {
135
135
  return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
@@ -141,11 +141,11 @@ function DropdownMenuShortcut(props) {
141
141
  }
142
142
  exports.DropdownMenuGroup = (0, react_1.forwardRef)((props, ref) => {
143
143
  const { inset, title, children, ...rest } = props;
144
- return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [atoms_1.pt[2], inset ? atoms_1.gap[2] : atoms_1.gap[1]], ref: ref, ...rest, children: [title && ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [atoms_1.textColors.gray[400], atoms_1.pb[1], atoms_1.pl[2]], children: title })), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
144
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [atoms_1.pt[2], inset && atoms_1.gap[2]], ref: ref, ...rest, children: [title && ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [atoms_1.textColors.gray[400], atoms_1.pb[1], atoms_1.pl[2]], children: title })), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
145
145
  atoms_1.bg.gray[900],
146
- react_native_1.Platform.OS === "web" ? atoms_1.px[2] : atoms_1.p[2],
147
- atoms_1.gap[2],
148
- { borderRadius: atoms_1.borderRadius.lg, gap: 10 },
146
+ react_native_1.Platform.OS === "web" ? [atoms_1.px[2], atoms_1.py[1]] : atoms_1.p[2],
147
+ atoms_1.gap.all[1],
148
+ { borderRadius: atoms_1.borderRadius.lg },
149
149
  ], children: children })] }));
150
150
  });
151
151
  exports.DropdownMenuInfo = (0, react_1.forwardRef)(({ description, ...props }, ref) => {
@@ -15,7 +15,9 @@ tslib_1.__exportStar(require("./icons"), exports);
15
15
  tslib_1.__exportStar(require("./input"), exports);
16
16
  tslib_1.__exportStar(require("./loader"), exports);
17
17
  tslib_1.__exportStar(require("./resizeable"), exports);
18
+ tslib_1.__exportStar(require("./slider"), exports);
18
19
  tslib_1.__exportStar(require("./text"), exports);
20
+ tslib_1.__exportStar(require("./textarea"), exports);
19
21
  tslib_1.__exportStar(require("./toast"), exports);
20
22
  tslib_1.__exportStar(require("./view"), exports);
21
23
  // Component collections for easy importing
@@ -121,7 +121,6 @@ const primitiveStyles = react_native_1.StyleSheet.create({
121
121
  padding: 16,
122
122
  },
123
123
  content: {
124
- backgroundColor: "white",
125
124
  borderRadius: 8,
126
125
  overflow: "hidden",
127
126
  },
@@ -97,16 +97,25 @@ function Resizable({ startingPercentage, isPlayerRatioGreater, style = {}, child
97
97
  minWidth: "100%",
98
98
  },
99
99
  style,
100
- ], children: [(0, jsx_runtime_1.jsx)(view_1.View, { style: [atoms_1.layout.flex.row, atoms_1.layout.flex.justifyCenter], children: (0, jsx_runtime_1.jsx)(react_native_gesture_handler_1.GestureDetector, { gesture: panGesture, children: (0, jsx_runtime_1.jsx)(view_1.View, { hitSlop: { top: 20, bottom: 20, left: 20, right: 20 }, style: [
101
- atoms_1.w[32],
102
- {
103
- height: 6,
104
- transform: [{ translateY: -10 }],
105
- backgroundColor: "#eeeeee66",
106
- alignItems: "center",
107
- justifyContent: "center",
108
- borderRadius: 999,
109
- },
110
- ] }) }) }), children] })] }));
100
+ ], children: [(0, jsx_runtime_1.jsx)(view_1.View, { style: [atoms_1.layout.flex.row, atoms_1.layout.flex.justifyCenter, atoms_1.h[2]], children: (0, jsx_runtime_1.jsx)(react_native_gesture_handler_1.GestureDetector, { gesture: panGesture, children: (0, jsx_runtime_1.jsx)(view_1.View
101
+ // Make the touch area much larger, but keep the visible handle small
102
+ , {
103
+ // Make the touch area much larger, but keep the visible handle small
104
+ style: {
105
+ height: 30, // Large touch area
106
+ width: 120, // Wide enough for thumbs
107
+ alignItems: "center",
108
+ justifyContent: "center",
109
+ //backgroundColor: "rgba(0,255,255,0.1)",
110
+ transform: [{ translateY: -30 }],
111
+ }, children: (0, jsx_runtime_1.jsx)(view_1.View, { style: [
112
+ atoms_1.w[32],
113
+ {
114
+ height: 6,
115
+ backgroundColor: "#eeeeee66",
116
+ borderRadius: 999,
117
+ transform: [{ translateY: 5 }],
118
+ },
119
+ ] }) }) }) }), children] })] }));
111
120
  }
112
121
  Resizable.displayName = "ResizableChatSheet";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Slider = void 0;
4
+ const tslib_1 = require("tslib");
5
+ exports.Slider = tslib_1.__importStar(require("@rn-primitives/slider"));
@@ -9,5 +9,6 @@ tslib_1.__exportStar(require("./useKeyboardSlide"), exports);
9
9
  tslib_1.__exportStar(require("./useLivestreamInfo"), exports);
10
10
  tslib_1.__exportStar(require("./useOuterAndInnerDimensions"), exports);
11
11
  tslib_1.__exportStar(require("./usePlayerDimensions"), exports);
12
+ tslib_1.__exportStar(require("./usePointerDevice"), exports);
12
13
  tslib_1.__exportStar(require("./useSegmentDimensions"), exports);
13
14
  tslib_1.__exportStar(require("./useSegmentTiming"), exports);
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePointerDevice = usePointerDevice;
4
+ const react_1 = require("react");
5
+ const react_native_1 = require("react-native");
6
+ /**
7
+ * Hook to detect if the device is primarily mouse-driven vs touch-driven
8
+ * Uses CSS media queries to detect hover and pointer capabilities
9
+ */
10
+ function usePointerDevice() {
11
+ const [pointerDevice, setPointerDevice] = (0, react_1.useState)(() => {
12
+ // Default values for non-web platforms
13
+ if (react_native_1.Platform.OS !== "web") {
14
+ return {
15
+ hasHover: false,
16
+ hasFinePointer: false,
17
+ isMouseDriven: false,
18
+ isTouchDriven: true,
19
+ };
20
+ }
21
+ // Initial web detection
22
+ if (typeof window !== "undefined" && window.matchMedia) {
23
+ const hasHover = window.matchMedia("(hover: hover)").matches;
24
+ const hasFinePointer = window.matchMedia("(pointer: fine)").matches;
25
+ return {
26
+ hasHover,
27
+ hasFinePointer,
28
+ isMouseDriven: hasHover && hasFinePointer,
29
+ isTouchDriven: !hasHover || !hasFinePointer,
30
+ };
31
+ }
32
+ // Fallback for SSR or environments without matchMedia
33
+ return {
34
+ hasHover: false,
35
+ hasFinePointer: false,
36
+ isMouseDriven: false,
37
+ isTouchDriven: true,
38
+ };
39
+ });
40
+ (0, react_1.useEffect)(() => {
41
+ // Only run on web platforms
42
+ if (react_native_1.Platform.OS !== "web" ||
43
+ typeof window === "undefined" ||
44
+ !window.matchMedia) {
45
+ return;
46
+ }
47
+ const hoverQuery = window.matchMedia("(hover: hover)");
48
+ const pointerQuery = window.matchMedia("(pointer: fine)");
49
+ const updatePointerDevice = () => {
50
+ const hasHover = hoverQuery.matches;
51
+ const hasFinePointer = pointerQuery.matches;
52
+ setPointerDevice({
53
+ hasHover,
54
+ hasFinePointer,
55
+ isMouseDriven: hasHover && hasFinePointer,
56
+ isTouchDriven: !hasHover || !hasFinePointer,
57
+ });
58
+ };
59
+ // Set up listeners for media query changes
60
+ hoverQuery.addEventListener("change", updatePointerDevice);
61
+ pointerQuery.addEventListener("change", updatePointerDevice);
62
+ // Initial update
63
+ updatePointerDevice();
64
+ // Cleanup
65
+ return () => {
66
+ hoverQuery.removeEventListener("change", updatePointerDevice);
67
+ pointerQuery.removeEventListener("change", updatePointerDevice);
68
+ };
69
+ }, []);
70
+ return pointerDevice;
71
+ }
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.atoms = exports.theme = exports.ui = exports.PlayerUI = exports.Player = exports.usePlayerContext = exports.withPlayerProvider = exports.PlayerProvider = void 0;
3
+ 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);
@@ -18,8 +18,15 @@ Object.defineProperty(exports, "Player", { enumerable: true, get: function () {
18
18
  Object.defineProperty(exports, "PlayerUI", { enumerable: true, get: function () { return player_1.PlayerUI; } });
19
19
  exports.ui = tslib_1.__importStar(require("./components/ui"));
20
20
  tslib_1.__exportStar(require("./components/ui"), exports);
21
- exports.theme = tslib_1.__importStar(require("./lib/theme"));
22
- exports.atoms = tslib_1.__importStar(require("./lib/theme/atoms"));
21
+ exports.zero = tslib_1.__importStar(require("./ui"));
23
22
  tslib_1.__exportStar(require("./hooks"), exports);
23
+ // Theme system exports
24
+ tslib_1.__exportStar(require("./lib/theme"), exports);
24
25
  tslib_1.__exportStar(require("./components/chat/chat"), exports);
25
26
  tslib_1.__exportStar(require("./components/chat/chat-box"), exports);
27
+ tslib_1.__exportStar(require("./components/chat/system-message"), exports);
28
+ var video_retry_1 = require("./components/mobile-player/video-retry");
29
+ Object.defineProperty(exports, "VideoRetry", { enumerable: true, get: function () { return tslib_1.__importDefault(video_retry_1).default; } });
30
+ tslib_1.__exportStar(require("./lib/system-messages"), exports);
31
+ tslib_1.__exportStar(require("./components/share/sharesheet"), exports);
32
+ tslib_1.__exportStar(require("./components/keep-awake"), exports);
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSystemMessageMetadata = exports.getSystemMessageType = exports.isSystemMessage = exports.SystemMessages = exports.createSystemMessage = exports.SystemMessageType = void 0;
4
+ var SystemMessageType;
5
+ (function (SystemMessageType) {
6
+ SystemMessageType["stream_start"] = "stream_start";
7
+ SystemMessageType["stream_end"] = "stream_end";
8
+ SystemMessageType["notification"] = "notification";
9
+ })(SystemMessageType || (exports.SystemMessageType = SystemMessageType = {}));
10
+ /**
11
+ * Creates a system message with the proper structure
12
+ * @param type The type of system message
13
+ * @param text The message text
14
+ * @param metadata Optional metadata for the message
15
+ * @returns A properly formatted ChatMessageViewHydrated object
16
+ */
17
+ const createSystemMessage = (type, text, metadata, date = new Date()) => {
18
+ const now = date;
19
+ return {
20
+ uri: `at://did:sys:system/place.stream.chat.message/${now.getTime()}`,
21
+ cid: `system-${now.getTime()}`,
22
+ author: {
23
+ did: "did:sys:system",
24
+ handle: type, // Use handle to specify the type of system message
25
+ },
26
+ record: {
27
+ text,
28
+ createdAt: now.toISOString(),
29
+ streamer: "system",
30
+ $type: "place.stream.chat.message",
31
+ },
32
+ indexedAt: now.toISOString(),
33
+ chatProfile: {
34
+ color: { red: 128, green: 128, blue: 128 }, // Gray color for system messages
35
+ },
36
+ };
37
+ };
38
+ exports.createSystemMessage = createSystemMessage;
39
+ /**
40
+ * System message factory functions for common scenarios
41
+ */
42
+ exports.SystemMessages = {
43
+ streamStart: (streamerName) => (0, exports.createSystemMessage)(SystemMessageType.stream_start, `Now streaming - ${streamerName}`, {
44
+ streamerName,
45
+ }),
46
+ // technically, streams can't 'end' on Streamplace
47
+ // possibly we could use deleting or editing streams (`endedAt` param) for this?
48
+ streamEnd: (duration) => (0, exports.createSystemMessage)(SystemMessageType.stream_end, duration ? `Stream has ended. Duration: ${duration}` : "Stream has ended", { duration }),
49
+ notification: (message) => (0, exports.createSystemMessage)(SystemMessageType.notification, message),
50
+ };
51
+ /**
52
+ * Checks if a message is a system message
53
+ * @param message The message to check
54
+ * @returns True if the message is a system message
55
+ */
56
+ const isSystemMessage = (message) => {
57
+ return message.author.did === "did:sys:system";
58
+ };
59
+ exports.isSystemMessage = isSystemMessage;
60
+ /**
61
+ * Gets the system message type from a message
62
+ * @param message The message to check
63
+ * @returns The system message type or null if not a system message
64
+ */
65
+ const getSystemMessageType = (message) => {
66
+ if (!(0, exports.isSystemMessage)(message)) {
67
+ return null;
68
+ }
69
+ return message.author.handle;
70
+ };
71
+ exports.getSystemMessageType = getSystemMessageType;
72
+ /**
73
+ * Parses metadata from a system message based on its type
74
+ * @param message The system message to parse
75
+ * @returns The parsed metadata
76
+ */
77
+ const parseSystemMessageMetadata = (message) => {
78
+ const metadata = {};
79
+ const type = (0, exports.getSystemMessageType)(message);
80
+ const text = message.record.text;
81
+ if (!type)
82
+ return metadata;
83
+ switch (type) {
84
+ case "stream_end": {
85
+ const durationMatch = text.match(/Duration:\s*(\d+:\d+(?::\d+)?)/);
86
+ if (durationMatch) {
87
+ metadata.duration = durationMatch[1];
88
+ }
89
+ break;
90
+ }
91
+ case "stream_start": {
92
+ const streamerMatch = text.match(/^(.+?)\s+is now live!/);
93
+ if (streamerMatch) {
94
+ metadata.streamerName = streamerMatch[1];
95
+ }
96
+ break;
97
+ }
98
+ }
99
+ return metadata;
100
+ };
101
+ exports.parseSystemMessageMetadata = parseSystemMessageMetadata;