@streamplace/components 0.6.37 → 0.7.1

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 (142) hide show
  1. package/dist/components/chat/chat-box.js +109 -0
  2. package/dist/components/chat/chat-message.js +76 -0
  3. package/dist/components/chat/chat.js +56 -0
  4. package/dist/components/chat/mention-suggestions.js +39 -0
  5. package/dist/components/chat/mod-view.js +33 -0
  6. package/dist/components/mobile-player/fullscreen.js +69 -0
  7. package/dist/components/mobile-player/fullscreen.native.js +151 -0
  8. package/dist/components/mobile-player/player.js +103 -0
  9. package/dist/components/mobile-player/props.js +1 -0
  10. package/dist/components/mobile-player/shared.js +51 -0
  11. package/dist/components/mobile-player/ui/countdown.js +79 -0
  12. package/dist/components/mobile-player/ui/index.js +6 -0
  13. package/dist/components/mobile-player/ui/input.js +38 -0
  14. package/dist/components/mobile-player/ui/metrics.js +40 -0
  15. package/dist/components/mobile-player/ui/streamer-context-menu.js +4 -0
  16. package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -0
  17. package/dist/components/mobile-player/ui/viewers.js +19 -0
  18. package/dist/components/mobile-player/use-webrtc.js +232 -0
  19. package/dist/components/mobile-player/video.js +375 -0
  20. package/dist/components/mobile-player/video.native.js +255 -0
  21. package/dist/components/mobile-player/webrtc-diagnostics.js +106 -0
  22. package/dist/components/mobile-player/webrtc-primitives.js +25 -0
  23. package/dist/components/mobile-player/webrtc-primitives.native.js +1 -0
  24. package/dist/components/ui/button.js +220 -0
  25. package/dist/components/ui/dialog.js +203 -0
  26. package/dist/components/ui/dropdown.js +148 -0
  27. package/dist/components/ui/icons.js +22 -0
  28. package/dist/components/ui/index.js +22 -0
  29. package/dist/components/ui/input.js +202 -0
  30. package/dist/components/ui/loader.js +7 -0
  31. package/dist/components/ui/primitives/button.js +121 -0
  32. package/dist/components/ui/primitives/input.js +202 -0
  33. package/dist/components/ui/primitives/modal.js +203 -0
  34. package/dist/components/ui/primitives/text.js +286 -0
  35. package/dist/components/ui/resizeable.js +108 -0
  36. package/dist/components/ui/text.js +175 -0
  37. package/dist/components/ui/textarea.js +17 -0
  38. package/dist/components/ui/toast.js +129 -0
  39. package/dist/components/ui/view.js +250 -0
  40. package/dist/hooks/index.js +10 -0
  41. package/dist/hooks/useAvatars.js +32 -0
  42. package/dist/hooks/useCameraToggle.js +9 -0
  43. package/dist/hooks/useKeyboard.js +33 -0
  44. package/dist/hooks/useKeyboardSlide.js +11 -0
  45. package/dist/hooks/useLivestreamInfo.js +62 -0
  46. package/dist/hooks/useOuterAndInnerDimensions.js +27 -0
  47. package/dist/hooks/usePlayerDimensions.js +19 -0
  48. package/dist/hooks/useSegmentDimensions.js +14 -0
  49. package/dist/hooks/useSegmentTiming.js +62 -0
  50. package/dist/index.js +10 -0
  51. package/dist/lib/facet.js +88 -0
  52. package/dist/lib/theme/atoms.js +620 -0
  53. package/dist/lib/theme/atoms.types.js +5 -0
  54. package/dist/lib/theme/index.js +9 -0
  55. package/dist/lib/theme/theme.js +248 -0
  56. package/dist/lib/theme/tokens.js +383 -0
  57. package/dist/lib/utils.js +94 -0
  58. package/dist/livestream-provider/index.js +8 -3
  59. package/dist/livestream-store/chat.js +89 -65
  60. package/dist/livestream-store/index.js +1 -0
  61. package/dist/livestream-store/livestream-store.js +3 -0
  62. package/dist/livestream-store/stream-key.js +115 -0
  63. package/dist/player-store/player-provider.js +0 -1
  64. package/dist/player-store/player-store.js +13 -0
  65. package/dist/streamplace-store/block.js +23 -0
  66. package/dist/streamplace-store/index.js +1 -0
  67. package/dist/streamplace-store/stream.js +193 -0
  68. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  69. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
  70. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/67b1eb60 +0 -0
  71. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/7c275f90 +0 -0
  72. package/package.json +21 -5
  73. package/src/components/chat/chat-box.tsx +195 -0
  74. package/src/components/chat/chat-message.tsx +192 -0
  75. package/src/components/chat/chat.tsx +128 -0
  76. package/src/components/chat/mention-suggestions.tsx +71 -0
  77. package/src/components/chat/mod-view.tsx +118 -0
  78. package/src/components/mobile-player/fullscreen.native.tsx +193 -0
  79. package/src/components/mobile-player/fullscreen.tsx +79 -0
  80. package/src/components/mobile-player/player.tsx +134 -0
  81. package/src/components/mobile-player/props.tsx +11 -0
  82. package/src/components/mobile-player/shared.tsx +56 -0
  83. package/src/components/mobile-player/ui/countdown.tsx +119 -0
  84. package/src/components/mobile-player/ui/index.ts +6 -0
  85. package/src/components/mobile-player/ui/input.tsx +85 -0
  86. package/src/components/mobile-player/ui/metrics.tsx +69 -0
  87. package/src/components/mobile-player/ui/streamer-context-menu.tsx +3 -0
  88. package/src/components/mobile-player/ui/viewer-context-menu.tsx +71 -0
  89. package/src/components/mobile-player/ui/viewers.tsx +32 -0
  90. package/src/components/mobile-player/use-webrtc.tsx +282 -0
  91. package/src/components/mobile-player/video.native.tsx +405 -0
  92. package/src/components/mobile-player/video.tsx +557 -0
  93. package/src/components/mobile-player/webrtc-diagnostics.tsx +149 -0
  94. package/src/components/mobile-player/webrtc-primitives.native.tsx +6 -0
  95. package/src/components/mobile-player/webrtc-primitives.tsx +33 -0
  96. package/src/components/ui/button.tsx +309 -0
  97. package/src/components/ui/dialog.tsx +376 -0
  98. package/src/components/ui/dropdown.tsx +399 -0
  99. package/src/components/ui/icons.tsx +50 -0
  100. package/src/components/ui/index.ts +33 -0
  101. package/src/components/ui/input.tsx +350 -0
  102. package/src/components/ui/loader.tsx +9 -0
  103. package/src/components/ui/primitives/button.tsx +292 -0
  104. package/src/components/ui/primitives/input.tsx +422 -0
  105. package/src/components/ui/primitives/modal.tsx +421 -0
  106. package/src/components/ui/primitives/text.tsx +499 -0
  107. package/src/components/ui/resizeable.tsx +181 -0
  108. package/src/components/ui/text.tsx +330 -0
  109. package/src/components/ui/textarea.tsx +34 -0
  110. package/src/components/ui/toast.tsx +203 -0
  111. package/src/components/ui/view.tsx +344 -0
  112. package/src/hooks/index.ts +10 -0
  113. package/src/hooks/useAvatars.tsx +44 -0
  114. package/src/hooks/useCameraToggle.ts +12 -0
  115. package/src/hooks/useKeyboard.tsx +41 -0
  116. package/src/hooks/useKeyboardSlide.ts +12 -0
  117. package/src/hooks/useLivestreamInfo.ts +67 -0
  118. package/src/hooks/useOuterAndInnerDimensions.tsx +32 -0
  119. package/src/hooks/usePlayerDimensions.ts +23 -0
  120. package/src/hooks/useSegmentDimensions.tsx +18 -0
  121. package/src/hooks/useSegmentTiming.tsx +88 -0
  122. package/src/index.tsx +21 -0
  123. package/src/lib/facet.ts +131 -0
  124. package/src/lib/theme/atoms.ts +760 -0
  125. package/src/lib/theme/atoms.types.ts +258 -0
  126. package/src/lib/theme/index.ts +48 -0
  127. package/src/lib/theme/theme.tsx +436 -0
  128. package/src/lib/theme/tokens.ts +409 -0
  129. package/src/lib/utils.ts +132 -0
  130. package/src/livestream-provider/index.tsx +13 -2
  131. package/src/livestream-store/chat.tsx +115 -78
  132. package/src/livestream-store/index.tsx +1 -0
  133. package/src/livestream-store/livestream-state.tsx +3 -0
  134. package/src/livestream-store/livestream-store.tsx +3 -0
  135. package/src/livestream-store/stream-key.tsx +124 -0
  136. package/src/player-store/player-provider.tsx +0 -1
  137. package/src/player-store/player-state.tsx +28 -0
  138. package/src/player-store/player-store.tsx +22 -0
  139. package/src/streamplace-store/block.tsx +29 -0
  140. package/src/streamplace-store/index.tsx +1 -0
  141. package/src/streamplace-store/stream.tsx +262 -0
  142. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,405 @@
1
+ import { useVideoPlayer, VideoPlayerEvents, VideoView } from "expo-video";
2
+ import { ArrowRight } from "lucide-react-native";
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { LayoutChangeEvent, Linking } from "react-native";
5
+ import {
6
+ MediaStream,
7
+ RTCView,
8
+ RTCView as RTCViewIngest,
9
+ } from "react-native-webrtc";
10
+ import {
11
+ Button,
12
+ IngestMediaSource,
13
+ PlayerStatus as IngestPlayerStatus,
14
+ PlayerProtocol,
15
+ PlayerStatus,
16
+ Text,
17
+ usePlayerStore as useIngestPlayerStore,
18
+ usePlayerStore,
19
+ useStreamplaceStore,
20
+ View,
21
+ } from "../..";
22
+ import {
23
+ borderRadius,
24
+ colors,
25
+ fontWeight,
26
+ gap,
27
+ h,
28
+ layout,
29
+ m,
30
+ p,
31
+ } from "../../lib/theme/atoms";
32
+ import { srcToUrl } from "./shared";
33
+ import useWebRTC, { useWebRTCIngest } from "./use-webrtc";
34
+ import { mediaDevices, WebRTCMediaStream } from "./webrtc-primitives.native";
35
+
36
+ // Add NativeIngestPlayer to the switch below!
37
+ export default function VideoNative() {
38
+ const protocol = usePlayerStore((x) => x.protocol);
39
+ const ingest = usePlayerStore((x) => x.ingestConnectionState) != null;
40
+
41
+ return (
42
+ <View>
43
+ {ingest ? (
44
+ <NativeIngestPlayer />
45
+ ) : protocol === PlayerProtocol.WEBRTC ? (
46
+ <NativeWHEP />
47
+ ) : (
48
+ <NativeVideo />
49
+ )}
50
+ </View>
51
+ );
52
+ }
53
+
54
+ export function NativeVideo() {
55
+ const videoRef = useRef<VideoView | null>(null);
56
+ const protocol = usePlayerStore((x) => x.protocol);
57
+
58
+ const selectedRendition = usePlayerStore((x) => x.selectedRendition);
59
+ const src = usePlayerStore((x) => x.src);
60
+ const { url } = srcToUrl({ src: src, selectedRendition }, protocol);
61
+ const setStatus = usePlayerStore((x) => x.setStatus);
62
+ const muted = usePlayerStore((x) => x.muted);
63
+ const volume = usePlayerStore((x) => x.volume);
64
+ const setFullscreen = usePlayerStore((x) => x.setFullscreen);
65
+ const fullscreen = usePlayerStore((x) => x.fullscreen);
66
+ const playerEvent = usePlayerStore((x) => x.playerEvent);
67
+ const spurl = useStreamplaceStore((x) => x.url);
68
+
69
+ const setPlayerWidth = usePlayerStore((x) => x.setPlayerWidth);
70
+ const setPlayerHeight = usePlayerStore((x) => x.setPlayerHeight);
71
+
72
+ // State for live dimensions
73
+ const [dimensions, setDimensions] = useState<{
74
+ width: number;
75
+ height: number;
76
+ }>({ width: 0, height: 0 });
77
+
78
+ const handleLayout = useCallback((event: LayoutChangeEvent) => {
79
+ const { width, height } = event.nativeEvent.layout;
80
+ setDimensions({ width, height });
81
+ setPlayerWidth(width);
82
+ setPlayerHeight(height);
83
+ }, []);
84
+
85
+ useEffect(() => {
86
+ return () => {
87
+ setStatus(PlayerStatus.START);
88
+ };
89
+ }, [setStatus]);
90
+
91
+ const player = useVideoPlayer(url, (player) => {
92
+ player.loop = true;
93
+ player.muted = muted;
94
+ player.play();
95
+ });
96
+
97
+ useEffect(() => {
98
+ player.muted = muted;
99
+ }, [muted, player]);
100
+
101
+ useEffect(() => {
102
+ player.volume = volume;
103
+ }, [volume, player]);
104
+
105
+ useEffect(() => {
106
+ const subs = (
107
+ [
108
+ "playToEnd",
109
+ "playbackRateChange",
110
+ "playingChange",
111
+ "sourceChange",
112
+ "statusChange",
113
+ "volumeChange",
114
+ ] as (keyof VideoPlayerEvents)[]
115
+ ).map((evType) => {
116
+ return player.addListener(evType, (...args) => {
117
+ const now = new Date();
118
+ playerEvent(spurl, now.toISOString(), evType, { args: args });
119
+ });
120
+ });
121
+
122
+ subs.push(
123
+ player.addListener("playingChange", (newIsPlaying) => {
124
+ if (newIsPlaying) {
125
+ setStatus(PlayerStatus.PLAYING);
126
+ } else {
127
+ setStatus(PlayerStatus.WAITING);
128
+ }
129
+ }),
130
+ );
131
+
132
+ return () => {
133
+ for (const sub of subs) {
134
+ sub.remove();
135
+ }
136
+ };
137
+ }, [player, playerEvent, setStatus, spurl]);
138
+
139
+ return (
140
+ <>
141
+ <VideoView
142
+ ref={videoRef}
143
+ player={player}
144
+ allowsFullscreen
145
+ nativeControls={fullscreen}
146
+ onFullscreenEnter={() => {
147
+ setFullscreen(true);
148
+ }}
149
+ onFullscreenExit={() => {
150
+ setFullscreen(false);
151
+ }}
152
+ allowsPictureInPicture
153
+ onLayout={handleLayout}
154
+ />
155
+ </>
156
+ );
157
+ }
158
+
159
+ export function NativeWHEP() {
160
+ const selectedRendition = usePlayerStore((x) => x.selectedRendition);
161
+ const src = usePlayerStore((x) => x.src);
162
+ const { url } = srcToUrl(
163
+ { src: src, selectedRendition },
164
+ PlayerProtocol.WEBRTC,
165
+ );
166
+ const [stream, stuck] = useWebRTC(url);
167
+
168
+ const setPlayerWidth = usePlayerStore((x) => x.setPlayerWidth);
169
+ const setPlayerHeight = usePlayerStore((x) => x.setPlayerHeight);
170
+
171
+ // PiP support: wire up videoRef (no direct ref for RTCView)
172
+ const setVideoRef = usePlayerStore((x) => x.setVideoRef);
173
+
174
+ // State for live dimensions
175
+ const [dimensions, setDimensions] = useState<{
176
+ width: number;
177
+ height: number;
178
+ }>({ width: 0, height: 0 });
179
+
180
+ const handleLayout = useCallback((event: LayoutChangeEvent) => {
181
+ const { width, height } = event.nativeEvent.layout;
182
+ setDimensions({ width, height });
183
+ setPlayerWidth(width);
184
+ setPlayerHeight(height);
185
+ }, []);
186
+
187
+ const setStatus = usePlayerStore((x) => x.setStatus);
188
+ const muted = usePlayerStore((x) => x.muted);
189
+ const volume = usePlayerStore((x) => x.volume);
190
+
191
+ useEffect(() => {
192
+ if (stuck) {
193
+ setStatus(PlayerStatus.STALLED);
194
+ } else {
195
+ setStatus(PlayerStatus.PLAYING);
196
+ }
197
+ }, [stuck, setStatus]);
198
+
199
+ const mediaStream = stream as unknown as MediaStream;
200
+
201
+ useEffect(() => {
202
+ if (!mediaStream) {
203
+ setStatus(PlayerStatus.WAITING);
204
+ return;
205
+ }
206
+ setStatus(PlayerStatus.PLAYING);
207
+ }, [mediaStream, setStatus]);
208
+
209
+ useEffect(() => {
210
+ if (!mediaStream) {
211
+ return;
212
+ }
213
+ mediaStream.getTracks().forEach((track) => {
214
+ if (track.kind === "audio") {
215
+ track._setVolume(muted ? 0 : volume);
216
+ }
217
+ });
218
+ }, [mediaStream, muted, volume]);
219
+
220
+ // Keep the playerStore videoRef in sync for PiP (if possible)
221
+ useEffect(() => {
222
+ if (typeof setVideoRef === "function") {
223
+ setVideoRef(null); // No direct ref for RTCView, but keep API consistent
224
+ }
225
+ }, [setVideoRef]);
226
+
227
+ if (!mediaStream) {
228
+ return <View></View>;
229
+ }
230
+
231
+ return (
232
+ <>
233
+ <RTCView
234
+ mirror={false}
235
+ objectFit={"contain"}
236
+ streamURL={mediaStream.toURL()}
237
+ onLayout={handleLayout}
238
+ pictureInPictureEnabled={true}
239
+ autoStartPictureInPicture={true}
240
+ pictureInPicturePreferredSize={{
241
+ width: 160,
242
+ height: 90,
243
+ }}
244
+ style={{
245
+ minWidth: "100%",
246
+ minHeight: "100%",
247
+ flex: 1,
248
+ }}
249
+ />
250
+ </>
251
+ );
252
+ }
253
+
254
+ export function NativeIngestPlayer() {
255
+ const ingestStarting = useIngestPlayerStore((x) => x.ingestStarting);
256
+ const ingestMediaSource = useIngestPlayerStore((x) => x.ingestMediaSource);
257
+ const ingestAutoStart = useIngestPlayerStore((x) => x.ingestAutoStart);
258
+ const setStatus = useIngestPlayerStore((x) => x.setStatus);
259
+ const setVideoRef = usePlayerStore((x) => x.setVideoRef);
260
+
261
+ const [error, setError] = useState<Error | null>(null);
262
+
263
+ const ingestCamera = useIngestPlayerStore((x) => x.ingestCamera);
264
+
265
+ useEffect(() => {
266
+ setStatus(IngestPlayerStatus.PLAYING);
267
+ }, [setStatus]);
268
+
269
+ useEffect(() => {
270
+ if (typeof setVideoRef === "function") {
271
+ setVideoRef(null);
272
+ }
273
+ }, [setVideoRef]);
274
+
275
+ const url = useStreamplaceStore((x) => x.url);
276
+ const [lms, setLocalMediaStream] = useState<WebRTCMediaStream | null>(null);
277
+ const [, setRemoteMediaStream] = useWebRTCIngest({
278
+ endpoint: `${url}/api/ingest/webrtc`,
279
+ });
280
+
281
+ // Use lms directly as localMediaStream
282
+ const localMediaStream = lms;
283
+
284
+ useEffect(() => {
285
+ if (ingestMediaSource === IngestMediaSource.DISPLAY) {
286
+ mediaDevices
287
+ .getDisplayMedia()
288
+ .then((stream: WebRTCMediaStream) => {
289
+ console.log("display media", stream);
290
+ setLocalMediaStream(stream);
291
+ })
292
+ .catch((e: any) => {
293
+ console.log("error getting display media", e);
294
+ console.error("error getting display media", e);
295
+ });
296
+ } else {
297
+ mediaDevices
298
+ .getUserMedia({
299
+ audio: {
300
+ // deviceId: "audio-1",
301
+ // echoCancellation: true,
302
+ // autoGainControl: true,
303
+ // noiseSuppression: true,
304
+ // latency: false,
305
+ // channelCount: false,
306
+ },
307
+ video: {
308
+ facingMode: ingestCamera,
309
+ width: { min: 200, ideal: 1080, max: 2160 },
310
+ height: { min: 200, ideal: 1920, max: 3840 },
311
+ },
312
+ })
313
+ .then((stream: WebRTCMediaStream) => {
314
+ setLocalMediaStream(stream);
315
+
316
+ let errs: string[] = [];
317
+ if (stream.getAudioTracks().length === 0) {
318
+ console.warn("No audio tracks found in user media stream");
319
+ errs.push("microphone");
320
+ }
321
+ if (stream.getVideoTracks().length === 0) {
322
+ console.warn("No video tracks found in user media stream");
323
+ errs.push("camera");
324
+ }
325
+ if (errs.length > 0) {
326
+ setError(
327
+ new Error(
328
+ `We could not access your ${errs.join(" and ")}. To stream, you need to give us permission to access these.`,
329
+ ),
330
+ );
331
+ } else {
332
+ setError(null);
333
+ }
334
+ })
335
+ .catch((e: any) => {
336
+ console.error("error getting user media", e);
337
+ setError(
338
+ new Error(
339
+ "We could not access your camera or microphone. To stream, you need to give us permission to access these.",
340
+ ),
341
+ );
342
+ });
343
+ }
344
+ }, [ingestMediaSource, ingestCamera]);
345
+
346
+ useEffect(() => {
347
+ if (!ingestStarting && !ingestAutoStart) {
348
+ setRemoteMediaStream(null);
349
+ return;
350
+ }
351
+ if (!localMediaStream) {
352
+ return;
353
+ }
354
+ console.log("setting remote media stream", localMediaStream);
355
+ // @ts-expect-error: WebRTCMediaStream may not have all MediaStream properties, but is compatible for our use
356
+ setRemoteMediaStream(localMediaStream);
357
+ }, [localMediaStream, ingestStarting, ingestAutoStart, setRemoteMediaStream]);
358
+
359
+ if (!localMediaStream) {
360
+ return null;
361
+ }
362
+
363
+ if (error) {
364
+ return (
365
+ <View
366
+ backgroundColor={colors.destructive[900]}
367
+ style={[p[4], m[4], gap.all[2], { borderRadius: borderRadius.md }]}
368
+ >
369
+ <View>
370
+ <Text style={[fontWeight.semibold]} size="2xl">
371
+ Error encountered!
372
+ </Text>
373
+ </View>
374
+ <Text>{error.message}</Text>
375
+ {error.message.includes(
376
+ "To stream, you need to give us permission to access these.",
377
+ ) && (
378
+ <Button
379
+ onPress={Linking.openSettings}
380
+ style={[h[10]]}
381
+ variant="secondary"
382
+ >
383
+ <View style={[layout.flex.row, gap.all[1]]}>
384
+ <Text>Open Settings</Text> <ArrowRight color="white" size="18" />
385
+ </View>
386
+ </Button>
387
+ )}
388
+ </View>
389
+ );
390
+ }
391
+
392
+ return (
393
+ <RTCViewIngest
394
+ mirror={ingestCamera !== "environment"}
395
+ objectFit={"contain"}
396
+ streamURL={localMediaStream.toURL()}
397
+ zOrder={0}
398
+ style={{
399
+ minWidth: "100%",
400
+ minHeight: "100%",
401
+ flex: 1,
402
+ }}
403
+ />
404
+ );
405
+ }