···1-export { RTCPeerConnection, RTCSessionDescription } from "react-native-webrtc";
00000
···1+export {
2+ RTCPeerConnection,
3+ RTCSessionDescription,
4+ MediaStream as WebRTCMediaStream,
5+ mediaDevices,
6+} from "react-native-webrtc";
+2
js/app/components/player/webrtc-primitives.tsx
···2627export const RTCPeerConnection = window.RTCPeerConnection;
28export const RTCSessionDescription = window.RTCSessionDescription;
002930// Export the compatibility checker for use in other components
31export { checkWebRTCSupport };
···2627export const RTCPeerConnection = window.RTCPeerConnection;
28export const RTCSessionDescription = window.RTCSessionDescription;
29+export const WebRTCMediaStream = window.MediaStream;
30+export const mediaDevices = navigator.mediaDevices;
3132// Export the compatibility checker for use in other components
33export { checkWebRTCSupport };
+37-17
js/app/features/bluesky/blueskySlice.tsx
···8} from "@atproto/api";
9import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
10import { bytesToMultibase, Secp256k1Keypair } from "@atproto/crypto";
11-import { OAuthSession } from "@atproto/oauth-client";
12import { DID_KEY, hydrate, STORED_KEY_KEY } from "features/base/baseSlice";
13import { openLoginLink } from "features/platform/platformSlice";
14import {
···885 }
886 } else {
887 // No custom thumbnail: fetch the server-side image and upload it
00888 try {
889- const thumbnailRes = await fetch(
890- `${u.protocol}//${u.host}/api/playback/${profile.handle}/stream.png`,
891- );
892- if (!thumbnailRes.ok) {
893- throw new Error(
894- `Failed to fetch thumbnail: ${thumbnailRes.status})`,
895- );
000000000000000000000000000896 }
897- const thumbnailBlob = await thumbnailRes.blob();
898- console.log(thumbnailBlob);
899- thumbnail = await uploadThumbnail(
900- profile.handle,
901- u,
902- bluesky.pdsAgent,
903- profile,
904- thumbnailBlob,
905- );
906 } catch (e) {
907 throw new Error(`Thumbnail upload failed ${e}`);
908 }
···8} from "@atproto/api";
9import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
10import { bytesToMultibase, Secp256k1Keypair } from "@atproto/crypto";
11+import { OAuthSession } from "@streamplace/atproto-oauth-client-react-native";
12import { DID_KEY, hydrate, STORED_KEY_KEY } from "features/base/baseSlice";
13import { openLoginLink } from "features/platform/platformSlice";
14import {
···885 }
886 } else {
887 // No custom thumbnail: fetch the server-side image and upload it
888+ // try thrice lel
889+ let tries = 0;
890 try {
891+ for (; tries < 3; tries++) {
892+ try {
893+ console.log(
894+ `Fetching thumbnail from ${u.protocol}//${u.host}/api/playback/${profile.handle}/stream.png`,
895+ );
896+ const thumbnailRes = await fetch(
897+ `${u.protocol}//${u.host}/api/playback/${profile.handle}/stream.png`,
898+ );
899+ if (!thumbnailRes.ok) {
900+ throw new Error(
901+ `Failed to fetch thumbnail: ${thumbnailRes.status})`,
902+ );
903+ }
904+ const thumbnailBlob = await thumbnailRes.blob();
905+ console.log(thumbnailBlob);
906+ thumbnail = await uploadThumbnail(
907+ profile.handle,
908+ u,
909+ bluesky.pdsAgent,
910+ profile,
911+ thumbnailBlob,
912+ );
913+ } catch (e) {
914+ console.warn(
915+ `Failed to fetch thumbnail, retrying (${tries + 1}/3): ${e}`,
916+ );
917+ // Wait 1 second before retrying
918+ await new Promise((resolve) => setTimeout(resolve, 2000));
919+ if (tries === 2) {
920+ throw new Error(
921+ `Failed to fetch thumbnail after 3 tries: ${e}`,
922+ );
923+ }
924+ }
925 }
000000000926 } catch (e) {
927 throw new Error(`Thumbnail upload failed ${e}`);
928 }
···1+import { theme } from "@streamplace/components";
2+import { Player } from "components/mobile/player";
3+import { FullscreenProvider } from "contexts/FullscreenContext";
4+import { selectUserProfile } from "features/bluesky/blueskySlice";
5+import { useAppSelector } from "store/hooks";
6+import { Text } from "tamagui";
7+8+export default function MobileGoLive() {
9+ const userProfile = useAppSelector(selectUserProfile);
10+11+ if (!userProfile) {
12+ // If user profile is not available, redirect to login or show an error
13+ return <Text>You need to log in to go live!</Text>;
14+ }
15+ // get player
16+ return (
17+ <theme.ThemeProvider>
18+ <FullscreenProvider>
19+ <Player ingest src={userProfile.did} name={userProfile.handle} />
20+ </FullscreenProvider>
21+ </theme.ThemeProvider>
22+ );
23+}
+30
js/app/src/screens/mobile-stream.tsx
···000000000000000000000000000000
···1+import { useNavigation } from "@react-navigation/native";
2+import { theme } from "@streamplace/components";
3+import { Player } from "components/mobile/player";
4+import { PlayerProps } from "components/player/props";
5+import { FullscreenProvider } from "contexts/FullscreenContext";
6+import { isWeb } from "tamagui";
7+import { queryToProps } from "./util";
8+9+export default function MobileStream({ route }) {
10+ const { user, protocol, url } = route.params;
11+ const navigation = useNavigation();
12+ let extraProps: Partial<PlayerProps> = {};
13+ if (isWeb) {
14+ extraProps = queryToProps(new URLSearchParams(window.location.search));
15+ }
16+ let src = user;
17+ if (user === "stream") {
18+ src = url;
19+ }
20+21+ console.log(src);
22+23+ return (
24+ <theme.ThemeProvider>
25+ <FullscreenProvider>
26+ <Player src={src} {...extraProps} />
27+ </FullscreenProvider>
28+ </theme.ThemeProvider>
29+ );
30+}
···1+// Browser compatibility checks for WebRTC
2+const checkWebRTCSupport = () => {
3+ if (typeof window === "undefined") {
4+ throw new Error("WebRTC is not available in non-browser environments");
5+ }
6+7+ if (!window.RTCPeerConnection) {
8+ throw new Error(
9+ "RTCPeerConnection is not supported in this browser. Please use a modern browser that supports WebRTC.",
10+ );
11+ }
12+13+ if (!window.RTCSessionDescription) {
14+ throw new Error(
15+ "RTCSessionDescription is not supported in this browser. Please use a modern browser that supports WebRTC.",
16+ );
17+ }
18+};
19+20+// Check support immediately
21+try {
22+ checkWebRTCSupport();
23+} catch (error) {
24+ console.error("WebRTC Compatibility Error:", error.message);
25+}
26+27+export const RTCPeerConnection = window.RTCPeerConnection;
28+export const RTCSessionDescription = window.RTCSessionDescription;
29+export const WebRTCMediaStream = window.MediaStream;
30+export const mediaDevices = navigator.mediaDevices;
31+32+// Export the compatibility checker for use in other components
33+export { checkWebRTCSupport };
···1+import { type LucideProps } from "lucide-react-native";
2+import React from "react";
3+import { useTheme } from "../../lib/theme";
4+5+// Simple icon wrapper that integrates with theme
6+export interface IconProps {
7+ variant?:
8+ | "default"
9+ | "muted"
10+ | "primary"
11+ | "secondary"
12+ | "destructive"
13+ | "success"
14+ | "warning";
15+ size?: number | "sm" | "md" | "lg" | "xl";
16+ color?: string;
17+}
18+19+// Size mapping
20+const sizeMap = {
21+ sm: 16,
22+ md: 20,
23+ lg: 24,
24+ xl: 32,
25+} as const;
26+27+// HOC to create themed icons
28+export function createThemedIcon(
29+ IconComponent: React.ComponentType<LucideProps>,
30+): React.FC<IconProps> {
31+ return ({ variant = "default", size = "md", color, ...restProps }) => {
32+ let theme = useTheme(); // Ensure theme is available
33+ // Calculate size
34+ const iconSize = typeof size === "number" ? size : sizeMap[size];
35+36+ // Calculate color if not provided using atoms
37+ const iconColor =
38+ color ||
39+ theme.theme.colors[variant] ||
40+ theme.theme.colors.secondaryForeground;
41+42+ return (
43+ <IconComponent
44+ size={iconSize}
45+ color={iconColor}
46+ {...(restProps as Omit<LucideProps, "size" | "color">)}
47+ />
48+ );
49+ };
50+}
+31
js/components/src/components/ui/index.ts
···0000000000000000000000000000000
···1+// Export primitive components
2+export * from "./primitives/button";
3+export * from "./primitives/input";
4+export * from "./primitives/modal";
5+export * from "./primitives/text";
6+7+// Export styled components
8+export * from "./button";
9+export * from "./dialog";
10+export * from "./dropdown";
11+export * from "./icons";
12+export * from "./input";
13+export * from "./text";
14+export * from "./toast";
15+export * from "./view";
16+17+// Component collections for easy importing
18+export { ButtonPrimitive } from "./primitives/button";
19+export { InputPrimitive } from "./primitives/input";
20+export { ModalPrimitive } from "./primitives/modal";
21+export { TextPrimitive } from "./primitives/text";
22+23+// Re-export commonly used types
24+export type { Theme } from "../../lib/theme/theme";
25+export type { ButtonProps } from "./button";
26+export type { DialogProps } from "./dialog";
27+export type { InputProps } from "./input";
28+export type { TextProps } from "./text";
29+export type { ViewProps } from "./view";
30+31+export * from "../../lib/theme";
···4export * from "./player-store";
5export * from "./streamplace-provider";
6export * from "./streamplace-store";
000000000000000000000
···4export * from "./player-store";
5export * from "./streamplace-provider";
6export * from "./streamplace-store";
7+8+// export PlayerProvider and related hooks/types for direct package imports
9+export {
10+ PlayerProvider,
11+ withPlayerProvider,
12+} from "./player-store/player-provider";
13+export { usePlayerContext } from "./player-store/player-store";
14+15+// export Player and PlayerProps for direct package imports
16+export { Player, PlayerUI } from "./components/mobile-player/player";
17+export { PlayerProps } from "./components/mobile-player/props";
18+19+// export theme
20+export * as ui from "./components/ui";
21+22+// export all UI components at the root for direct imports
23+export * from "./components/ui";
24+25+// export atoms and theme for direct imports
26+export * as theme from "./lib/theme";
27+export * as atoms from "./lib/theme/atoms";