├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── README.md ├── client ├── .babelrc ├── .env.example ├── .gitignore ├── App.js ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── build_defs.bzl │ │ ├── debug.keystore │ │ ├── google-services.json │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── call │ │ │ │ └── trigger │ │ │ │ └── ReactNativeFlipper.java │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets │ │ │ └── fonts │ │ │ │ ├── Roboto-Black.ttf │ │ │ │ ├── Roboto-BlackItalic.ttf │ │ │ │ ├── Roboto-Bold.ttf │ │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ │ ├── Roboto-Italic.ttf │ │ │ │ ├── Roboto-Light.ttf │ │ │ │ ├── Roboto-LightItalic.ttf │ │ │ │ ├── Roboto-Medium.ttf │ │ │ │ ├── Roboto-MediumItalic.ttf │ │ │ │ ├── Roboto-Regular.ttf │ │ │ │ ├── Roboto-Thin.ttf │ │ │ │ └── Roboto-ThinItalic.ttf │ │ │ ├── java │ │ │ └── com │ │ │ │ └── call │ │ │ │ └── trigger │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── link-assets-manifest.json │ └── settings.gradle ├── app.json ├── babel.config.js ├── firebase.json ├── index.js ├── ios │ ├── BroadcastScreen │ │ ├── Atomic.swift │ │ ├── BroadcastScreen.entitlements │ │ ├── DarwinNotificationCenter.swift │ │ ├── Info.plist │ │ ├── SampleHandler.swift │ │ ├── SampleUploader.swift │ │ └── SocketConnection.swift │ ├── ExportOptions.plist │ ├── Podfile │ ├── Podfile.lock │ ├── ReactNativeCallTrigger-Bridging-Header.h │ ├── ReactNativeCallTrigger.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── ReactNativeCallTrigger.xcscheme │ ├── ReactNativeCallTrigger.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── ReactNativeCallTrigger │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── 1024.png │ │ │ │ ├── 114.png │ │ │ │ ├── 120.png │ │ │ │ ├── 180.png │ │ │ │ ├── 29.png │ │ │ │ ├── 40.png │ │ │ │ ├── 57.png │ │ │ │ ├── 58.png │ │ │ │ ├── 60.png │ │ │ │ ├── 80.png │ │ │ │ ├── 87.png │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ ├── ReactNativeCallTrigger.entitlements │ │ └── main.m │ ├── ReactNativeCallTriggerTests │ │ ├── Info.plist │ │ └── ReactNativeCallTriggerTests.m │ ├── VideosdkRPK.m │ ├── VideosdkRPK.swift │ └── link-assets-manifest.json ├── metro.config.js ├── package.json ├── react-native.config.js └── src │ ├── api │ └── api.js │ ├── assets │ ├── animation │ │ └── joining_lottie.json │ ├── fonts │ │ └── Roboto │ │ │ ├── Roboto-Black.ttf │ │ │ ├── Roboto-BlackItalic.ttf │ │ │ ├── Roboto-Bold.ttf │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ ├── Roboto-Italic.ttf │ │ │ ├── Roboto-Light.ttf │ │ │ ├── Roboto-LightItalic.ttf │ │ │ ├── Roboto-Medium.ttf │ │ │ ├── Roboto-MediumItalic.ttf │ │ │ ├── Roboto-Regular.ttf │ │ │ ├── Roboto-Thin.ttf │ │ │ └── Roboto-ThinItalic.ttf │ └── icons │ │ ├── CallEnd.js │ │ ├── CameraSwitch.js │ │ ├── Copy.js │ │ ├── Leave.js │ │ ├── MicOff.js │ │ ├── MicOn.js │ │ ├── VideoOff.js │ │ ├── VideoOn.js │ │ └── index.js │ ├── components │ ├── Avatar │ │ └── index.js │ ├── Button │ │ └── index.js │ ├── IconContainer │ │ └── index.js │ └── TextInputContainer │ │ └── index.js │ ├── navigators │ └── screenNames.js │ ├── scenes │ ├── home │ │ └── index.js │ └── meeting │ │ ├── Components │ │ └── WaitingToJoinView.js │ │ ├── MeetingContainer.js │ │ ├── OneToOne │ │ ├── LargeView │ │ │ ├── LargeVideoRTCView.js │ │ │ └── index.js │ │ ├── LocalViewContainer.js │ │ ├── MiniView │ │ │ ├── MiniVideoRTCView.js │ │ │ └── index.js │ │ ├── ParticipantLimitViewer.js │ │ └── index.js │ │ └── index.js │ ├── styles │ ├── colors.js │ ├── fonts.js │ └── spacing.js │ └── utils │ └── incoming-video-call.js ├── iOS_SETUP.md ├── public ├── FIR_1.png ├── FIR_2.png ├── code.png ├── image-1.png ├── image-11.png ├── image-2.png ├── image-3.png ├── image-4.png ├── image-5.png ├── image-6.png ├── image-7.png ├── image-8.png ├── ot-1.png ├── ot-2.png ├── ot-3.png ├── ot-4.png ├── xcd-1.png ├── xcd-2.png ├── xcd-3.png ├── xiomi-1.png └── xiomi-2.png └── server ├── .firebaserc ├── .gitignore ├── firebase.json ├── functions ├── .gitignore ├── index.js ├── package.json └── yarn.lock └── n ├── 404.html └── index.html /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Smartphone (please complete the following information):** 27 | - Device: [e.g. iPhone6] 28 | - OS: [e.g. iOS8.1] 29 | - Browser [e.g. stock browser, safari] 30 | - Version [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React native Call Trigger with VideoSDK 2 | 3 | ## Demo App 4 | 5 | 📲 Download the Sample iOS app here: _COMING SOON_ 6 | 7 | 📱 Download the Sample Android app here: https://appdistribution.firebase.dev/i/e977b56536d45796 8 |
9 | 10 | Before continuing, let's keep an eye on the third-party libraries used in this repository. 11 | 12 | 1. [React Native CallKeep](https://www.npmjs.com/package/react-native-callkeep) 13 | 2. [React Native VoIP Push Notification](https://www.npmjs.com/package/react-native-voip-push-notification) 14 | 3. [VideoSDK RN Android Overlay Permission](https://www.npmjs.com/package/videosdk-rn-android-overlay-permission) 15 | 4. [React Native Firebase - Messaging](https://rnfirebase.io/messaging/usage) 16 | 5. [React Native Firebase - Firestore](https://rnfirebase.io/firestore/usage) 17 | 18 | ⚠️ CallKit(iOS) and ConnectionService(Android) are only available on real devices, this app will not work on simulators. 19 | 20 | ## Client 21 | 22 | ### Step 1: Clone the sample project 23 | 24 | Clone the repository to your local environment. 25 | 26 | ```js 27 | https://github.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example.git 28 | ``` 29 | 30 | ### Step 2: Go to client folder 31 | 32 | ```js 33 | cd client 34 | ``` 35 | 36 | ### Step 3: Copy the .env.example file to .env file 37 | 38 | Open your favorite code editor and copy `.env.example` to `.env` file. 39 | 40 | ```js 41 | cp.env.example.env; 42 | ``` 43 | 44 | ### Step 4: Modify .env file 45 | 46 | Generate temporary token from [Video SDK Account](https://app.videosdk.live/signup) and add it to `.env` file. 47 | 48 | ```js title=".env" 49 | REACT_APP_VIDEOSDK_TOKEN = "TEMPORARY-TOKEN"; 50 | ``` 51 | 52 | ## iOS Setup 53 | 54 | Please follow the guidance of [iOS setup](./iOS_SETUP.md) 55 | 56 | ### Step 1: Install packages and pods 57 | 58 | ```js 59 | npm install 60 | ``` 61 | 62 | ```js 63 | cd ios && pod install 64 | ``` 65 | 66 | ### Step 2: Run the application 67 | 68 | ```js 69 | npm run ios 70 | ``` 71 | 72 | ## Android Setup 73 | 74 | ### Step 1: Setup Firebase 75 | 76 | #### FCM setup 77 | 78 | - Replace your firebase app `google-services.json` file at `/android/app/google-services.json` 79 | 80 | #### Firestore setup 81 | 82 | - Create web app in your firebase project and replace configuration at `client/database/firebaseDb.js` 83 | - Add `users` collection in firestore database. 84 | 85 | ![plot](./public/image-1.png) 86 | 87 | ### Step 2: Install packages and run the project 88 | 89 | ```js 90 | npm install 91 | ``` 92 | 93 | ```js 94 | npm run android 95 | ``` 96 | 97 | ### Step 3: Allow calling and overlay permissions 98 | 99 | After successfully installing the app, app will ask `Display over other apps` and `Access phone accounts` permission. 100 | 101 | For an app to handle calls in a dead or background state, both permissions are required. 102 | 103 | ### 1. Display over other apps permission 104 | 105 | #### Xiomi device 106 | 107 |

108 | 109 |

110 | 111 | #### Other device 112 | 113 |

114 | 115 |

116 | 117 | ### 2. Access phone accounts permission 118 | 119 |

120 | 121 |

122 | 123 | Now, different device have different ways to allow call account permissions. 124 | 125 | #### Xiomi device 126 | 127 |

128 | 129 |

130 | 131 | #### Other device 132 | 133 |

134 | 135 |

136 | 137 | Click on `All calling accounts` and allow the app to receive call. 138 | 139 |

140 | 141 |

142 | 143 | _**NOTE : It is necesary to setup local server before run the project.**_ 144 | 145 | ## Server Setup 146 | 147 | ### Step 1: Go to server folder 148 | 149 | ```js 150 | cd server 151 | ``` 152 | 153 | ### Step 2: Setup Firebase Admin SDK for managing FCM 154 | 155 | Download private Key from firebase and repplace it with `server/serviceAccountKey.json` 156 | ![plot](./public/image-3.png) 157 | 158 | ### Step 3: Install Package and start server 159 | 160 | ```js 161 | npm install 162 | ``` 163 | 164 | ```js 165 | npm run start 166 | ``` 167 | 168 | ### Step 4: Add local server url in client 169 | 170 | Add Local server ip address in `client/api/api.js` file. 171 | 172 | ```js title="api.js" 173 | const FCM_SERVER_URL = "http://192.168.1.10:9000"; 174 | ``` 175 | 176 | ## Issue 177 | 178 | You can generate the issue on [Github](https://github.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/issues) or ping us on [Discord](https://discord.gg/bsEukaNhrD) 179 | 180 | ## Other Information 181 | 182 | ### Tested on Devices 183 | 184 | - Samsung 185 | - Xiomi 186 | - Realme 187 | - Oppo 188 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["module:react-native-dotenv", { 4 | "moduleName": "@env", 5 | "path": ".env", 6 | "blocklist": null, 7 | "allowlist": null, 8 | "safe": false, 9 | "allowUndefined": true, 10 | "verbose": false 11 | }] 12 | ] 13 | } -------------------------------------------------------------------------------- /client/.env.example: -------------------------------------------------------------------------------- 1 | 2 | 3 | #Provide your Static authentication token below 4 | REACT_APP_VIDEOSDK_TOKEN = "" 5 | 6 | #OR 7 | 8 | #Provide your authentication server base url 9 | REACT_APP_AUTH_URL = "" 10 | 11 | #Any one value from above is mandatory for testing purpose 12 | #For production deployment, we recommend using an Authentication server instead of static token -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | google-services.json 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | !debug.keystore 44 | 45 | # Bundle artifact 46 | *.jsbundle 47 | 48 | # CocoaPods 49 | /ios/Pods/ 50 | /ios/GoogleService-Info.plist 51 | .env 52 | yarn.lock 53 | android/local.properties 54 | 55 | database/firebaseDb.js -------------------------------------------------------------------------------- /client/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import "react-native-gesture-handler"; 3 | import { NavigationContainer } from "@react-navigation/native"; 4 | import { createStackNavigator } from "@react-navigation/stack"; 5 | import { SCREEN_NAMES } from "./src/navigators/screenNames"; 6 | import Meeting from "./src/scenes/meeting"; 7 | import { LogBox, Text, Alert } from "react-native"; 8 | import Home from "./src/scenes/home"; 9 | import OverlayPermissionModule from "videosdk-rn-android-overlay-permission"; 10 | import RNCallKeep from "react-native-callkeep"; 11 | LogBox.ignoreLogs(["Warning: ..."]); 12 | LogBox.ignoreAllLogs(); 13 | 14 | const { Navigator, Screen } = createStackNavigator(); 15 | 16 | const linking = { 17 | prefixes: ["videocalling://"], 18 | config: { 19 | screens: { 20 | meetingscreen: { 21 | path: `meetingscreen/:token/:meetingId`, 22 | }, 23 | }, 24 | }, 25 | }; 26 | 27 | export default function App() { 28 | useEffect(() => { 29 | const options = { 30 | ios: { 31 | appName: "VideoSDK", 32 | }, 33 | android: { 34 | alertTitle: "Permissions required", 35 | alertDescription: 36 | "This application needs to access your phone accounts", 37 | cancelButton: "Cancel", 38 | okButton: "ok", 39 | imageName: "phone_account_icon", 40 | }, 41 | }; 42 | RNCallKeep.setup(options); 43 | RNCallKeep.setAvailable(true); 44 | 45 | if (Platform.OS === "android") { 46 | OverlayPermissionModule.requestOverlayPermission(); 47 | } 48 | }, []); 49 | 50 | return ( 51 | Loading...}> 52 | 59 | 64 | 69 | 70 | 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /client/android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.call.trigger", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.call.trigger", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /client/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "com.google.gms.google-services" 3 | apply plugin: "com.google.firebase.crashlytics" 4 | import com.android.build.OutputFile 5 | 6 | /** 7 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 8 | * and bundleReleaseJsAndAssets). 9 | * These basically call `react-native bundle` with the correct arguments during the Android build 10 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 11 | * bundle directly from the development server. Below you can see all the possible configurations 12 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 13 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 14 | * 15 | * project.ext.react = [ 16 | * // the name of the generated asset file containing your JS bundle 17 | * bundleAssetName: "index.android.bundle", 18 | * 19 | * // the entry file for bundle generation. If none specified and 20 | * // "index.android.js" exists, it will be used. Otherwise "index.js" is 21 | * // default. Can be overridden with ENTRY_FILE environment variable. 22 | * entryFile: "index.android.js", 23 | * 24 | * // https://reactnative.dev/docs/performance#enable-the-ram-format 25 | * bundleCommand: "ram-bundle", 26 | * 27 | * // whether to bundle JS and assets in debug mode 28 | * bundleInDebug: false, 29 | * 30 | * // whether to bundle JS and assets in release mode 31 | * bundleInRelease: true, 32 | * 33 | * // whether to bundle JS and assets in another build variant (if configured). 34 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 35 | * // The configuration property can be in the following formats 36 | * // 'bundleIn${productFlavor}${buildType}' 37 | * // 'bundleIn${buildType}' 38 | * // bundleInFreeDebug: true, 39 | * // bundleInPaidRelease: true, 40 | * // bundleInBeta: true, 41 | * 42 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 43 | * // for example: to disable dev mode in the staging build type (if configured) 44 | * devDisabledInStaging: true, 45 | * // The configuration property can be in the following formats 46 | * // 'devDisabledIn${productFlavor}${buildType}' 47 | * // 'devDisabledIn${buildType}' 48 | * 49 | * // the root of your project, i.e. where "package.json" lives 50 | * root: "../../", 51 | * 52 | * // where to put the JS bundle asset in debug mode 53 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 54 | * 55 | * // where to put the JS bundle asset in release mode 56 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 57 | * 58 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 59 | * // require('./image.png')), in debug mode 60 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 61 | * 62 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 63 | * // require('./image.png')), in release mode 64 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 65 | * 66 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 67 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 68 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 69 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 70 | * // for example, you might want to remove it from here. 71 | * inputExcludes: ["android/**", "ios/**"], 72 | * 73 | * // override which node gets called and with what additional arguments 74 | * nodeExecutableAndArgs: ["node"], 75 | * 76 | * // supply additional arguments to the packager 77 | * extraPackagerArgs: [] 78 | * ] 79 | */ 80 | 81 | project.ext.react = [ 82 | enableHermes: false, // clean and rebuild if changing 83 | ] 84 | 85 | apply from: "../../node_modules/react-native/react.gradle" 86 | 87 | /** 88 | * Set this to true to create two separate APKs instead of one: 89 | * - An APK that only works on ARM devices 90 | * - An APK that only works on x86 devices 91 | * The advantage is the size of the APK is reduced by about 4MB. 92 | * Upload all the APKs to the Play Store and people will download 93 | * the correct one based on the CPU architecture of their device. 94 | */ 95 | def enableSeparateBuildPerCPUArchitecture = false 96 | 97 | /** 98 | * Run Proguard to shrink the Java bytecode in release builds. 99 | */ 100 | def enableProguardInReleaseBuilds = false 101 | 102 | /** 103 | * The preferred build flavor of JavaScriptCore. 104 | * 105 | * For example, to use the international variant, you can use: 106 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 107 | * 108 | * The international variant includes ICU i18n library and necessary data 109 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 110 | * give correct results when using with locales other than en-US. Note that 111 | * this variant is about 6MiB larger per architecture than default. 112 | */ 113 | def jscFlavor = 'org.webkit:android-jsc:+' 114 | 115 | /** 116 | * Whether to enable the Hermes VM. 117 | * 118 | * This should be set on project.ext.react and mirrored here. If it is not set 119 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode 120 | * and the benefits of using Hermes will therefore be sharply reduced. 121 | */ 122 | def enableHermes = project.ext.react.get("enableHermes", false); 123 | 124 | android { 125 | ndkVersion rootProject.ext.ndkVersion 126 | 127 | compileSdkVersion rootProject.ext.compileSdkVersion 128 | 129 | compileOptions { 130 | sourceCompatibility JavaVersion.VERSION_1_8 131 | targetCompatibility JavaVersion.VERSION_1_8 132 | } 133 | 134 | defaultConfig { 135 | applicationId "com.call.trigger" 136 | minSdkVersion rootProject.ext.minSdkVersion 137 | targetSdkVersion rootProject.ext.targetSdkVersion 138 | versionCode 6 139 | versionName "2.0" 140 | } 141 | splits { 142 | abi { 143 | reset() 144 | enable enableSeparateBuildPerCPUArchitecture 145 | universalApk false // If true, also generate a universal APK 146 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 147 | } 148 | } 149 | signingConfigs { 150 | debug { 151 | storeFile file('debug.keystore') 152 | storePassword 'android' 153 | keyAlias 'androiddebugkey' 154 | keyPassword 'android' 155 | } 156 | release { 157 | storeFile file('videosdk_callkit.keystore') 158 | storePassword "videosdk" 159 | keyAlias "videosdk_callkit" 160 | keyPassword "videosdk" 161 | } 162 | } 163 | buildTypes { 164 | debug { 165 | signingConfig signingConfigs.debug 166 | } 167 | release { 168 | // Caution! In production, you need to generate your own keystore file. 169 | // see https://reactnative.dev/docs/signed-apk-android. 170 | signingConfig signingConfigs.debug 171 | minifyEnabled enableProguardInReleaseBuilds 172 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 173 | } 174 | } 175 | 176 | // applicationVariants are e.g. debug, release 177 | applicationVariants.all { variant -> 178 | variant.outputs.each { output -> 179 | // For each separate APK per architecture, set a unique version code as described here: 180 | // https://developer.android.com/studio/build/configure-apk-splits.html 181 | // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. 182 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] 183 | def abi = output.getFilter(OutputFile.ABI) 184 | if (abi != null) { // null for the universal-debug, universal-release variants 185 | output.versionCodeOverride = 186 | defaultConfig.versionCode * 1000 + versionCodes.get(abi) 187 | } 188 | 189 | } 190 | } 191 | 192 | lintOptions { 193 | checkReleaseBuilds false 194 | } 195 | } 196 | 197 | dependencies { 198 | implementation fileTree(dir: "libs", include: ["*.jar"]) 199 | //noinspection GradleDynamicVersion 200 | implementation "com.facebook.react:react-native:+" // From node_modules 201 | implementation platform('com.google.firebase:firebase-bom:31.1.1') 202 | implementation 'com.google.firebase:firebase-analytics' 203 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" 204 | 205 | implementation project(':rnfgservice') 206 | implementation project(':rnwebrtc') 207 | implementation project(':rnincallmanager') 208 | implementation project(':lottie-react-native') 209 | 210 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { 211 | exclude group:'com.facebook.fbjni' 212 | } 213 | 214 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { 215 | exclude group:'com.facebook.flipper' 216 | exclude group:'com.squareup.okhttp3', module:'okhttp' 217 | } 218 | 219 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { 220 | exclude group:'com.facebook.flipper' 221 | } 222 | 223 | if (enableHermes) { 224 | def hermesPath = "../../node_modules/hermes-engine/android/"; 225 | debugImplementation files(hermesPath + "hermes-debug.aar") 226 | releaseImplementation files(hermesPath + "hermes-release.aar") 227 | } else { 228 | implementation jscFlavor 229 | } 230 | } 231 | 232 | // Run this once to be able to run the application with BUCK 233 | // puts all compile dependencies into folder libs for BUCK to use 234 | task copyDownloadableDepsToLibs(type: Copy) { 235 | from configurations.implementation 236 | into 'libs' 237 | } 238 | 239 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 240 | -------------------------------------------------------------------------------- /client/android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /client/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/debug.keystore -------------------------------------------------------------------------------- /client/android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "140434318989", 4 | "project_id": "react-native-callkit", 5 | "storage_bucket": "react-native-callkit.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "XXXX", 11 | "android_client_info": { 12 | "package_name": "com.call.trigger" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "XXXX.com", 18 | "client_type": 3 19 | } 20 | ], 21 | "api_key": [ 22 | { 23 | "current_key": "XXXX" 24 | } 25 | ], 26 | "services": { 27 | "appinvite_service": { 28 | "other_platform_oauth_client": [ 29 | { 30 | "client_id": "XXXX.googleusercontent.com", 31 | "client_type": 3 32 | } 33 | ] 34 | } 35 | } 36 | } 37 | ], 38 | "configuration_version": "1" 39 | } -------------------------------------------------------------------------------- /client/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /client/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/android/app/src/debug/java/com/call/trigger/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.call.trigger; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin; 21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | public class ReactNativeFlipper { 28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 29 | if (FlipperUtils.shouldEnableFlipper(context)) { 30 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 31 | 32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 33 | client.addPlugin(new ReactFlipperPlugin()); 34 | client.addPlugin(new DatabasesFlipperPlugin(context)); 35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 36 | client.addPlugin(CrashReporterPlugin.getInstance()); 37 | 38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 39 | NetworkingModule.setCustomClientBuilder( 40 | new NetworkingModule.CustomClientBuilder() { 41 | @Override 42 | public void apply(OkHttpClient.Builder builder) { 43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 44 | } 45 | }); 46 | client.addPlugin(networkFlipperPlugin); 47 | client.start(); 48 | 49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 50 | // Hence we run if after all native modules have been initialized 51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 52 | if (reactContext == null) { 53 | reactInstanceManager.addReactInstanceEventListener( 54 | new ReactInstanceManager.ReactInstanceEventListener() { 55 | @Override 56 | public void onReactContextInitialized(ReactContext reactContext) { 57 | reactInstanceManager.removeReactInstanceEventListener(this); 58 | reactContext.runOnNativeModulesQueueThread( 59 | new Runnable() { 60 | @Override 61 | public void run() { 62 | client.addPlugin(new FrescoFlipperPlugin()); 63 | } 64 | }); 65 | } 66 | }); 67 | } else { 68 | client.addPlugin(new FrescoFlipperPlugin()); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /client/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 36 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 63 | 67 | 68 | 69 | 70 | 71 | 72 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/assets/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/assets/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /client/android/app/src/main/java/com/call/trigger/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.call.trigger; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import android.os.Bundle; 5 | 6 | public class MainActivity extends ReactActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(null); 11 | } 12 | /** 13 | * Returns the name of the main component registered from JavaScript. This is used to schedule 14 | * rendering of the component. 15 | */ 16 | @Override 17 | protected String getMainComponentName() { 18 | return "ReactNativeCallTrigger"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/android/app/src/main/java/com/call/trigger/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.call.trigger; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import com.facebook.react.PackageList; 6 | import com.facebook.react.ReactApplication; 7 | import com.facebook.react.ReactInstanceManager; 8 | import com.facebook.react.ReactNativeHost; 9 | import com.facebook.react.ReactPackage; 10 | import com.facebook.soloader.SoLoader; 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.util.List; 13 | import com.airbnb.android.react.lottie.LottiePackage; 14 | import live.videosdk.rnfgservice.ForegroundServicePackage; 15 | import live.videosdk.rnincallmanager.InCallManagerPackage; 16 | import live.videosdk.rnwebrtc.WebRTCModulePackage; 17 | 18 | 19 | public class MainApplication extends Application implements ReactApplication { 20 | 21 | private final ReactNativeHost mReactNativeHost = 22 | new ReactNativeHost(this) { 23 | @Override 24 | public boolean getUseDeveloperSupport() { 25 | return BuildConfig.DEBUG; 26 | } 27 | 28 | @Override 29 | protected List getPackages() { 30 | @SuppressWarnings("UnnecessaryLocalVariable") 31 | List packages = new PackageList(this).getPackages(); 32 | // Packages that cannot be autolinked yet can be added manually here, for example: 33 | // packages.add(new MyReactNativePackage()); 34 | packages.add(new ForegroundServicePackage()); 35 | packages.add(new InCallManagerPackage()); 36 | packages.add(new WebRTCModulePackage()); 37 | packages.add(new LottiePackage()); 38 | 39 | return packages; 40 | } 41 | 42 | @Override 43 | protected String getJSMainModuleName() { 44 | return "index"; 45 | } 46 | }; 47 | 48 | @Override 49 | public ReactNativeHost getReactNativeHost() { 50 | return mReactNativeHost; 51 | } 52 | 53 | @Override 54 | public void onCreate() { 55 | super.onCreate(); 56 | SoLoader.init(this, /* native exopackage */ false); 57 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 58 | } 59 | 60 | /** 61 | * Loads Flipper in React Native templates. Call this in the onCreate method with something like 62 | * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 63 | * 64 | * @param context 65 | * @param reactInstanceManager 66 | */ 67 | private static void initializeFlipper( 68 | Context context, ReactInstanceManager reactInstanceManager) { 69 | if (BuildConfig.DEBUG) { 70 | try { 71 | /* 72 | We use reflection here to pick up the class that initializes Flipper, 73 | since Flipper library is not available in release mode 74 | */ 75 | Class aClass = Class.forName("com.call.trigger.ReactNativeFlipper"); 76 | aClass 77 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) 78 | .invoke(null, context, reactInstanceManager); 79 | } catch (ClassNotFoundException e) { 80 | e.printStackTrace(); 81 | } catch (NoSuchMethodException e) { 82 | e.printStackTrace(); 83 | } catch (IllegalAccessException e) { 84 | e.printStackTrace(); 85 | } catch (InvocationTargetException e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /client/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /client/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /client/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /client/android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | #FC0303 3 | 4 | @color/red 5 | 6 | -------------------------------------------------------------------------------- /client/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | VideoSDK Call Trigger App 3 | 4 | -------------------------------------------------------------------------------- /client/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "30.0.3" 6 | minSdkVersion = 23 7 | compileSdkVersion = 31 8 | targetSdkVersion = 31 9 | ndkVersion = "20.1.5948944" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath("com.android.tools.build:gradle:7.2.1") 17 | classpath 'com.google.gms:google-services:4.3.10' 18 | classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1' 19 | // NOTE: Do not place your application dependencies here; they belong 20 | // in the individual module build.gradle files 21 | } 22 | } 23 | 24 | allprojects { 25 | repositories { 26 | mavenLocal() 27 | maven { 28 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 29 | url("$rootDir/../node_modules/react-native/android") 30 | } 31 | maven { 32 | // Android JSC is installed from npm 33 | url("$rootDir/../node_modules/jsc-android/dist") 34 | } 35 | 36 | google() 37 | jcenter() 38 | maven { url 'https://www.jitpack.io' } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.105.0 29 | android.enableDexingArtifactTransform.desugaring=false 30 | -------------------------------------------------------------------------------- /client/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /client/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /client/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /client/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /client/android/link-assets-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "migIndex": 1, 3 | "data": [ 4 | { 5 | "path": "src/assets/fonts/Roboto/Roboto-Black.ttf", 6 | "sha1": "ee52f7cf8e54f3ee2afbc474a352d5c19514d9c1" 7 | }, 8 | { 9 | "path": "src/assets/fonts/Roboto/Roboto-BlackItalic.ttf", 10 | "sha1": "94ba47a5510d3e21ae4db0da0425b855686de586" 11 | }, 12 | { 13 | "path": "src/assets/fonts/Roboto/Roboto-Bold.ttf", 14 | "sha1": "3dd713113ff2d79b94d2df343e2e28fa8e7279cf" 15 | }, 16 | { 17 | "path": "src/assets/fonts/Roboto/Roboto-BoldItalic.ttf", 18 | "sha1": "60b9860b7fc93d6b0b322f170634105bc6a8cc8d" 19 | }, 20 | { 21 | "path": "src/assets/fonts/Roboto/Roboto-Italic.ttf", 22 | "sha1": "dc6756127707ab2d6e388a6023087351fa41999c" 23 | }, 24 | { 25 | "path": "src/assets/fonts/Roboto/Roboto-Light.ttf", 26 | "sha1": "92cc3b6f9440193c12fd02ed690e434d685a9cc8" 27 | }, 28 | { 29 | "path": "src/assets/fonts/Roboto/Roboto-LightItalic.ttf", 30 | "sha1": "a0440f60a96a59c4105c3eb639cb2573826f84fe" 31 | }, 32 | { 33 | "path": "src/assets/fonts/Roboto/Roboto-Medium.ttf", 34 | "sha1": "f6783010d5def128c4a1539333324f75701d9bab" 35 | }, 36 | { 37 | "path": "src/assets/fonts/Roboto/Roboto-MediumItalic.ttf", 38 | "sha1": "e4e31e55d279a9b12c32327a60a3a65c8350e5df" 39 | }, 40 | { 41 | "path": "src/assets/fonts/Roboto/Roboto-Regular.ttf", 42 | "sha1": "096c9245b6a192d1403a82848e104a65f578a8ec" 43 | }, 44 | { 45 | "path": "src/assets/fonts/Roboto/Roboto-Thin.ttf", 46 | "sha1": "711e666e7f52210ac487c7ed81a0ac68a2b52261" 47 | }, 48 | { 49 | "path": "src/assets/fonts/Roboto/Roboto-ThinItalic.ttf", 50 | "sha1": "75ffde032d49005faa643338eda97c311ad7d316" 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /client/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ReactNativeCallTrigger' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | 4 | include ':rnwebrtc' 5 | project(':rnwebrtc').projectDir = new File(rootProject.projectDir, '../node_modules/@videosdk.live/react-native-webrtc/android') 6 | 7 | include ':rnincallmanager' 8 | project(':rnincallmanager').projectDir = new File(rootProject.projectDir, '../node_modules/@videosdk.live/react-native-incallmanager/android') 9 | 10 | include ':rnfgservice' 11 | project(':rnfgservice').projectDir = new File(rootProject.projectDir, '../node_modules/@videosdk.live/react-native-foreground-service/android') 12 | 13 | include ':lottie-react-native' 14 | project(':lottie-react-native').projectDir = new File(rootProject.projectDir, '../node_modules/lottie-react-native/src/android') 15 | 16 | include ':app' 17 | -------------------------------------------------------------------------------- /client/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactNativeCallTrigger", 3 | "displayName": "ReactNativeCallTrigger" 4 | } -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /client/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "react-native": { 3 | "crashlytics_debug_enabled": true, 4 | "crashlytics_disable_auto_disabler": true, 5 | "crashlytics_auto_collection_enabled": true, 6 | "crashlytics_is_error_generation_on_js_crash_enabled": true, 7 | "crashlytics_javascript_exception_handler_chaining_enabled": false 8 | } 9 | } -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import { AppRegistry, StatusBar, Linking, Platform } from "react-native"; 6 | import App from "./App"; 7 | import { name as appName } from "./app.json"; 8 | import { register } from "@videosdk.live/react-native-sdk"; 9 | import colors from "./src/styles/colors"; 10 | import messaging from "@react-native-firebase/messaging"; 11 | import React from "react"; 12 | import Incomingvideocall from "./src/utils/incoming-video-call"; 13 | import { updateCallStatus } from "./src/api/api"; 14 | 15 | Platform.OS == "android" && StatusBar.setBackgroundColor(colors.primary[900]); 16 | 17 | // Register the VideoSDK service 18 | register(); 19 | 20 | const firebaseListener = async (remoteMessage) => { 21 | const { callerInfo, videoSDKInfo, type } = JSON.parse( 22 | remoteMessage.data.info 23 | ); 24 | 25 | if (type === "CALL_INITIATED") { 26 | const incomingCallAnswer = ({ callUUID }) => { 27 | Incomingvideocall.backToForeground(); 28 | updateCallStatus({ 29 | callerInfo, 30 | type: "ACCEPTED", 31 | }); 32 | Incomingvideocall.endIncomingcallAnswer(callUUID); 33 | Linking.openURL( 34 | `videocalling://meetingscreen/${videoSDKInfo.token}/${videoSDKInfo.meetingId}` 35 | ).catch((err) => { 36 | Toast.show(`Error`, err); 37 | }); 38 | }; 39 | 40 | const endIncomingCall = () => { 41 | Incomingvideocall.endIncomingcallAnswer(); 42 | updateCallStatus({ callerInfo, type: "REJECTED" }); 43 | }; 44 | 45 | Incomingvideocall.configure(incomingCallAnswer, endIncomingCall); 46 | Incomingvideocall.displayIncomingCall(callerInfo.name); 47 | Incomingvideocall.backToForeground(); 48 | } 49 | }; 50 | 51 | // Register background handler 52 | messaging().setBackgroundMessageHandler(firebaseListener); 53 | 54 | function HeadlessCheck({ isHeadless }) { 55 | if (isHeadless) { 56 | // App has been launched in the background by iOS, ignore 57 | return null; 58 | } 59 | 60 | return ; 61 | } 62 | 63 | AppRegistry.registerComponent(appName, () => HeadlessCheck); 64 | -------------------------------------------------------------------------------- /client/ios/BroadcastScreen/Atomic.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | @propertyWrapper 5 | struct Atomic { 6 | 7 | private var value: Value 8 | private let lock = NSLock() 9 | 10 | init(wrappedValue value: Value) { 11 | self.value = value 12 | } 13 | 14 | var wrappedValue: Value { 15 | get { load() } 16 | set { store(newValue: newValue) } 17 | } 18 | 19 | func load() -> Value { 20 | lock.lock() 21 | defer { lock.unlock() } 22 | return value 23 | } 24 | 25 | mutating func store(newValue: Value) { 26 | lock.lock() 27 | defer { lock.unlock() } 28 | value = newValue 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /client/ios/BroadcastScreen/BroadcastScreen.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.example.broadcastScreen 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/ios/BroadcastScreen/DarwinNotificationCenter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum DarwinNotification: String { 4 | case broadcastStarted = "iOS_BroadcastStarted" 5 | case broadcastStopped = "iOS_BroadcastStopped" 6 | } 7 | 8 | class DarwinNotificationCenter { 9 | 10 | static let shared = DarwinNotificationCenter() 11 | 12 | private let notificationCenter: CFNotificationCenter 13 | 14 | init() { 15 | notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() 16 | } 17 | 18 | func postNotification(_ name: DarwinNotification) { 19 | CFNotificationCenterPostNotification(notificationCenter, CFNotificationName(rawValue: name.rawValue as CFString), nil, nil, true) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/ios/BroadcastScreen/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.broadcast-services-upload 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SampleHandler 11 | RPBroadcastProcessMode 12 | RPBroadcastProcessModeSampleBuffer 13 | 14 | CFBundleURLTypes 15 | 16 | 17 | CFBundleURLSchemes 18 | 19 | videocalling 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /client/ios/BroadcastScreen/SampleHandler.swift: -------------------------------------------------------------------------------- 1 | import ReplayKit 2 | 3 | 4 | private enum Constants { 5 | // the App Group ID value that the app and the broadcast extension targets are setup with. It differs for each app. 6 | static let appGroupIdentifier = "group.com.example.broadcastScreen" 7 | } 8 | 9 | class SampleHandler: RPBroadcastSampleHandler { 10 | 11 | private var clientConnection: SocketConnection? 12 | private var uploader: SampleUploader? 13 | 14 | private var frameCount: Int = 0 15 | 16 | var socketFilePath: String { 17 | let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.appGroupIdentifier) 18 | return sharedContainer?.appendingPathComponent("rtc_SSFD").path ?? "" 19 | } 20 | 21 | override init() { 22 | super.init() 23 | if let connection = SocketConnection(filePath: socketFilePath) { 24 | clientConnection = connection 25 | setupConnection() 26 | 27 | uploader = SampleUploader(connection: connection) 28 | } 29 | } 30 | 31 | override func broadcastStarted(withSetupInfo setupInfo: [String: NSObject]?) { 32 | // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional. 33 | frameCount = 0 34 | 35 | DarwinNotificationCenter.shared.postNotification(.broadcastStarted) 36 | 37 | openConnection() 38 | let notificationName = CFNotificationName("com.notification.start" as CFString) 39 | let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() 40 | CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, true) 41 | } 42 | 43 | override func broadcastPaused() { 44 | // User has requested to pause the broadcast. Samples will stop being delivered. 45 | } 46 | 47 | override func broadcastResumed() { 48 | // User has requested to resume the broadcast. Samples delivery will resume. 49 | } 50 | 51 | override func broadcastFinished() { 52 | // User has requested to finish the broadcast. 53 | DarwinNotificationCenter.shared.postNotification(.broadcastStopped) 54 | clientConnection?.close() 55 | 56 | let notificationName = CFNotificationName("com.notification.stop" as CFString) 57 | let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() 58 | CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, true) 59 | } 60 | 61 | override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) { 62 | switch sampleBufferType { 63 | case RPSampleBufferType.video: 64 | // very simple mechanism for adjusting frame rate by using every third frame 65 | frameCount += 1 66 | if frameCount % 3 == 0 { 67 | uploader?.send(sample: sampleBuffer) 68 | } 69 | default: 70 | break 71 | } 72 | } 73 | } 74 | 75 | private extension SampleHandler { 76 | 77 | func setupConnection() { 78 | clientConnection?.didClose = { [weak self] error in 79 | print("client connection did close \(String(describing: error))") 80 | 81 | if let error = error { 82 | self?.finishBroadcastWithError(error) 83 | } else { 84 | // the displayed failure message is more user friendly when using NSError instead of Error 85 | let JMScreenSharingStopped = 10001 86 | let customError = NSError(domain: RPRecordingErrorDomain, code: JMScreenSharingStopped, userInfo: [NSLocalizedDescriptionKey: "Screen sharing stopped"]) 87 | self?.finishBroadcastWithError(customError) 88 | } 89 | } 90 | } 91 | 92 | func openConnection() { 93 | let queue = DispatchQueue(label: "broadcast.connectTimer") 94 | let timer = DispatchSource.makeTimerSource(queue: queue) 95 | timer.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(500)) 96 | timer.setEventHandler { [weak self] in 97 | guard self?.clientConnection?.open() == true else { 98 | return 99 | } 100 | 101 | timer.cancel() 102 | } 103 | 104 | timer.resume() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /client/ios/BroadcastScreen/SampleUploader.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ReplayKit 3 | 4 | private enum Constants { 5 | static let bufferMaxLength = 10240 6 | } 7 | 8 | class SampleUploader { 9 | 10 | private static var imageContext = CIContext(options: nil) 11 | 12 | @Atomic private var isReady = false 13 | private var connection: SocketConnection 14 | 15 | private var dataToSend: Data? 16 | private var byteIndex = 0 17 | 18 | private let serialQueue: DispatchQueue 19 | 20 | init(connection: SocketConnection) { 21 | self.connection = connection 22 | self.serialQueue = DispatchQueue(label: "org.videosdk.broadcast.sampleUploader") 23 | 24 | setupConnection() 25 | } 26 | 27 | @discardableResult func send(sample buffer: CMSampleBuffer) -> Bool { 28 | guard isReady else { 29 | return false 30 | } 31 | 32 | isReady = false 33 | 34 | dataToSend = prepare(sample: buffer) 35 | byteIndex = 0 36 | 37 | serialQueue.async { [weak self] in 38 | self?.sendDataChunk() 39 | } 40 | 41 | return true 42 | } 43 | } 44 | 45 | private extension SampleUploader { 46 | 47 | func setupConnection() { 48 | connection.didOpen = { [weak self] in 49 | self?.isReady = true 50 | } 51 | connection.streamHasSpaceAvailable = { [weak self] in 52 | self?.serialQueue.async { 53 | if let success = self?.sendDataChunk() { 54 | self?.isReady = !success 55 | } 56 | } 57 | } 58 | } 59 | 60 | @discardableResult func sendDataChunk() -> Bool { 61 | guard let dataToSend = dataToSend else { 62 | return false 63 | } 64 | 65 | var bytesLeft = dataToSend.count - byteIndex 66 | var length = bytesLeft > Constants.bufferMaxLength ? Constants.bufferMaxLength : bytesLeft 67 | 68 | length = dataToSend[byteIndex..<(byteIndex + length)].withUnsafeBytes { 69 | guard let ptr = $0.bindMemory(to: UInt8.self).baseAddress else { 70 | return 0 71 | } 72 | 73 | return connection.writeToStream(buffer: ptr, maxLength: length) 74 | } 75 | 76 | if length > 0 { 77 | byteIndex += length 78 | bytesLeft -= length 79 | 80 | if bytesLeft == 0 { 81 | self.dataToSend = nil 82 | byteIndex = 0 83 | } 84 | } else { 85 | print("writeBufferToStream failure") 86 | } 87 | 88 | return true 89 | } 90 | 91 | func prepare(sample buffer: CMSampleBuffer) -> Data? { 92 | guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else { 93 | print("image buffer not available") 94 | return nil 95 | } 96 | 97 | CVPixelBufferLockBaseAddress(imageBuffer, .readOnly) 98 | 99 | let scaleFactor = 2.0 100 | let width = CVPixelBufferGetWidth(imageBuffer)/Int(scaleFactor) 101 | let height = CVPixelBufferGetHeight(imageBuffer)/Int(scaleFactor) 102 | let orientation = CMGetAttachment(buffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil)?.uintValue ?? 0 103 | 104 | let scaleTransform = CGAffineTransform(scaleX: CGFloat(1.0/scaleFactor), y: CGFloat(1.0/scaleFactor)) 105 | let bufferData = self.jpegData(from: imageBuffer, scale: scaleTransform) 106 | 107 | CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) 108 | 109 | guard let messageData = bufferData else { 110 | print("corrupted image buffer") 111 | return nil 112 | } 113 | 114 | let httpResponse = CFHTTPMessageCreateResponse(nil, 200, nil, kCFHTTPVersion1_1).takeRetainedValue() 115 | CFHTTPMessageSetHeaderFieldValue(httpResponse, "Content-Length" as CFString, String(messageData.count) as CFString) 116 | CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Width" as CFString, String(width) as CFString) 117 | CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Height" as CFString, String(height) as CFString) 118 | CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Orientation" as CFString, String(orientation) as CFString) 119 | 120 | CFHTTPMessageSetBody(httpResponse, messageData as CFData) 121 | 122 | let serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse)?.takeRetainedValue() as Data? 123 | 124 | return serializedMessage 125 | } 126 | 127 | func jpegData(from buffer: CVPixelBuffer, scale scaleTransform: CGAffineTransform) -> Data? { 128 | let image = CIImage(cvPixelBuffer: buffer).transformed(by: scaleTransform) 129 | 130 | guard let colorSpace = image.colorSpace else { 131 | return nil 132 | } 133 | 134 | let options: [CIImageRepresentationOption: Float] = [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: 1.0] 135 | 136 | return SampleUploader.imageContext.jpegRepresentation(of: image, colorSpace: colorSpace, options: options) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /client/ios/BroadcastScreen/SocketConnection.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class SocketConnection: NSObject { 4 | var didOpen: (() -> Void)? 5 | var didClose: ((Error?) -> Void)? 6 | var streamHasSpaceAvailable: (() -> Void)? 7 | 8 | private let filePath: String 9 | private var socketHandle: Int32 = -1 10 | private var address: sockaddr_un? 11 | 12 | private var inputStream: InputStream? 13 | private var outputStream: OutputStream? 14 | 15 | private var networkQueue: DispatchQueue? 16 | private var shouldKeepRunning = false 17 | 18 | init?(filePath path: String) { 19 | filePath = path 20 | socketHandle = Darwin.socket(AF_UNIX, SOCK_STREAM, 0) 21 | 22 | guard socketHandle != -1 else { 23 | print("failure: create socket") 24 | return nil 25 | } 26 | } 27 | 28 | func open() -> Bool { 29 | print("open socket connection") 30 | 31 | guard FileManager.default.fileExists(atPath: filePath) else { 32 | print("failure: socket file missing") 33 | return false 34 | } 35 | 36 | guard setupAddress() == true else { 37 | return false 38 | } 39 | 40 | guard connectSocket() == true else { 41 | return false 42 | } 43 | 44 | setupStreams() 45 | 46 | inputStream?.open() 47 | outputStream?.open() 48 | 49 | return true 50 | } 51 | 52 | func close() { 53 | unscheduleStreams() 54 | 55 | inputStream?.delegate = nil 56 | outputStream?.delegate = nil 57 | 58 | inputStream?.close() 59 | outputStream?.close() 60 | 61 | inputStream = nil 62 | outputStream = nil 63 | } 64 | 65 | func writeToStream(buffer: UnsafePointer, maxLength length: Int) -> Int { 66 | outputStream?.write(buffer, maxLength: length) ?? 0 67 | } 68 | } 69 | 70 | extension SocketConnection: StreamDelegate { 71 | 72 | func stream(_ aStream: Stream, handle eventCode: Stream.Event) { 73 | switch eventCode { 74 | case .openCompleted: 75 | print("client stream open completed") 76 | if aStream == outputStream { 77 | didOpen?() 78 | } 79 | case .hasBytesAvailable: 80 | if aStream == inputStream { 81 | var buffer: UInt8 = 0 82 | let numberOfBytesRead = inputStream?.read(&buffer, maxLength: 1) 83 | if numberOfBytesRead == 0 && aStream.streamStatus == .atEnd { 84 | print("server socket closed") 85 | close() 86 | notifyDidClose(error: nil) 87 | } 88 | } 89 | case .hasSpaceAvailable: 90 | if aStream == outputStream { 91 | streamHasSpaceAvailable?() 92 | } 93 | case .errorOccurred: 94 | print("client stream error occured: \(String(describing: aStream.streamError))") 95 | close() 96 | notifyDidClose(error: aStream.streamError) 97 | 98 | default: 99 | break 100 | } 101 | } 102 | } 103 | 104 | private extension SocketConnection { 105 | 106 | func setupAddress() -> Bool { 107 | var addr = sockaddr_un() 108 | guard filePath.count < MemoryLayout.size(ofValue: addr.sun_path) else { 109 | print("failure: fd path is too long") 110 | return false 111 | } 112 | 113 | _ = withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in 114 | filePath.withCString { 115 | strncpy(ptr, $0, filePath.count) 116 | } 117 | } 118 | 119 | address = addr 120 | return true 121 | } 122 | 123 | func connectSocket() -> Bool { 124 | guard var addr = address else { 125 | return false 126 | } 127 | 128 | let status = withUnsafePointer(to: &addr) { ptr in 129 | ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { 130 | Darwin.connect(socketHandle, $0, socklen_t(MemoryLayout.size)) 131 | } 132 | } 133 | 134 | guard status == noErr else { 135 | print("failure: \(status)") 136 | return false 137 | } 138 | 139 | return true 140 | } 141 | 142 | func setupStreams() { 143 | var readStream: Unmanaged? 144 | var writeStream: Unmanaged? 145 | 146 | CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketHandle, &readStream, &writeStream) 147 | 148 | inputStream = readStream?.takeRetainedValue() 149 | inputStream?.delegate = self 150 | inputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String)) 151 | 152 | outputStream = writeStream?.takeRetainedValue() 153 | outputStream?.delegate = self 154 | outputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String)) 155 | 156 | scheduleStreams() 157 | } 158 | 159 | func scheduleStreams() { 160 | shouldKeepRunning = true 161 | 162 | networkQueue = DispatchQueue.global(qos: .userInitiated) 163 | networkQueue?.async { [weak self] in 164 | self?.inputStream?.schedule(in: .current, forMode: .common) 165 | self?.outputStream?.schedule(in: .current, forMode: .common) 166 | RunLoop.current.run() 167 | 168 | var isRunning = false 169 | 170 | repeat { 171 | isRunning = self?.shouldKeepRunning ?? false && RunLoop.current.run(mode: .default, before: .distantFuture) 172 | } while (isRunning) 173 | } 174 | } 175 | 176 | func unscheduleStreams() { 177 | networkQueue?.sync { [weak self] in 178 | self?.inputStream?.remove(from: .current, forMode: .common) 179 | self?.outputStream?.remove(from: .current, forMode: .common) 180 | } 181 | 182 | shouldKeepRunning = false 183 | } 184 | 185 | func notifyDidClose(error: Error?) { 186 | if didClose != nil { 187 | didClose?(error) 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /client/ios/ExportOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | method 6 | app-store 7 | teamID 8 | 8GZ776NSU2 9 | signingStyle 10 | manual 11 | provisioningProfiles 12 | 13 | org.reactjs.RNCodeSample 14 | React Native iOS Example 15 | org.reactjs.RNCodeSample.broadcastscreen 16 | React Native iOS BroadcastScreen 17 | 18 | 19 | -------------------------------------------------------------------------------- /client/ios/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../node_modules/react-native/scripts/react_native_pods' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | platform :ios, '13.0' 5 | 6 | target 'ReactNativeCallTrigger' do 7 | config = use_native_modules! 8 | 9 | use_react_native!( 10 | :path => config[:reactNativePath], 11 | # to enable hermes on iOS, change `false` to `true` and then install pods 12 | :hermes_enabled => false 13 | ) 14 | 15 | pod 'react-native-webrtc', :path => '../node_modules/@videosdk.live/react-native-webrtc' 16 | pod 'Firebase', :modular_headers => true 17 | pod 'FirebaseCoreInternal', :modular_headers => true 18 | pod 'FirebaseCore', :modular_headers => true 19 | pod 'GoogleUtilities', :modular_headers => true 20 | pod 'RNCallKeep', :path => '../node_modules/react-native-callkeep' 21 | 22 | target 'ReactNativeCallTriggerTests' do 23 | inherit! :complete 24 | # Pods for testing 25 | end 26 | 27 | # Enables Flipper. 28 | # 29 | # Note that if you have use_frameworks! enabled, Flipper will not work and 30 | # you should disable the next line. 31 | # use_flipper!() 32 | 33 | post_install do |installer| 34 | react_native_post_install(installer) 35 | end 36 | end -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | 2 | #import "React/RCTEventEmitter.h" 3 | 4 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger.xcodeproj/xcshareddata/xcschemes/ReactNativeCallTrigger.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : UIResponder 5 | 6 | @property (nonatomic, strong) UIWindow *window; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | #import 5 | #import 6 | #import 7 | #import "RNCallKeep.h" 8 | #import /* <------ add this line */ 9 | #import "RNVoipPushNotificationManager.h" 10 | #import 11 | 12 | @implementation AppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | 17 | [FIRApp configure]; 18 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 19 | 20 | [RNCallKeep setup:@{ 21 | @"appName": @"VideoSDK Call Trigger", 22 | @"maximumCallGroups": @3, 23 | @"maximumCallsPerCallGroup": @1, 24 | @"supportsVideo": @YES, 25 | }]; 26 | 27 | [RNVoipPushNotificationManager voipRegistration]; 28 | 29 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 30 | moduleName:@"ReactNativeCallTrigger" 31 | initialProperties:nil]; 32 | 33 | if (@available(iOS 13.0, *)) { 34 | rootView.backgroundColor = [UIColor systemBackgroundColor]; 35 | } else { 36 | rootView.backgroundColor = [UIColor whiteColor]; 37 | } 38 | 39 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 40 | UIViewController *rootViewController = [UIViewController new]; 41 | rootViewController.view = rootView; 42 | self.window.rootViewController = rootViewController; 43 | [self.window makeKeyAndVisible]; 44 | return YES; 45 | } 46 | 47 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 48 | { 49 | #if DEBUG 50 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 51 | #else 52 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 53 | #endif 54 | } 55 | 56 | - (BOOL)application:(UIApplication *)application 57 | continueUserActivity:(NSUserActivity *)userActivity 58 | restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler 59 | { 60 | return [RNCallKeep application:application 61 | continueUserActivity:userActivity 62 | restorationHandler:restorationHandler]; 63 | } 64 | 65 | - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type { 66 | // Register VoIP push token (a property of PKPushCredentials) with server 67 | [RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type]; 68 | } 69 | 70 | - (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type 71 | { 72 | // --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token. 73 | } 74 | 75 | - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion { 76 | 77 | 78 | // --- NOTE: apple forced us to invoke callkit ASAP when we receive voip push 79 | // --- see: react-native-callkeep 80 | 81 | // --- Retrieve information from your voip push payload 82 | NSString *uuid = payload.dictionaryPayload[@"uuid"]; 83 | NSString *callerName = [NSString stringWithFormat:@"%@ Calling from VideoSDK", payload.dictionaryPayload[@"callerName"]]; 84 | NSString *handle = payload.dictionaryPayload[@"handle"]; 85 | 86 | // --- this is optional, only required if you want to call `completion()` on the js side 87 | [RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion]; 88 | 89 | // --- Process the received push 90 | [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type]; 91 | // NSDictionary *extra = [payload.dictionaryPayload valueForKeyPath:@"custom.path.to.data"]; 92 | 93 | [RNCallKeep reportNewIncomingCall: uuid 94 | handle: handle 95 | handleType: @"generic" 96 | hasVideo: YES 97 | localizedCallerName: callerName 98 | supportsHolding: YES 99 | supportsDTMF: YES 100 | supportsGrouping: YES 101 | supportsUngrouping: YES 102 | fromPushKit: YES 103 | payload: nil 104 | withCompletionHandler: completion]; 105 | 106 | // --- You don't need to call it if you stored `completion()` and will call it on the js side. 107 | completion(); 108 | } 109 | 110 | - (BOOL)application:(UIApplication *)application 111 | openURL:(NSURL *)url 112 | options:(NSDictionary *)options 113 | { 114 | return [RCTLinkingManager application:application openURL:url options:options]; 115 | } 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "60x60", 5 | "expected-size": "180", 6 | "filename": "180.png", 7 | "folder": "Images.xcassets/AppIcon.appiconset/", 8 | "idiom": "iphone", 9 | "scale": "3x" 10 | }, 11 | { 12 | "size": "40x40", 13 | "expected-size": "80", 14 | "filename": "80.png", 15 | "folder": "Images.xcassets/AppIcon.appiconset/", 16 | "idiom": "iphone", 17 | "scale": "2x" 18 | }, 19 | { 20 | "size": "40x40", 21 | "expected-size": "120", 22 | "filename": "120.png", 23 | "folder": "Images.xcassets/AppIcon.appiconset/", 24 | "idiom": "iphone", 25 | "scale": "3x" 26 | }, 27 | { 28 | "size": "60x60", 29 | "expected-size": "120", 30 | "filename": "120.png", 31 | "folder": "Images.xcassets/AppIcon.appiconset/", 32 | "idiom": "iphone", 33 | "scale": "2x" 34 | }, 35 | { 36 | "size": "57x57", 37 | "expected-size": "57", 38 | "filename": "57.png", 39 | "folder": "Images.xcassets/AppIcon.appiconset/", 40 | "idiom": "iphone", 41 | "scale": "1x" 42 | }, 43 | { 44 | "size": "29x29", 45 | "expected-size": "58", 46 | "filename": "58.png", 47 | "folder": "Images.xcassets/AppIcon.appiconset/", 48 | "idiom": "iphone", 49 | "scale": "2x" 50 | }, 51 | { 52 | "size": "29x29", 53 | "expected-size": "29", 54 | "filename": "29.png", 55 | "folder": "Images.xcassets/AppIcon.appiconset/", 56 | "idiom": "iphone", 57 | "scale": "1x" 58 | }, 59 | { 60 | "size": "29x29", 61 | "expected-size": "87", 62 | "filename": "87.png", 63 | "folder": "Images.xcassets/AppIcon.appiconset/", 64 | "idiom": "iphone", 65 | "scale": "3x" 66 | }, 67 | { 68 | "size": "57x57", 69 | "expected-size": "114", 70 | "filename": "114.png", 71 | "folder": "Images.xcassets/AppIcon.appiconset/", 72 | "idiom": "iphone", 73 | "scale": "2x" 74 | }, 75 | { 76 | "size": "20x20", 77 | "expected-size": "40", 78 | "filename": "40.png", 79 | "folder": "Images.xcassets/AppIcon.appiconset/", 80 | "idiom": "iphone", 81 | "scale": "2x" 82 | }, 83 | { 84 | "size": "20x20", 85 | "expected-size": "60", 86 | "filename": "60.png", 87 | "folder": "Images.xcassets/AppIcon.appiconset/", 88 | "idiom": "iphone", 89 | "scale": "3x" 90 | }, 91 | { 92 | "size": "1024x1024", 93 | "filename": "1024.png", 94 | "expected-size": "1024", 95 | "idiom": "ios-marketing", 96 | "folder": "Images.xcassets/AppIcon.appiconset/", 97 | "scale": "1x" 98 | } 99 | ] 100 | } -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | React Native VideoSDK App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.2 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLName 29 | videocalling 30 | CFBundleURLSchemes 31 | 32 | videocalling 33 | 34 | 35 | 36 | CFBundleVersion 37 | 12 38 | LSRequiresIPhoneOS 39 | 40 | NSAppTransportSecurity 41 | 42 | NSExceptionDomains 43 | 44 | localhost 45 | 46 | NSExceptionAllowsInsecureHTTPLoads 47 | 48 | 49 | 50 | 51 | NSCameraUsageDescription 52 | Camera permission description 53 | NSLocationWhenInUseUsageDescription 54 | 55 | NSMicrophoneUsageDescription 56 | Microphone permission description 57 | RTCAppGroupIdentifier 58 | group.com.example.broadcastScreen 59 | RTCScreenSharingExtension 60 | org.reactjs.ReactNativeCallTrigger.broadcastscreen 61 | UIAppFonts 62 | 63 | Roboto-Black.ttf 64 | Roboto-BlackItalic.ttf 65 | Roboto-Bold.ttf 66 | Roboto-BoldItalic.ttf 67 | Roboto-Italic.ttf 68 | Roboto-Light.ttf 69 | Roboto-LightItalic.ttf 70 | Roboto-Medium.ttf 71 | Roboto-MediumItalic.ttf 72 | Roboto-Regular.ttf 73 | Roboto-Thin.ttf 74 | Roboto-ThinItalic.ttf 75 | 76 | UIBackgroundModes 77 | 78 | fetch 79 | processing 80 | remote-notification 81 | voip 82 | 83 | UILaunchStoryboardName 84 | LaunchScreen 85 | UIRequiredDeviceCapabilities 86 | 87 | armv7 88 | 89 | UISupportedInterfaceOrientations 90 | 91 | UIInterfaceOrientationPortrait 92 | UIInterfaceOrientationLandscapeLeft 93 | UIInterfaceOrientationLandscapeRight 94 | 95 | UIViewControllerBasedStatusBarAppearance 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/ReactNativeCallTrigger.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.security.application-groups 8 | 9 | group.com.example.broadcastScreen 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTrigger/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTriggerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /client/ios/ReactNativeCallTriggerTests/ReactNativeCallTriggerTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface ReactNativeCallTriggerTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation ReactNativeCallTriggerTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 38 | if (level >= RCTLogLevelError) { 39 | redboxError = message; 40 | } 41 | }); 42 | #endif 43 | 44 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 45 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 46 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | 48 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 49 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 50 | return YES; 51 | } 52 | return NO; 53 | }]; 54 | } 55 | 56 | #ifdef DEBUG 57 | RCTSetLogFunction(RCTDefaultLogFunction); 58 | #endif 59 | 60 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 61 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 62 | } 63 | 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /client/ios/VideosdkRPK.m: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | #import 4 | #import "React/RCTEventEmitter.h" 5 | 6 | @interface RCT_EXTERN_MODULE(VideosdkRPK, RCTEventEmitter) 7 | RCT_EXTERN_METHOD(startBroadcast) 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /client/ios/VideosdkRPK.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ReplayKit 3 | import Photos 4 | import UIKit 5 | 6 | @objc(VideosdkRPK) 7 | class VideosdkRPK: RCTEventEmitter { 8 | 9 | private var status = "Empty" 10 | 11 | 12 | var start_notificaion_callback: CFNotificationCallback = { center, observer, name, object, info in 13 | NotificationCenter.default.post(name: Notification.Name("START_BROADCAST"), object: nil) 14 | } 15 | 16 | 17 | var stop_notificaion_callback: CFNotificationCallback = { center, observer, name, object, info in 18 | NotificationCenter.default.post(name: Notification.Name("STOP_BROADCAST"), object: nil) 19 | } 20 | 21 | 22 | override init() { 23 | super.init() 24 | NotificationCenter.default.addObserver(self, selector: #selector(self.startBroadcastCallback(notification:)), name: Notification.Name("START_BROADCAST"), object: nil) 25 | 26 | 27 | NotificationCenter.default.addObserver(self, selector: #selector(self.stopBroadcastCallback(notification:)), name: Notification.Name("STOP_BROADCAST"), object: nil) 28 | 29 | let notificationStartIdentifier = "com.notification.start" as CFString 30 | let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() 31 | 32 | CFNotificationCenterAddObserver(notificationCenter, 33 | nil, 34 | start_notificaion_callback, 35 | notificationStartIdentifier, 36 | nil, 37 | CFNotificationSuspensionBehavior.deliverImmediately) 38 | 39 | 40 | let notificationStopIdentifier = "com.notification.stop" as CFString 41 | 42 | CFNotificationCenterAddObserver(notificationCenter, 43 | nil, 44 | stop_notificaion_callback, 45 | notificationStopIdentifier, 46 | nil, 47 | CFNotificationSuspensionBehavior.deliverImmediately) 48 | 49 | 50 | } 51 | 52 | @objc func startBroadcastCallback(notification: NSNotification){ 53 | status = "START_BROADCAST" 54 | sendEvent(withName: "onScreenShare", body: status) 55 | status="STARTED_BROADCASTING" 56 | } 57 | 58 | 59 | @objc func stopBroadcastCallback(notification: NSNotification){ 60 | status = "STOP_BROADCAST" 61 | sendEvent(withName: "onScreenShare", body: status) 62 | status = "Empty" 63 | } 64 | 65 | 66 | @objc 67 | func startBroadcast() { 68 | DispatchQueue.main.async { [self] in 69 | let pickerView = RPSystemBroadcastPickerView( 70 | frame: CGRect(x: 0, y: 0, width: 0, height: 0)) 71 | var tap = pickerView.subviews.first as! UIButton 72 | pickerView.translatesAutoresizingMaskIntoConstraints = false 73 | pickerView.preferredExtension = "org.reactjs.native.example.RNCodeSample" 74 | tap.sendActions(for: .touchUpInside) 75 | 76 | } 77 | } 78 | 79 | override func supportedEvents() -> [String]! { 80 | return ["onScreenShare"] 81 | } 82 | override func constantsToExport() -> [AnyHashable : Any]! { 83 | return ["initialCount": status] 84 | } 85 | override static func requiresMainQueueSetup() -> Bool { 86 | return true 87 | } 88 | 89 | } 90 | 91 | -------------------------------------------------------------------------------- /client/ios/link-assets-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "migIndex": 1, 3 | "data": [ 4 | { 5 | "path": "src/assets/fonts/Roboto/Roboto-Black.ttf", 6 | "sha1": "ee52f7cf8e54f3ee2afbc474a352d5c19514d9c1" 7 | }, 8 | { 9 | "path": "src/assets/fonts/Roboto/Roboto-BlackItalic.ttf", 10 | "sha1": "94ba47a5510d3e21ae4db0da0425b855686de586" 11 | }, 12 | { 13 | "path": "src/assets/fonts/Roboto/Roboto-Bold.ttf", 14 | "sha1": "3dd713113ff2d79b94d2df343e2e28fa8e7279cf" 15 | }, 16 | { 17 | "path": "src/assets/fonts/Roboto/Roboto-BoldItalic.ttf", 18 | "sha1": "60b9860b7fc93d6b0b322f170634105bc6a8cc8d" 19 | }, 20 | { 21 | "path": "src/assets/fonts/Roboto/Roboto-Italic.ttf", 22 | "sha1": "dc6756127707ab2d6e388a6023087351fa41999c" 23 | }, 24 | { 25 | "path": "src/assets/fonts/Roboto/Roboto-Light.ttf", 26 | "sha1": "92cc3b6f9440193c12fd02ed690e434d685a9cc8" 27 | }, 28 | { 29 | "path": "src/assets/fonts/Roboto/Roboto-LightItalic.ttf", 30 | "sha1": "a0440f60a96a59c4105c3eb639cb2573826f84fe" 31 | }, 32 | { 33 | "path": "src/assets/fonts/Roboto/Roboto-Medium.ttf", 34 | "sha1": "f6783010d5def128c4a1539333324f75701d9bab" 35 | }, 36 | { 37 | "path": "src/assets/fonts/Roboto/Roboto-MediumItalic.ttf", 38 | "sha1": "e4e31e55d279a9b12c32327a60a3a65c8350e5df" 39 | }, 40 | { 41 | "path": "src/assets/fonts/Roboto/Roboto-Regular.ttf", 42 | "sha1": "096c9245b6a192d1403a82848e104a65f578a8ec" 43 | }, 44 | { 45 | "path": "src/assets/fonts/Roboto/Roboto-Thin.ttf", 46 | "sha1": "711e666e7f52210ac487c7ed81a0ac68a2b52261" 47 | }, 48 | { 49 | "path": "src/assets/fonts/Roboto/Roboto-ThinItalic.ttf", 50 | "sha1": "75ffde032d49005faa643338eda97c311ad7d316" 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /client/metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: true, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videosdk-rtc-react-native-call-trigger-example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "test": "jest", 10 | "lint": "eslint .", 11 | "watch-need-help": "echo 256 | sudo tee -a /proc/sys/fs/inotify/max_user_instances && echo 32768 | sudo tee -a /proc/sys/fs/inotify/max_queued_events && echo 65536 | sudo tee -a /proc/sys/fs/inotify/max_user_watches && watchman shutdown-server", 12 | "android-linux": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res && react-native run-android" 13 | }, 14 | "dependencies": { 15 | "@react-native-firebase/app": "^16.4.6", 16 | "@react-native-firebase/firestore": "^16.4.6", 17 | "@react-native-firebase/messaging": "^16.4.6", 18 | "@react-navigation/native": "^6.0.13", 19 | "@react-navigation/stack": "^6.3.2", 20 | "@videosdk.live/react-native-incallmanager": "^0.0.9", 21 | "@videosdk.live/react-native-sdk": "^0.0.40", 22 | "firebase": "^9.15.0", 23 | "lottie-ios": "3.4.0", 24 | "lottie-react-native": "^5.1.4", 25 | "moment": "^2.29.4", 26 | "react": "18.1.0", 27 | "react-native": "0.70.6", 28 | "react-native-callkeep": "git+https://github.com/react-native-webrtc/react-native-callkeep#4b1fa98a685f6502d151875138b7c81baf1ec680", 29 | "react-native-dotenv": "^3.1.1", 30 | "react-native-gesture-handler": "^2.7.0", 31 | "react-native-responsive-fontsize": "^0.5.1", 32 | "react-native-safe-area-context": "^4.4.1", 33 | "react-native-screens": "^3.18.0", 34 | "react-native-simple-toast": "^1.1.4", 35 | "react-native-svg": "^12.1.1", 36 | "react-native-uuid": "^2.0.1", 37 | "react-native-voip-push-notification": "^3.3.1", 38 | "videosdk-rn-android-overlay-permission": "1.0.6" 39 | }, 40 | "devDependencies": { 41 | "@babel/core": "^7.14.6", 42 | "@babel/runtime": "^7.14.6", 43 | "@react-native-community/eslint-config": "^3.0.0", 44 | "babel-jest": "^27.0.6", 45 | "eslint": "^7.30.0", 46 | "jest": "^27.0.6", 47 | "metro-react-native-babel-preset": "^0.66.1", 48 | "react-test-renderer": "17.0.1" 49 | }, 50 | "jest": { 51 | "preset": "react-native" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/react-native.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | assets: ["./src/assets/fonts/Roboto"], 3 | }; 4 | -------------------------------------------------------------------------------- /client/src/api/api.js: -------------------------------------------------------------------------------- 1 | const API_BASE_URL = "https://api.videosdk.live/v2"; 2 | const VIDEOSDK_TOKEN = process.env.REACT_APP_VIDEOSDK_TOKEN; 3 | 4 | const FCM_SERVER_URL = "LOCAL_SERVER_URL:9000"; 5 | 6 | export const getToken = () => { 7 | return VIDEOSDK_TOKEN; 8 | }; 9 | 10 | export const createMeeting = async ({ token }) => { 11 | const url = `${API_BASE_URL}/rooms`; 12 | const options = { 13 | method: "POST", 14 | headers: { Authorization: token, "Content-Type": "application/json" }, 15 | }; 16 | 17 | const { roomId } = await fetch(url, options) 18 | .then((response) => response.json()) 19 | .catch((error) => console.error("error", error)); 20 | 21 | return roomId; 22 | }; 23 | 24 | export const initiateCall = async ({ 25 | callerInfo, 26 | calleeInfo, 27 | videoSDKInfo, 28 | }) => { 29 | await fetch(`${FCM_SERVER_URL}/initiate-call`, { 30 | method: "POST", 31 | headers: { "Content-Type": "application/json" }, 32 | body: JSON.stringify({ 33 | callerInfo, 34 | calleeInfo, 35 | videoSDKInfo, 36 | }), 37 | }) 38 | .then((response) => { 39 | console.log(" RESP", response); 40 | }) 41 | .catch((error) => console.error("error", error)); 42 | }; 43 | 44 | export const updateCallStatus = async ({ callerInfo, type }) => { 45 | await fetch(`${FCM_SERVER_URL}/update-call`, { 46 | method: "POST", 47 | headers: { "Content-Type": "application/json" }, 48 | body: JSON.stringify({ 49 | callerInfo, 50 | type, 51 | }), 52 | }) 53 | .then((response) => { 54 | console.log("##RESP", response); 55 | }) 56 | .catch((error) => console.error("error", error)); 57 | }; 58 | -------------------------------------------------------------------------------- /client/src/assets/animation/joining_lottie.json: -------------------------------------------------------------------------------- 1 | {"v":"5.7.8","fr":25,"ip":0,"op":28,"w":1000,"h":414,"nm":"loading_1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"ball_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":12,"s":[754.951,204.902,0],"to":[0,-13.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[754.951,122.902,0],"to":[0,0,0],"ti":[0,-13.667,0]},{"t":24,"s":[754.951,204.902,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[127.049,127.049],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.333333333333,0.407843137255,0.996078431373,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":101,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"ball_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":6,"s":[497.451,204.902,0],"to":[0,-13.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":12,"s":[497.451,122.902,0],"to":[0,0,0],"ti":[0,-13.667,0]},{"t":18,"s":[497.451,204.902,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[127.049,127.049],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.333333333333,0.407843137255,0.996078431373,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":101,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"ball_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[239.951,204.902,0],"to":[0,-13.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":6,"s":[239.951,122.902,0],"to":[0,0,0],"ti":[0,-13.667,0]},{"t":12,"s":[239.951,204.902,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[127.049,127.049],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.333333333333,0.407843137255,0.996078431373,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":101,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-Black.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-Italic.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videosdk-live/videosdk-rtc-react-native-call-trigger-example/186712534584d2c24a0c3fdb96c43177e311a976/client/src/assets/fonts/Roboto/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/icons/CallEnd.js: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import Svg, { Path } from "react-native-svg" 3 | 4 | function SvgComponent(props) { 5 | return ( 6 | 7 | 11 | 12 | ) 13 | } 14 | 15 | export default SvgComponent 16 | -------------------------------------------------------------------------------- /client/src/assets/icons/CameraSwitch.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Svg, { Defs, Path } from "react-native-svg"; 3 | 4 | function CameraSwitch(props) { 5 | return ( 6 | 7 | 8 | 13 | 18 | 19 | ); 20 | } 21 | 22 | export default CameraSwitch; 23 | -------------------------------------------------------------------------------- /client/src/assets/icons/Copy.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Svg, { Path } from "react-native-svg"; 3 | 4 | function Copy(props) { 5 | return ( 6 | 7 | 12 | 13 | ); 14 | } 15 | 16 | export default Copy; 17 | -------------------------------------------------------------------------------- /client/src/assets/icons/Leave.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Svg, { G, Path, Defs, ClipPath } from "react-native-svg"; 3 | 4 | function Leave(props) { 5 | return ( 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | 27 | export default Leave; 28 | -------------------------------------------------------------------------------- /client/src/assets/icons/MicOff.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Svg, {Path} from 'react-native-svg'; 3 | 4 | function MicOff(props) { 5 | return ( 6 | 7 | 8 | 12 | 13 | ); 14 | } 15 | 16 | export default MicOff; 17 | -------------------------------------------------------------------------------- /client/src/assets/icons/MicOn.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Svg, {Defs, ClipPath, Path, G} from 'react-native-svg'; 3 | 4 | function MicOn(props) { 5 | return ( 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 22 | 27 | 28 | 29 | ); 30 | } 31 | 32 | export default MicOn; 33 | -------------------------------------------------------------------------------- /client/src/assets/icons/VideoOff.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Svg, { Defs, ClipPath, Path, G } from "react-native-svg"; 3 | 4 | function VideoOff(props) { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | ); 22 | } 23 | 24 | export default VideoOff; 25 | -------------------------------------------------------------------------------- /client/src/assets/icons/VideoOn.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Svg, { Path } from "react-native-svg"; 3 | 4 | function VideoOn(props) { 5 | return ( 6 | 7 | 11 | 12 | ); 13 | } 14 | 15 | export default VideoOn; 16 | -------------------------------------------------------------------------------- /client/src/assets/icons/index.js: -------------------------------------------------------------------------------- 1 | import MicOn from "./MicOn"; 2 | import MicOff from "./MicOff"; 3 | 4 | import VideoOn from "./VideoOn"; 5 | import VideoOff from "./VideoOff"; 6 | 7 | import CallEnd from "./CallEnd"; 8 | import Leave from "./Leave"; 9 | import CameraSwitch from "./CameraSwitch"; 10 | import Copy from "./Copy"; 11 | 12 | export { MicOff, MicOn, VideoOff, VideoOn, CallEnd, CameraSwitch, Copy, Leave }; 13 | -------------------------------------------------------------------------------- /client/src/components/Avatar/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-native/no-inline-styles */ 2 | import React from "react"; 3 | import { View, Text } from "react-native"; 4 | import colors from "../../styles/colors"; 5 | import { convertRFValue } from "../../styles/spacing"; 6 | 7 | export default function Avatar({ 8 | fullName, 9 | style, 10 | fontSize, 11 | containerBackgroundColor, 12 | }) { 13 | return ( 14 | 24 | 32 | 38 | {fullName && fullName.charAt(0).toUpperCase()} 39 | 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /client/src/components/Button/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TouchableOpacity, Text } from "react-native"; 3 | import colors from "../../styles/colors"; 4 | import { ROBOTO_FONTS } from "../../styles/fonts"; 5 | 6 | const Button = ({ 7 | text, 8 | backgroundColor, 9 | onPress, 10 | style = {}, 11 | textStyle = {}, 12 | }) => { 13 | return ( 14 | 26 | 34 | {text} 35 | 36 | 37 | ); 38 | }; 39 | export default Button; 40 | -------------------------------------------------------------------------------- /client/src/components/IconContainer/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TouchableOpacity, View } from "react-native"; 3 | 4 | const buttonStyle = { 5 | height: 50, 6 | aspectRatio: 1, 7 | justifyContent: "center", 8 | alignItems: "center", 9 | }; 10 | const IconContainer = ({ backgroundColor, onPress, Icon, style }) => { 11 | return ( 12 | 24 | 25 | 26 | ); 27 | }; 28 | export default IconContainer; 29 | -------------------------------------------------------------------------------- /client/src/components/TextInputContainer/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, TextInput } from "react-native"; 3 | import colors from "../../styles/colors"; 4 | import { ROBOTO_FONTS } from "../../styles/fonts"; 5 | const TextInputContainer = ({ placeholder, value, setValue, keyboardType }) => { 6 | return ( 7 | 17 | { 33 | setValue(text); 34 | }} 35 | value={value} 36 | keyboardType={keyboardType} 37 | /> 38 | 39 | ); 40 | }; 41 | 42 | export default TextInputContainer; 43 | -------------------------------------------------------------------------------- /client/src/navigators/screenNames.js: -------------------------------------------------------------------------------- 1 | export const SCREEN_NAMES = { 2 | Home: "homescreen", 3 | Meeting: "meetingscreen", 4 | }; 5 | -------------------------------------------------------------------------------- /client/src/scenes/home/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from "react"; 2 | import { 3 | Platform, 4 | KeyboardAvoidingView, 5 | TouchableWithoutFeedback, 6 | Keyboard, 7 | View, 8 | Text, 9 | Clipboard, 10 | Alert, 11 | Linking, 12 | } from "react-native"; 13 | import { TouchableOpacity } from "react-native-gesture-handler"; 14 | import { CallEnd, Copy } from "../../assets/icons"; 15 | import TextInputContainer from "../../components/TextInputContainer"; 16 | import colors from "../../styles/colors"; 17 | import { ROBOTO_FONTS } from "../../styles/fonts"; 18 | import firestore from "@react-native-firebase/firestore"; 19 | import messaging from "@react-native-firebase/messaging"; 20 | import Toast from "react-native-simple-toast"; 21 | import { 22 | updateCallStatus, 23 | initiateCall, 24 | getToken, 25 | createMeeting, 26 | } from "../../api/api"; 27 | import { SCREEN_NAMES } from "../../navigators/screenNames"; 28 | import Incomingvideocall from "../../utils/incoming-video-call"; 29 | import VoipPushNotification from "react-native-voip-push-notification"; 30 | 31 | export default function Home({ navigation }) { 32 | const [number, setNumber] = useState(""); 33 | const [firebaseUserConfig, setfirebaseUserConfig] = useState(null); 34 | const [isCalling, setisCalling] = useState(false); 35 | const [videosdkToken, setVideosdkToken] = useState(null); 36 | const [videosdkMeeting, setVideosdkMeeting] = useState(null); 37 | const [APN, setAPN] = useState(null); 38 | 39 | const videosdkTokenRef = useRef(); 40 | const videosdkMeetingRef = useRef(); 41 | const APNRef = useRef(); 42 | videosdkTokenRef.current = videosdkToken; 43 | videosdkMeetingRef.current = videosdkMeeting; 44 | APNRef.current = APN; 45 | 46 | useEffect(() => { 47 | async function getFCMtoken() { 48 | const authStatus = await messaging().requestPermission(); 49 | const enabled = 50 | authStatus === messaging.AuthorizationStatus.AUTHORIZED || 51 | authStatus === messaging.AuthorizationStatus.PROVISIONAL; 52 | Platform.OS === "ios" && VoipPushNotification.registerVoipToken(); 53 | 54 | if (enabled) { 55 | const token = await messaging().getToken(); 56 | const querySnapshot = await firestore() 57 | .collection("users") 58 | .where("token", "==", token) 59 | .get(); 60 | 61 | const uids = querySnapshot.docs.map((doc) => { 62 | if (doc && doc?.data()?.callerId) { 63 | const { token, platform, APN, callerId } = doc?.data(); 64 | setfirebaseUserConfig({ 65 | callerId, 66 | token, 67 | platform, 68 | APN, 69 | }); 70 | } 71 | return doc; 72 | }); 73 | 74 | if (uids && uids.length == 0) { 75 | addUser({ token }); 76 | } else { 77 | console.log("Token Found"); 78 | } 79 | } 80 | } 81 | 82 | async function getTokenAndMeetingId() { 83 | const videoSDKtoken = getToken(); 84 | const videoSDKMeetingId = await createMeeting({ token: videoSDKtoken }); 85 | setVideosdkToken(videoSDKtoken); 86 | setVideosdkMeeting(videoSDKMeetingId); 87 | } 88 | getFCMtoken(); 89 | getTokenAndMeetingId(); 90 | }, []); 91 | 92 | useEffect(() => { 93 | const unsubscribe = messaging().onMessage((remoteMessage) => { 94 | const { callerInfo, videoSDKInfo, type } = JSON.parse( 95 | remoteMessage.data.info 96 | ); 97 | switch (type) { 98 | case "CALL_INITIATED": 99 | const incomingCallAnswer = ({ callUUID }) => { 100 | updateCallStatus({ 101 | callerInfo, 102 | type: "ACCEPTED", 103 | }); 104 | Incomingvideocall.endIncomingcallAnswer(callUUID); 105 | setisCalling(false); 106 | Linking.openURL( 107 | `videocalling://meetingscreen/${videoSDKInfo.token}/${videoSDKInfo.meetingId}` 108 | ).catch((err) => { 109 | Toast.show(`Error`, err); 110 | }); 111 | }; 112 | 113 | const endIncomingCall = () => { 114 | Incomingvideocall.endIncomingcallAnswer(); 115 | updateCallStatus({ callerInfo, type: "REJECTED" }); 116 | }; 117 | 118 | Incomingvideocall.configure(incomingCallAnswer, endIncomingCall); 119 | Incomingvideocall.displayIncomingCall(callerInfo.name); 120 | 121 | break; 122 | case "ACCEPTED": 123 | setisCalling(false); 124 | navigation.navigate(SCREEN_NAMES.Meeting, { 125 | name: "Person B", 126 | token: videosdkTokenRef.current, 127 | meetingId: videosdkMeetingRef.current, 128 | }); 129 | break; 130 | case "REJECTED": 131 | Toast.show("Call Rejected"); 132 | setisCalling(false); 133 | break; 134 | case "DISCONNECT": 135 | Platform.OS === "ios" 136 | ? Incomingvideocall.endAllCall() 137 | : Incomingvideocall.endIncomingcallAnswer(); 138 | break; 139 | default: 140 | Toast.show("Call Could not placed"); 141 | } 142 | }); 143 | 144 | return () => { 145 | unsubscribe(); 146 | }; 147 | }, []); 148 | 149 | useEffect(() => { 150 | VoipPushNotification.addEventListener("register", (token) => { 151 | setAPN(token); 152 | }); 153 | 154 | VoipPushNotification.addEventListener("notification", (notification) => { 155 | const { callerInfo, videoSDKInfo, type } = notification; 156 | if (type === "CALL_INITIATED") { 157 | const incomingCallAnswer = ({ callUUID }) => { 158 | updateCallStatus({ 159 | callerInfo, 160 | type: "ACCEPTED", 161 | }); 162 | navigation.navigate(SCREEN_NAMES.Meeting, { 163 | name: "Person B", 164 | token: videoSDKInfo.token, 165 | meetingId: videoSDKInfo.meetingId, 166 | }); 167 | }; 168 | const endIncomingCall = () => { 169 | Incomingvideocall.endAllCall(); 170 | updateCallStatus({ callerInfo, type: "REJECTED" }); 171 | }; 172 | Incomingvideocall.configure(incomingCallAnswer, endIncomingCall); 173 | } else if (type === "DISCONNECT") { 174 | Incomingvideocall.endAllCall(); 175 | } 176 | VoipPushNotification.onVoipNotificationCompleted(notification.uuid); 177 | }); 178 | 179 | VoipPushNotification.addEventListener("didLoadWithEvents", (events) => { 180 | const { callerInfo, videoSDKInfo, type } = 181 | events.length > 1 && events[1].data; 182 | if (type === "CALL_INITIATED") { 183 | const incomingCallAnswer = ({ callUUID }) => { 184 | updateCallStatus({ 185 | callerInfo, 186 | type: "ACCEPTED", 187 | }); 188 | navigation.navigate(SCREEN_NAMES.Meeting, { 189 | name: "Person B", 190 | token: videoSDKInfo.token, 191 | meetingId: videoSDKInfo.meetingId, 192 | }); 193 | }; 194 | 195 | const endIncomingCall = () => { 196 | Incomingvideocall.endAllCall(); 197 | updateCallStatus({ callerInfo, type: "REJECTED" }); 198 | }; 199 | 200 | Incomingvideocall.configure(incomingCallAnswer, endIncomingCall); 201 | } 202 | }); 203 | 204 | return () => { 205 | VoipPushNotification.removeEventListener("didLoadWithEvents"); 206 | VoipPushNotification.removeEventListener("register"); 207 | VoipPushNotification.removeEventListener("notification"); 208 | }; 209 | }, []); 210 | 211 | const addUser = ({ token }) => { 212 | const platform = Platform.OS === "android" ? "ANDROID" : "iOS"; 213 | const obj = { 214 | callerId: Math.floor(10000000 + Math.random() * 90000000).toString(), 215 | token, 216 | platform, 217 | }; 218 | if (platform == "iOS") { 219 | obj.APN = APNRef.current; 220 | } 221 | firestore() 222 | .collection("users") 223 | .add(obj) 224 | .then(() => { 225 | setfirebaseUserConfig(obj); 226 | console.log("User added!"); 227 | }); 228 | }; 229 | 230 | const getCallee = async (num) => { 231 | const querySnapshot = await firestore() 232 | .collection("users") 233 | .where("callerId", "==", num.toString()) 234 | .get(); 235 | return querySnapshot.docs.map((doc) => { 236 | return doc; 237 | }); 238 | }; 239 | return ( 240 | 249 | {!isCalling ? ( 250 | 251 | <> 252 | 261 | 268 | Your Caller ID 269 | 270 | 277 | 285 | {firebaseUserConfig 286 | ? firebaseUserConfig.callerId 287 | : "Loading.."} 288 | 289 | { 300 | Clipboard.setString( 301 | firebaseUserConfig && firebaseUserConfig.callerId 302 | ); 303 | if (Platform.OS === "android") { 304 | Toast.show("Copied"); 305 | Alert.alert( 306 | "Information", 307 | "This callerId will be unavailable, once you uninstall the App." 308 | ); 309 | } 310 | }} 311 | > 312 | 313 | 314 | 315 | 316 | 317 | 326 | 333 | Enter call id of another user 334 | 335 | 341 | { 343 | if (number) { 344 | const data = await getCallee(number); 345 | if (data) { 346 | if (data.length === 0) { 347 | Toast.show("CallerId Does not Match"); 348 | } else { 349 | Toast.show("CallerId Match!"); 350 | const { token, platform, APN } = data[0]?.data(); 351 | initiateCall({ 352 | callerInfo: { 353 | name: "Person A", 354 | ...firebaseUserConfig, 355 | }, 356 | calleeInfo: { 357 | token, 358 | platform, 359 | APN, 360 | }, 361 | videoSDKInfo: { 362 | token: videosdkTokenRef.current, 363 | meetingId: videosdkMeetingRef.current, 364 | }, 365 | }); 366 | setisCalling(true); 367 | } 368 | } 369 | } else { 370 | Toast.show("Please provide CallerId"); 371 | } 372 | }} 373 | style={{ 374 | height: 50, 375 | backgroundColor: "#5568FE", 376 | justifyContent: "center", 377 | alignItems: "center", 378 | borderRadius: 12, 379 | marginTop: 16, 380 | }} 381 | > 382 | 388 | Call Now 389 | 390 | 391 | 392 | 393 | 394 | ) : ( 395 | 396 | 404 | 411 | Calling to... 412 | 413 | 414 | 423 | {number} 424 | 425 | 426 | 432 | { 434 | const data = await getCallee(number); 435 | if (data) { 436 | updateCallStatus({ 437 | callerInfo: data[0]?.data(), 438 | type: "DISCONNECT", 439 | }); 440 | setisCalling(false); 441 | } 442 | }} 443 | style={{ 444 | backgroundColor: "#FF5D5D", 445 | borderRadius: 30, 446 | height: 60, 447 | aspectRatio: 1, 448 | justifyContent: "center", 449 | alignItems: "center", 450 | }} 451 | > 452 | 453 | 454 | 455 | 456 | )} 457 | 458 | ); 459 | } 460 | -------------------------------------------------------------------------------- /client/src/scenes/meeting/Components/WaitingToJoinView.js: -------------------------------------------------------------------------------- 1 | import { ROBOTO_FONTS } from "../../../styles/fonts"; 2 | import { convertRFValue } from "../../../styles/spacing"; 3 | import React from "react"; 4 | import { Text, View } from "react-native"; 5 | import colors from "../../../styles/colors"; 6 | import Lottie from "lottie-react-native"; 7 | import joining_animation from "../../../assets/animation/joining_lottie.json"; 8 | export default function WaitingToJoinView() { 9 | return ( 10 | 19 | 28 | 36 | Creating a room 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /client/src/scenes/meeting/MeetingContainer.js: -------------------------------------------------------------------------------- 1 | import { 2 | useMeeting, 3 | ReactNativeForegroundService, 4 | } from "@videosdk.live/react-native-sdk"; 5 | import { useEffect, useState } from "react"; 6 | import OneToOneMeetingViewer from "./OneToOne"; 7 | import ParticipantLimitViewer from "./OneToOne/ParticipantLimitViewer"; 8 | import WaitingToJoinView from "./Components/WaitingToJoinView"; 9 | import React from "react"; 10 | 11 | export default function MeetingContainer({ webcamEnabled }) { 12 | const [isJoined, setJoined] = useState(false); 13 | const [participantLimit, setParticipantLimit] = useState(false); 14 | 15 | const { join, changeWebcam, participants, leave } = useMeeting({ 16 | onMeetingJoined: () => { 17 | setTimeout(() => { 18 | setJoined(true); 19 | }, 500); 20 | }, 21 | onParticipantLeft: () => { 22 | if (participants.size < 2) { 23 | setParticipantLimit(false); 24 | } 25 | }, 26 | }); 27 | 28 | useEffect(() => { 29 | if (isJoined) { 30 | if (participants.size > 2) { 31 | setParticipantLimit(true); 32 | } 33 | } 34 | }, [isJoined]); 35 | 36 | useEffect(() => { 37 | setTimeout(() => { 38 | if (!isJoined) { 39 | join(); 40 | if (webcamEnabled) changeWebcam(); 41 | } 42 | }, 1000); 43 | 44 | return () => { 45 | leave(); 46 | ReactNativeForegroundService.stopAll(); 47 | }; 48 | }, []); 49 | 50 | return isJoined ? ( 51 | participantLimit ? ( 52 | 53 | ) : ( 54 | 55 | ) 56 | ) : ( 57 | 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /client/src/scenes/meeting/OneToOne/LargeView/LargeVideoRTCView.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { RTCView, MediaStream } from "@videosdk.live/react-native-sdk"; 3 | import Avatar from "../../../../components/Avatar"; 4 | import colors from "../../../../styles/colors"; 5 | 6 | export default LargeVideoRTCView = ({ 7 | stream, 8 | displayName, 9 | isOn, 10 | objectFit, 11 | isLocal = { isLocal }, 12 | }) => { 13 | return isOn && stream ? ( 14 | 20 | ) : ( 21 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /client/src/scenes/meeting/OneToOne/LargeView/index.js: -------------------------------------------------------------------------------- 1 | import { useParticipant } from "@videosdk.live/react-native-sdk"; 2 | import React, { useEffect } from "react"; 3 | import { View } from "react-native"; 4 | import colors from "../../../../styles/colors"; 5 | import LargeVideoRTCView from "./LargeVideoRTCView"; 6 | 7 | export default LargeViewContainer = ({ participantId }) => { 8 | const { webcamOn, webcamStream, displayName, setQuality, isLocal } = 9 | useParticipant(participantId, {}); 10 | 11 | useEffect(() => { 12 | setQuality("high"); 13 | }, []); 14 | 15 | return ( 16 | 24 | 31 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /client/src/scenes/meeting/OneToOne/LocalViewContainer.js: -------------------------------------------------------------------------------- 1 | import { useParticipant } from "@videosdk.live/react-native-sdk"; 2 | import React, { useEffect } from "react"; 3 | import { View } from "react-native"; 4 | import colors from "../../../styles/colors"; 5 | import LargeVideoRTCView from "./LargeView/LargeVideoRTCView"; 6 | 7 | export default function LocalViewContainer({ participantId }) { 8 | const { webcamOn, webcamStream, displayName, setQuality, isLocal } = 9 | useParticipant(participantId, {}); 10 | 11 | useEffect(() => { 12 | setQuality("high"); 13 | }, []); 14 | 15 | return ( 16 | 24 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /client/src/scenes/meeting/OneToOne/MiniView/MiniVideoRTCView.js: -------------------------------------------------------------------------------- 1 | import { RTCView, MediaStream } from "@videosdk.live/react-native-sdk"; 2 | import React from "react"; 3 | import { View } from "react-native"; 4 | import Avatar from "../../../../components/Avatar"; 5 | import colors from "../../../../styles/colors"; 6 | 7 | export default MiniVideoRTCView = ({ stream, isOn, displayName, isLocal }) => { 8 | return ( 9 | 21 | {isOn && stream ? ( 22 | 29 | ) : ( 30 | 41 | )} 42 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /client/src/scenes/meeting/OneToOne/MiniView/index.js: -------------------------------------------------------------------------------- 1 | import { useParticipant } from "@videosdk.live/react-native-sdk"; 2 | import React, { useEffect } from "react"; 3 | import MiniVideoRTCView from "./MiniVideoRTCView"; 4 | 5 | export default MiniViewContainer = ({ participantId }) => { 6 | const { webcamOn, webcamStream, displayName, setQuality, isLocal } = 7 | useParticipant(participantId, {}); 8 | 9 | useEffect(() => { 10 | setQuality("high"); 11 | }, []); 12 | 13 | return ( 14 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /client/src/scenes/meeting/OneToOne/ParticipantLimitViewer.js: -------------------------------------------------------------------------------- 1 | import { ROBOTO_FONTS } from "../../../styles/fonts"; 2 | import React from "react"; 3 | import { convertRFValue } from "../../../styles/spacing"; 4 | import { Text, View } from "react-native"; 5 | import colors from "../../../styles/colors"; 6 | import Button from "../../../components/Button"; 7 | import { useMeeting } from "@videosdk.live/react-native-sdk"; 8 | 9 | export default function ParticipantLimitViewer() { 10 | const { leave } = useMeeting({}); 11 | return ( 12 | 21 | 28 | OOPS !! 29 | 30 | 39 | Maximun 2 participants can join this meeting. 40 | 41 | 49 | Please try again later 50 | 51 | 52 |