Skip to content
This repository was archived by the owner on Jun 28, 2024. It is now read-only.

Commit 96b090e

Browse files
committedJun 10, 2024
Merge remote-tracking branch 'origin/main' into feat/update-example
2 parents 15a507d + 5c7297e commit 96b090e

File tree

13 files changed

+134
-171
lines changed

13 files changed

+134
-171
lines changed
 

‎README.md

+71
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,77 @@ toggleScreencast({screencastMetadata: { displayName: "Annie's desktop" }});
345345

346346
Use track metadata to differentiate between video and screencast tracks.
347347

348+
### Android foreground service
349+
350+
In order for the call to continue running when app is in background, you need to
351+
set up and start a foreground service. You can use a 3rd party library for this,
352+
for example [notifee](https://notifee.app/).
353+
354+
In `AndroidManifest.xml` specify necessary permissions:
355+
356+
```xml
357+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
358+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
359+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
360+
```
361+
362+
And add foreground service:
363+
364+
```xml
365+
<application
366+
...
367+
>
368+
...
369+
<service
370+
android:name="app.notifee.core.ForegroundService"
371+
android:foregroundServiceType="mediaProjection|camera|microphone" />
372+
</application>
373+
```
374+
375+
Then to start the foreground service:
376+
377+
```ts
378+
import notifee, { AndroidImportance } from '@notifee/react-native';
379+
380+
const startForegroundService = async () => {
381+
if (Platform.OS === 'android') return;
382+
const channelId = await notifee.createChannel({
383+
id: 'video_call',
384+
name: 'Video call',
385+
lights: false,
386+
vibration: false,
387+
importance: AndroidImportance.DEFAULT,
388+
});
389+
390+
await notifee.displayNotification({
391+
title: 'Your video call is ongoing',
392+
body: 'Tap to return to the call.',
393+
android: {
394+
channelId,
395+
asForegroundService: true,
396+
ongoing: true,
397+
pressAction: {
398+
id: 'default',
399+
},
400+
},
401+
});
402+
};
403+
```
404+
405+
Don't forget to also stop the service when the call has ended:
406+
407+
```ts
408+
notifee.stopForegroundService();
409+
```
410+
411+
Also add this code in your `index.js` to register the service:
412+
413+
```js
414+
notifee.registerForegroundService((notification) => {
415+
return new Promise(() => {});
416+
});
417+
```
418+
348419
### Developing
349420

350421
Run `./scripts/init.sh` in the main directory to install swift-format and set up

‎android/src/main/java/org/membraneframework/reactnative/MembraneWebRTC.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ class MembraneWebRTC(val sendEvent: (name: String, data: Map<String, Any?>) -> U
168168
}
169169
videoParameters = videoParameters.copy(
170170
dimensions = if (createOptions.flipVideo) videoParameters.dimensions.flip() else videoParameters.dimensions,
171-
simulcastConfig = videoSimulcastConfig,
171+
simulcastConfig = getSimulcastConfigFromOptions(createOptions.simulcastConfig),
172172
maxBitrate = videoMaxBandwidth
173173
)
174174
return videoParameters

‎example/android/app/src/main/AndroidManifest.xml

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
<uses-permission android:name="android.permission.INTERNET"/>
44
<uses-permission android:name="android.permission.CAMERA"/>
5-
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
65
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
6+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
7+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
78
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
89

910

@@ -27,5 +28,8 @@
2728
<category android:name="android.intent.category.LAUNCHER"/>
2829
</intent-filter>
2930
</activity>
31+
<service
32+
android:name="app.notifee.core.ForegroundService"
33+
android:foregroundServiceType="mediaProjection|camera|microphone" />
3034
</application>
3135
</manifest>

‎example/hooks/useJoinRoom.ts

+32
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import {
44
useMicrophone,
55
VideoQuality,
66
} from '@fishjam-dev/react-native-client';
7+
import notifee, {
8+
AndroidImportance,
9+
AndroidColor,
10+
} from '@notifee/react-native';
711
import { useCallback } from 'react';
812
import { Platform } from 'react-native';
913

@@ -12,6 +16,33 @@ interface Props {
1216
isMicrophoneAvailable: boolean;
1317
}
1418

19+
async function startForegroundService() {
20+
if (Platform.OS === 'android') {
21+
const channelId = await notifee.createChannel({
22+
id: 'video_call',
23+
name: 'Video call',
24+
lights: false,
25+
vibration: false,
26+
importance: AndroidImportance.DEFAULT,
27+
});
28+
29+
await notifee.displayNotification({
30+
title: 'Your video call is ongoing',
31+
body: 'Tap to return to the call.',
32+
android: {
33+
channelId,
34+
asForegroundService: true,
35+
ongoing: true,
36+
color: AndroidColor.BLUE,
37+
colorized: true,
38+
pressAction: {
39+
id: 'default',
40+
},
41+
},
42+
});
43+
}
44+
}
45+
1546
export function useJoinRoom({
1647
isCameraAvailable,
1748
isMicrophoneAvailable,
@@ -21,6 +52,7 @@ export function useJoinRoom({
2152
const { startMicrophone } = useMicrophone();
2253

2354
const joinRoom = useCallback(async () => {
55+
await startForegroundService();
2456
await startCamera({
2557
simulcastConfig: {
2658
enabled: true,

‎example/index.js

+5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import { AppRegistry } from 'react-native';
66
import App from './App';
77
import { name as appName } from './app.json';
88
import { LogBox } from 'react-native';
9+
import notifee from '@notifee/react-native';
910

1011
LogBox.ignoreLogs(['new NativeEventEmitter']);
1112

13+
notifee.registerForegroundService((notification) => {
14+
return new Promise(() => {});
15+
});
16+
1217
AppRegistry.registerComponent(appName, () => App);

‎example/ios/Podfile.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -1410,4 +1410,4 @@ SPEC CHECKSUMS:
14101410

14111411
PODFILE CHECKSUM: 8daa796ed34dbeeeb2b648d198968b27b4c2ae8d
14121412

1413-
COCOAPODS: 1.13.0
1413+
COCOAPODS: 1.14.3

‎example/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@expo-google-fonts/noto-sans": "^0.2.3",
1818
"@fishjam-dev/react-native-client": "file:..",
1919
"@gorhom/bottom-sheet": "^4",
20+
"@notifee/react-native": "^7.8.2",
2021
"@react-navigation/bottom-tabs": "^6.5.20",
2122
"@react-navigation/native": "^6.1.9",
2223
"@react-navigation/native-stack": "^6.9.17",

‎example/screens/RoomScreen.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
useAudioSettings,
99
} from '@fishjam-dev/react-native-client';
1010
import BottomSheet from '@gorhom/bottom-sheet';
11+
import notifee from '@notifee/react-native';
1112
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
1213
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
1314
import { Platform, SafeAreaView, StyleSheet, View } from 'react-native';
@@ -94,6 +95,12 @@ const RoomScreen = ({ navigation, route }: Props) => {
9495
}
9596
}, [audioSettings]);
9697

98+
useEffect(() => {
99+
return () => {
100+
notifee.stopForegroundService();
101+
};
102+
}, []);
103+
97104
return (
98105
<SafeAreaView style={styles.container}>
99106
{tracks.length > 0 ? (

‎example/yarn.lock

+6
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,7 @@
16111611
"@fishjam-dev/react-native-client@file:..":
16121612
version "0.3.0"
16131613
dependencies:
1614+
"@notifee/react-native" "^7.8.2"
16141615
phoenix "^1.7.6"
16151616
promise-fs "^2.1.1"
16161617
protobufjs "^7.2.4"
@@ -1978,6 +1979,11 @@
19781979
"@nodelib/fs.scandir" "2.1.5"
19791980
fastq "^1.6.0"
19801981

1982+
"@notifee/react-native@^7.8.2":
1983+
version "7.8.2"
1984+
resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-7.8.2.tgz#72d3199ae830b4128ddaef3c1c2f11604759c9c4"
1985+
integrity sha512-VG4IkWJIlOKqXwa3aExC3WFCVCGCC9BA55Ivg0SMRfEs+ruvYy/zlLANcrVGiPtgkUEryXDhA8SXx9+JcO8oLA==
1986+
19811987
"@npmcli/fs@^1.0.0":
19821988
version "1.1.1"
19831989
resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257"

‎ios/MembraneWebRTC.swift

+5-3
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,12 @@ class MembraneWebRTC: MembraneRTCDelegate {
157157
initLocalEndpoint()
158158
}
159159

160-
func getVideoParametersFromOptions(connectionOptions: CameraConfig) -> VideoParameters {
160+
func getVideoParametersFromOptions(connectionOptions: CameraConfig) throws -> VideoParameters {
161161
let videoQuality = connectionOptions.quality
162162
let flipVideo = connectionOptions.flipVideo
163163
let videoBandwidthLimit = getMaxBandwidthFromOptions(
164164
maxBandwidth: connectionOptions.maxBandwidth)
165+
let simulcastConfig = try getSimulcastConfigFromOptions(simulcastConfig: connectionOptions.simulcastConfig) ?? SimulcastConfig()
165166

166167
let preset: VideoParameters = {
167168
switch videoQuality {
@@ -192,7 +193,7 @@ class MembraneWebRTC: MembraneRTCDelegate {
192193
let videoParameters = VideoParameters(
193194
dimensions: flipVideo ? preset.dimensions.flip() : preset.dimensions,
194195
maxBandwidth: videoBandwidthLimit,
195-
simulcastConfig: self.videoSimulcastConfig
196+
simulcastConfig: simulcastConfig
196197
)
197198
return videoParameters
198199
}
@@ -284,9 +285,10 @@ class MembraneWebRTC: MembraneRTCDelegate {
284285
try addTrackToLocalEndpoint(cameraTrack, config.videoTrackMetadata.toMetadata(), simulcastConfig)
285286
try setCameraTrackState(cameraTrack: cameraTrack, isEnabled: config.cameraEnabled)
286287
}
288+
287289
private func createCameraTrack(config: CameraConfig) throws -> LocalVideoTrack? {
288290
try ensureConnected()
289-
let videoParameters = getVideoParametersFromOptions(connectionOptions: config)
291+
let videoParameters = try getVideoParametersFromOptions(connectionOptions: config)
290292
guard
291293
let simulcastConfig = try getSimulcastConfigFromOptions(
292294
simulcastConfig: config.simulcastConfig)

‎src/__mocks__/index.tsx

-7
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,6 @@ const emptyPromise = async (): Promise<void> => {
3838
});
3939
};
4040

41-
export const useWebRTC = () => {
42-
return {
43-
connect: emptyPromise,
44-
disconnect: emptyPromise,
45-
error: null,
46-
};
47-
};
4841
export const useEndpoints = () => [];
4942
export const setTargetTrackEncoding = emptyPromise;
5043
export const useCamera = () => {

0 commit comments

Comments
 (0)
Failed to load comments.