@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.
- package/package.json +13 -15
- package/src/components/mobile-player/fullscreen.native.tsx +14 -3
- package/src/components/mobile-player/fullscreen.tsx +10 -2
- package/src/components/mobile-player/player.tsx +7 -1
- package/src/components/mobile-player/props.tsx +7 -0
- package/src/components/mobile-player/video-async.native.tsx +436 -0
- package/src/components/mobile-player/video.native.tsx +16 -406
- package/src/components/mobile-player/video.tsx +14 -3
- package/src/hooks/useLivestreamInfo.ts +6 -2
- package/src/lib/browser.ts +27 -0
- package/src/livestream-store/stream-key.tsx +1 -28
- package/src/streamplace-store/stream.tsx +52 -13
- package/dist/assets/emoji-data.json +0 -19371
- package/dist/components/chat/chat-box.js +0 -314
- package/dist/components/chat/chat-message.js +0 -87
- package/dist/components/chat/chat.js +0 -149
- package/dist/components/chat/emoji-suggestions.js +0 -35
- package/dist/components/chat/mention-suggestions.js +0 -42
- package/dist/components/chat/mod-view.js +0 -94
- package/dist/components/chat/system-message.js +0 -19
- package/dist/components/dashboard/chat-panel.js +0 -38
- package/dist/components/dashboard/header.js +0 -80
- package/dist/components/dashboard/index.js +0 -14
- package/dist/components/dashboard/information-widget.js +0 -234
- package/dist/components/dashboard/mod-actions.js +0 -71
- package/dist/components/dashboard/problems.js +0 -74
- package/dist/components/icons/bluesky-icon.js +0 -9
- package/dist/components/keep-awake.js +0 -7
- package/dist/components/keep-awake.native.js +0 -16
- package/dist/components/mobile-player/fullscreen.js +0 -74
- package/dist/components/mobile-player/fullscreen.native.js +0 -141
- package/dist/components/mobile-player/player.js +0 -94
- package/dist/components/mobile-player/props.js +0 -2
- package/dist/components/mobile-player/shared.js +0 -54
- package/dist/components/mobile-player/ui/countdown.js +0 -83
- package/dist/components/mobile-player/ui/index.js +0 -11
- package/dist/components/mobile-player/ui/input.js +0 -42
- package/dist/components/mobile-player/ui/metrics.js +0 -44
- package/dist/components/mobile-player/ui/report-modal.js +0 -90
- package/dist/components/mobile-player/ui/streamer-context-menu.js +0 -7
- package/dist/components/mobile-player/ui/streamer-loading-overlay.js +0 -104
- package/dist/components/mobile-player/ui/viewer-context-menu.js +0 -51
- package/dist/components/mobile-player/ui/viewer-loading-overlay.js +0 -49
- package/dist/components/mobile-player/ui/viewers.js +0 -23
- package/dist/components/mobile-player/use-webrtc.js +0 -243
- package/dist/components/mobile-player/video-retry.js +0 -29
- package/dist/components/mobile-player/video.js +0 -460
- package/dist/components/mobile-player/video.native.js +0 -276
- package/dist/components/mobile-player/webrtc-diagnostics.js +0 -110
- package/dist/components/mobile-player/webrtc-primitives.js +0 -27
- package/dist/components/mobile-player/webrtc-primitives.native.js +0 -8
- package/dist/components/share/sharesheet.js +0 -91
- package/dist/components/ui/button.js +0 -223
- package/dist/components/ui/dialog.js +0 -206
- package/dist/components/ui/dropdown.js +0 -172
- package/dist/components/ui/icons.js +0 -25
- package/dist/components/ui/index.js +0 -34
- package/dist/components/ui/info-box.js +0 -31
- package/dist/components/ui/info-row.js +0 -23
- package/dist/components/ui/input.js +0 -205
- package/dist/components/ui/loader.js +0 -10
- package/dist/components/ui/primitives/button.js +0 -125
- package/dist/components/ui/primitives/input.js +0 -206
- package/dist/components/ui/primitives/modal.js +0 -206
- package/dist/components/ui/primitives/text.js +0 -292
- package/dist/components/ui/resizeable.js +0 -121
- package/dist/components/ui/slider.js +0 -5
- package/dist/components/ui/text.js +0 -177
- package/dist/components/ui/textarea.js +0 -19
- package/dist/components/ui/toast.js +0 -175
- package/dist/components/ui/view.js +0 -252
- package/dist/hooks/index.js +0 -14
- package/dist/hooks/useAvatars.js +0 -35
- package/dist/hooks/useCameraToggle.js +0 -12
- package/dist/hooks/useKeyboard.js +0 -36
- package/dist/hooks/useKeyboardSlide.js +0 -14
- package/dist/hooks/useLivestreamInfo.js +0 -65
- package/dist/hooks/useOuterAndInnerDimensions.js +0 -30
- package/dist/hooks/usePlayerDimensions.js +0 -22
- package/dist/hooks/usePointerDevice.js +0 -71
- package/dist/hooks/useSegmentDimensions.js +0 -17
- package/dist/hooks/useSegmentTiming.js +0 -65
- package/dist/index.js +0 -34
- package/dist/lib/facet.js +0 -92
- package/dist/lib/system-messages.js +0 -101
- package/dist/lib/theme/atoms.js +0 -646
- package/dist/lib/theme/atoms.types.js +0 -6
- package/dist/lib/theme/index.js +0 -35
- package/dist/lib/theme/theme.js +0 -256
- package/dist/lib/theme/tokens.js +0 -659
- package/dist/lib/utils.js +0 -105
- package/dist/livestream-provider/index.js +0 -30
- package/dist/livestream-provider/websocket.js +0 -45
- package/dist/livestream-store/chat.js +0 -286
- package/dist/livestream-store/context.js +0 -5
- package/dist/livestream-store/index.js +0 -7
- package/dist/livestream-store/livestream-state.js +0 -2
- package/dist/livestream-store/livestream-store.js +0 -58
- package/dist/livestream-store/problems.js +0 -76
- package/dist/livestream-store/stream-key.js +0 -119
- package/dist/livestream-store/websocket-consumer.js +0 -94
- package/dist/player-store/context.js +0 -5
- package/dist/player-store/index.js +0 -9
- package/dist/player-store/player-provider.js +0 -57
- package/dist/player-store/player-state.js +0 -25
- package/dist/player-store/player-store.js +0 -199
- package/dist/player-store/single-player-provider.js +0 -121
- package/dist/streamplace-provider/context.js +0 -5
- package/dist/streamplace-provider/index.js +0 -20
- package/dist/streamplace-provider/poller.js +0 -49
- package/dist/streamplace-provider/xrpc.js +0 -0
- package/dist/streamplace-store/block.js +0 -65
- package/dist/streamplace-store/index.js +0 -6
- package/dist/streamplace-store/stream.js +0 -218
- package/dist/streamplace-store/streamplace-store.js +0 -47
- package/dist/streamplace-store/user.js +0 -52
- package/dist/streamplace-store/xrpc.js +0 -15
- package/dist/ui/index.js +0 -79
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/67b1eb60 +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/7c275f90 +0 -0
- package/tsconfig.tsbuildinfo +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamplace/components",
|
|
3
|
-
"version": "0.7.
|
|
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": "
|
|
38
|
-
"expo-video": "
|
|
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.
|
|
35
|
+
"react-native": "^0.79.0",
|
|
42
36
|
"react-native-edge-to-edge": "^1.6.2",
|
|
43
|
-
"react-native-gesture-handler": "
|
|
44
|
-
"react-native-reanimated": "
|
|
45
|
-
"react-native-safe-area-context": "5.
|
|
46
|
-
"react-native-svg": "15.
|
|
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
|
-
"
|
|
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: {
|
|
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: {
|
|
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
|
|
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
|
+
}
|