Skip to content
This repository was archived by the owner on Feb 10, 2026. It is now read-only.

Commit a60616b

Browse files
FCE-1657: WHIP: Mobile support (#460)
1 parent 831a2f6 commit a60616b

13 files changed

Lines changed: 366 additions & 94 deletions

File tree

internal/fishjam-chat/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ EXPO_PUBLIC_FISHJAM_PEER_TOKEN=
1414
EXPO_PUBLIC_VIDEOROOM_STAGING_ROOM_MANAGER=
1515
# URL to production VideoRoom's Room Manager
1616
EXPO_PUBLIC_VIDEOROOM_PRODUCTION_ROOM_MANAGER=
17+
18+
# Fishjam id from cloud dashboard
19+
EXPO_PUBLIC_FISHJAM_ID=

internal/fishjam-chat/navigators/AppNavigator.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { appNavigationLabels } from '../types/ComponentLabels';
1515
import { AdditionalColors, BrandColors } from '../utils/Colors';
1616
import { ConnectWithFishjamRoom } from '../screens/ConnectWithFishjamRoom';
1717
import ConnectToLivestreamScreen from '../screens/ConnectToLivestreamScreen';
18-
import LivestreamScreen from '../screens/LivestreamScreen/LivestreamScreen';
18+
import LivestreamStreamerScreen from '../screens/LivestreamScreen/LivestreamStreamerScreen';
19+
import LivestreamViewerScreen from '../screens/LivestreamScreen/LivestreamViewerScreen';
1920

2021
export type AppRootStackParamList = {
2122
Home: undefined;
@@ -24,9 +25,13 @@ export type AppRootStackParamList = {
2425
fishjamUrl: string;
2526
peerToken: string;
2627
};
27-
LivestreamScreen: {
28-
livestreamUrl: string;
29-
viewerToken: string;
28+
LivestreamViewerScreen: {
29+
fishjamId: string;
30+
roomName: string;
31+
};
32+
LivestreamStreamerScreen: {
33+
fishjamId: string;
34+
roomName: string;
3035
};
3136
Room: {
3237
isCameraOn: boolean;
@@ -135,7 +140,14 @@ export default function AppNavigator() {
135140
component={TabNavigator}
136141
/>
137142
<Stack.Screen name="Preview" component={PreviewScreen} />
138-
<Stack.Screen name="LivestreamScreen" component={LivestreamScreen} />
143+
<Stack.Screen
144+
name="LivestreamStreamerScreen"
145+
component={LivestreamStreamerScreen}
146+
/>
147+
<Stack.Screen
148+
name="LivestreamViewerScreen"
149+
component={LivestreamViewerScreen}
150+
/>
139151
<Stack.Screen
140152
name="Room"
141153
options={{

internal/fishjam-chat/screens/ConnectToLivestreamScreen.tsx

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,30 @@ export default function ConnectToLivestreamScreen({ navigation }: Props) {
2626
const [connectionError, setConnectionError] = useState<string | null>(null);
2727
const [loading, setLoading] = useState(false);
2828

29-
const [livestreamUrl, setLivestreamUrl] = useState('');
30-
const [viewerToken, setViewerToken] = useState('');
29+
const [fishjamId, setFishjamId] = useState(
30+
process.env.EXPO_PUBLIC_FISHJAM_ID ?? '',
31+
);
32+
const [roomName, setRoomName] = useState('');
3133

32-
const onTapConnectButton = async () => {
33-
try {
34-
if (!livestreamUrl) {
35-
setConnectionError('Livestream URL is required');
36-
return;
37-
}
34+
const validateInputs = () => {
35+
if (!fishjamId) {
36+
throw new Error('Fishjam ID is required');
37+
}
3838

39-
if (!viewerToken) {
40-
setConnectionError('Viewer token is required');
41-
return;
42-
}
39+
if (!roomName) {
40+
throw new Error('Room name is required');
41+
}
42+
};
4343

44+
const onTapConnectViewerButton = async () => {
45+
try {
46+
validateInputs();
4447
setConnectionError(null);
4548
setLoading(true);
4649

47-
navigation.navigate('LivestreamScreen', {
48-
livestreamUrl,
49-
viewerToken,
50+
navigation.navigate('LivestreamViewerScreen', {
51+
fishjamId,
52+
roomName,
5053
});
5154
} catch (e) {
5255
const message =
@@ -56,7 +59,24 @@ export default function ConnectToLivestreamScreen({ navigation }: Props) {
5659
setLoading(false);
5760
}
5861
};
62+
const onTapConnectStreamerButton = async () => {
63+
try {
64+
validateInputs();
65+
setConnectionError(null);
66+
setLoading(true);
5967

68+
navigation.navigate('LivestreamStreamerScreen', {
69+
fishjamId,
70+
roomName,
71+
});
72+
} catch (e) {
73+
const message =
74+
'message' in (e as Error) ? (e as Error).message : 'Unknown error';
75+
setConnectionError(message);
76+
} finally {
77+
setLoading(false);
78+
}
79+
};
6080
return (
6181
<DismissKeyboard>
6282
<SafeAreaView style={styles.safeArea}>
@@ -70,18 +90,23 @@ export default function ConnectToLivestreamScreen({ navigation }: Props) {
7090
resizeMode="contain"
7191
/>
7292
<TextInput
73-
onChangeText={setLivestreamUrl}
74-
placeholder="Livestream URL"
75-
defaultValue={livestreamUrl}
93+
onChangeText={setFishjamId}
94+
placeholder="Fishjam ID"
95+
defaultValue={fishjamId}
7696
/>
7797
<TextInput
78-
onChangeText={setViewerToken}
79-
placeholder="Viewer Token"
80-
defaultValue={viewerToken}
98+
onChangeText={setRoomName}
99+
placeholder="Room Name"
100+
defaultValue={roomName}
81101
/>
82102
<Button
83103
title="Connect to Livestream"
84-
onPress={onTapConnectButton}
104+
onPress={onTapConnectViewerButton}
105+
disabled={loading}
106+
/>
107+
<Button
108+
title="Stream Livestream"
109+
onPress={onTapConnectStreamerButton}
85110
disabled={loading}
86111
/>
87112
</KeyboardAvoidingView>

internal/fishjam-chat/screens/LivestreamScreen/LivestreamScreen.tsx renamed to internal/fishjam-chat/screens/LivestreamScreen/LivestreamStreamerScreen.tsx

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
1+
import {
2+
LivestreamStreamer,
3+
useLivestreamStreamer,
4+
useSandbox,
5+
cameras,
6+
} from '@fishjam-cloud/react-native-client';
17
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
28
import React, { useCallback, useEffect } from 'react';
39
import { SafeAreaView, StyleSheet, View } from 'react-native';
410
import { AppRootStackParamList } from '../../navigators/AppNavigator';
511
import { BrandColors } from '../../utils/Colors';
6-
import {
7-
useLivestream,
8-
LivestreamView,
9-
} from '@fishjam-cloud/react-native-client';
1012

11-
type Props = NativeStackScreenProps<AppRootStackParamList, 'LivestreamScreen'>;
13+
type Props = NativeStackScreenProps<
14+
AppRootStackParamList,
15+
'LivestreamStreamerScreen'
16+
>;
1217

13-
export default function LivestreamScreen({ route }: Props) {
14-
const { livestreamUrl, viewerToken } = route.params;
18+
export default function LivestreamStreamerScreen({ route }: Props) {
19+
const { fishjamId, roomName } = route.params;
1520

16-
const { connect, disconnect } = useLivestream();
21+
const { getSandboxLivestream } = useSandbox({
22+
fishjamId,
23+
});
24+
25+
const { connect, disconnect } = useLivestreamStreamer({ camera: cameras[0] });
1726

1827
const handleConnect = useCallback(async () => {
1928
try {
20-
await connect(livestreamUrl, viewerToken);
29+
const { streamerToken } = await getSandboxLivestream(roomName, true);
30+
await connect(streamerToken);
2131
} catch (err) {
2232
console.log(err);
2333
}
24-
}, [connect, livestreamUrl, viewerToken]);
34+
}, [connect, getSandboxLivestream, roomName]);
2535

2636
useEffect(() => {
2737
handleConnect();
@@ -35,7 +45,7 @@ export default function LivestreamScreen({ route }: Props) {
3545
<SafeAreaView style={styles.container}>
3646
<View style={styles.box}>
3747
<View style={styles.videoView}>
38-
<LivestreamView style={styles.whepView} />
48+
<LivestreamStreamer style={styles.whepView} />
3949
</View>
4050
</View>
4151
</SafeAreaView>
@@ -67,25 +77,4 @@ const styles = StyleSheet.create({
6777
flex: 1,
6878
backgroundColor: '#000',
6979
},
70-
pausedVideo: {
71-
flex: 1,
72-
backgroundColor: '#333',
73-
justifyContent: 'center',
74-
alignItems: 'center',
75-
},
76-
connectedText: {
77-
color: 'white',
78-
fontSize: 16,
79-
fontWeight: 'bold',
80-
},
81-
pausedText: {
82-
color: 'white',
83-
fontSize: 18,
84-
fontWeight: 'bold',
85-
},
86-
controls: {
87-
flexDirection: 'row',
88-
justifyContent: 'center',
89-
gap: 20,
90-
},
9180
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {
2+
LivestreamViewer,
3+
useLivestreamViewer,
4+
useSandbox,
5+
} from '@fishjam-cloud/react-native-client';
6+
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
7+
import React, { useCallback, useEffect } from 'react';
8+
import { SafeAreaView, StyleSheet, View } from 'react-native';
9+
import { AppRootStackParamList } from '../../navigators/AppNavigator';
10+
import { BrandColors } from '../../utils/Colors';
11+
12+
type Props = NativeStackScreenProps<
13+
AppRootStackParamList,
14+
'LivestreamViewerScreen'
15+
>;
16+
17+
export default function LivestreamViewerScreen({ route }: Props) {
18+
const { fishjamId, roomName } = route.params;
19+
20+
const { getSandboxViewerToken } = useSandbox({
21+
fishjamId,
22+
});
23+
24+
const { connect, disconnect } = useLivestreamViewer();
25+
26+
const handleConnect = useCallback(async () => {
27+
try {
28+
const token = await getSandboxViewerToken(roomName);
29+
await connect({ token });
30+
} catch (err) {
31+
console.error(err);
32+
}
33+
}, [connect, getSandboxViewerToken, roomName]);
34+
35+
useEffect(() => {
36+
handleConnect();
37+
38+
return () => {
39+
disconnect();
40+
};
41+
}, [handleConnect, disconnect]);
42+
43+
return (
44+
<SafeAreaView style={styles.container}>
45+
<View style={styles.box}>
46+
<View style={styles.videoView}>
47+
<LivestreamViewer style={styles.whepView} />
48+
</View>
49+
</View>
50+
</SafeAreaView>
51+
);
52+
}
53+
54+
const styles = StyleSheet.create({
55+
container: {
56+
flex: 1,
57+
backgroundColor: '#F1FAFE',
58+
padding: 24,
59+
},
60+
box: {
61+
flex: 1,
62+
justifyContent: 'center',
63+
alignItems: 'center',
64+
gap: 20,
65+
},
66+
videoView: {
67+
width: '100%',
68+
height: '70%',
69+
backgroundColor: '#E0E0E0',
70+
borderRadius: 12,
71+
overflow: 'hidden',
72+
borderWidth: 1,
73+
borderColor: BrandColors.darkBlue80,
74+
},
75+
whepView: {
76+
flex: 1,
77+
backgroundColor: '#000',
78+
},
79+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { CSSProperties } from 'react';
2+
import { StyleProp, ViewStyle } from 'react-native';
3+
import { WhipClientView } from 'react-native-whip-whep';
4+
5+
/**
6+
* Props of the LivestreamView component
7+
*/
8+
export type LivestreamStreamerProps = {
9+
/**
10+
* Styles of the LivestreamView component
11+
*/
12+
style?: StyleProp<ViewStyle>;
13+
};
14+
15+
/**
16+
* Renders a video player playing the livestream set up with {@link useLivestreamStreamer} hook.
17+
*
18+
* @category Components
19+
* @param {object} props
20+
* @param {object} props.style
21+
*/
22+
export const LivestreamStreamer = ({ style }: LivestreamStreamerProps) => (
23+
<WhipClientView style={style as CSSProperties} />
24+
);

packages/react-native-client/src/components/LivestreamView.tsx renamed to packages/react-native-client/src/components/LivestreamViewer.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { CSSProperties, Ref } from 'react';
22
import { StyleProp, ViewStyle } from 'react-native';
33
import { WhepClientView, WhepClientViewRef } from 'react-native-whip-whep';
44

5-
export type LivestreamViewRef = WhepClientViewRef;
5+
export type LivestreamViewerRef = WhepClientViewRef;
66

77
/**
88
* Props of the LivestreamView component
99
*/
10-
export type LivestreamViewProps = {
10+
export type LivestreamViewerProps = {
1111
/**
1212
* Styles of the LivestreamView component
1313
*/
@@ -41,25 +41,25 @@ export type LivestreamViewProps = {
4141
height: number;
4242
};
4343

44-
ref?: Ref<LivestreamViewRef>;
44+
ref?: Ref<LivestreamViewerRef>;
4545
};
4646

4747
/**
48-
* Renders a video player playing the livestream set up with {@link useLivestream} hook.
48+
* Renders a video player playing the livestream set up with {@link useLivestreamViewer} hook.
4949
*
5050
* @category Components
5151
* @param {object} props
5252
* @param {object} props.style
5353
*/
54-
export const LivestreamView = ({
54+
export const LivestreamViewer = ({
5555
style,
5656
orientation,
5757
pipEnabled,
5858
autoStartPip,
5959
autoStopPip,
6060
pipSize,
6161
ref,
62-
}: LivestreamViewProps) => (
62+
}: LivestreamViewerProps) => (
6363
<WhepClientView
6464
ref={ref}
6565
style={style as CSSProperties}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
const FISHJAM_CONNECT_PATH = 'fishjam.io/api/v1/connect';
1+
const FISHJAM_URL_BASE = 'fishjam.io/api/v1';
2+
const FISHJAM_CONNECT_PATH = `${FISHJAM_URL_BASE}/connect`;
23

34
export const FISHJAM_HTTP_CONNECT_URL = `https://${FISHJAM_CONNECT_PATH}`;
45
export const FISHJAM_WS_CONNECT_URL = `wss://${FISHJAM_CONNECT_PATH}`;
6+
7+
const FISHJAM_LIVE_URL = `https://${FISHJAM_URL_BASE}/live`;
8+
9+
export const FISHJAM_WHIP_URL = `${FISHJAM_LIVE_URL}/api/whip`;
10+
export const FISHJAM_WHEP_URL = `${FISHJAM_LIVE_URL}/api/whep`;

0 commit comments

Comments
 (0)