@streamplace/components 0.7.14 → 0.7.17

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