@streamplace/components 0.7.26 → 0.7.29

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 (61) hide show
  1. package/dist/components/chat/chat-box.js +2 -2
  2. package/dist/components/chat/chat.js +1 -1
  3. package/dist/components/mobile-player/ui/autoplay-button.js +1 -0
  4. package/dist/components/mobile-player/ui/viewer-context-menu.js +1 -1
  5. package/dist/components/mobile-player/ui/viewer-loading-overlay.js +2 -2
  6. package/dist/components/mobile-player/use-webrtc.js +37 -1
  7. package/dist/components/mobile-player/video.native.js +10 -1
  8. package/dist/components/ui/button.js +107 -155
  9. package/dist/components/ui/dialog.js +83 -116
  10. package/dist/components/ui/dropdown.js +41 -18
  11. package/dist/components/ui/input.js +53 -128
  12. package/dist/components/ui/primitives/button.js +0 -2
  13. package/dist/components/ui/primitives/modal.js +2 -2
  14. package/dist/components/ui/primitives/text.js +48 -8
  15. package/dist/components/ui/text.js +37 -66
  16. package/dist/components/ui/toast.js +78 -40
  17. package/dist/components/ui/view.js +28 -41
  18. package/dist/crypto-polyfill.js +0 -0
  19. package/dist/crypto-polyfill.native.js +24 -0
  20. package/dist/index.js +1 -0
  21. package/dist/lib/theme/index.js +1 -2
  22. package/dist/lib/theme/theme.js +106 -54
  23. package/dist/lib/theme/tokens.js +94 -1
  24. package/dist/livestream-store/chat.js +0 -2
  25. package/dist/livestream-store/stream-key.js +1 -1
  26. package/dist/player-store/player-provider.js +10 -2
  27. package/dist/player-store/single-player-provider.js +1 -1
  28. package/dist/streamplace-store/stream.js +1 -1
  29. package/dist/ui/index.js +2 -3
  30. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  31. package/package.json +3 -2
  32. package/src/components/chat/chat-box.tsx +6 -3
  33. package/src/components/chat/chat.tsx +1 -0
  34. package/src/components/mobile-player/ui/autoplay-button.tsx +1 -0
  35. package/src/components/mobile-player/ui/viewer-context-menu.tsx +2 -2
  36. package/src/components/mobile-player/ui/viewer-loading-overlay.tsx +2 -2
  37. package/src/components/mobile-player/use-webrtc.tsx +41 -1
  38. package/src/components/mobile-player/video.native.tsx +19 -4
  39. package/src/components/ui/button.tsx +110 -172
  40. package/src/components/ui/dialog.tsx +96 -138
  41. package/src/components/ui/dropdown.tsx +60 -22
  42. package/src/components/ui/input.tsx +57 -144
  43. package/src/components/ui/primitives/button.tsx +0 -2
  44. package/src/components/ui/primitives/modal.tsx +0 -2
  45. package/src/components/ui/primitives/text.tsx +51 -8
  46. package/src/components/ui/text.tsx +42 -67
  47. package/src/components/ui/toast.tsx +108 -90
  48. package/src/components/ui/view.tsx +27 -41
  49. package/src/crypto-polyfill.native.tsx +24 -0
  50. package/src/crypto-polyfill.tsx +0 -0
  51. package/src/index.tsx +2 -0
  52. package/src/lib/theme/index.ts +0 -2
  53. package/src/lib/theme/theme.tsx +179 -72
  54. package/src/lib/theme/tokens.ts +97 -0
  55. package/src/livestream-store/chat.tsx +0 -3
  56. package/src/livestream-store/stream-key.tsx +1 -1
  57. package/src/player-store/player-provider.tsx +13 -1
  58. package/src/player-store/single-player-provider.tsx +1 -1
  59. package/src/streamplace-store/stream.tsx +1 -1
  60. package/src/ui/index.ts +0 -2
  61. package/tsconfig.tsbuildinfo +1 -1
@@ -304,6 +304,7 @@ export function Chat({
304
304
  updateCellsBatchingPeriod={50}
305
305
  onScroll={handleScroll}
306
306
  scrollEventThrottle={16}
307
+ nestedScrollEnabled={true}
307
308
  />
308
309
  <ModView />
309
310
  </View>
@@ -17,6 +17,7 @@ export function AutoplayButton() {
17
17
  .play()
18
18
  .then(() => {
19
19
  setAutoplayFailed(false);
20
+ setMuted(false);
20
21
  setUserInteraction();
21
22
  })
22
23
  .catch((err) => {
@@ -1,5 +1,5 @@
1
1
  import { useRootContext } from "@rn-primitives/dropdown-menu";
2
- import { Settings } from "lucide-react-native";
2
+ import { Menu } from "lucide-react-native";
3
3
  import { Platform, View } from "react-native";
4
4
  import { colors } from "../../../lib/theme";
5
5
  import { useLivestreamStore } from "../../../livestream-store";
@@ -57,7 +57,7 @@ export function ContextMenu({
57
57
  return (
58
58
  <DropdownMenu>
59
59
  <DropdownMenuTrigger>
60
- <Settings color={colors.gray[200]} />
60
+ <Menu color={colors.gray[200]} />
61
61
  </DropdownMenuTrigger>
62
62
  <Portal container={dropdownPortalContainer}>
63
63
  <DropdownMenuContent side="top" align="end">
@@ -15,7 +15,7 @@ import {
15
15
 
16
16
  export function ViewerLoadingOverlay() {
17
17
  const status = usePlayerStore((x) => x.status);
18
- const theme = useTheme();
18
+ const { theme, zero: zt } = useTheme();
19
19
  const opacity = useSharedValue(0);
20
20
 
21
21
  useEffect(() => {
@@ -42,7 +42,7 @@ export function ViewerLoadingOverlay() {
42
42
 
43
43
  let spinner = <Loader size="large" />;
44
44
  if (status === PlayerStatus.PAUSE) {
45
- spinner = <Play size="$12" color={theme.styles.text.primary["color"]} />;
45
+ spinner = <Play size="$12" color={theme.colors.foreground} />;
46
46
  }
47
47
 
48
48
  return (
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
+ import * as sdpTransform from "sdp-transform";
2
3
  import { PlayerStatus, usePlayerStore, useStreamKey } from "../..";
3
4
  import { RTCPeerConnection, RTCSessionDescription } from "./webrtc-primitives";
4
5
 
@@ -107,6 +108,13 @@ export async function negotiateConnectionWithClientOffer(
107
108
  offerToReceiveAudio: true,
108
109
  offerToReceiveVideo: true,
109
110
  });
111
+ if (!offer.sdp) {
112
+ throw Error("no SDP in offer");
113
+ }
114
+
115
+ const newSDP = forceStereoAudio(offer.sdp);
116
+
117
+ offer.sdp = newSDP;
110
118
  /** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setLocalDescription */
111
119
  await peerConnection.setLocalDescription(offer);
112
120
 
@@ -205,6 +213,11 @@ export function useWebRTCIngest({
205
213
  (x) => x.setIngestConnectionState,
206
214
  );
207
215
  const storedKey = useStreamKey();
216
+ useEffect(() => {
217
+ if (storedKey?.error) {
218
+ console.error("error creating stream key", storedKey.error);
219
+ }
220
+ }, [storedKey?.error]);
208
221
  const [peerConnection, setPeerConnection] =
209
222
  useState<RTCPeerConnection | null>(null);
210
223
 
@@ -249,7 +262,11 @@ export function useWebRTCIngest({
249
262
  });
250
263
 
251
264
  peerConnection.addEventListener("track", (ev) => {
252
- console.log(ev);
265
+ console.log(
266
+ `got peerconnection track with ${ev.track.kind}`,
267
+ ev.track.id,
268
+ );
269
+ // console.log(ev);
253
270
  });
254
271
 
255
272
  setPeerConnection(peerConnection);
@@ -288,3 +305,26 @@ export function useWebRTCIngest({
288
305
 
289
306
  return [mediaStream, setMediaStream];
290
307
  }
308
+
309
+ export function forceStereoAudio(sdp: string): string {
310
+ const parsedSDP = sdpTransform.parse(sdp);
311
+ const audioMedia = parsedSDP.media.find((m) => m.type === "audio");
312
+ if (!audioMedia) {
313
+ throw Error("no audio media in SDP");
314
+ }
315
+ const opusCodec = audioMedia.rtp.find((c) => c.codec === "opus");
316
+ if (!opusCodec) {
317
+ throw Error("no opus codec in SDP");
318
+ }
319
+ const opusFMTP = audioMedia.fmtp.find((c) => c.payload === opusCodec.payload);
320
+ if (!opusFMTP) {
321
+ throw Error("no opus fmtp in SDP");
322
+ }
323
+ const opusParams = sdpTransform.parseParams(opusFMTP.config);
324
+ opusParams.stereo = 1;
325
+ const newParams = Object.entries(opusParams)
326
+ .map(([k, v]) => `${k}=${v}`)
327
+ .join(";");
328
+ opusFMTP.config = newParams;
329
+ return sdpTransform.write(parsedSDP);
330
+ }
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useState } from "react";
2
- import { View } from "react-native";
2
+ import { Text, View } from "react-native";
3
3
  import { VideoNativeProps } from "./props";
4
4
 
5
5
  let importPromise: Promise<typeof import("./video-async.native")> | null = null;
@@ -13,12 +13,27 @@ export default function VideoNative(props: VideoNativeProps) {
13
13
  typeof import("./video-async.native") | null
14
14
  >(null);
15
15
 
16
+ const [error, setError] = useState<string | null>(null);
17
+
16
18
  useEffect(() => {
17
- importPromise?.then((module) => {
18
- setVideoNativeModule(module);
19
- });
19
+ importPromise
20
+ ?.then((module) => {
21
+ setVideoNativeModule(module);
22
+ })
23
+ .catch((err) => {
24
+ setError(err.message);
25
+ });
20
26
  }, []);
21
27
 
28
+ if (error) {
29
+ console.error(error);
30
+ return (
31
+ <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
32
+ <Text>{error}</Text>
33
+ </View>
34
+ );
35
+ }
36
+
22
37
  if (!videoNativeModule) {
23
38
  return <View></View>;
24
39
  }
@@ -1,8 +1,8 @@
1
1
  import { cva, type VariantProps } from "class-variance-authority";
2
2
  import React, { forwardRef, useMemo } from "react";
3
- import { ActivityIndicator, StyleSheet } from "react-native";
3
+ import { ActivityIndicator } from "react-native";
4
4
  import { useTheme } from "../../lib/theme/theme";
5
- import * as tokens from "../../lib/theme/tokens";
5
+ import * as zero from "../../ui";
6
6
  import { ButtonPrimitive, ButtonPrimitiveProps } from "./primitives/button";
7
7
  import { TextPrimitive } from "./primitives/text";
8
8
 
@@ -57,44 +57,117 @@ export const Button = forwardRef<any, ButtonProps>(
57
57
  },
58
58
  ref,
59
59
  ) => {
60
- const { theme } = useTheme();
60
+ const { zero: zt, icons } = useTheme();
61
61
 
62
- // Create dynamic styles based on theme
63
- const styles = useMemo(() => createStyles(theme), [theme]);
64
-
65
- // Get variant styles
62
+ // Get variant styles using theme.zero
66
63
  const buttonStyle = useMemo(() => {
67
- const variantStyle = styles[`${variant}Button` as keyof typeof styles];
68
- const sizeStyle = styles[`${size}Button` as keyof typeof styles];
69
- return [variantStyle, sizeStyle];
70
- }, [variant, size, styles]);
64
+ switch (variant) {
65
+ case "primary":
66
+ return zt.button.primary;
67
+ case "secondary":
68
+ return zt.button.secondary;
69
+ case "outline":
70
+ return zt.button.outline;
71
+ case "ghost":
72
+ return zt.button.ghost;
73
+ case "destructive":
74
+ return [zt.bg.destructive, zero.shadows.sm];
75
+ case "success":
76
+ return [zt.bg.success, zero.shadows.sm];
77
+ default:
78
+ return zt.button.primary;
79
+ }
80
+ }, [variant, zt]);
71
81
 
72
- // Get inner styles for button content
73
- const buttonInnerStyle = useMemo(() => {
74
- const sizeInnerStyle =
75
- styles[`${size}ButtonInner` as keyof typeof styles];
76
- return sizeInnerStyle;
77
- }, [size, styles]);
82
+ // Get text styles using theme.zero
83
+ const textStyle = useMemo(() => {
84
+ switch (variant) {
85
+ case "primary":
86
+ return [zt.text.primaryForeground, { fontWeight: "600" }];
87
+ case "secondary":
88
+ return [zt.text.secondaryForeground, { fontWeight: "500" }];
89
+ case "outline":
90
+ case "ghost":
91
+ return [zt.text.foreground, { fontWeight: "500" }];
92
+ case "destructive":
93
+ return [zt.text.destructiveForeground, { fontWeight: "600" }];
94
+ case "success":
95
+ return [zt.text.successForeground, { fontWeight: "600" }];
96
+ default:
97
+ return [zt.text.primaryForeground, { fontWeight: "600" }];
98
+ }
99
+ }, [variant, zt]);
78
100
 
79
- const textStyle = React.useMemo(() => {
80
- const variantTextStyle = styles[`${variant}Text` as keyof typeof styles];
81
- const sizeTextStyle = styles[`${size}Text` as keyof typeof styles];
82
- return [variantTextStyle, sizeTextStyle];
83
- }, [variant, size, styles]);
101
+ // Size styles using theme.zero
102
+ const sizeStyles = useMemo(() => {
103
+ switch (size) {
104
+ case "sm":
105
+ return {
106
+ button: [
107
+ zero.px[3],
108
+ zero.py[2],
109
+ { borderRadius: zero.borderRadius.md },
110
+ ],
111
+ inner: { gap: 4 },
112
+ text: zt.text.sm,
113
+ };
114
+ case "lg":
115
+ return {
116
+ button: [
117
+ zero.px[6],
118
+ zero.py[3],
119
+ { borderRadius: zero.borderRadius.md },
120
+ ],
121
+ inner: { gap: 8 },
122
+ text: zt.text.lg,
123
+ };
124
+ case "xl":
125
+ return {
126
+ button: [
127
+ zero.px[8],
128
+ zero.py[4],
129
+ { borderRadius: zero.borderRadius.lg },
130
+ ],
131
+ inner: { gap: 12 },
132
+ text: zt.text.xl,
133
+ };
134
+ case "pill":
135
+ return {
136
+ button: [
137
+ zero.px[4],
138
+ zero.py[2],
139
+ { borderRadius: zero.borderRadius.full },
140
+ ],
141
+ inner: { gap: 4 },
142
+ text: zt.text.sm,
143
+ };
144
+ case "md":
145
+ default:
146
+ return {
147
+ button: [
148
+ zero.px[4],
149
+ zero.py[2],
150
+ { borderRadius: zero.borderRadius.md },
151
+ ],
152
+ inner: { gap: 6 },
153
+ text: zt.text.md,
154
+ };
155
+ }
156
+ }, [size, zt]);
84
157
 
85
158
  const iconSize = React.useMemo(() => {
86
159
  switch (size) {
87
160
  case "sm":
88
- return 16;
161
+ return icons.size.sm;
89
162
  case "lg":
90
- return 20;
163
+ return icons.size.lg;
91
164
  case "xl":
92
- return 24;
165
+ return icons.size.xl;
93
166
  case "md":
94
167
  default:
95
- return 18;
168
+ return icons.size.md;
96
169
  }
97
- }, [size]);
170
+ }, [size, icons]);
98
171
 
99
172
  const spinnerSize = useMemo(() => {
100
173
  switch (size) {
@@ -113,24 +186,26 @@ export const Button = forwardRef<any, ButtonProps>(
113
186
  switch (variant) {
114
187
  case "outline":
115
188
  case "ghost":
116
- return theme.colors.primary;
189
+ return icons.color.primary;
117
190
  case "secondary":
118
- return theme.colors.secondaryForeground;
191
+ return icons.color.secondary;
119
192
  case "destructive":
120
- return theme.colors.destructiveForeground;
193
+ return icons.color.destructive;
194
+ case "success":
195
+ return icons.color.success;
121
196
  default:
122
- return theme.colors.primaryForeground;
197
+ return icons.color.default;
123
198
  }
124
- }, [variant, theme.colors]);
199
+ }, [variant, icons]);
125
200
 
126
201
  return (
127
202
  <ButtonPrimitive.Root
128
203
  ref={ref}
129
204
  disabled={disabled || loading}
130
- style={[buttonStyle, style]}
205
+ style={[buttonStyle, sizeStyles.button, style]}
131
206
  {...props}
132
207
  >
133
- <ButtonPrimitive.Content style={buttonInnerStyle}>
208
+ <ButtonPrimitive.Content style={sizeStyles.inner}>
134
209
  {loading && !leftIcon ? (
135
210
  <ButtonPrimitive.Icon position="left">
136
211
  <ActivityIndicator size={spinnerSize} color={spinnerColor} />
@@ -144,7 +219,7 @@ export const Button = forwardRef<any, ButtonProps>(
144
219
  </ButtonPrimitive.Icon>
145
220
  ) : null}
146
221
 
147
- <TextPrimitive.Root style={textStyle}>
222
+ <TextPrimitive.Root style={[textStyle, sizeStyles.text]}>
148
223
  {loading && loadingText ? loadingText : children}
149
224
  </TextPrimitive.Root>
150
225
 
@@ -168,142 +243,5 @@ export const Button = forwardRef<any, ButtonProps>(
168
243
 
169
244
  Button.displayName = "Button";
170
245
 
171
- // Create theme-based styles
172
- function createStyles(theme: any) {
173
- return StyleSheet.create({
174
- // Variant styles
175
- primaryButton: {
176
- backgroundColor: theme.colors.primary,
177
- borderWidth: 0,
178
- ...theme.shadows.sm,
179
- },
180
- primaryText: {
181
- color: theme.colors.primaryForeground,
182
- fontWeight: "600",
183
- },
184
-
185
- secondaryButton: {
186
- backgroundColor: theme.colors.secondary,
187
- borderWidth: 0,
188
- },
189
- secondaryText: {
190
- color: theme.colors.secondaryForeground,
191
- fontWeight: "500",
192
- },
193
-
194
- outlineButton: {
195
- backgroundColor: "transparent",
196
- borderWidth: 1,
197
- borderColor: theme.colors.border,
198
- },
199
- outlineText: {
200
- color: theme.colors.foreground,
201
- fontWeight: "500",
202
- },
203
-
204
- ghostButton: {
205
- backgroundColor: "transparent",
206
- borderWidth: 0,
207
- },
208
- ghostText: {
209
- color: theme.colors.foreground,
210
- fontWeight: "500",
211
- },
212
-
213
- destructiveButton: {
214
- backgroundColor: theme.colors.destructive,
215
- borderWidth: 0,
216
- ...theme.shadows.sm,
217
- },
218
- destructiveText: {
219
- color: theme.colors.destructiveForeground,
220
- fontWeight: "600",
221
- },
222
-
223
- successButton: {
224
- backgroundColor: theme.colors.success,
225
- borderWidth: 0,
226
- ...theme.shadows.sm,
227
- },
228
- successText: {
229
- color: theme.colors.successForeground,
230
- fontWeight: "600",
231
- },
232
-
233
- pillButton: {
234
- paddingHorizontal: theme.spacing[2],
235
- paddingVertical: theme.spacing[1],
236
- borderRadius: tokens.borderRadius.full,
237
- minHeight: tokens.touchTargets.minimum / 2,
238
- },
239
-
240
- pillText: {
241
- color: theme.colors.primaryForeground,
242
- fontWeight: "400",
243
- },
244
-
245
- // Size styles
246
- smButton: {
247
- paddingHorizontal: theme.spacing[3],
248
- paddingVertical: theme.spacing[2],
249
- borderRadius: tokens.borderRadius.md,
250
- minHeight: tokens.touchTargets.minimum,
251
- gap: theme.spacing[1],
252
- },
253
- smButtonInner: {
254
- gap: theme.spacing[1],
255
- },
256
- smText: {
257
- fontSize: 14,
258
- lineHeight: 16,
259
- },
260
-
261
- mdButton: {
262
- paddingHorizontal: theme.spacing[4],
263
- paddingVertical: theme.spacing[3],
264
- borderRadius: tokens.borderRadius.md,
265
- minHeight: tokens.touchTargets.minimum,
266
- gap: theme.spacing[2],
267
- },
268
- mdButtonInner: {
269
- gap: theme.spacing[2],
270
- },
271
- mdText: {
272
- fontSize: 16,
273
- lineHeight: 18,
274
- },
275
-
276
- lgButton: {
277
- paddingHorizontal: theme.spacing[6],
278
- paddingVertical: theme.spacing[4],
279
- borderRadius: tokens.borderRadius.md,
280
- minHeight: tokens.touchTargets.comfortable,
281
- gap: theme.spacing[3],
282
- },
283
- lgButtonInner: {
284
- gap: theme.spacing[3],
285
- },
286
- lgText: {
287
- fontSize: 18,
288
- lineHeight: 20,
289
- },
290
-
291
- xlButton: {
292
- paddingHorizontal: theme.spacing[8],
293
- paddingVertical: theme.spacing[5],
294
- borderRadius: tokens.borderRadius.lg,
295
- minHeight: tokens.touchTargets.large,
296
- gap: theme.spacing[4],
297
- },
298
- xlButtonInner: {
299
- gap: theme.spacing[4],
300
- },
301
- xlText: {
302
- fontSize: 20,
303
- lineHeight: 24,
304
- },
305
- });
306
- }
307
-
308
246
  // Export button variants for external use
309
247
  export { buttonVariants };