Skip to content
This repository was archived by the owner on Feb 10, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions internal/fishjam-chat/navigators/AppNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ConnectWithFishjamRoom } from '../screens/ConnectWithFishjamRoom';
import ConnectToLivestreamScreen from '../screens/ConnectToLivestreamScreen';
import LivestreamStreamerScreen from '../screens/LivestreamScreen/LivestreamStreamerScreen';
import LivestreamViewerScreen from '../screens/LivestreamScreen/LivestreamViewerScreen';
import LivestreamScreenSharingScreen from '../screens/LivestreamScreen/LivestreamScreenSharingScreen';

export type AppRootStackParamList = {
Home: undefined;
Expand All @@ -31,6 +32,10 @@ export type AppRootStackParamList = {
fishjamId: string;
roomName: string;
};
LivestreamScreenSharingScreen: {
fishjamId: string;
roomName: string;
};
Room: {
isCameraOn: boolean;
userName?: string;
Expand Down Expand Up @@ -144,6 +149,10 @@ export default function AppNavigator() {
name="LivestreamViewerScreen"
component={LivestreamViewerScreen}
/>
<Stack.Screen
name="LivestreamScreenSharingScreen"
component={LivestreamScreenSharingScreen}
/>
<Stack.Screen
name="Room"
options={{
Expand Down
4 changes: 2 additions & 2 deletions internal/fishjam-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
"@react-navigation/bottom-tabs": "^6.6.1",
"@react-navigation/native": "^6.1.18",
"@react-navigation/native-stack": "^6.11.0",
"expo": "53.0.17",
"expo": "53.0.25",
"expo-build-properties": "~0.14.8",
"expo-device": "~7.1.4",
"expo-status-bar": "~2.2.3",
"react": "19.0.0",
"react-native": "0.79.5",
"react-native": "0.79.6",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "~3.17.4",
"react-native-safe-area-context": "5.4.0",
Expand Down
23 changes: 23 additions & 0 deletions internal/fishjam-chat/screens/ConnectToLivestreamScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ export default function ConnectToLivestreamScreen({ navigation }: Props) {
setLoading(false);
}
};
const onTapConnectScreenSharingButton = async () => {
try {
validateInputs();
setConnectionError(null);
setLoading(true);

navigation.navigate('LivestreamScreenSharingScreen', {
fishjamId,
roomName,
});
} catch (e) {
const message =
'message' in (e as Error) ? (e as Error).message : 'Unknown error';
setConnectionError(message);
} finally {
setLoading(false);
}
};
return (
<DismissKeyboard>
<SafeAreaView style={styles.safeArea}>
Expand Down Expand Up @@ -109,6 +127,11 @@ export default function ConnectToLivestreamScreen({ navigation }: Props) {
onPress={onTapConnectStreamerButton}
disabled={loading}
/>
<Button
title="Stream Screen Sharing"
onPress={onTapConnectScreenSharingButton}
disabled={loading}
/>
</KeyboardAvoidingView>
</SafeAreaView>
</DismissKeyboard>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useSandbox } from '@fishjam-cloud/react-native-client';
import {
LivestreamStreamer,
useLivestreamScreenSharingStreamer,
VideoParameters,
} from '@fishjam-cloud/react-native-client/livestream';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import React, { useCallback } from 'react';
import { Button, SafeAreaView, StyleSheet, View } from 'react-native';
import { AppRootStackParamList } from '../../navigators/AppNavigator';
import { BrandColors } from '../../utils/Colors';

type Props = NativeStackScreenProps<
AppRootStackParamList,
'LivestreamScreenSharingScreen'
>;

export default function LivestreamScreenSharingScreen({ route }: Props) {
const { fishjamId, roomName } = route.params;

const { getSandboxLivestream } = useSandbox({
fishjamId,
});

const { connect, disconnect, isConnected, whipClientRef } =
useLivestreamScreenSharingStreamer({
audioEnabled: true,
videoParameters: VideoParameters.presetHD169,
});

const handleConnect = useCallback(async () => {
try {
const { streamerToken } = await getSandboxLivestream(roomName, false);
await connect(streamerToken);
} catch (err) {
console.log(err);
}
}, [connect, getSandboxLivestream, roomName]);

const handleDisconnect = useCallback(() => {
disconnect();
}, [disconnect]);

return (
<SafeAreaView style={styles.container}>
<View style={styles.box}>
<View style={styles.videoView}>
<LivestreamStreamer style={styles.whepView} ref={whipClientRef} />
<Button
title={isConnected ? 'Disconnect' : 'Connect'}
onPress={isConnected ? handleDisconnect : handleConnect}
/>
</View>
</View>
</SafeAreaView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F1FAFE',
padding: 24,
},
box: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 20,
},
videoView: {
width: '100%',
height: '70%',
backgroundColor: '#E0E0E0',
borderRadius: 12,
overflow: 'hidden',
borderWidth: 1,
borderColor: BrandColors.darkBlue80,
},
whepView: {
flex: 1,
backgroundColor: '#000',
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ internal class PeerConnectionFactoryWrapper(

init {
PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions.builder(appContext).createInitializationOptions()
PeerConnectionFactory.InitializationOptions
.builder(appContext)
.setFieldTrials("WebRTC-Network-UseNWPathMonitor/Disabled/")
.setEnableInternalTracer(false)
.createInitializationOptions()
)

eglBase = EglBase.create()
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"dependencies": {
"promise-fs": "^2.1.1",
"protobufjs": "^7.4.0",
"react-native-whip-whep": "0.6.0",
"react-native-whip-whep": "0.7.1",
"zod": "^3.25.71"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/react-native-client/plugin/src/withFishjam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { FishjamPluginOptions } from './types';
import withFishjamIos from './withFishjamIos';

const withFishjam: ConfigPlugin<FishjamPluginOptions> = (config, options) => {
withFishjamAndroid(config, options);
withFishjamIos(config, options);
config = withFishjamAndroid(config, options);
config = withFishjamIos(config, options);
return config;
};

Expand Down
71 changes: 65 additions & 6 deletions packages/react-native-client/plugin/src/withFishjamAndroid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const withFishjamForegroundService: ConfigPlugin<FishjamPluginOptions> = (
const mainApplication = getMainApplicationOrThrow(configuration.modResults);
mainApplication.service = mainApplication.service || [];

const newService = {
const fishjamService = {
$: {
'android:name':
'io.fishjam.reactnative.foregroundService.FishjamForegroundService',
Expand All @@ -27,14 +27,72 @@ const withFishjamForegroundService: ConfigPlugin<FishjamPluginOptions> = (
},
};

const existingServiceIndex = mainApplication.service.findIndex(
(service) => service.$['android:name'] === newService.$['android:name'],
const whipWhepService = {
$: {
'android:name':
'com.swmansion.reactnativeclient.foregroundService.ScreenCaptureService',
'android:foregroundServiceType': 'mediaProjection',
'android:stopWithTask': 'true',
},
};

// Add Fishjam service
const existingFishjamServiceIndex = mainApplication.service.findIndex(
(service) =>
service.$['android:name'] === fishjamService.$['android:name'],
);

if (existingServiceIndex !== -1) {
mainApplication.service[existingServiceIndex] = newService;
if (existingFishjamServiceIndex !== -1) {
mainApplication.service[existingFishjamServiceIndex] = fishjamService;
} else {
mainApplication.service.push(newService);
mainApplication.service.push(fishjamService);
}

// Add WhipWhep service
const existingWhipWhepServiceIndex = mainApplication.service.findIndex(
(service) =>
service.$['android:name'] === whipWhepService.$['android:name'],
);

if (existingWhipWhepServiceIndex !== -1) {
mainApplication.service[existingWhipWhepServiceIndex] = whipWhepService;
} else {
mainApplication.service.push(whipWhepService);
}

return configuration;
});

const withFishjamForegroundServicePermission: ConfigPlugin<
FishjamPluginOptions
> = (config, props) =>
withAndroidManifest(config, (configuration) => {
if (!props?.android?.enableForegroundService) {
return configuration;
}

const mainApplication = configuration.modResults;
if (!mainApplication.manifest) {
return configuration;
}

if (!mainApplication.manifest['uses-permission']) {
mainApplication.manifest['uses-permission'] = [];
}

const permissions = mainApplication.manifest['uses-permission'];

const hasForegroundServicePermission = permissions.some(
(perm) =>
perm.$?.['android:name'] === 'android.permission.FOREGROUND_SERVICE',
);

if (!hasForegroundServicePermission) {
permissions.push({
$: {
'android:name': 'android.permission.FOREGROUND_SERVICE',
},
});
}

return configuration;
Expand All @@ -61,6 +119,7 @@ export const withFishjamAndroid: ConfigPlugin<FishjamPluginOptions> = (
config,
props,
) => {
config = withFishjamForegroundServicePermission(config, props);
config = withFishjamForegroundService(config, props);
config = withFishjamPictureInPicture(config, props);
return config;
Expand Down
Loading
Loading