浏览代码

feat(rnsdk) add initial React Native SDK

Co-authored-by: Calin-Teodor <calin.chitu@8x8.com>
Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>
factor2
Filip Rejmus 2 年前
父节点
当前提交
935a391525

+ 3
- 0
react-native-sdk/.npmrc 查看文件

1
+package-lock=true
2
+; FIXME Set legacy-peer-deps=false when we upgrade RN.
3
+legacy-peer-deps=true

+ 48
- 0
react-native-sdk/README.md 查看文件

1
+# <p align="center">Jitsi Meet React Native SDK</p>
2
+
3
+
4
+## Installation
5
+ Inside your project, run `npm i @jitsi/react-native-sdk`.<br/><br/>Additionally if not already installed, the following dependencies need to be added:
6
+ <br/>`npm i @react-native-async-storage/async-storage react-native-webrtc`
7
+
8
+ [comment]: # (These deps definitely need to be added manually, more could be neccesary)
9
+
10
+### iOS
11
+
12
+#### Project Info.plist
13
+- Add a *Privacy - Camera Usage Description*
14
+- Add a *Privacy - Microphone Usage Description*
15
+
16
+#### General
17
+- Signing & capabilites:
18
+    - Add Background modes
19
+        - Audio
20
+        - Voice over IP
21
+        - Background fetch
22
+- Add Copy Sounds step:
23
+
24
+```
25
+    SOUNDS_DIR="${PROJECT_DIR}/../node_modules/rnsdk/sounds"
26
+    cp $SOUNDS_DIR/* ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/
27
+```
28
+#### Podfile
29
+- At the beginning of your target step add `pod 'ObjectiveDropboxOfficial', :modular_headers => true`
30
+
31
+Run `cd ios && pod install && cd ..`
32
+
33
+### Android
34
+
35
+- In your build.gradle have at least `minSdkVersion = 24`
36
+- TODO: HOW TO ADD COPY SOUNDS STEP
37
+- Under the `</application>`  tag of your AndroidManifest.xml make sure that it includes
38
+  ```
39
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
40
+    <uses-permission android:name="android.permission.CAMERA" />
41
+  ```
42
+
43
+### TODOS
44
+- Ref ConnectionService to not rely on ReactInstanceHolder anymore
45
+- Add Copy Sounds step to build.gradle
46
+- Include copy sounds step in podspec (if possible)
47
+- Add ranges for dependencies
48
+- Add Build_Config for react native to AppInfoModule

+ 140
- 0
react-native-sdk/android/build.gradle 查看文件

1
+buildscript {
2
+  repositories {
3
+      google()
4
+      mavenCentral()
5
+  }
6
+
7
+  dependencies {
8
+    classpath 'com.android.tools.build:gradle:3.5.3'
9
+  }
10
+}
11
+
12
+def isNewArchitectureEnabled() {
13
+  return rootProject.hasProperty("newArchEnabled") &&  rootProject.getProperty("newArchEnabled") == "true"
14
+}
15
+
16
+apply plugin: 'com.android.library'
17
+
18
+if (isNewArchitectureEnabled()) {
19
+  apply plugin: 'com.facebook.react'
20
+}
21
+
22
+def getExtOrDefault(name) {
23
+  return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['JitsiMeetReactNative_' + name]
24
+}
25
+
26
+def getExtOrIntegerDefault(name) {
27
+  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['JitsiMeetReactNative_' + name]).toInteger()
28
+}
29
+
30
+android {
31
+  compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
32
+
33
+  defaultConfig {
34
+    minSdkVersion getExtOrIntegerDefault('minSdkVersion')
35
+    targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
36
+    buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
37
+  }
38
+  buildTypes {
39
+    release {
40
+      minifyEnabled false
41
+    }
42
+  }
43
+
44
+  lintOptions {
45
+    disable 'GradleCompatible'
46
+  }
47
+
48
+  compileOptions {
49
+    sourceCompatibility JavaVersion.VERSION_1_8
50
+    targetCompatibility JavaVersion.VERSION_1_8
51
+  }
52
+}
53
+
54
+repositories {
55
+  mavenCentral()
56
+  google()
57
+
58
+  def found = false
59
+  def defaultDir = null
60
+  def androidSourcesName = 'React Native sources'
61
+
62
+  if (rootProject.ext.has('reactNativeAndroidRoot')) {
63
+    defaultDir = rootProject.ext.get('reactNativeAndroidRoot')
64
+  } else {
65
+    defaultDir = new File(
66
+      projectDir,
67
+      '/../../../node_modules/react-native/android'
68
+    )
69
+  }
70
+
71
+  if (defaultDir.exists()) {
72
+    maven {
73
+      url defaultDir.toString()
74
+      name androidSourcesName
75
+    }
76
+
77
+    logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}")
78
+    found = true
79
+  } else {
80
+    def parentDir = rootProject.projectDir
81
+
82
+    1.upto(5, {
83
+      if (found) return true
84
+      parentDir = parentDir.parentFile
85
+
86
+      def androidSourcesDir = new File(
87
+        parentDir,
88
+        'node_modules/react-native'
89
+      )
90
+
91
+      def androidPrebuiltBinaryDir = new File(
92
+        parentDir,
93
+        'node_modules/react-native/android'
94
+      )
95
+
96
+      if (androidPrebuiltBinaryDir.exists()) {
97
+        maven {
98
+          url androidPrebuiltBinaryDir.toString()
99
+          name androidSourcesName
100
+        }
101
+
102
+        logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}")
103
+        found = true
104
+      } else if (androidSourcesDir.exists()) {
105
+        maven {
106
+          url androidSourcesDir.toString()
107
+          name androidSourcesName
108
+        }
109
+
110
+        logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}")
111
+        found = true
112
+      }
113
+    })
114
+  }
115
+
116
+  if (!found) {
117
+    throw new GradleException(
118
+      "${project.name}: unable to locate React Native android sources. " +
119
+      "Ensure you have you installed React Native as a dependency in your project and try again."
120
+    )
121
+  }
122
+}
123
+
124
+
125
+dependencies {
126
+    //noinspection GradleDynamicVersion
127
+  implementation "com.facebook.react:react-native:+"
128
+  implementation 'com.squareup.duktape:duktape-android:1.3.0'
129
+  implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
130
+  implementation 'com.jakewharton.timber:timber:4.7.1'
131
+// From node_modules
132
+}
133
+
134
+if (isNewArchitectureEnabled()) {
135
+  react {
136
+    jsRootDir = file("../src/")
137
+    libraryName = "JitsiMeetReactNative"
138
+    codegenJavaPackageName = "org.jitsi.meet.sdk"
139
+  }
140
+}

+ 5
- 0
react-native-sdk/android/gradle.properties 查看文件

1
+JitsiMeetReactNative_kotlinVersion=1.7.0
2
+JitsiMeetReactNative_minSdkVersion=21
3
+JitsiMeetReactNative_targetSdkVersion=31
4
+JitsiMeetReactNative_compileSdkVersion=31
5
+JitsiMeetReactNative_ndkversion=21.4.7075529

+ 4
- 0
react-native-sdk/android/src/main/AndroidManifest.xml 查看文件

1
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+          package="org.jitsi.meet.sdk">
3
+
4
+</manifest>

+ 39
- 0
react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetReactNativePackage.java 查看文件

1
+package org.jitsi.meet.sdk;
2
+
3
+import androidx.annotation.NonNull;
4
+
5
+import com.facebook.react.ReactPackage;
6
+import com.facebook.react.bridge.NativeModule;
7
+import com.facebook.react.bridge.ReactApplicationContext;
8
+import com.facebook.react.uimanager.ViewManager;
9
+
10
+import java.util.ArrayList;
11
+import java.util.Arrays;
12
+import java.util.Collections;
13
+import java.util.List;
14
+
15
+public class JitsiMeetReactNativePackage implements ReactPackage {
16
+    @NonNull
17
+    @Override
18
+    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
19
+        List<NativeModule> modules
20
+            = new ArrayList<>(Arrays.<NativeModule>asList(
21
+                new AndroidSettingsModule(reactContext),
22
+                new AppInfoModule(reactContext),
23
+                new AudioModeModule(reactContext),
24
+                new JavaScriptSandboxModule(reactContext),
25
+                new LocaleDetector(reactContext),
26
+                new LogBridgeModule(reactContext),
27
+                new PictureInPictureModule(reactContext),
28
+                new ProximityModule(reactContext),
29
+                new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
30
+                ));
31
+        return modules;
32
+    }
33
+
34
+    @NonNull
35
+    @Override
36
+    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
37
+        return Collections.emptyList();
38
+    }
39
+}

+ 99
- 0
react-native-sdk/components/JitsiMeet.tsx 查看文件

1
+/* eslint-disable lines-around-comment,  no-undef, no-unused-vars  */
2
+
3
+import 'react-native-gesture-handler';
4
+// Apply all necessary polyfills as early as possible
5
+// to make sure anything imported henceforth sees them.
6
+import 'react-native-get-random-values';
7
+import '../react/features/mobile/polyfills';
8
+
9
+// @ts-ignore
10
+import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
11
+import { View } from 'react-native';
12
+
13
+import { convertPropsToURL } from '../functions';
14
+import { appNavigate } from '../react/features/app/actions.native';
15
+import { App } from '../react/features/app/components/App.native';
16
+import { setAudioMuted, setVideoMuted } from '../react/features/base/media/actions';
17
+// @ts-ignore
18
+import JitsiThemePaperProvider from '../react/features/base/ui/components/JitsiThemeProvider';
19
+
20
+
21
+interface IAppProps {
22
+    flags: [];
23
+    meetingOptions: {
24
+        domain: string;
25
+        roomName: string;
26
+        onReadyToClose?: Function;
27
+        onConferenceJoined?: Function;
28
+        onConferenceWillJoin?: Function;
29
+        onConferenceLeft?: Function;
30
+        onParticipantJoined?: Function;
31
+        settings?: {
32
+            startWithAudioMuted?: boolean;
33
+            startAudioOnly?: boolean;
34
+            startWithVideoMuted?: boolean;
35
+        }
36
+    };
37
+    style?: Object;
38
+}
39
+
40
+/**
41
+ * Main React Native SDK component that displays a Jitsi Meet conference and gets all required params as props
42
+ */
43
+const JitsiMeet = forwardRef(({ flags, meetingOptions, style }: IAppProps, ref) => {
44
+    const [ appProps, setAppProps ] = useState({});
45
+    const app = useRef(null);
46
+
47
+    // eslint-disable-next-line arrow-body-style
48
+    useImperativeHandle(ref, () => ({
49
+        close: () => {
50
+            const dispatch = app.current.state.store.dispatch;
51
+
52
+            dispatch(appNavigate(undefined));
53
+        },
54
+        setAudioMuted: muted => {
55
+            const dispatch = app.current.state.store.dispatch;
56
+
57
+            dispatch(setAudioMuted(muted));
58
+        },
59
+        setVideoMuted: muted => {
60
+            const dispatch = app.current.state.store.dispatch;
61
+
62
+            dispatch(setVideoMuted(muted));
63
+        }
64
+    }));
65
+
66
+    useEffect(
67
+        () => {
68
+            const url = convertPropsToURL(meetingOptions.domain, meetingOptions.roomName);
69
+
70
+            setAppProps({
71
+                'url': {
72
+                    url,
73
+                    config: meetingOptions.settings
74
+                },
75
+                'rnSdkHandlers': {
76
+                    onReadyToClose: meetingOptions.onReadyToClose,
77
+                    onConferenceJoined: meetingOptions.onConferenceJoined,
78
+                    onConferenceWillJoin: meetingOptions.onConferenceWillJoin,
79
+                    onConferenceLeft: meetingOptions.onConferenceLeft,
80
+                    onParticipantJoined: meetingOptions.onParticipantJoined
81
+                },
82
+                'flags': { ...flags }
83
+            });
84
+        }, []
85
+    );
86
+
87
+    return (
88
+        <View style = { style }>
89
+            <JitsiThemePaperProvider>
90
+                {/* @ts-ignore */}
91
+                <App
92
+                    { ...appProps }
93
+                    ref = { app } />
94
+            </JitsiThemePaperProvider>
95
+        </View>
96
+    );
97
+});
98
+
99
+export default JitsiMeet;

+ 7
- 0
react-native-sdk/constants.ts 查看文件

1
+
2
+module.exports = {
3
+    androidSourcePath: '../android/sdk/src/main/java/org/jitsi/meet/sdk',
4
+    androidTargetPath: './android/src/main/java/org/jitsi/meet/sdk',
5
+    iosSrcPath: '../ios/sdk/src',
6
+    iosDestPath: './ios/src'
7
+};

+ 8
- 0
react-native-sdk/functions.ts 查看文件

1
+/**
2
+ * Converts the meetingOptions domain and roomName to a URL that can be passed to the App component.
3
+ * @param {*} domain domain address from props.
4
+ * @param {*} roomName room name from props.
5
+ */
6
+export function convertPropsToURL(domain, roomName) {
7
+    return `${domain}/${roomName}`;
8
+}

+ 26
- 0
react-native-sdk/jitsi-meet-react-native.podspec 查看文件

1
+require 'json'
2
+
3
+package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+Pod::Spec.new do |s|
6
+  s.name           = package['name']
7
+  s.version        = package['version']
8
+  s.summary        = package['description']
9
+  s.description    = package['description']
10
+  s.license        = package['license']
11
+  s.author         = package['author']
12
+  s.homepage       = package['homepage']
13
+  s.source         = { :git => package['repository']['url'], :tag => s.version }
14
+
15
+  s.requires_arc   = true
16
+  s.platform       = :ios, '12.4'
17
+
18
+  s.preserve_paths = 'ios/**/*'
19
+  s.source_files   =  'ios/**/*.{h,m,swift}'
20
+
21
+
22
+  s.dependency 'React-Core'
23
+  s.dependency 'ObjectiveDropboxOfficial', '6.2.3'
24
+  s.dependency 'JitsiWebRTC', '~> 111.0.0'
25
+
26
+end

+ 6075
- 0
react-native-sdk/package-lock.json
文件差异内容过多而无法显示
查看文件


+ 107
- 0
react-native-sdk/package.json 查看文件

1
+{
2
+    "name": "@jitsi/react-native-sdk",
3
+    "version": "0.1.0",
4
+    "description": "React Native SDK for Jitsi Meet.",
5
+    "main": "index.js",
6
+    "license": "Apache-2.0",
7
+    "author": "",
8
+    "homepage": "https://jitsi.org",
9
+    "repository": {
10
+        "type": "git",
11
+        "url": "git://github.com/jitsi/jitsi-meet.git"
12
+    },
13
+    "dependencies": {
14
+        "@amplitude/react-native": "2.7.0",
15
+        "@giphy/react-components": "6.8.1",
16
+        "@giphy/react-native-sdk": "2.3.0",
17
+        "@hapi/bourne": "2.0.0",
18
+        "@jitsi/js-utils": "2.0.5",
19
+        "@jitsi/logger": "2.0.0",
20
+        "@jitsi/rtcstats": "9.5.1",
21
+        "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
22
+        "@react-navigation/bottom-tabs": "6.5.3",
23
+        "@react-navigation/elements": "1.3.13",
24
+        "@react-navigation/material-top-tabs": "6.5.2",
25
+        "@react-navigation/native": "6.1.2",
26
+        "@react-navigation/stack": "6.3.11",
27
+        "@xmldom/xmldom": "0.8.7",
28
+        "base64-js": "1.3.1",
29
+        "grapheme-splitter": "1.0.4",
30
+        "i18n-iso-countries": "6.8.0",
31
+        "i18next": "17.0.6",
32
+        "i18next-browser-languagedetector": "3.0.1",
33
+        "i18next-xhr-backend": "3.0.0",
34
+        "js-md5": "0.6.1",
35
+        "js-sha512": "0.8.0",
36
+        "jwt-decode": "2.2.0",
37
+        "lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1643.0.0+0748d89a/lib-jitsi-meet.tgz",
38
+        "lodash": "4.17.21",
39
+        "moment": "2.29.4",
40
+        "moment-duration-format": "2.2.2",
41
+        "optional-require": "1.0.3",
42
+        "promise.allsettled": "1.0.4",
43
+        "punycode": "2.1.1",
44
+        "react-emoji-render": "1.2.4",
45
+        "react-i18next": "10.11.4",
46
+        "react-linkify": "1.0.0-alpha",
47
+        "react-redux": "7.1.0",
48
+        "react-window": "1.8.6",
49
+        "react-youtube": "10.1.0",
50
+        "redux": "4.0.4",
51
+        "redux-thunk": "2.4.1",
52
+        "unorm": "1.6.0",
53
+        "util": "0.12.1",
54
+        "uuid": "8.3.2",
55
+        "zxcvbn": "4.4.2"
56
+    },
57
+    "peerDependencies": {
58
+        "@react-native-async-storage/async-storage": "1.17.3",
59
+        "@react-native-community/clipboard": "1.5.1",
60
+        "@react-native-community/netinfo": "7.1.7",
61
+        "@react-native-community/slider": "4.1.12",
62
+        "@react-native-google-signin/google-signin": "7.0.4",
63
+        "@react-native-masked-view/masked-view": "0.2.6",
64
+        "react-native": "*",
65
+        "react": "*",
66
+        "react-dom": "*",
67
+        "react-native-background-timer": "2.4.1",
68
+        "react-native-calendar-events": "2.2.0",
69
+        "react-native-callstats": "3.73.7",
70
+        "react-native-collapsible": "1.6.0",
71
+        "react-native-default-preference": "1.4.4",
72
+        "react-native-device-info": "8.4.8",
73
+        "react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
74
+        "react-native-get-random-values": "1.7.2",
75
+        "react-native-immersive": "2.0.0",
76
+        "react-native-keep-awake": "4.0.0",
77
+        "react-native-pager-view": "5.4.9",
78
+        "react-native-paper": "4.11.1",
79
+        "react-native-performance": "2.1.0",
80
+        "react-native-orientation-locker": "1.5.0",
81
+        "react-native-sound": "0.11.1",
82
+        "react-native-splash-screen": "3.3.0",
83
+        "react-native-svg": "12.4.3",
84
+        "react-native-svg-transformer": "1.0.0",
85
+        "react-native-tab-view": "3.1.1",
86
+        "react-native-url-polyfill": "1.3.0",
87
+        "react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
88
+        "react-native-watch-connectivity": "1.0.11",
89
+        "react-native-webrtc": "111.0.0",
90
+        "react-native-webview": "11.15.1",
91
+        "react-native-youtube-iframe": "2.2.1"
92
+    },
93
+    "overrides": {
94
+        "strophe.js@1.5.0": {
95
+            "@xmldom/xmldom": "0.8.7"
96
+        }
97
+    },
98
+    "scripts": {
99
+        "prepare": "node prepare_sdk.js"
100
+    },
101
+    "bugs": {
102
+        "url": "https://github.com/jitsi/jitsi-meet/issues"
103
+    },
104
+    "keywords": [
105
+        "react-native"
106
+    ]
107
+}

+ 214
- 0
react-native-sdk/prepare_sdk.js 查看文件

1
+const fs = require('fs');
2
+const path = require('path');
3
+
4
+const packageJSON = require('../package.json');
5
+
6
+const {
7
+    androidSourcePath,
8
+    androidTargetPath,
9
+    iosDestPath,
10
+    iosSrcPath
11
+} = require('./constants.ts');
12
+const SDKPackageJSON = require('./package.json');
13
+
14
+
15
+/**
16
+ * Copies a specified file in a way that recursive copy is possible.
17
+ */
18
+function copyFileSync(source, target) {
19
+
20
+    let targetFile = target;
21
+
22
+    // If target is a directory, a new file with the same name will be created
23
+    if (fs.existsSync(target)) {
24
+        if (fs.lstatSync(target).isDirectory()) {
25
+            targetFile = path.join(target, path.basename(source));
26
+        }
27
+    }
28
+
29
+    fs.copyFileSync(source, targetFile);
30
+}
31
+
32
+
33
+/**
34
+ * Copies a specified directory recursively.
35
+ */
36
+function copyFolderRecursiveSync(source, target) {
37
+    let files = [];
38
+    const targetFolder = path.join(target, path.basename(source));
39
+
40
+    if (!fs.existsSync(targetFolder)) {
41
+        fs.mkdirSync(targetFolder, { recursive: true });
42
+    }
43
+
44
+    if (fs.lstatSync(source).isDirectory()) {
45
+        files = fs.readdirSync(source);
46
+        files.forEach(file => {
47
+            const curSource = path.join(source, file);
48
+
49
+            if (fs.lstatSync(curSource).isDirectory()) {
50
+                copyFolderRecursiveSync(curSource, targetFolder);
51
+            } else {
52
+                copyFileSync(curSource, targetFolder);
53
+            }
54
+        });
55
+    }
56
+}
57
+
58
+/**
59
+ * Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
60
+ */
61
+function mergeDependencyVersions() {
62
+    for (const key in SDKPackageJSON.dependencies) {
63
+        if (SDKPackageJSON.dependencies.hasOwnProperty(key)) {
64
+            SDKPackageJSON.dependencies[key] = packageJSON.dependencies[key] || packageJSON.devDependencies[key];
65
+        }
66
+    }
67
+    const data = JSON.stringify(SDKPackageJSON, null, 4);
68
+
69
+    fs.writeFileSync('package.json', data);
70
+}
71
+
72
+// TODO: put this in a seperate step
73
+mergeDependencyVersions();
74
+
75
+copyFolderRecursiveSync(
76
+    '../images',
77
+    '.'
78
+);
79
+copyFolderRecursiveSync(
80
+    '../sounds',
81
+    '.'
82
+);
83
+copyFolderRecursiveSync(
84
+    '../lang',
85
+    '.'
86
+);
87
+copyFolderRecursiveSync(
88
+    '../modules',
89
+    '.'
90
+);
91
+copyFolderRecursiveSync(
92
+    '../react',
93
+    '.'
94
+);
95
+copyFolderRecursiveSync(
96
+    '../service',
97
+    '.'
98
+);
99
+copyFolderRecursiveSync(
100
+    '../ios/sdk/sdk.xcodeproj',
101
+    './ios'
102
+);
103
+copyFolderRecursiveSync(
104
+    `${iosSrcPath}/callkit`,
105
+    iosDestPath
106
+);
107
+copyFolderRecursiveSync(
108
+    `${iosSrcPath}/dropbox`,
109
+    iosDestPath
110
+);
111
+copyFolderRecursiveSync(
112
+    '../ios/sdk/src/picture-in-picture',
113
+    iosDestPath
114
+);
115
+fs.copyFileSync(
116
+    `${iosSrcPath}/AppInfo.m`,
117
+    `${iosDestPath}/AppInfo.m`
118
+);
119
+fs.copyFileSync(
120
+    `${iosSrcPath}/AudioMode.m`,
121
+    `${iosDestPath}/AudioMode.m`
122
+);
123
+fs.copyFileSync(
124
+    `${iosSrcPath}/InfoPlistUtil.m`,
125
+    `${iosDestPath}/InfoPlistUtil.m`
126
+);
127
+fs.copyFileSync(
128
+    `${iosSrcPath}/InfoPlistUtil.h`,
129
+    `${iosDestPath}/InfoPlistUtil.h`
130
+);
131
+fs.copyFileSync(
132
+    `${iosSrcPath}/JavaScriptSandbox.m`,
133
+    `${iosDestPath}/JavaScriptSandbox.m`
134
+);
135
+fs.copyFileSync(
136
+    `${iosSrcPath}/JitsiAudioSession.m`,
137
+    `${iosDestPath}/JitsiAudioSession.m`
138
+);
139
+fs.copyFileSync(
140
+    `${iosSrcPath}/JitsiAudioSession.h`,
141
+    `${iosDestPath}/JitsiAudioSession.h`
142
+);
143
+fs.copyFileSync(
144
+    `${iosSrcPath}/JitsiAudioSession+Private.h`,
145
+    `${iosDestPath}/JitsiAudioSession+Private.h`
146
+);
147
+fs.copyFileSync(
148
+    `${iosSrcPath}/LocaleDetector.m`,
149
+    `${iosDestPath}/LocaleDetector.m`
150
+);
151
+fs.copyFileSync(
152
+    `${iosSrcPath}/POSIX.m`,
153
+    `${iosDestPath}/POSIX.m`
154
+);
155
+fs.copyFileSync(
156
+    `${iosSrcPath}/Proximity.m`,
157
+    `${iosDestPath}/Proximity.m`
158
+);
159
+copyFolderRecursiveSync(
160
+    `${androidSourcePath}/log`,
161
+     `${androidTargetPath}/log`
162
+);
163
+copyFolderRecursiveSync(
164
+    `${androidSourcePath}/net`,
165
+    `${androidTargetPath}/log`
166
+);
167
+fs.copyFileSync(
168
+    `${androidSourcePath}/AndroidSettingsModule.java`,
169
+    `${androidTargetPath}/AndroidSettingsModule.java`
170
+);
171
+fs.copyFileSync(
172
+    `${androidSourcePath}/AppInfoModule.java`,
173
+    `${androidTargetPath}/AppInfoModule.java`
174
+);
175
+fs.copyFileSync(
176
+    `${androidSourcePath}/AudioDeviceHandlerConnectionService.java`,
177
+    `${androidTargetPath}/AudioDeviceHandlerConnectionService.java`
178
+);
179
+fs.copyFileSync(
180
+    `${androidSourcePath}/AudioDeviceHandlerGeneric.java`,
181
+    `${androidTargetPath}/AudioDeviceHandlerGeneric.java`
182
+);
183
+fs.copyFileSync(
184
+    `${androidSourcePath}/AudioModeModule.java`,
185
+    `${androidTargetPath}/AudioModeModule.java`
186
+);
187
+fs.copyFileSync(
188
+    `${androidSourcePath}/ConnectionService.java`,
189
+    `${androidTargetPath}/ConnectionService.java`
190
+);
191
+fs.copyFileSync(
192
+    `${androidSourcePath}/JavaScriptSandboxModule.java`,
193
+    `${androidTargetPath}/JavaScriptSandboxModule.java`
194
+);
195
+fs.copyFileSync(
196
+    `${androidSourcePath}/LocaleDetector.java`,
197
+    `${androidTargetPath}/LocaleDetector.java`
198
+);
199
+fs.copyFileSync(
200
+    `${androidSourcePath}/LogBridgeModule.java`,
201
+    `${androidTargetPath}/LogBridgeModule.java`
202
+);
203
+fs.copyFileSync(
204
+    `${androidSourcePath}/PictureInPictureModule.java`,
205
+    `${androidTargetPath}/PictureInPictureModule.java`
206
+);
207
+fs.copyFileSync(
208
+    `${androidSourcePath}/ProximityModule.java`,
209
+    `${androidTargetPath}/ProximityModule.java`
210
+);
211
+fs.copyFileSync(
212
+    `${androidSourcePath}/RNConnectionService.java`,
213
+    `${androidTargetPath}/RNConnectionService.java`
214
+);

+ 1
- 0
react/features/app/middlewares.native.ts 查看文件

10
 import '../mobile/permissions/middleware';
10
 import '../mobile/permissions/middleware';
11
 import '../mobile/proximity/middleware';
11
 import '../mobile/proximity/middleware';
12
 import '../mobile/wake-lock/middleware';
12
 import '../mobile/wake-lock/middleware';
13
+import '../mobile/react-native-sdk/middleware';
13
 import '../mobile/watchos/middleware';
14
 import '../mobile/watchos/middleware';
14
 import '../share-room/middleware';
15
 import '../share-room/middleware';
15
 import '../shared-video/middleware';
16
 import '../shared-video/middleware';

+ 20
- 0
react/features/mobile/external-api/functions.ts 查看文件

1
 import debounce from 'lodash/debounce';
1
 import debounce from 'lodash/debounce';
2
 import { NativeModules } from 'react-native';
2
 import { NativeModules } from 'react-native';
3
 
3
 
4
+import { IParticipant } from '../../base/participants/types';
5
+
4
 import { readyToClose } from './actions';
6
 import { readyToClose } from './actions';
5
 
7
 
6
 
8
 
25
 export const _sendReadyToClose = debounce(dispatch => {
27
 export const _sendReadyToClose = debounce(dispatch => {
26
     dispatch(readyToClose());
28
     dispatch(readyToClose());
27
 }, 2500, { leading: true });
29
 }, 2500, { leading: true });
30
+
31
+/**
32
+ * Returns a participant info object based on the passed participant object from redux.
33
+ *
34
+ * @param {Participant} participant - The participant object from the redux store.
35
+ * @returns {Object} - The participant info object.
36
+ */
37
+export function participantToParticipantInfo(participant: IParticipant) {
38
+    return {
39
+        isLocal: participant.local,
40
+        email: participant.email,
41
+        name: participant.name,
42
+        participantId: participant.id,
43
+        displayName: participant.displayName,
44
+        avatarUrl: participant.avatarURL,
45
+        role: participant.role
46
+    };
47
+}

+ 33
- 42
react/features/mobile/external-api/middleware.ts 查看文件

1
+/* eslint-disable lines-around-comment */
2
+
1
 import debounce from 'lodash/debounce';
3
 import debounce from 'lodash/debounce';
2
 import { NativeEventEmitter, NativeModules } from 'react-native';
4
 import { NativeEventEmitter, NativeModules } from 'react-native';
3
 import { AnyAction } from 'redux';
5
 import { AnyAction } from 'redux';
4
 
6
 
5
 // @ts-expect-error
7
 // @ts-expect-error
6
 import { ENDPOINT_TEXT_MESSAGE_NAME } from '../../../../modules/API/constants';
8
 import { ENDPOINT_TEXT_MESSAGE_NAME } from '../../../../modules/API/constants';
7
-import { appNavigate } from '../../app/actions';
9
+import { appNavigate } from '../../app/actions.native';
8
 import { IStore } from '../../app/types';
10
 import { IStore } from '../../app/types';
9
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
11
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
10
 import {
12
 import {
35
 import {
37
 import {
36
     getLocalParticipant,
38
     getLocalParticipant,
37
     getParticipantById,
39
     getParticipantById,
38
-    getRemoteParticipants,
39
-    isScreenShareParticipant
40
+    getRemoteParticipants
40
 } from '../../base/participants/functions';
41
 } from '../../base/participants/functions';
41
-import { IParticipant } from '../../base/participants/types';
42
 import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
42
 import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
43
 import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
43
 import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
44
 import { toggleScreensharing } from '../../base/tracks/actions.native';
44
 import { toggleScreensharing } from '../../base/tracks/actions.native';
45
 import { getLocalTracks, isLocalTrackMuted } from '../../base/tracks/functions.native';
45
 import { getLocalTracks, isLocalTrackMuted } from '../../base/tracks/functions.native';
46
 import { ITrack } from '../../base/tracks/types';
46
 import { ITrack } from '../../base/tracks/types';
47
 import { CLOSE_CHAT, OPEN_CHAT } from '../../chat/actionTypes';
47
 import { CLOSE_CHAT, OPEN_CHAT } from '../../chat/actionTypes';
48
-import { openChat } from '../../chat/actions';
49
-import { closeChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.any';
48
+import { closeChat, openChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.native';
50
 import { setRequestingSubtitles } from '../../subtitles/actions.any';
49
 import { setRequestingSubtitles } from '../../subtitles/actions.any';
51
-import { muteLocal } from '../../video-menu/actions';
50
+import { muteLocal } from '../../video-menu/actions.native';
52
 import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
51
 import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
52
+// @ts-ignore
53
+import { isExternalAPIAvailable } from '../react-native-sdk/functions';
53
 
54
 
54
 import { READY_TO_CLOSE } from './actionTypes';
55
 import { READY_TO_CLOSE } from './actionTypes';
55
 import { setParticipantsWithScreenShare } from './actions';
56
 import { setParticipantsWithScreenShare } from './actions';
56
-import { sendEvent } from './functions';
57
+import { participantToParticipantInfo, sendEvent } from './functions';
57
 import logger from './logger';
58
 import logger from './logger';
58
 
59
 
59
 /**
60
 /**
90
  */
91
  */
91
 const PARTICIPANTS_INFO_RETRIEVED = 'PARTICIPANTS_INFO_RETRIEVED';
92
 const PARTICIPANTS_INFO_RETRIEVED = 'PARTICIPANTS_INFO_RETRIEVED';
92
 
93
 
94
+const externalAPIEnabled = isExternalAPIAvailable();
95
+
96
+let eventEmitter: any;
97
+
93
 const { ExternalAPI } = NativeModules;
98
 const { ExternalAPI } = NativeModules;
94
-const eventEmitter = new NativeEventEmitter(ExternalAPI);
99
+
100
+if (externalAPIEnabled) {
101
+    eventEmitter = new NativeEventEmitter(ExternalAPI);
102
+}
95
 
103
 
96
 /**
104
 /**
97
  * Middleware that captures Redux actions and uses the ExternalAPI module to
105
  * Middleware that captures Redux actions and uses the ExternalAPI module to
100
  * @param {Store} store - Redux store.
108
  * @param {Store} store - Redux store.
101
  * @returns {Function}
109
  * @returns {Function}
102
  */
110
  */
103
-MiddlewareRegistry.register(store => next => action => {
111
+externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
104
     const oldAudioMuted = store.getState()['features/base/media'].audio.muted;
112
     const oldAudioMuted = store.getState()['features/base/media'].audio.muted;
105
     const result = next(action);
113
     const result = next(action);
106
     const { type } = action;
114
     const { type } = action;
144
 
152
 
145
     case CONNECTION_DISCONNECTED: {
153
     case CONNECTION_DISCONNECTED: {
146
         // FIXME: This is a hack. See the description in the JITSI_CONNECTION_CONFERENCE_KEY constant definition.
154
         // FIXME: This is a hack. See the description in the JITSI_CONNECTION_CONFERENCE_KEY constant definition.
147
-        // Check if this connection was attached to any conference. If it wasn't, fake a CONFERENCE_TERMINATED event.
155
+        // Check if this connection was attached to any conference.
156
+        // If it wasn't, fake a CONFERENCE_TERMINATED event.
148
         const { connection } = action;
157
         const { connection } = action;
149
         const conference = connection[JITSI_CONNECTION_CONFERENCE_KEY];
158
         const conference = connection[JITSI_CONNECTION_CONFERENCE_KEY];
150
 
159
 
189
 
198
 
190
         const { participant } = action;
199
         const { participant } = action;
191
 
200
 
192
-        if (isScreenShareParticipant(participant)) {
201
+        if (participant?.isVirtualScreenshareParticipant) {
193
             break;
202
             break;
194
         }
203
         }
195
 
204
 
196
         sendEvent(
205
         sendEvent(
197
             store,
206
             store,
198
             action.type,
207
             action.type,
199
-            _participantToParticipantInfo(participant) /* data */
208
+            participantToParticipantInfo(participant) /* data */
200
         );
209
         );
201
         break;
210
         break;
202
     }
211
     }
239
  * The listener is debounced to avoid state thrashing that might occur,
248
  * The listener is debounced to avoid state thrashing that might occur,
240
  * especially when switching in or out of p2p.
249
  * especially when switching in or out of p2p.
241
  */
250
  */
242
-StateListenerRegistry.register(
251
+externalAPIEnabled && StateListenerRegistry.register(
243
     /* selector */ state => state['features/base/tracks'],
252
     /* selector */ state => state['features/base/tracks'],
244
     /* listener */ debounce((tracks: ITrack[], store: IStore) => {
253
     /* listener */ debounce((tracks: ITrack[], store: IStore) => {
245
         const oldScreenShares = store.getState()['features/mobile/external-api'].screenShares || [];
254
         const oldScreenShares = store.getState()['features/mobile/external-api'].screenShares || [];
275
 
284
 
276
     }, 100));
285
     }, 100));
277
 
286
 
278
-/**
279
- * Returns a participant info object based on the passed participant object from redux.
280
- *
281
- * @param {Participant} participant - The participant object from the redux store.
282
- * @returns {Object} - The participant info object.
283
- */
284
-function _participantToParticipantInfo(participant: IParticipant) {
285
-    return {
286
-        isLocal: participant.local,
287
-        email: participant.email,
288
-        name: participant.name,
289
-        participantId: participant.id,
290
-        displayName: participant.displayName,
291
-        avatarUrl: participant.avatarURL,
292
-        role: participant.role
293
-    };
294
-}
295
-
296
 /**
287
 /**
297
  * Registers for events sent from the native side via NativeEventEmitter.
288
  * Registers for events sent from the native side via NativeEventEmitter.
298
  *
289
  *
307
         dispatch(appNavigate(undefined));
298
         dispatch(appNavigate(undefined));
308
     });
299
     });
309
 
300
 
310
-    eventEmitter.addListener(ExternalAPI.SET_AUDIO_MUTED, ({ muted }) => {
301
+    eventEmitter.addListener(ExternalAPI.SET_AUDIO_MUTED, ({ muted }: any) => {
311
         dispatch(muteLocal(muted, MEDIA_TYPE.AUDIO));
302
         dispatch(muteLocal(muted, MEDIA_TYPE.AUDIO));
312
     });
303
     });
313
 
304
 
314
-    eventEmitter.addListener(ExternalAPI.SET_VIDEO_MUTED, ({ muted }) => {
305
+    eventEmitter.addListener(ExternalAPI.SET_VIDEO_MUTED, ({ muted }: any) => {
315
         dispatch(muteLocal(muted, MEDIA_TYPE.VIDEO));
306
         dispatch(muteLocal(muted, MEDIA_TYPE.VIDEO));
316
     });
307
     });
317
 
308
 
318
-    eventEmitter.addListener(ExternalAPI.SEND_ENDPOINT_TEXT_MESSAGE, ({ to, message }) => {
309
+    eventEmitter.addListener(ExternalAPI.SEND_ENDPOINT_TEXT_MESSAGE, ({ to, message }: any) => {
319
         const conference = getCurrentConference(getState());
310
         const conference = getCurrentConference(getState());
320
 
311
 
321
         try {
312
         try {
328
         }
319
         }
329
     });
320
     });
330
 
321
 
331
-    eventEmitter.addListener(ExternalAPI.TOGGLE_SCREEN_SHARE, ({ enabled }) => {
322
+    eventEmitter.addListener(ExternalAPI.TOGGLE_SCREEN_SHARE, ({ enabled }: any) => {
332
         dispatch(toggleScreensharing(enabled));
323
         dispatch(toggleScreensharing(enabled));
333
     });
324
     });
334
 
325
 
335
-    eventEmitter.addListener(ExternalAPI.RETRIEVE_PARTICIPANTS_INFO, ({ requestId }) => {
326
+    eventEmitter.addListener(ExternalAPI.RETRIEVE_PARTICIPANTS_INFO, ({ requestId }: any) => {
336
 
327
 
337
         const participantsInfo = [];
328
         const participantsInfo = [];
338
         const remoteParticipants = getRemoteParticipants(store);
329
         const remoteParticipants = getRemoteParticipants(store);
339
         const localParticipant = getLocalParticipant(store);
330
         const localParticipant = getLocalParticipant(store);
340
 
331
 
341
-        localParticipant && participantsInfo.push(_participantToParticipantInfo(localParticipant));
332
+        localParticipant && participantsInfo.push(participantToParticipantInfo(localParticipant));
342
         remoteParticipants.forEach(participant => {
333
         remoteParticipants.forEach(participant => {
343
             if (!participant.fakeParticipant) {
334
             if (!participant.fakeParticipant) {
344
-                participantsInfo.push(_participantToParticipantInfo(participant));
335
+                participantsInfo.push(participantToParticipantInfo(participant));
345
             }
336
             }
346
         });
337
         });
347
 
338
 
354
             });
345
             });
355
     });
346
     });
356
 
347
 
357
-    eventEmitter.addListener(ExternalAPI.OPEN_CHAT, ({ to }) => {
348
+    eventEmitter.addListener(ExternalAPI.OPEN_CHAT, ({ to }: any) => {
358
         const participant = getParticipantById(store, to);
349
         const participant = getParticipantById(store, to);
359
 
350
 
360
         dispatch(openChat(participant));
351
         dispatch(openChat(participant));
364
         dispatch(closeChat());
355
         dispatch(closeChat());
365
     });
356
     });
366
 
357
 
367
-    eventEmitter.addListener(ExternalAPI.SEND_CHAT_MESSAGE, ({ message, to }) => {
358
+    eventEmitter.addListener(ExternalAPI.SEND_CHAT_MESSAGE, ({ message, to }: any) => {
368
         const participant = getParticipantById(store, to);
359
         const participant = getParticipantById(store, to);
369
 
360
 
370
         if (participant) {
361
         if (participant) {
374
         dispatch(sendMessage(message));
365
         dispatch(sendMessage(message));
375
     });
366
     });
376
 
367
 
377
-    eventEmitter.addListener(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED, ({ enabled }) => {
368
+    eventEmitter.addListener(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED, ({ enabled }: any) => {
378
         dispatch(setRequestingSubtitles(enabled));
369
         dispatch(setRequestingSubtitles(enabled));
379
     });
370
     });
380
 }
371
 }

+ 13
- 0
react/features/mobile/react-native-sdk/functions.js 查看文件

1
+import { NativeModules } from 'react-native';
2
+
3
+
4
+/**
5
+ * Determimes if the ExternalAPI native module is available.
6
+ *
7
+ * @returns {boolean} If yes {@code true} otherwise {@code false}.
8
+ */
9
+export function isExternalAPIAvailable() {
10
+    const { ExternalAPI } = NativeModules;
11
+
12
+    return ExternalAPI !== null;
13
+}

+ 50
- 0
react/features/mobile/react-native-sdk/middleware.js 查看文件

1
+import { getAppProp } from '../../base/app/functions';
2
+import {
3
+    CONFERENCE_JOINED,
4
+    CONFERENCE_LEFT,
5
+    CONFERENCE_WILL_JOIN
6
+} from '../../base/conference/actionTypes';
7
+import { PARTICIPANT_JOINED } from '../../base/participants/actionTypes';
8
+import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
9
+import { READY_TO_CLOSE } from '../external-api/actionTypes';
10
+import { participantToParticipantInfo } from '../external-api/functions';
11
+
12
+import { isExternalAPIAvailable } from './functions';
13
+
14
+const externalAPIEnabled = isExternalAPIAvailable();
15
+
16
+/**
17
+ * Check if native modules are being used or not. If not then the init of middleware doesn't happen.
18
+ */
19
+!externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
20
+    const result = next(action);
21
+    const { type } = action;
22
+    const rnSdkHandlers = getAppProp(store, 'rnSdkHandlers');
23
+
24
+    switch (type) {
25
+    case READY_TO_CLOSE:
26
+        rnSdkHandlers.onReadyToClose && rnSdkHandlers.onReadyToClose();
27
+        break;
28
+    case CONFERENCE_JOINED:
29
+        rnSdkHandlers.onConferenceJoined && rnSdkHandlers.onConferenceJoined();
30
+        break;
31
+    case CONFERENCE_WILL_JOIN:
32
+        rnSdkHandlers.onConferenceWillJoin && rnSdkHandlers.onConferenceWillJoin();
33
+        break;
34
+    case CONFERENCE_LEFT:
35
+        //  Props are torn down at this point, perhaps need to leave this one out
36
+        break;
37
+    case PARTICIPANT_JOINED: {
38
+        const { participant } = action;
39
+        const participantInfo = participantToParticipantInfo(participant);
40
+
41
+        rnSdkHandlers.onParticipantJoined && rnSdkHandlers.onParticipantJoined(participantInfo);
42
+        break;
43
+    }
44
+    }
45
+
46
+
47
+    return result;
48
+}
49
+);
50
+

正在加载...
取消
保存