├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── CODE_OF_CONDUCT.md ├── Example ├── .buckconfig ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .watchmanconfig ├── __tests__ │ ├── index.android.js │ └── index.ios.js ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── network_security_config.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── keystores │ │ ├── BUCK │ │ └── debug.keystore.properties │ └── settings.gradle ├── app.json ├── babel.config.js ├── index.js ├── ios │ ├── Example-tvOS │ │ └── Info.plist │ ├── Example-tvOSTests │ │ └── Info.plist │ ├── Example.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── Example-tvOS.xcscheme │ │ │ └── Example.xcscheme │ ├── Example.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ ├── Example │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── main.m │ ├── ExampleTests │ │ ├── ExampleTests.m │ │ └── Info.plist │ ├── Podfile │ └── Podfile.lock ├── package.json ├── styles.js └── yarn.lock ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── twiliorn │ └── library │ ├── CustomTwilioVideoView.java │ ├── CustomTwilioVideoViewManager.java │ ├── PatchedVideoView.java │ ├── RNVideoViewGroup.java │ ├── TwilioPackage.java │ ├── TwilioRemotePreview.java │ ├── TwilioRemotePreviewManager.java │ ├── TwilioVideoPreview.java │ └── TwilioVideoPreviewManager.java ├── app.plugin.js ├── docs ├── README.md └── react-native-banner.svg ├── gulpfile.js ├── index.d.ts ├── index.js ├── ios ├── RCTTWLocalVideoViewManager.h ├── RCTTWLocalVideoViewManager.m ├── RCTTWRemoteVideoViewManager.h ├── RCTTWRemoteVideoViewManager.m ├── RCTTWSerializable.h ├── RCTTWSerializable.m ├── RCTTWVideoModule.h ├── RCTTWVideoModule.m └── RNTwilioVideoWebRTC.xcodeproj │ └── project.pbxproj ├── issue_template.md ├── jest └── react-native-twilio-video-webrtc-mock.js ├── package-lock.json ├── package.json ├── plugin ├── __tests__ │ └── .gitkeep ├── build │ ├── index.d.ts │ ├── index.js │ ├── withAndroidTwilioVideoWebrtc.d.ts │ ├── withAndroidTwilioVideoWebrtc.js │ ├── withIosTwilioVideoWebrtc.d.ts │ └── withIosTwilioVideoWebrtc.js ├── jest.config.js ├── src │ ├── index.ts │ ├── withAndroidTwilioVideoWebrtc.ts │ └── withIosTwilioVideoWebrtc.ts └── tsconfig.json ├── react-native-twilio-video-webrtc.podspec ├── src ├── TwilioVideo.android.js ├── TwilioVideo.ios.js ├── TwilioVideoLocalView.android.js ├── TwilioVideoLocalView.ios.js ├── TwilioVideoParticipantView.android.js └── TwilioVideoParticipantView.ios.js └── yarn.lock /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node-version: [20.x, 22.x] 18 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: 'yarn' 26 | - name: Install dependencies 27 | run: yarn install 28 | - name: Run tests 29 | run: yarn run ci 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Global 2 | .DS_Store 3 | 4 | # Node 5 | node_modules 6 | 7 | # Xcode 8 | # 9 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 10 | 11 | ## Build generated 12 | DerivedData/ 13 | .idea 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xcuserstate 28 | project.xcworkspace 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | # CocoaPods 37 | # 38 | # We recommend against adding the Pods directory to your .gitignore. However 39 | # you should judge for yourself, the pros and cons are mentioned at: 40 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 41 | # 42 | Pods/ 43 | 44 | # Android/IJ 45 | # 46 | *.iml 47 | .idea 48 | .gradle 49 | local.properties 50 | 51 | # vscode 52 | .vscode/ 53 | **/.project 54 | **/.settings/**/* -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | Example/ 2 | .idea 3 | .circleci 4 | .gradle 5 | .github 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at and . The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /Example/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /Example/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | .*/Libraries/react-native/ReactNative.js 16 | 17 | [include] 18 | 19 | [libs] 20 | node_modules/react-native/Libraries/react-native/react-native-interface.js 21 | node_modules/react-native/flow 22 | flow/ 23 | 24 | [options] 25 | emoji=true 26 | 27 | module.system=haste 28 | 29 | experimental.strict_type_args=true 30 | 31 | munge_underscores=true 32 | 33 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 34 | 35 | suppress_type=$FlowIssue 36 | suppress_type=$FlowFixMe 37 | suppress_type=$FixMe 38 | 39 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-2]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 40 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-2]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 41 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 42 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 43 | 44 | unsafe.enable_getters_and_setters=true 45 | 46 | [version] 47 | ^0.42.0 48 | -------------------------------------------------------------------------------- /Example/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /Example/.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 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 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 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 50 | 51 | fastlane/report.xml 52 | fastlane/Preview.html 53 | fastlane/screenshots 54 | -------------------------------------------------------------------------------- /Example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /Example/__tests__/index.android.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.android.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /Example/__tests__/index.ios.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.ios.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /Example/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 | lib_deps = [] 12 | 13 | for jarfile in glob(['libs/*.jar']): 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 | 21 | for aarfile in glob(['libs/*.aar']): 22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')] 23 | lib_deps.append(':' + name) 24 | android_prebuilt_aar( 25 | name = name, 26 | aar = aarfile, 27 | ) 28 | 29 | android_library( 30 | name = "all-libs", 31 | exported_deps = lib_deps, 32 | ) 33 | 34 | android_library( 35 | name = "app-code", 36 | srcs = glob([ 37 | "src/main/java/**/*.java", 38 | ]), 39 | deps = [ 40 | ":all-libs", 41 | ":build_config", 42 | ":res", 43 | ], 44 | ) 45 | 46 | android_build_config( 47 | name = "build_config", 48 | package = "com.example", 49 | ) 50 | 51 | android_resource( 52 | name = "res", 53 | package = "com.example", 54 | res = "src/main/res", 55 | ) 56 | 57 | android_binary( 58 | name = "app", 59 | keystore = "//android/keystores:debug", 60 | manifest = "src/main/AndroidManifest.xml", 61 | package_type = "debug", 62 | deps = [ 63 | ":app-code", 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /Example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // whether to bundle JS and assets in debug mode 22 | * bundleInDebug: false, 23 | * 24 | * // whether to bundle JS and assets in release mode 25 | * bundleInRelease: true, 26 | * 27 | * // whether to bundle JS and assets in another build variant (if configured). 28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 29 | * // The configuration property can be in the following formats 30 | * // 'bundleIn${productFlavor}${buildType}' 31 | * // 'bundleIn${buildType}' 32 | * // bundleInFreeDebug: true, 33 | * // bundleInPaidRelease: true, 34 | * // bundleInBeta: true, 35 | * 36 | * // the root of your project, i.e. where "package.json" lives 37 | * root: "../../", 38 | * 39 | * // where to put the JS bundle asset in debug mode 40 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 41 | * 42 | * // where to put the JS bundle asset in release mode 43 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 44 | * 45 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 46 | * // require('./image.png')), in debug mode 47 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 48 | * 49 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 50 | * // require('./image.png')), in release mode 51 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 52 | * 53 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 54 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 55 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 56 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 57 | * // for example, you might want to remove it from here. 58 | * inputExcludes: ["android/**", "ios/**"], 59 | * 60 | * // override which node gets called and with what additional arguments 61 | * nodeExecutableAndArgs: ["node"], 62 | * 63 | * // supply additional arguments to the packager 64 | * extraPackagerArgs: [] 65 | * ] 66 | */ 67 | 68 | apply from: "../../node_modules/react-native/react.gradle" 69 | 70 | /** 71 | * Set this to true to create two separate APKs instead of one: 72 | * - An APK that only works on ARM devices 73 | * - An APK that only works on x86 devices 74 | * The advantage is the size of the APK is reduced by about 4MB. 75 | * Upload all the APKs to the Play Store and people will download 76 | * the correct one based on the CPU architecture of their device. 77 | */ 78 | def enableSeparateBuildPerCPUArchitecture = false 79 | 80 | /** 81 | * Run Proguard to shrink the Java bytecode in release builds. 82 | */ 83 | def enableProguardInReleaseBuilds = false 84 | 85 | android { 86 | compileSdkVersion 28 87 | buildToolsVersion "28.0.3" 88 | 89 | defaultConfig { 90 | applicationId "com.example" 91 | minSdkVersion 21 92 | targetSdkVersion 28 93 | versionCode 1 94 | versionName "1.0" 95 | ndk { 96 | abiFilters "armeabi-v7a", "x86" 97 | } 98 | } 99 | splits { 100 | abi { 101 | reset() 102 | enable enableSeparateBuildPerCPUArchitecture 103 | universalApk false // If true, also generate a universal APK 104 | include "armeabi-v7a", "x86" 105 | } 106 | } 107 | buildTypes { 108 | release { 109 | minifyEnabled enableProguardInReleaseBuilds 110 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 111 | } 112 | } 113 | // applicationVariants are e.g. debug, release 114 | applicationVariants.all { variant -> 115 | variant.outputs.each { output -> 116 | // For each separate APK per architecture, set a unique version code as described here: 117 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 118 | def versionCodes = ["armeabi-v7a":1, "x86":2] 119 | def abi = output.getFilter(OutputFile.ABI) 120 | if (abi != null) { // null for the universal-debug, universal-release variants 121 | output.versionCodeOverride = 122 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 123 | } 124 | } 125 | } 126 | compileOptions { 127 | sourceCompatibility 1.8 128 | targetCompatibility 1.8 129 | } 130 | } 131 | 132 | dependencies { 133 | implementation fileTree(dir: "libs", include: ["*.jar"]) 134 | implementation "com.android.support:appcompat-v7:28.0.0" 135 | implementation "com.facebook.react:react-native:+" // From node_modules 136 | implementation project(':react-native-twilio-video-webrtc') 137 | } 138 | 139 | // Run this once to be able to run the application with BUCK 140 | // puts all compile dependencies into folder libs for BUCK to use 141 | task copyDownloadableDepsToLibs(type: Copy) { 142 | from configurations.compile 143 | into 'libs' 144 | } 145 | -------------------------------------------------------------------------------- /Example/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 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # TextLayoutBuilder uses a non-public Android constructor within StaticLayout. 54 | # See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details. 55 | -dontwarn android.text.StaticLayout 56 | 57 | # okhttp 58 | 59 | -keepattributes Signature 60 | -keepattributes *Annotation* 61 | -keep class okhttp3.** { *; } 62 | -keep interface okhttp3.** { *; } 63 | -dontwarn okhttp3.** 64 | 65 | # okio 66 | 67 | -keep class sun.misc.Unsafe { *; } 68 | -dontwarn java.nio.file.* 69 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 70 | -dontwarn okio.** 71 | 72 | -keep class com.twilio.** { *; } 73 | -keep class tvi.webrtc.** { *; } 74 | -------------------------------------------------------------------------------- /Example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Example/android/app/src/main/java/com/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "Example"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/android/app/src/main/java/com/example/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactApplication; 6 | import com.facebook.react.ReactNativeHost; 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.shell.MainReactPackage; 9 | import com.twiliorn.library.TwilioPackage; 10 | import com.facebook.soloader.SoLoader; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | public class MainApplication extends Application implements ReactApplication { 16 | 17 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 18 | @Override 19 | public boolean getUseDeveloperSupport() { 20 | return BuildConfig.DEBUG; 21 | } 22 | 23 | @Override 24 | protected List getPackages() { 25 | return Arrays.asList( 26 | new MainReactPackage(), 27 | new TwilioPackage() 28 | ); 29 | } 30 | 31 | @Override 32 | protected String getJSMainModuleName() { 33 | return "index"; 34 | } 35 | }; 36 | 37 | @Override 38 | public ReactNativeHost getReactNativeHost() { 39 | return mReactNativeHost; 40 | } 41 | 42 | @Override 43 | public void onCreate() { 44 | super.onCreate(); 45 | SoLoader.init(this, /* native exopackage */ false); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackuy/react-native-twilio-video-webrtc/721af58f8e5e6bf6d263d7fad105f66235e66fc8/Example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackuy/react-native-twilio-video-webrtc/721af58f8e5e6bf6d263d7fad105f66235e66fc8/Example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackuy/react-native-twilio-video-webrtc/721af58f8e5e6bf6d263d7fad105f66235e66fc8/Example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackuy/react-native-twilio-video-webrtc/721af58f8e5e6bf6d263d7fad105f66235e66fc8/Example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Example 3 | 4 | -------------------------------------------------------------------------------- /Example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | localhost 5 | 6 | -------------------------------------------------------------------------------- /Example/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.0.1' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | mavenLocal() 20 | jcenter() 21 | maven { 22 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 23 | url "$rootDir/../node_modules/react-native/android" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Example/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 | android.useAndroidX=true 21 | 22 | # android.useDeprecatedNdk=true 23 | -------------------------------------------------------------------------------- /Example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackuy/react-native-twilio-video-webrtc/721af58f8e5e6bf6d263d7fad105f66235e66fc8/Example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jul 14 01:42:45 PKT 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /Example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /Example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /Example/android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = "debug", 3 | properties = "debug.keystore.properties", 4 | store = "debug.keystore", 5 | visibility = [ 6 | "PUBLIC", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /Example/android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /Example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Example' 2 | 3 | include ':react-native-twilio-video-webrtc' 4 | project(':react-native-twilio-video-webrtc').projectDir = new File(rootProject.projectDir, '../../android') 5 | 6 | include ':app' 7 | -------------------------------------------------------------------------------- /Example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Example", 3 | "displayName": "Example" 4 | } -------------------------------------------------------------------------------- /Example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /Example/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import { 3 | AppRegistry, 4 | StyleSheet, 5 | Text, 6 | TextInput, 7 | View, 8 | Button, 9 | PermissionsAndroid, 10 | Platform, 11 | TouchableOpacity, 12 | } from "react-native"; 13 | 14 | import { 15 | TwilioVideoLocalView, 16 | TwilioVideoParticipantView, 17 | TwilioVideo, 18 | } from "react-native-twilio-video-webrtc"; 19 | 20 | import styleSheet from "./styles"; 21 | 22 | const styles = StyleSheet.create(styleSheet); 23 | 24 | const Example = (props) => { 25 | const [isAudioEnabled, setIsAudioEnabled] = useState(true); 26 | const [isVideoEnabled, setIsVideoEnabled] = useState(true); 27 | const [isScreenShareEnabled, setIsScreenShareEnabled] = useState(false); 28 | const [status, setStatus] = useState("disconnected"); 29 | const [participants, setParticipants] = useState(new Map()); 30 | const [videoTracks, setVideoTracks] = useState(new Map()); 31 | const [token, setToken] = useState(""); 32 | const twilioVideo = useRef(null); 33 | 34 | const _onConnectButtonPress = async () => { 35 | if (Platform.OS === "android") { 36 | await _requestAudioPermission(); 37 | await _requestCameraPermission(); 38 | } 39 | twilioVideo.current.connect({ 40 | accessToken: token, 41 | enableNetworkQualityReporting: true, 42 | dominantSpeakerEnabled: true, 43 | }); 44 | setStatus("connecting"); 45 | }; 46 | 47 | const _onEndButtonPress = () => { 48 | twilioVideo.current.disconnect(); 49 | }; 50 | 51 | const _onMuteButtonPress = () => { 52 | twilioVideo.current 53 | .setLocalAudioEnabled(!isAudioEnabled) 54 | .then((isEnabled) => setIsAudioEnabled(isEnabled)); 55 | }; 56 | 57 | const _onShareButtonPressed = () => { 58 | twilioVideo.current.toggleScreenSharing(!isSharing); 59 | setIsSharing(!isSharing); 60 | }; 61 | 62 | const _onFlipButtonPress = () => { 63 | twilioVideo.current.flipCamera(); 64 | }; 65 | 66 | const _onRoomDidConnect = () => { 67 | setStatus("connected"); 68 | }; 69 | 70 | const _onRoomDidDisconnect = ({ error }) => { 71 | console.log("ERROR: ", error); 72 | 73 | setStatus("disconnected"); 74 | }; 75 | 76 | const _onRoomDidFailToConnect = (error) => { 77 | console.log("ERROR: ", error); 78 | 79 | setStatus("disconnected"); 80 | }; 81 | 82 | const _onParticipantAddedVideoTrack = ({ participant, track }) => { 83 | console.log("onParticipantAddedVideoTrack: ", participant, track); 84 | 85 | setVideoTracks((originalVideoTracks) => { 86 | originalVideoTracks.set(track.trackSid, { 87 | participantSid: participant.sid, 88 | videoTrackSid: track.trackSid, 89 | }); 90 | return new Map(originalVideoTracks); 91 | }); 92 | }; 93 | 94 | const _onParticipantRemovedVideoTrack = ({ participant, track }) => { 95 | console.log("onParticipantRemovedVideoTrack: ", participant, track); 96 | 97 | setVideoTracks((originalVideoTracks) => { 98 | originalVideoTracks.delete(track.trackSid); 99 | return new Map(originalVideoTracks); 100 | }); 101 | }; 102 | 103 | const _onNetworkLevelChanged = ({ participant, isLocalUser, quality }) => { 104 | console.log( 105 | "Participant", 106 | participant, 107 | "isLocalUser", 108 | isLocalUser, 109 | "quality", 110 | quality 111 | ); 112 | }; 113 | 114 | const _onDominantSpeakerDidChange = ({ roomName, roomSid, participant }) => { 115 | console.log( 116 | "onDominantSpeakerDidChange", 117 | `roomName: ${roomName}`, 118 | `roomSid: ${roomSid}`, 119 | "participant:", 120 | participant 121 | ); 122 | }; 123 | 124 | const _requestAudioPermission = () => { 125 | return PermissionsAndroid.request( 126 | PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, 127 | { 128 | title: "Need permission to access microphone", 129 | message: 130 | "To run this demo we need permission to access your microphone", 131 | buttonNegative: "Cancel", 132 | buttonPositive: "OK", 133 | } 134 | ); 135 | }; 136 | 137 | const _requestCameraPermission = () => { 138 | return PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, { 139 | title: "Need permission to access camera", 140 | message: "To run this demo we need permission to access your camera", 141 | buttonNegative: "Cancel", 142 | buttonPositive: "OK", 143 | }); 144 | }; 145 | 146 | return ( 147 | 148 | {status === "disconnected" && ( 149 | 150 | React Native Twilio Video 151 | setToken(text)} 156 | > 157 | 162 | 163 | )} 164 | 165 | {(status === "connected" || status === "connecting") && ( 166 | 167 | {status === "connected" && ( 168 | 169 | {Array.from(videoTracks, ([trackSid, trackIdentifier]) => { 170 | return ( 171 | 176 | ); 177 | })} 178 | 179 | )} 180 | 181 | 185 | End 186 | 187 | 191 | 192 | {isAudioEnabled ? "Mute" : "Unmute"} 193 | 194 | 195 | 199 | Flip 200 | 201 | 205 | 206 | {isSharing ? "Stop Sharing" : "Start Sharing"} 207 | 208 | 209 | 210 | 211 | 212 | )} 213 | 214 | 224 | 225 | ); 226 | }; 227 | 228 | AppRegistry.registerComponent("Example", () => Example); 229 | -------------------------------------------------------------------------------- /Example/ios/Example-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | NSLocationWhenInUseUsageDescription 40 | 41 | NSAppTransportSecurity 42 | 43 | 44 | NSExceptionDomains 45 | 46 | localhost 47 | 48 | NSExceptionAllowsInsecureHTTPLoads 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Example/ios/Example-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 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 | -------------------------------------------------------------------------------- /Example/ios/Example.xcodeproj/xcshareddata/xcschemes/Example-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /Example/ios/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /Example/ios/Example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/ios/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/ios/Example.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | PreviewsEnabled 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/ios/Example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Example/ios/Example/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import 13 | #import 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | NSURL *jsCodeLocation; 20 | 21 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 22 | 23 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 24 | moduleName:@"Example" 25 | initialProperties:nil 26 | launchOptions:launchOptions]; 27 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 28 | 29 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 30 | UIViewController *rootViewController = [UIViewController new]; 31 | rootViewController.view = rootView; 32 | self.window.rootViewController = rootViewController; 33 | [self.window makeKeyAndVisible]; 34 | return YES; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Example/ios/Example/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/ios/Example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Example/ios/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSMicrophoneUsageDescription 6 | We are going to use the microphone 7 | NSCameraUsageDescription 8 | We are going to use the camera 9 | CFBundleDevelopmentRegion 10 | en 11 | CFBundleDisplayName 12 | Example 13 | CFBundleExecutable 14 | $(EXECUTABLE_NAME) 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | $(PRODUCT_NAME) 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 1.0 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | 1 29 | LSRequiresIPhoneOS 30 | 31 | NSAppTransportSecurity 32 | 33 | NSExceptionDomains 34 | 35 | localhost 36 | 37 | NSExceptionAllowsInsecureHTTPLoads 38 | 39 | 40 | 41 | 42 | NSLocationWhenInUseUsageDescription 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UIViewControllerBasedStatusBarAppearance 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Example/ios/Example/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/ios/ExampleTests/ExampleTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | #import 14 | #import 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface ExampleTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation ExampleTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Example/ios/ExampleTests/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 | -------------------------------------------------------------------------------- /Example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | source 'https://github.com/CocoaPods/Specs.git' 3 | 4 | platform :ios, '11.0' 5 | 6 | target 'Example' do 7 | pod 'yoga', path: '../node_modules/react-native/ReactCommon/yoga' 8 | pod 'React', path: '../node_modules/react-native', :subspecs => [ 9 | 'Core', 10 | 'CxxBridge' 11 | ] 12 | pod 'react-native-twilio-video-webrtc', path: '../../' 13 | pod 'Folly', podspec: '../node_modules/react-native/third-party-podspecs/Folly.podspec' 14 | end 15 | -------------------------------------------------------------------------------- /Example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - boost-for-react-native (1.63.0) 3 | - DoubleConversion (1.1.5) 4 | - Folly (2018.10.22.00): 5 | - boost-for-react-native 6 | - DoubleConversion 7 | - glog 8 | - glog (0.3.4) 9 | - React (0.59.9): 10 | - React/Core (= 0.59.9) 11 | - react-native-twilio-video-webrtc (2.0.0): 12 | - React 13 | - TwilioVideo (~> 4.1) 14 | - React/Core (0.59.9): 15 | - yoga (= 0.59.9.React) 16 | - React/CxxBridge (0.59.9): 17 | - Folly (= 2018.10.22.00) 18 | - React/Core 19 | - React/cxxreact 20 | - React/jsiexecutor 21 | - React/cxxreact (0.59.9): 22 | - boost-for-react-native (= 1.63.0) 23 | - DoubleConversion 24 | - Folly (= 2018.10.22.00) 25 | - glog 26 | - React/jsinspector 27 | - React/jsi (0.59.9): 28 | - DoubleConversion 29 | - Folly (= 2018.10.22.00) 30 | - glog 31 | - React/jsiexecutor (0.59.9): 32 | - DoubleConversion 33 | - Folly (= 2018.10.22.00) 34 | - glog 35 | - React/cxxreact 36 | - React/jsi 37 | - React/jsinspector (0.59.9) 38 | - TwilioVideo (4.1.0) 39 | - yoga (0.59.9.React) 40 | 41 | DEPENDENCIES: 42 | - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`) 43 | - react-native-twilio-video-webrtc (from `../../`) 44 | - React/Core (from `../node_modules/react-native`) 45 | - React/CxxBridge (from `../node_modules/react-native`) 46 | - yoga (from `../node_modules/react-native/ReactCommon/yoga`) 47 | 48 | SPEC REPOS: 49 | https://github.com/CocoaPods/Specs.git: 50 | - boost-for-react-native 51 | - DoubleConversion 52 | - glog 53 | - TwilioVideo 54 | 55 | EXTERNAL SOURCES: 56 | Folly: 57 | :podspec: "../node_modules/react-native/third-party-podspecs/Folly.podspec" 58 | React: 59 | :path: "../node_modules/react-native" 60 | react-native-twilio-video-webrtc: 61 | :path: "../../" 62 | yoga: 63 | :path: "../node_modules/react-native/ReactCommon/yoga" 64 | 65 | SPEC CHECKSUMS: 66 | boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c 67 | DoubleConversion: e22e0762848812a87afd67ffda3998d9ef29170c 68 | Folly: de497beb10f102453a1afa9edbf8cf8a251890de 69 | glog: 1de0bb937dccdc981596d3b5825ebfb765017ded 70 | React: a86b92f00edbe1873a63e4a212c29b7a7ad5224f 71 | react-native-twilio-video-webrtc: 5b40ee0129687cb89fb5055680aed35c8db4be07 72 | TwilioVideo: 17bfe3dcac945c6c1235c4584dc37e07a28ccb61 73 | yoga: 03ff42a6f223fb88deeaed60249020d80c3091ee 74 | 75 | PODFILE CHECKSUM: a22eec28b45c6a20b9c752a17cdef0f608e13c10 76 | 77 | COCOAPODS: 1.10.0 78 | -------------------------------------------------------------------------------- /Example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "test": "jest" 8 | }, 9 | "dependencies": { 10 | "react": "16.8.3", 11 | "react-native": "0.59.9" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.3.3", 15 | "babel-jest": "24.8.0", 16 | "jest": "24.8.0", 17 | "metro-react-native-babel-preset": "^0.51.1", 18 | "react-native-twilio-video-webrtc": "file:../", 19 | "react-test-renderer": "16.8.3", 20 | "semver": "5.7.2" 21 | }, 22 | "jest": { 23 | "preset": "react-native" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Example/styles.js: -------------------------------------------------------------------------------- 1 | export default { 2 | container: { 3 | flex: 1, 4 | backgroundColor: "white", 5 | }, 6 | callContainer: { 7 | flex: 1, 8 | position: "absolute", 9 | bottom: 0, 10 | top: 0, 11 | left: 0, 12 | right: 0, 13 | }, 14 | welcome: { 15 | fontSize: 30, 16 | textAlign: "center", 17 | paddingTop: 40, 18 | }, 19 | input: { 20 | height: 50, 21 | borderWidth: 1, 22 | marginRight: 70, 23 | marginLeft: 70, 24 | marginTop: 50, 25 | textAlign: "center", 26 | backgroundColor: "white", 27 | }, 28 | button: { 29 | marginTop: 100, 30 | }, 31 | localVideo: { 32 | flex: 1, 33 | width: 150, 34 | height: 250, 35 | position: "absolute", 36 | right: 10, 37 | bottom: 10, 38 | }, 39 | remoteGrid: { 40 | flex: 1, 41 | flexDirection: "row", 42 | flexWrap: "wrap", 43 | }, 44 | remoteVideo: { 45 | marginTop: 20, 46 | marginLeft: 10, 47 | marginRight: 10, 48 | width: 100, 49 | height: 120, 50 | }, 51 | optionsContainer: { 52 | position: "absolute", 53 | left: 0, 54 | bottom: 0, 55 | right: 0, 56 | height: 100, 57 | backgroundColor: "blue", 58 | flexDirection: "row", 59 | alignItems: "center", 60 | }, 61 | optionButton: { 62 | width: 60, 63 | height: 60, 64 | marginLeft: 10, 65 | marginRight: 10, 66 | borderRadius: 100 / 2, 67 | backgroundColor: "grey", 68 | justifyContent: "center", 69 | alignItems: "center", 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2024 Gaston Morixe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twilio Video (WebRTC) for React Native 2 | 3 | > [!NOTE] 4 | > **October 21 2024:** _Good news!_ Twilio just announced **Twilio Video service is here to stay**, they are reversing the deprecation decision. [Here's their official announcement.](https://www.twilio.com/en-us/blog/twilio-video-update-2024). 5 | > 6 | > If you or your company need React Native support, contact me [gaston@gastonmorixe.com](mailto:gaston@gastonmorixe.com). We have premium react native features like PiP support, Live Activities, Typescript, and many more. - Gaston 7 | 8 | 9 | [![react-native-twilio-video-webrtc](./docs/react-native-banner.svg)](https://github.com/blackuy/react-native-twilio-video-webrtc) 10 | 11 | [![GitHub Repo stars](https://img.shields.io/github/stars/blackuy/react-native-twilio-video-webrtc)](https://github.com/blackuy/react-native-twilio-video-webrtc/stargazers) 12 | [![Weekly Views](https://shieldsdev.tech/badge/react-native-twilio-video-webrtc/totals)](https://npm-stat.com/charts.html?package=react-native-twilio-video-webrtc&from=2016-01-01) 13 | [![GitHub License](https://img.shields.io/github/license/blackuy/react-native-twilio-video-webrtc)](https://github.com/blackuy/react-native-twilio-video-webrtc/blob/master/LICENSE) 14 | [![NPM version](https://img.shields.io/npm/v/react-native-twilio-video-webrtc)](https://www.npmjs.com/package/react-native-twilio-video-webrtc) 15 | [![NPM Downloads](https://img.shields.io/npm/dy/react-native-twilio-video-webrtc)](https://npm-stat.com/charts.html?package=react-native-twilio-video-webrtc&from=2016-01-01) 16 | 17 | Platforms: 18 | 19 | - iOS 20 | - Android 21 | 22 | People using a version < 1.0.1 please move to 1.0.1 since the project changed a lot internally to support the stable TwilioVideo version. 23 | 24 | ## Installation 25 | 26 | - react-native >= 0.40.0: install react-native-twilio-video-webrtc@1.0.1 27 | - react-native < 0.40.0: install react-native-twilio-video-webrtc@1.0.0 28 | 29 | ### Install Node Package 30 | 31 | [![NPM version](https://img.shields.io/npm/v/react-native-twilio-video-webrtc)](https://www.npmjs.com/package/react-native-twilio-video-webrtc) 32 | 33 | #### Option A: yarn 34 | 35 | ```shell 36 | yarn add react-native-twilio-video-webrtc 37 | ``` 38 | 39 | #### Option B: npm 40 | 41 | ```shell 42 | npm install react-native-twilio-video-webrtc 43 | ``` 44 | 45 | ### Usage with Expo 46 | 47 | To use this library with [`Expo`](https://expo.dev) we recommend using our config plugin that you can configure like the following example: 48 | 49 | ```json 50 | { 51 | "name": "my app", 52 | "plugins": [ 53 | [ 54 | "react-native-twilio-video-webrtc", 55 | { 56 | "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera", 57 | "microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone" 58 | } 59 | ] 60 | ] 61 | } 62 | ``` 63 | 64 | Also you will need to install `expo-build-properties` package: 65 | 66 | ```shell 67 | npx expo install expo-build-properties 68 | ``` 69 | 70 | #### Expo Config Plugin Props 71 | 72 | The plugin support the following properties: 73 | 74 | - `cameraPermission`: Specifies the text to show when requesting the camera permission to the user. 75 | 76 | - `microphonePermission`: Specifies the text to show when requesting the microphone permission to the user. 77 | 78 | ### iOS 79 | 80 | #### Option A: Install with CocoaPods (recommended) 81 | 82 | 1. Add this package to your Podfile 83 | 84 | ```ruby 85 | pod 'react-native-twilio-video-webrtc', path: '../node_modules/react-native-twilio-video-webrtc' 86 | ``` 87 | 88 | Note that this will automatically pull in the appropriate version of the underlying `TwilioVideo` pod. 89 | 90 | 2. Install Pods with 91 | 92 | ```shell 93 | pod install 94 | ``` 95 | 96 | #### Option B: Install without CocoaPods (manual approach) 97 | 98 | 1. Add the Twilio dependency to your Podfile 99 | 100 | ```ruby 101 | pod 'TwilioVideo' 102 | ``` 103 | 104 | 2. Install Pods with 105 | 106 | ```shell 107 | pod install 108 | ``` 109 | 110 | 3. Add the XCode project to your own XCode project's `Libraries` directory from 111 | 112 | ``` 113 | node_modules/react-native-twilio-video-webrtc/ios/RNTwilioVideoWebRTC.xcodeproj 114 | ``` 115 | 116 | 4. Add `libRNTwilioVideoWebRTC.a` to your XCode project target's `Linked Frameworks and Libraries` 117 | 118 | 5. Update `Build Settings` 119 | 120 | Find `Search Paths` and add `$(SRCROOT)/../node_modules/react-native-twilio-video-webrtc/ios` with `recursive` to `Framework Search Paths` and `Library Search Paths` 121 | 122 | #### Post install 123 | 124 | Be sure to increment your iOS Deployment Target to at least iOS 11 through XCode and your `Podfile` contains 125 | 126 | ``` 127 | platform :ios, '11.0' 128 | ``` 129 | 130 | #### Permissions 131 | 132 | To enable camera usage and microphone usage you will need to add the following entries to your `Info.plist` file: 133 | 134 | ``` 135 | NSCameraUsageDescription 136 | Your message to user when the camera is accessed for the first time 137 | NSMicrophoneUsageDescription 138 | Your message to user when the microphone is accessed for the first time 139 | ``` 140 | 141 | #### Known Issues 142 | 143 | TwilioVideo version 1.3.8 has the following know issues. 144 | 145 | - Participant disconnect event can take up to 120 seconds to occur. [Issue 99](https://github.com/twilio/video-quickstart-swift/issues/99) 146 | - AVPlayer audio content does not mix properly with Room audio. [Issue 62](https://github.com/twilio/video-quickstart-objc/issues/62) 147 | 148 | ### Android 149 | 150 | As with iOS, make sure the package is installed: 151 | 152 | ```shell 153 | yarn add react-native-twilio-video-webrtc 154 | ``` 155 | 156 | Then add the library to your `settings.gradle` file: 157 | 158 | ``` 159 | include ':react-native-twilio-video-webrtc' 160 | project(':react-native-twilio-video-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-twilio-video-webrtc/android') 161 | ``` 162 | 163 | And include the library in your dependencies in `android/app/build.gradle`: 164 | (if using gradle 4 or lower, replace `implementation` with `compile` below) 165 | 166 | ``` 167 | dependencies { 168 | ..... 169 | ..... 170 | ..... 171 | implementation project(':react-native-twilio-video-webrtc') 172 | } 173 | ``` 174 | 175 | You will also need to update this file so that you compile with java 8 features: 176 | 177 | ``` 178 | android { 179 | compileOptions { 180 | sourceCompatibility 1.8 181 | targetCompatibility 1.8 182 | } 183 | } 184 | ``` 185 | 186 | Now you're ready to load the package in `MainApplication.java`. In the imports section, add this: 187 | 188 | ```java 189 | import com.twiliorn.library.TwilioPackage; 190 | ``` 191 | 192 | Then update the `getPackages()` method: 193 | 194 | ```java 195 | protected List getPackages() { 196 | return Arrays.asList( 197 | ... 198 | new TwilioPackage() 199 | ); 200 | } 201 | ``` 202 | 203 | ### Permissions 204 | 205 | For most applications, you'll want to add camera and audio permissions to your `AndroidManifest.xml` file: 206 | 207 | ```xml 208 | 209 | 210 | 211 | 212 | 213 | 214 | ``` 215 | 216 | Newer versions of Android have a different permissions model. You will need to use the `PermissionsAndroid` 217 | class in `react-native` in order to request the `CAMERA` and `RECORD_AUDIO` permissions. 218 | 219 | ### Additional Tips 220 | 221 | Under default settings, the Android build will fail if the total number of symbols exceeds a certain threshold. If you should encounter this issue when adding this library (e.g., if your build fails with `com.android.dex.DexIndexOverflowException`), you can turn on jumbo mode by editing your `app/build.gradle`: 222 | 223 | ``` 224 | android { 225 | ... 226 | dexOptions { 227 | jumboMode true 228 | } 229 | } 230 | ``` 231 | 232 | If you are using proguard (very likely), you will also need to ensure that the symbols needed by 233 | this library are not stripped. To do that, add these two lines to `proguard-rules.pro`: 234 | 235 | ``` 236 | -keep class com.twilio.** { *; } 237 | -keep class tvi.webrtc.** { *; } 238 | ``` 239 | 240 | ## Docs 241 | 242 | You can see the documentation [here](./docs). 243 | 244 | ## Usage 245 | 246 | We have three important components to understand: 247 | 248 | ```javascript 249 | import { 250 | TwilioVideo, 251 | TwilioVideoLocalView, 252 | TwilioVideoParticipantView, 253 | } from "react-native-twilio-video-webrtc"; 254 | ``` 255 | 256 | - `TwilioVideo` / is responsible for connecting to rooms, events delivery and camera/audio. 257 | - `TwilioVideoLocalView` / is responsible local camera feed view 258 | - `TwilioVideoParticipantView` / is responsible remote peer's camera feed view 259 | 260 | Here you can see a complete example of a simple application that uses almost all the apis: 261 | 262 | ```javascript 263 | import React, { Component, useRef } from "react"; 264 | import { 265 | TwilioVideoLocalView, 266 | TwilioVideoParticipantView, 267 | TwilioVideo, 268 | } from "react-native-twilio-video-webrtc"; 269 | 270 | const Example = (props) => { 271 | const [isAudioEnabled, setIsAudioEnabled] = useState(true); 272 | const [isVideoEnabled, setIsVideoEnabled] = useState(true); 273 | const [status, setStatus] = useState("disconnected"); 274 | const [participants, setParticipants] = useState(new Map()); 275 | const [videoTracks, setVideoTracks] = useState(new Map()); 276 | const [token, setToken] = useState(""); 277 | const twilioRef = useRef(null); 278 | 279 | const _onConnectButtonPress = () => { 280 | twilioRef.current.connect({ accessToken: token }); 281 | setStatus("connecting"); 282 | }; 283 | 284 | const _onEndButtonPress = () => { 285 | twilioRef.current.disconnect(); 286 | }; 287 | 288 | const _onMuteButtonPress = () => { 289 | twilioRef.current 290 | .setLocalAudioEnabled(!isAudioEnabled) 291 | .then((isEnabled) => setIsAudioEnabled(isEnabled)); 292 | }; 293 | 294 | const _onFlipButtonPress = () => { 295 | twilioRef.current.flipCamera(); 296 | }; 297 | 298 | const _onRoomDidConnect = ({ roomName, error }) => { 299 | console.log("onRoomDidConnect: ", roomName); 300 | 301 | setStatus("connected"); 302 | }; 303 | 304 | const _onRoomDidDisconnect = ({ roomName, error }) => { 305 | console.log("[Disconnect]ERROR: ", error); 306 | 307 | setStatus("disconnected"); 308 | }; 309 | 310 | const _onRoomDidFailToConnect = (error) => { 311 | console.log("[FailToConnect]ERROR: ", error); 312 | 313 | setStatus("disconnected"); 314 | }; 315 | 316 | const _onParticipantAddedVideoTrack = ({ participant, track }) => { 317 | console.log("onParticipantAddedVideoTrack: ", participant, track); 318 | 319 | setVideoTracks((originalVideoTracks) => { 320 | originalVideoTracks.set(track.trackSid, { 321 | participantSid: participant.sid, 322 | videoTrackSid: track.trackSid, 323 | }); 324 | return new Map(originalVideoTracks); 325 | }); 326 | }; 327 | 328 | const _onParticipantRemovedVideoTrack = ({ participant, track }) => { 329 | console.log("onParticipantRemovedVideoTrack: ", participant, track); 330 | 331 | setVideoTracks((originalVideoTracks) => { 332 | originalVideoTracks.delete(track.trackSid); 333 | return new Map(originalVideoTracks); 334 | }); 335 | }; 336 | 337 | return ( 338 | 339 | {status === "disconnected" && ( 340 | 341 | React Native Twilio Video 342 | setToken(text)} 347 | > 348 | 353 | 354 | )} 355 | 356 | {(status === "connected" || status === "connecting") && ( 357 | 358 | {status === "connected" && ( 359 | 360 | {Array.from(videoTracks, ([trackSid, trackIdentifier]) => { 361 | return ( 362 | 367 | ); 368 | })} 369 | 370 | )} 371 | 372 | 376 | End 377 | 378 | 382 | 383 | {isAudioEnabled ? "Mute" : "Unmute"} 384 | 385 | 386 | 390 | Flip 391 | 392 | 393 | 394 | 395 | )} 396 | 397 | 405 | 406 | ); 407 | }; 408 | 409 | AppRegistry.registerComponent("Example", () => Example); 410 | ``` 411 | 412 | ## Run the Example Application 413 | 414 | To run the example application: 415 | 416 | - Move to the Example directory: `cd Example` 417 | - Install node dependencies: `yarn install` 418 | - Install objective-c dependencies: `cd ios && pod install` 419 | - Open the xcworkspace and run the app: `open Example.xcworkspace` 420 | 421 | ## Migrating from 1.x to 2.x 422 | 423 | - Make sure your pod dependencies are updated. If you manually specified a pod version, you'll want to update it as follows: 424 | 425 | ``` 426 | s.dependency 'TwilioVideo', '~> 2.2.0' 427 | ``` 428 | 429 | - Both participants and tracks are uniquely identified by their `sid`/`trackSid` field. 430 | The `trackId` field no longer exists and should be replaced by `trackSid`. Commensurate with this change, 431 | participant views now expect `participantSid` and `videoTrackSid` keys in the `trackIdentity` prop (instead of 432 | `identity` and `trackId`). 433 | 434 | - Make sure you're listening to participant events via `onParticipant{Added/Removed}VideoTrack` rather than `onParticipant{Enabled/Disabled}Track`. 435 | 436 | ## Contact 437 | 438 | - Original Author: **Gaston Morixe** ([@gastonmorixe](https://github.com/gastonmorixe)) 439 | - Core Contributor: **Martín Fernández** ([@bilby91](https://github.com/bilby91)) 440 | 441 | ## License 442 | 443 | The MIT License (MIT) 444 | 445 | Copyright (c) 2016-2024 Gaston Morixe 446 | 447 | **Full License text** you must include and attribute in your project: [LICENSE](/LICENSE). 448 | 449 | **Compliance Requirement:** All users must include the full text of the MIT License, including the copyright notice and permission notice, in any copies or substantial portions of the Software. 450 | 451 | **Commercial Use:** Commercial entities using this software please ensure compliance with the license terms and proper attribution. 452 | 453 | **Consequences of Violation:** Failure to comply with the MIT License constitutes copyright infringement and may result in legal action, including injunctions and monetary damages. Please ensure to respect the open source project. 454 | 455 | For any questions regarding licensing or to request additional permissions, please contact the author. 456 | 457 | ## Star History 458 | 459 | 460 | 461 | 462 | 463 | Star History Chart 464 | 465 | 466 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml 3 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | def DEFAULT_COMPILE_SDK_VERSION = 30 4 | def DEFAULT_BUILD_TOOLS_VERSION = "27.0.3" 5 | def DEFAULT_TARGET_SDK_VERSION = 30 6 | def DEFAULT_ANDROID_SUPPORT_VERSION = "27.1.0" 7 | def DEFAULT_ANDROID_MIN_SDK_VERSION = 21 8 | 9 | android { 10 | namespace = "com.twiliorn.library" 11 | compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION 12 | buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION 13 | 14 | compileOptions { 15 | sourceCompatibility JavaVersion.VERSION_1_8 16 | targetCompatibility JavaVersion.VERSION_1_8 17 | } 18 | defaultConfig { 19 | minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : DEFAULT_ANDROID_MIN_SDK_VERSION 20 | targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : DEFAULT_TARGET_SDK_VERSION 21 | versionCode 1 22 | versionName "1.0" 23 | } 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | 31 | // Specify that we want to split up the APK based on ABI 32 | splits { 33 | abi { 34 | // Enable ABI split 35 | enable true 36 | 37 | // Clear list of ABIs 38 | reset() 39 | 40 | // Specify each architecture currently supported by the Video SDK 41 | include "armeabi-v7a", "arm64-v8a", "x86", "x86_64" 42 | 43 | // Specify that we do not want an additional universal SDK 44 | universalApk false 45 | } 46 | } 47 | } 48 | 49 | dependencies { 50 | def androidSupportVersion = rootProject.hasProperty("androidSupportVersion") ? rootProject.androidSupportVersion : DEFAULT_ANDROID_SUPPORT_VERSION 51 | 52 | implementation fileTree(dir: 'libs', include: ['*.jar']) 53 | implementation "com.android.support:appcompat-v7:$androidSupportVersion" 54 | implementation "com.twilio:video-android:7.6.1" 55 | implementation "com.facebook.react:react-native:+" // From node_modules 56 | } 57 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /android/src/main/java/com/twiliorn/library/CustomTwilioVideoViewManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Component to orchestrate the Twilio Video connection and the various video 3 | * views. 4 | *

5 | * Authors: 6 | * Ralph Pina 7 | * Jonathan Chang 8 | */ 9 | package com.twiliorn.library; 10 | 11 | import androidx.annotation.Nullable; 12 | 13 | import com.facebook.react.bridge.ReadableArray; 14 | import com.facebook.react.bridge.ReadableMap; 15 | import com.facebook.react.common.MapBuilder; 16 | import com.facebook.react.uimanager.SimpleViewManager; 17 | import com.facebook.react.uimanager.ThemedReactContext; 18 | import com.facebook.react.uimanager.annotations.ReactProp; 19 | 20 | import java.util.Map; 21 | 22 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_AUDIO_CHANGED; 23 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CAMERA_SWITCHED; 24 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CONNECTED; 25 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CONNECT_FAILURE; 26 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_DISCONNECTED; 27 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_CONNECTED; 28 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_DISCONNECTED; 29 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_VIDEO_CHANGED; 30 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_REMOVED_DATA_TRACK; 31 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_ADDED_DATA_TRACK; 32 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_DATATRACK_MESSAGE_RECEIVED; 33 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_ADDED_VIDEO_TRACK; 34 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_REMOVED_VIDEO_TRACK; 35 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_ADDED_AUDIO_TRACK; 36 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_REMOVED_AUDIO_TRACK; 37 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_ENABLED_VIDEO_TRACK; 38 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_DISABLED_VIDEO_TRACK; 39 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_ENABLED_AUDIO_TRACK; 40 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_PARTICIPANT_DISABLED_AUDIO_TRACK; 41 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_STATS_RECEIVED; 42 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_NETWORK_QUALITY_LEVELS_CHANGED; 43 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_DOMINANT_SPEAKER_CHANGED; 44 | import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_LOCAL_PARTICIPANT_SUPPORTED_CODECS; 45 | 46 | public class CustomTwilioVideoViewManager extends SimpleViewManager { 47 | public static final String REACT_CLASS = "RNCustomTwilioVideoView"; 48 | 49 | private static final int CONNECT_TO_ROOM = 1; 50 | private static final int DISCONNECT = 2; 51 | private static final int SWITCH_CAMERA = 3; 52 | private static final int TOGGLE_VIDEO = 4; 53 | private static final int TOGGLE_SOUND = 5; 54 | private static final int GET_STATS = 6; 55 | private static final int DISABLE_OPENSL_ES = 7; 56 | private static final int TOGGLE_SOUND_SETUP = 8; 57 | private static final int TOGGLE_REMOTE_SOUND = 9; 58 | private static final int RELEASE_RESOURCE = 10; 59 | private static final int TOGGLE_BLUETOOTH_HEADSET = 11; 60 | private static final int SEND_STRING = 12; 61 | private static final int PUBLISH_VIDEO = 13; 62 | private static final int PUBLISH_AUDIO = 14; 63 | private static final int SET_REMOTE_AUDIO_PLAYBACK = 15; 64 | 65 | @Override 66 | public String getName() { 67 | return REACT_CLASS; 68 | } 69 | 70 | @Override 71 | protected CustomTwilioVideoView createViewInstance(ThemedReactContext reactContext) { 72 | return new CustomTwilioVideoView(reactContext); 73 | } 74 | 75 | @Override 76 | public void receiveCommand(CustomTwilioVideoView view, int commandId, @Nullable ReadableArray args) { 77 | switch (commandId) { 78 | case CONNECT_TO_ROOM: 79 | String roomName = args.getString(0); 80 | String accessToken = args.getString(1); 81 | boolean enableAudio = args.getBoolean(2); 82 | boolean enableVideo = args.getBoolean(3); 83 | boolean enableRemoteAudio = args.getBoolean(4); 84 | boolean enableNetworkQualityReporting = args.getBoolean(5); 85 | boolean dominantSpeakerEnabled = args.getBoolean(6); 86 | boolean maintainVideoTrackInBackground = args.getBoolean(7); 87 | String cameraType = args.getString(8); 88 | ReadableMap encodingParameters = args.getMap(9); 89 | boolean enableH264Codec = encodingParameters.hasKey("enableH264Codec") ? encodingParameters.getBoolean("enableH264Codec") : false; 90 | view.connectToRoomWrapper( 91 | roomName, 92 | accessToken, 93 | enableAudio, 94 | enableVideo, 95 | enableRemoteAudio, 96 | enableNetworkQualityReporting, 97 | dominantSpeakerEnabled, 98 | maintainVideoTrackInBackground, 99 | cameraType, 100 | enableH264Codec 101 | ); 102 | break; 103 | case DISCONNECT: 104 | view.disconnect(); 105 | break; 106 | case SWITCH_CAMERA: 107 | view.switchCamera(); 108 | break; 109 | case TOGGLE_VIDEO: 110 | Boolean videoEnabled = args.getBoolean(0); 111 | view.toggleVideo(videoEnabled); 112 | break; 113 | case TOGGLE_SOUND: 114 | Boolean audioEnabled = args.getBoolean(0); 115 | view.toggleAudio(audioEnabled); 116 | break; 117 | case GET_STATS: 118 | view.getStats(); 119 | break; 120 | case DISABLE_OPENSL_ES: 121 | view.disableOpenSLES(); 122 | break; 123 | case TOGGLE_SOUND_SETUP: 124 | Boolean speaker = args.getBoolean(0); 125 | view.toggleSoundSetup(speaker); 126 | break; 127 | case TOGGLE_REMOTE_SOUND: 128 | Boolean remoteAudioEnabled = args.getBoolean(0); 129 | view.toggleRemoteAudio(remoteAudioEnabled); 130 | break; 131 | case RELEASE_RESOURCE: 132 | view.releaseResource(); 133 | break; 134 | case TOGGLE_BLUETOOTH_HEADSET: 135 | Boolean headsetEnabled = args.getBoolean(0); 136 | view.toggleBluetoothHeadset(headsetEnabled); 137 | break; 138 | case SEND_STRING: 139 | view.sendString(args.getString(0)); 140 | break; 141 | case PUBLISH_VIDEO: 142 | view.publishLocalVideo(args.getBoolean(0)); 143 | break; 144 | case PUBLISH_AUDIO: 145 | view.publishLocalAudio(args.getBoolean(0)); 146 | break; 147 | case SET_REMOTE_AUDIO_PLAYBACK: 148 | String participantSid = args.getString(0); 149 | Boolean enabled = args.getBoolean(1); 150 | view.setRemoteAudioPlayback(participantSid, enabled); 151 | break; 152 | } 153 | } 154 | 155 | @Override 156 | @Nullable 157 | public Map getExportedCustomDirectEventTypeConstants() { 158 | Map> map = MapBuilder.of( 159 | ON_CAMERA_SWITCHED, MapBuilder.of("registrationName", ON_CAMERA_SWITCHED), 160 | ON_VIDEO_CHANGED, MapBuilder.of("registrationName", ON_VIDEO_CHANGED), 161 | ON_AUDIO_CHANGED, MapBuilder.of("registrationName", ON_AUDIO_CHANGED), 162 | ON_CONNECTED, MapBuilder.of("registrationName", ON_CONNECTED), 163 | ON_CONNECT_FAILURE, MapBuilder.of("registrationName", ON_CONNECT_FAILURE), 164 | ON_DISCONNECTED, MapBuilder.of("registrationName", ON_DISCONNECTED), 165 | ON_PARTICIPANT_CONNECTED, MapBuilder.of("registrationName", ON_PARTICIPANT_CONNECTED) 166 | ); 167 | 168 | map.putAll(MapBuilder.of( 169 | ON_PARTICIPANT_DISCONNECTED, MapBuilder.of("registrationName", ON_PARTICIPANT_DISCONNECTED), 170 | ON_DATATRACK_MESSAGE_RECEIVED, MapBuilder.of("registrationName", ON_DATATRACK_MESSAGE_RECEIVED), 171 | ON_PARTICIPANT_ADDED_DATA_TRACK, MapBuilder.of("registrationName", ON_PARTICIPANT_ADDED_DATA_TRACK), 172 | ON_PARTICIPANT_ADDED_VIDEO_TRACK, MapBuilder.of("registrationName", ON_PARTICIPANT_ADDED_VIDEO_TRACK), 173 | ON_PARTICIPANT_REMOVED_VIDEO_TRACK, MapBuilder.of("registrationName", ON_PARTICIPANT_REMOVED_VIDEO_TRACK), 174 | ON_PARTICIPANT_ADDED_AUDIO_TRACK, MapBuilder.of("registrationName", ON_PARTICIPANT_ADDED_AUDIO_TRACK), 175 | ON_PARTICIPANT_REMOVED_AUDIO_TRACK, MapBuilder.of("registrationName", ON_PARTICIPANT_REMOVED_AUDIO_TRACK) 176 | )); 177 | 178 | map.putAll(MapBuilder.of( 179 | ON_PARTICIPANT_REMOVED_DATA_TRACK, MapBuilder.of("registrationName", ON_PARTICIPANT_REMOVED_DATA_TRACK), 180 | ON_LOCAL_PARTICIPANT_SUPPORTED_CODECS, MapBuilder.of("registrationName", ON_LOCAL_PARTICIPANT_SUPPORTED_CODECS) 181 | )); 182 | 183 | map.putAll(MapBuilder.of( 184 | ON_PARTICIPANT_ENABLED_VIDEO_TRACK, MapBuilder.of("registrationName", ON_PARTICIPANT_ENABLED_VIDEO_TRACK), 185 | ON_PARTICIPANT_DISABLED_VIDEO_TRACK, MapBuilder.of("registrationName", ON_PARTICIPANT_DISABLED_VIDEO_TRACK), 186 | ON_PARTICIPANT_ENABLED_AUDIO_TRACK, MapBuilder.of("registrationName", ON_PARTICIPANT_ENABLED_AUDIO_TRACK), 187 | ON_PARTICIPANT_DISABLED_AUDIO_TRACK, MapBuilder.of("registrationName", ON_PARTICIPANT_DISABLED_AUDIO_TRACK), 188 | ON_STATS_RECEIVED, MapBuilder.of("registrationName", ON_STATS_RECEIVED), 189 | ON_NETWORK_QUALITY_LEVELS_CHANGED, MapBuilder.of("registrationName", ON_NETWORK_QUALITY_LEVELS_CHANGED), 190 | ON_DOMINANT_SPEAKER_CHANGED, MapBuilder.of("registrationName", ON_DOMINANT_SPEAKER_CHANGED) 191 | )); 192 | 193 | return map; 194 | } 195 | 196 | @Override 197 | @Nullable 198 | public Map getCommandsMap() { 199 | return MapBuilder.builder() 200 | .put("connectToRoom", CONNECT_TO_ROOM) 201 | .put("disconnect", DISCONNECT) 202 | .put("switchCamera", SWITCH_CAMERA) 203 | .put("toggleVideo", TOGGLE_VIDEO) 204 | .put("toggleSound", TOGGLE_SOUND) 205 | .put("getStats", GET_STATS) 206 | .put("disableOpenSLES", DISABLE_OPENSL_ES) 207 | .put("toggleRemoteSound", TOGGLE_REMOTE_SOUND) 208 | .put("toggleBluetoothHeadset", TOGGLE_BLUETOOTH_HEADSET) 209 | .put("sendString", SEND_STRING) 210 | .build(); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /android/src/main/java/com/twiliorn/library/PatchedVideoView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for patching black screen bug coming from Twilio VideoView 3 | * Authors: 4 | * Aaron Alaniz (@aaalaniz) 5 | */ 6 | 7 | package com.twiliorn.library; 8 | 9 | import android.content.Context; 10 | import android.os.Handler; 11 | import android.os.Looper; 12 | import android.util.AttributeSet; 13 | 14 | import com.twilio.video.VideoView; 15 | 16 | import tvi.webrtc.VideoFrame; 17 | 18 | /* 19 | * VideoView that notifies Listener of the first frame rendered and the first frame after a reset 20 | * request. 21 | */ 22 | public class PatchedVideoView extends VideoView { 23 | 24 | private boolean notifyFrameRendered = false; 25 | private Listener listener; 26 | private final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); 27 | 28 | public PatchedVideoView(Context context) { 29 | super(context); 30 | } 31 | 32 | public PatchedVideoView(Context context, AttributeSet attrs) { 33 | super(context, attrs); 34 | } 35 | 36 | @Override 37 | public void onFrame(VideoFrame frame) { 38 | if (notifyFrameRendered) { 39 | notifyFrameRendered = false; 40 | mainThreadHandler.post(new Runnable() { 41 | @Override 42 | public void run() { 43 | listener.onFirstFrame(); 44 | } 45 | }); 46 | } 47 | super.onFrame(frame); 48 | } 49 | 50 | /* 51 | * Set your listener 52 | */ 53 | public void setListener(Listener listener) { 54 | this.listener = listener; 55 | } 56 | 57 | /* 58 | * Reset the listener so next frame rendered results in callback 59 | */ 60 | public void resetListener() { 61 | notifyFrameRendered = true; 62 | } 63 | 64 | public interface Listener { 65 | void onFirstFrame(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /android/src/main/java/com/twiliorn/library/RNVideoViewGroup.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper component for the Twilio Video View to facilitate easier layout. 3 | *

4 | * Author: 5 | * Jonathan Chang 6 | */ 7 | package com.twiliorn.library; 8 | 9 | import android.content.Context; 10 | import android.graphics.Point; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import androidx.annotation.StringDef; 14 | 15 | import com.facebook.react.bridge.WritableMap; 16 | import com.facebook.react.bridge.WritableNativeMap; 17 | import com.facebook.react.uimanager.ThemedReactContext; 18 | import com.facebook.react.uimanager.events.RCTEventEmitter; 19 | import com.twilio.video.VideoScaleType; 20 | 21 | import tvi.webrtc.RendererCommon; 22 | import tvi.webrtc.VideoFrame; 23 | 24 | import java.lang.annotation.Retention; 25 | import java.lang.annotation.RetentionPolicy; 26 | 27 | import static com.twiliorn.library.RNVideoViewGroup.Events.ON_FRAME_DIMENSIONS_CHANGED; 28 | 29 | public class RNVideoViewGroup extends ViewGroup { 30 | private PatchedVideoView surfaceViewRenderer = null; 31 | private int videoWidth = 0; 32 | private int videoHeight = 0; 33 | private final Object layoutSync = new Object(); 34 | private RendererCommon.ScalingType scalingType = RendererCommon.ScalingType.SCALE_ASPECT_FILL; 35 | private final RCTEventEmitter eventEmitter; 36 | 37 | @Retention(RetentionPolicy.SOURCE) 38 | @StringDef({ON_FRAME_DIMENSIONS_CHANGED}) 39 | public @interface Events { 40 | String ON_FRAME_DIMENSIONS_CHANGED = "onFrameDimensionsChanged"; 41 | } 42 | 43 | void pushEvent(View view, String name, WritableMap data) { 44 | eventEmitter.receiveEvent(view.getId(), name, data); 45 | } 46 | 47 | public RNVideoViewGroup(ThemedReactContext themedReactContext) { 48 | super(themedReactContext); 49 | this.eventEmitter = themedReactContext.getJSModule(RCTEventEmitter.class); 50 | surfaceViewRenderer = new PatchedVideoView(themedReactContext); 51 | surfaceViewRenderer.setVideoScaleType(VideoScaleType.ASPECT_FILL); 52 | addView(surfaceViewRenderer); 53 | surfaceViewRenderer.setListener( 54 | new RendererCommon.RendererEvents() { 55 | @Override 56 | public void onFirstFrameRendered() { 57 | 58 | } 59 | 60 | @Override 61 | public void onFrameResolutionChanged(int vw, int vh, int rotation) { 62 | synchronized (layoutSync) { 63 | if (rotation == 90 || rotation == 270) { 64 | videoHeight = vw; 65 | videoWidth = vh; 66 | } else { 67 | videoHeight = vh; 68 | videoWidth = vw; 69 | } 70 | RNVideoViewGroup.this.forceLayout(); 71 | 72 | WritableMap event = new WritableNativeMap(); 73 | event.putInt("height", vh); 74 | event.putInt("width", vw); 75 | event.putInt("rotation", rotation); 76 | pushEvent(RNVideoViewGroup.this, ON_FRAME_DIMENSIONS_CHANGED, event); 77 | } 78 | } 79 | } 80 | ); 81 | } 82 | 83 | public PatchedVideoView getSurfaceViewRenderer() { 84 | return surfaceViewRenderer; 85 | } 86 | 87 | public void setScalingType(RendererCommon.ScalingType scalingType) { 88 | this.scalingType = scalingType; 89 | } 90 | 91 | @Override 92 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 93 | int height = b - t; 94 | int width = r - l; 95 | if (height == 0 || width == 0) { 96 | l = t = r = b = 0; 97 | } else { 98 | int videoHeight; 99 | int videoWidth; 100 | synchronized (layoutSync) { 101 | videoHeight = this.videoHeight; 102 | videoWidth = this.videoWidth; 103 | } 104 | 105 | if (videoHeight == 0 || videoWidth == 0) { 106 | // These are Twilio defaults. 107 | videoHeight = 480; 108 | videoWidth = 640; 109 | } 110 | 111 | Point displaySize = RendererCommon.getDisplaySize( 112 | this.scalingType, 113 | videoWidth / (float) videoHeight, 114 | width, 115 | height 116 | ); 117 | 118 | l = (width - displaySize.x) / 2; 119 | t = (height - displaySize.y) / 2; 120 | r = l + displaySize.x; 121 | b = t + displaySize.y; 122 | } 123 | surfaceViewRenderer.layout(l, t, r, b); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /android/src/main/java/com/twiliorn/library/TwilioPackage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Twilio Video for React Native. 3 | *

4 | * Authors: 5 | * Ralph Pina 6 | * Jonathan Chang 7 | */ 8 | 9 | package com.twiliorn.library; 10 | 11 | import com.facebook.react.ReactPackage; 12 | import com.facebook.react.bridge.JavaScriptModule; 13 | import com.facebook.react.bridge.NativeModule; 14 | import com.facebook.react.bridge.ReactApplicationContext; 15 | import com.facebook.react.uimanager.ViewManager; 16 | 17 | import java.util.Arrays; 18 | import java.util.Collections; 19 | import java.util.List; 20 | 21 | public class TwilioPackage implements ReactPackage { 22 | @Override 23 | public List createNativeModules(ReactApplicationContext reactContext) { 24 | return Collections.emptyList(); 25 | } 26 | 27 | // Deprecated by RN 0.47 28 | public List> createJSModules() { 29 | return Collections.emptyList(); 30 | } 31 | 32 | @Override 33 | public List createViewManagers(ReactApplicationContext reactContext) { 34 | return Arrays.asList( 35 | new CustomTwilioVideoViewManager(), 36 | new TwilioRemotePreviewManager(), 37 | new TwilioVideoPreviewManager() 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android/src/main/java/com/twiliorn/library/TwilioRemotePreview.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for Twilio Video participant views. 3 | *

4 | * Authors: 5 | * Jonathan Chang 6 | */ 7 | 8 | package com.twiliorn.library; 9 | 10 | import android.util.Log; 11 | 12 | import com.facebook.react.uimanager.ThemedReactContext; 13 | 14 | 15 | public class TwilioRemotePreview extends RNVideoViewGroup { 16 | 17 | private static final String TAG = "TwilioRemotePreview"; 18 | 19 | 20 | public TwilioRemotePreview(ThemedReactContext context, String trackSid) { 21 | super(context); 22 | Log.i("CustomTwilioVideoView", "Remote Prview Construct"); 23 | Log.i("CustomTwilioVideoView", trackSid); 24 | 25 | CustomTwilioVideoView.registerPrimaryVideoView(this.getSurfaceViewRenderer(), trackSid); 26 | } 27 | 28 | public void applyZOrder(boolean applyZOrder) { 29 | this.getSurfaceViewRenderer().applyZOrder(applyZOrder); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/java/com/twiliorn/library/TwilioRemotePreviewManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for Twilio Video participant views. 3 | *

4 | * Authors: 5 | * Jonathan Chang 6 | */ 7 | 8 | package com.twiliorn.library; 9 | 10 | import androidx.annotation.Nullable; 11 | import android.util.Log; 12 | 13 | import com.facebook.react.common.MapBuilder; 14 | import com.facebook.react.uimanager.SimpleViewManager; 15 | import com.facebook.react.uimanager.ThemedReactContext; 16 | import com.facebook.react.uimanager.annotations.ReactProp; 17 | 18 | import tvi.webrtc.RendererCommon; 19 | 20 | import java.util.Map; 21 | 22 | import static com.twiliorn.library.RNVideoViewGroup.Events.ON_FRAME_DIMENSIONS_CHANGED; 23 | 24 | public class TwilioRemotePreviewManager extends SimpleViewManager { 25 | 26 | public static final String REACT_CLASS = "RNTwilioRemotePreview"; 27 | public String myTrackSid = ""; 28 | 29 | @Override 30 | public String getName() { 31 | return REACT_CLASS; 32 | } 33 | 34 | @ReactProp(name = "scaleType") 35 | public void setScaleType(TwilioRemotePreview view, @Nullable String scaleType) { 36 | 37 | if (scaleType.equals("fit")) { 38 | view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT); 39 | } else { 40 | view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL); 41 | } 42 | } 43 | 44 | @ReactProp(name = "trackSid") 45 | public void setTrackId(TwilioRemotePreview view, @Nullable String trackSid) { 46 | 47 | Log.i("CustomTwilioVideoView", "Initialize Twilio REMOTE"); 48 | Log.i("CustomTwilioVideoView", trackSid); 49 | myTrackSid = trackSid; 50 | CustomTwilioVideoView.registerPrimaryVideoView(view.getSurfaceViewRenderer(), trackSid); 51 | } 52 | 53 | @ReactProp(name = "applyZOrder", defaultBoolean = false) 54 | public void setApplyZOrder(TwilioRemotePreview view, boolean applyZOrder) { 55 | view.applyZOrder(applyZOrder); 56 | } 57 | 58 | @Override 59 | protected TwilioRemotePreview createViewInstance(ThemedReactContext reactContext) { 60 | return new TwilioRemotePreview(reactContext, myTrackSid); 61 | } 62 | 63 | @Override 64 | public Map getExportedCustomBubblingEventTypeConstants() { 65 | return MapBuilder.builder() 66 | .put( 67 | ON_FRAME_DIMENSIONS_CHANGED, 68 | MapBuilder.of( 69 | "phasedRegistrationNames", 70 | MapBuilder.of("bubbled", ON_FRAME_DIMENSIONS_CHANGED))) 71 | .build(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /android/src/main/java/com/twiliorn/library/TwilioVideoPreview.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for Twilio Video local views. 3 | *

4 | * Authors: 5 | * Jonathan Chang 6 | */ 7 | 8 | package com.twiliorn.library; 9 | import com.facebook.react.bridge.WritableMap; 10 | import com.facebook.react.uimanager.ThemedReactContext; 11 | 12 | public class TwilioVideoPreview extends RNVideoViewGroup { 13 | 14 | private static final String TAG = "TwilioVideoPreview"; 15 | 16 | public TwilioVideoPreview(ThemedReactContext themedReactContext) { 17 | super(themedReactContext); 18 | CustomTwilioVideoView.registerThumbnailVideoView(this.getSurfaceViewRenderer()); 19 | } 20 | 21 | public void applyZOrder(boolean applyZOrder) { 22 | this.getSurfaceViewRenderer().applyZOrder(applyZOrder); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/src/main/java/com/twiliorn/library/TwilioVideoPreviewManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for Twilio Video local views. 3 | *

4 | * Authors: 5 | * Jonathan Chang 6 | */ 7 | 8 | package com.twiliorn.library; 9 | 10 | import androidx.annotation.Nullable; 11 | 12 | import com.facebook.react.bridge.ReadableArray; 13 | import com.facebook.react.common.MapBuilder; 14 | import com.facebook.react.uimanager.SimpleViewManager; 15 | import com.facebook.react.uimanager.ThemedReactContext; 16 | import com.facebook.react.uimanager.annotations.ReactProp; 17 | 18 | import java.util.Map; 19 | 20 | import tvi.webrtc.RendererCommon; 21 | 22 | import static com.twiliorn.library.RNVideoViewGroup.Events.ON_FRAME_DIMENSIONS_CHANGED; 23 | 24 | public class TwilioVideoPreviewManager extends SimpleViewManager { 25 | 26 | public static final String REACT_CLASS = "RNTwilioVideoPreview"; 27 | 28 | @Override 29 | public String getName() { 30 | return REACT_CLASS; 31 | } 32 | 33 | @ReactProp(name = "scaleType") 34 | public void setScaleType(TwilioVideoPreview view, @Nullable String scaleType) { 35 | if (scaleType.equals("fit")) { 36 | view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT); 37 | } else { 38 | view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL); 39 | } 40 | } 41 | 42 | @ReactProp(name = "applyZOrder", defaultBoolean = true) 43 | public void setApplyZOrder(TwilioVideoPreview view, boolean applyZOrder) { 44 | view.applyZOrder(applyZOrder); 45 | } 46 | 47 | @Override 48 | @Nullable 49 | public Map getExportedCustomDirectEventTypeConstants() { 50 | Map> map = MapBuilder.of( 51 | ON_FRAME_DIMENSIONS_CHANGED, MapBuilder.of("registrationName", ON_FRAME_DIMENSIONS_CHANGED) 52 | ); 53 | 54 | return map; 55 | } 56 | 57 | @Override 58 | protected TwilioVideoPreview createViewInstance(ThemedReactContext reactContext) { 59 | return new TwilioVideoPreview(reactContext); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app.plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./plugin/build"); 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Components 2 | ---------- 3 | 4 | 5 | **src/TwilioVideo.android.js** 6 | 7 | 8 | ### 1. CustomTwilioVideoView 9 | 10 | 11 | 12 | 13 | Property | Type | Required | Default value | Description 14 | :--- | :--- | :--- | :--- | :--- 15 | onCameraSwitched | func | no | | Callback that is called when camera source changes 16 | onVideoChanged | func | no | | Callback that is called when video is toggled. 17 | onAudioChanged | func | no | | Callback that is called when a audio is toggled. 18 | onRoomDidConnect | func | no | | Called when the room has connected @param {{roomName, participants, localParticipant}} 19 | onRoomDidFailToConnect | func | no | | Callback that is called when connecting to room fails. 20 | onRoomDidDisconnect | func | no | | Callback that is called when user is disconnected from room. 21 | onParticipantAddedDataTrack | func | no | | Called when a new data track has been added @param {{participant, track}} 22 | onParticipantRemovedDataTrack | func | no | | Called when a data track has been removed @param {{participant, track}} 23 | onDataTrackMessageReceived | func | no | | Called when an dataTrack receives a message @param {{message, trackSid}} 24 | onParticipantAddedVideoTrack | func | no | | Called when a new video track has been added @param {{participant, track, enabled}} 25 | onParticipantRemovedVideoTrack | func | no | | Called when a video track has been removed @param {{participant, track}} 26 | onParticipantAddedAudioTrack | func | no | | Called when a new audio track has been added @param {{participant, track}} 27 | onParticipantRemovedAudioTrack | func | no | | Called when a audio track has been removed @param {{participant, track}} 28 | onRoomParticipantDidConnect | func | no | | Callback called a participant enters a room. 29 | onRoomParticipantDidDisconnect | func | no | | Callback that is called when a participant exits a room. 30 | onParticipantEnabledVideoTrack | func | no | | Called when a video track has been enabled. @param {{participant, track}} 31 | onParticipantDisabledVideoTrack | func | no | | Called when a video track has been disabled. @param {{participant, track}} 32 | onParticipantEnabledAudioTrack | func | no | | Called when an audio track has been enabled. @param {{participant, track}} 33 | onParticipantDisabledAudioTrack | func | no | | Called when an audio track has been disabled. @param {{participant, track}} 34 | onStatsReceived | func | no | | Callback that is called when stats are received (after calling getStats) 35 | onNetworkQualityLevelsChanged | func | no | | Callback that is called when network quality levels are changed (only if enableNetworkQualityReporting in connect is set to true) 36 | onDominantSpeakerDidChange | func | no | | Called when dominant speaker changes @param {{ participant, room }} dominant participant and room 37 | ----- 38 | 39 | **src/TwilioVideo.ios.js** 40 | 41 | 42 | ### 1. TwilioVideo 43 | 44 | 45 | 46 | 47 | Property | Type | Required | Default value | Description 48 | :--- | :--- | :--- | :--- | :--- 49 | screenShare | bool | no | | Flag that enables screen sharing RCTRootView instead of camera capture 50 | onRoomDidConnect | func | no | | Called when the room has connected @param {{roomName, participants, localParticipant}} 51 | onRoomDidDisconnect | func | no | | Called when the room has disconnected @param {{roomName, error}} 52 | onRoomDidFailToConnect | func | no | | Called when connection with room failed @param {{roomName, error}} 53 | onRoomParticipantDidConnect | func | no | | Called when a new participant has connected @param {{roomName, participant}} 54 | onRoomParticipantDidDisconnect | func | no | | Called when a participant has disconnected @param {{roomName, participant}} 55 | onParticipantAddedVideoTrack | func | no | | Called when a new video track has been added @param {{participant, track, enabled}} 56 | onParticipantRemovedVideoTrack | func | no | | Called when a video track has been removed @param {{participant, track}} 57 | onParticipantAddedDataTrack | func | no | | Called when a new data track has been added @param {{participant, track}} 58 | onParticipantRemovedDataTrack | func | no | | Called when a data track has been removed @param {{participant, track}} 59 | onParticipantAddedAudioTrack | func | no | | Called when a new audio track has been added @param {{participant, track}} 60 | onParticipantRemovedAudioTrack | func | no | | Called when a audio track has been removed @param {{participant, track}} 61 | onParticipantEnabledVideoTrack | func | no | | Called when a video track has been enabled. @param {{participant, track}} 62 | onParticipantDisabledVideoTrack | func | no | | Called when a video track has been disabled. @param {{participant, track}} 63 | onParticipantEnabledAudioTrack | func | no | | Called when an audio track has been enabled. @param {{participant, track}} 64 | onParticipantDisabledAudioTrack | func | no | | Called when an audio track has been disabled. @param {{participant, track}} 65 | onDataTrackMessageReceived | func | no | | Called when an dataTrack receives a message @param {{message, trackSid}} 66 | onCameraDidStart | func | no | | Called when the camera has started 67 | onCameraWasInterrupted | func | no | | Called when the camera has been interrupted 68 | onCameraInterruptionEnded | func | no | | Called when the camera interruption has ended 69 | onCameraDidStopRunning | func | no | | Called when the camera has stopped runing with an error @param {{error}} The error message description 70 | onStatsReceived | func | no | | Called when stats are received (after calling getStats) 71 | onNetworkQualityLevelsChanged | func | no | | Called when the network quality levels of a participant have changed (only if enableNetworkQualityReporting is set to True when connecting) 72 | onDominantSpeakerDidChange | func | no | | Called when dominant speaker changes @param {{ participant, room }} dominant participant 73 | onLocalParticipantSupportedCodecs | func | no | | Always called on android with @param {{ supportedCodecs }} after connecting to the room 74 | 75 | ----- 76 | 77 | **src/TwilioVideoLocalView.android.js** 78 | 79 | 80 | ### 1. TwilioVideoPreview 81 | 82 | 83 | 84 | 85 | Property | Type | Required | Default value | Description 86 | :--- | :--- | :--- | :--- | :--- 87 | scaleType | enum('fit','fill',) | no | | How the video stream should be scaled to fit its container. 88 | ----- 89 | 90 | **src/TwilioVideoLocalView.ios.js** 91 | 92 | 93 | ### 1. TwilioVideoLocalView 94 | 95 | 96 | 97 | 98 | Property | Type | Required | Default value | Description 99 | :--- | :--- | :--- | :--- | :--- 100 | enabled | bool | YES | | Indicate if video feed is enabled. 101 | scaleType | enum('fit','fill',) | no | | How the video stream should be scaled to fit its container. 102 | ----- 103 | 104 | **src/TwilioVideoParticipantView.android.js** 105 | 106 | 107 | ### 1. TwilioRemotePreview 108 | 109 | 110 | 111 | 112 | Property | Type | Required | Default value | Description 113 | :--- | :--- | :--- | :--- | :--- 114 | trackIdentifier | shape(,) | no | |   115 | onFrameDimensionsChanged | func | no | |   116 | trackSid | string | no | |   117 | renderToHardwareTextureAndroid | string | no | |   118 | onLayout | string | no | |   119 | accessibilityLiveRegion | string | no | |   120 | accessibilityComponentType | string | no | |   121 | importantForAccessibility | string | no | |   122 | accessibilityLabel | string | no | |   123 | nativeID | string | no | |   124 | testID | string | no | |   125 | ----- 126 | 127 | **src/TwilioVideoParticipantView.ios.js** 128 | 129 | 130 | ### 1. TwilioVideoParticipantView 131 | 132 | 133 | 134 | 135 | Property | Type | Required | Default value | Description 136 | :--- | :--- | :--- | :--- | :--- 137 | trackIdentifier | shape(,,) | no | |   138 | ----- 139 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Required modules 4 | var gulp = require('gulp'), 5 | path = require('path'), 6 | _ = require('lodash'), 7 | doctoc = require('doctoc/lib/transform'), 8 | del = require('del'), 9 | $ = require('gulp-load-plugins')(), 10 | log = require('fancy-log'), 11 | reactDocsPlugin = require('gulp-react-docs'), 12 | child_process = require('child_process'); 13 | 14 | // Helper vars 15 | var docsDest = 'docs'; 16 | 17 | // Tasks 18 | gulp.task('react-docs', function() { 19 | var mdTitle = '# React Component Reference'; 20 | 21 | return gulp.src('./src/**/*.js') 22 | .pipe(reactDocsPlugin({ 23 | path: docsDest 24 | })) 25 | .pipe($.concat('README.md')) 26 | .pipe($.tap(function(file) { 27 | // Generate table of contents for components.md 28 | var mdWithToc = doctoc(file.contents.toString(), null, 2, mdTitle).data; 29 | file.contents = new Buffer(mdWithToc); 30 | })) 31 | .pipe(gulp.dest(docsDest)); 32 | }); 33 | 34 | gulp.task('default', gulp.series('react-docs')); 35 | 36 | gulp.task('clean', function(cb) { del(docsDest, cb) }); 37 | 38 | gulp.task('check:docs', gulp.series('react-docs', function(cb) { 39 | child_process.exec('git diff --name-only docs/', function(err, diffFiles) { 40 | if (diffFiles.indexOf('.md') > -1) { 41 | log('Automatically generated documentation is not up to \ 42 | date with the changes in the codebase. Please run `gulp` and commit the changes.'); 43 | cb(new Error('Docs not up to date!')); 44 | } else { 45 | log('Automatically generated documentation is up to date!'); 46 | } 47 | cb(); 48 | }); 49 | })); 50 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "react-native-twilio-video-webrtc" { 2 | import { ViewProps } from "react-native"; 3 | import React from "react"; 4 | 5 | export interface TrackIdentifier { 6 | participantSid: string; 7 | videoTrackSid: string; 8 | } 9 | 10 | type scaleType = "fit" | "fill"; 11 | type cameraType = "front" | "back"; 12 | 13 | interface TwilioVideoParticipantViewProps extends ViewProps { 14 | trackIdentifier: TrackIdentifier; 15 | ref?: React.Ref; 16 | scaleType?: scaleType; 17 | /** 18 | * Whether to apply Z ordering to this view. Setting this to true will cause 19 | * this view to appear above other Twilio Video views. 20 | */ 21 | applyZOrder?: boolean | undefined; 22 | } 23 | 24 | interface TwilioVideoLocalViewProps extends ViewProps { 25 | enabled: boolean; 26 | ref?: React.Ref; 27 | scaleType?: scaleType; 28 | /** 29 | * Whether to apply Z ordering to this view. Setting this to true will cause 30 | * this view to appear above other Twilio Video views. 31 | */ 32 | applyZOrder?: boolean | undefined; 33 | } 34 | 35 | interface Participant { 36 | sid: string; 37 | identity: string; 38 | } 39 | 40 | interface Track { 41 | enabled: boolean; 42 | trackName: string; 43 | trackSid: string; 44 | } 45 | 46 | export interface TrackEventCbArgs { 47 | participant: Participant; 48 | track: Track; 49 | } 50 | 51 | export type TrackEventCb = (t: TrackEventCbArgs) => void; 52 | 53 | export interface DataTrackEventCbArgs { 54 | message: string; 55 | trackSid: string; 56 | } 57 | 58 | export type DataTrackEventCb = (t: DataTrackEventCbArgs) => void; 59 | 60 | interface RoomEventCommonArgs { 61 | roomName: string; 62 | roomSid: string; 63 | } 64 | 65 | export type RoomErrorEventArgs = RoomEventCommonArgs & { 66 | error: any; 67 | }; 68 | 69 | type RoomEventArgs = RoomEventCommonArgs & { 70 | participants: Participant[]; 71 | localParticipant: Participant; 72 | }; 73 | 74 | type ParticipantEventArgs = RoomEventCommonArgs & { 75 | participant: Participant; 76 | }; 77 | 78 | type NetworkLevelChangeEventArgs = { 79 | participant: Participant; 80 | isLocalUser: boolean; 81 | quality: number; 82 | }; 83 | 84 | export type RoomEventCb = (p: RoomEventArgs) => void; 85 | export type RoomErrorEventCb = (t: RoomErrorEventArgs) => void; 86 | 87 | export type ParticipantEventCb = (p: ParticipantEventArgs) => void; 88 | 89 | export type NetworkLevelChangeEventCb = (p: NetworkLevelChangeEventArgs) => void; 90 | 91 | export type DominantSpeakerChangedEventArgs = RoomEventCommonArgs & { 92 | participant: Participant; 93 | } 94 | 95 | export type DominantSpeakerChangedCb = (d: DominantSpeakerChangedEventArgs) => void; 96 | 97 | export type LocalParticipantSupportedCodecsCbEventArgs = { 98 | supportedCodecs: Array; 99 | } 100 | 101 | export type LocalParticipantSupportedCodecsCb = (d: LocalParticipantSupportedCodecsCbEventArgs) => void; 102 | 103 | export type TwilioVideoProps = ViewProps & { 104 | onCameraDidStart?: () => void; 105 | onCameraDidStopRunning?: (err: any) => void; 106 | onCameraWasInterrupted?: () => void; 107 | onDominantSpeakerDidChange?: DominantSpeakerChangedCb; 108 | onParticipantAddedAudioTrack?: TrackEventCb; 109 | onParticipantAddedVideoTrack?: TrackEventCb; 110 | onParticipantDisabledVideoTrack?: TrackEventCb; 111 | onParticipantDisabledAudioTrack?: TrackEventCb; 112 | onParticipantEnabledVideoTrack?: TrackEventCb; 113 | onParticipantEnabledAudioTrack?: TrackEventCb; 114 | onParticipantRemovedAudioTrack?: TrackEventCb; 115 | onParticipantRemovedVideoTrack?: TrackEventCb; 116 | onParticipantAddedDataTrack?: TrackEventCb; 117 | onParticipantRemovedDataTrack?: TrackEventCb; 118 | onRoomDidConnect?: RoomEventCb; 119 | onRoomDidDisconnect?: RoomErrorEventCb; 120 | onRoomDidFailToConnect?: RoomErrorEventCb; 121 | onRoomParticipantDidConnect?: ParticipantEventCb; 122 | onRoomParticipantDidDisconnect?: ParticipantEventCb; 123 | onNetworkQualityLevelsChanged?: NetworkLevelChangeEventCb; 124 | onLocalParticipantSupportedCodecs?: LocalParticipantSupportedCodecsCb; 125 | 126 | onStatsReceived?: (data: any) => void; 127 | onDataTrackMessageReceived?: DataTrackEventCb; 128 | // iOS only 129 | autoInitializeCamera?: boolean; 130 | ref?: React.Ref; 131 | }; 132 | 133 | type iOSConnectParams = { 134 | roomName?: string; 135 | accessToken: string; 136 | cameraType?: cameraType; 137 | dominantSpeakerEnabled?: boolean; 138 | enableAudio?: boolean; 139 | enableVideo?: boolean; 140 | encodingParameters?: { 141 | enableH264Codec?: boolean; 142 | // if audioBitrate OR videoBitrate is provided, you must provide both 143 | audioBitrate?: number; 144 | videoBitrate?: number; 145 | }; 146 | enableNetworkQualityReporting?: boolean; 147 | }; 148 | 149 | type androidConnectParams = { 150 | roomName?: string; 151 | accessToken: string; 152 | cameraType?: cameraType; 153 | dominantSpeakerEnabled?: boolean; 154 | enableAudio?: boolean; 155 | enableVideo?: boolean; 156 | enableRemoteAudio?: boolean; 157 | encodingParameters?: { 158 | enableH264Codec?: boolean; 159 | }; 160 | enableNetworkQualityReporting?: boolean; 161 | maintainVideoTrackInBackground?: boolean; 162 | }; 163 | 164 | class TwilioVideo extends React.Component { 165 | setLocalVideoEnabled: (enabled: boolean) => Promise; 166 | setLocalAudioEnabled: (enabled: boolean) => Promise; 167 | setRemoteAudioEnabled: (enabled: boolean) => Promise; 168 | setBluetoothHeadsetConnected: (enabled: boolean) => Promise; 169 | connect: (options: iOSConnectParams | androidConnectParams) => void; 170 | disconnect: () => void; 171 | flipCamera: () => void; 172 | toggleSoundSetup: (speaker: boolean) => void; 173 | getStats: () => void; 174 | publishLocalAudio: () => void; 175 | unpublishLocalAudio: () => void; 176 | publishLocalVideo: () => void; 177 | unpublishLocalVideo: () => void; 178 | sendString: (message: string) => void; 179 | } 180 | 181 | class TwilioVideoLocalView extends React.Component< 182 | TwilioVideoLocalViewProps 183 | > {} 184 | 185 | class TwilioVideoParticipantView extends React.Component< 186 | TwilioVideoParticipantViewProps 187 | > {} 188 | 189 | export { TwilioVideoLocalView, TwilioVideoParticipantView, TwilioVideo }; 190 | } 191 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import TwilioVideoLocalView from './src/TwilioVideoLocalView' 2 | import TwilioVideoParticipantView from './src/TwilioVideoParticipantView' 3 | import TwilioVideo from './src/TwilioVideo' 4 | 5 | export { 6 | TwilioVideoLocalView, 7 | TwilioVideoParticipantView, 8 | TwilioVideo 9 | } 10 | -------------------------------------------------------------------------------- /ios/RCTTWLocalVideoViewManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTTWLocalVideoViewManager.h 3 | // Black 4 | // 5 | // Created by Martín Fernández on 6/13/17. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface RCTTWLocalVideoViewManager : RCTViewManager 13 | @end 14 | -------------------------------------------------------------------------------- /ios/RCTTWLocalVideoViewManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTTWLocalVideoViewManager.m 3 | // Black 4 | // 5 | // Created by Martín Fernández on 6/13/17. 6 | // 7 | // 8 | 9 | #import "RCTTWLocalVideoViewManager.h" 10 | 11 | #import "RCTTWVideoModule.h" 12 | 13 | @interface RCTTWLocalVideoViewManager() 14 | @end 15 | 16 | @implementation RCTTWLocalVideoViewManager 17 | 18 | RCT_EXPORT_MODULE() 19 | 20 | RCT_CUSTOM_VIEW_PROPERTY(scalesType, NSInteger, TVIVideoView) { 21 | view.subviews[0].contentMode = [RCTConvert NSInteger:json]; 22 | } 23 | 24 | - (UIView *)view { 25 | UIView *container = [[UIView alloc] init]; 26 | TVIVideoView *inner = [[TVIVideoView alloc] init]; 27 | inner.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth); 28 | [container addSubview:inner]; 29 | return container; 30 | } 31 | 32 | RCT_CUSTOM_VIEW_PROPERTY(enabled, BOOL, TVIVideoView) { 33 | if (json) { 34 | RCTTWVideoModule *videoModule = [self.bridge moduleForName:@"TWVideoModule"]; 35 | BOOL isEnabled = [RCTConvert BOOL:json]; 36 | 37 | if (isEnabled) { 38 | [videoModule addLocalView:view.subviews[0]]; 39 | } else { 40 | [videoModule removeLocalView:view.subviews[0]]; 41 | } 42 | } 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /ios/RCTTWRemoteVideoViewManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTTWRemoteVideoViewManager.h 3 | // Black 4 | // 5 | // Created by Martín Fernández on 6/13/17. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface RCTTWRemoteVideoViewManager : RCTViewManager 12 | @end 13 | -------------------------------------------------------------------------------- /ios/RCTTWRemoteVideoViewManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTTWRemoteVideoViewManager.m 3 | // Black 4 | // 5 | // Created by Martín Fernández on 6/13/17. 6 | // 7 | // 8 | 9 | #import "RCTTWRemoteVideoViewManager.h" 10 | 11 | #import 12 | #import "RCTTWVideoModule.h" 13 | 14 | @interface RCTTWVideoTrackIdentifier : NSObject 15 | 16 | @property (strong) NSString *participantSid; 17 | @property (strong) NSString *videoTrackSid; 18 | 19 | @end 20 | 21 | @implementation RCTTWVideoTrackIdentifier 22 | 23 | @end 24 | 25 | @interface RCTConvert(RCTTWVideoTrackIdentifier) 26 | 27 | + (RCTTWVideoTrackIdentifier *)RCTTWVideoTrackIdentifier:(id)json; 28 | 29 | @end 30 | 31 | @implementation RCTConvert(RCTTWVideoTrackIdentifier) 32 | 33 | + (RCTTWVideoTrackIdentifier *)RCTTWVideoTrackIdentifier:(id)json { 34 | RCTTWVideoTrackIdentifier *trackIdentifier = [[RCTTWVideoTrackIdentifier alloc] init]; 35 | trackIdentifier.participantSid = json[@"participantSid"]; 36 | trackIdentifier.videoTrackSid = json[@"videoTrackSid"]; 37 | 38 | return trackIdentifier; 39 | } 40 | 41 | @end 42 | 43 | @interface RCTTWRemoteVideoViewManager() 44 | @end 45 | 46 | @implementation RCTTWRemoteVideoViewManager 47 | 48 | RCT_EXPORT_MODULE() 49 | 50 | RCT_CUSTOM_VIEW_PROPERTY(scalesType, NSInteger, TVIVideoView) { 51 | view.subviews[0].contentMode = [RCTConvert NSInteger:json]; 52 | } 53 | 54 | - (UIView *)view { 55 | UIView *container = [[UIView alloc] init]; 56 | TVIVideoView *inner = [[TVIVideoView alloc] init]; 57 | inner.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth); 58 | [container addSubview:inner]; 59 | return container; 60 | } 61 | 62 | RCT_CUSTOM_VIEW_PROPERTY(trackIdentifier, RCTTWVideoTrackIdentifier, TVIVideoView) { 63 | if (json) { 64 | RCTTWVideoModule *videoModule = [self.bridge moduleForName:@"TWVideoModule"]; 65 | RCTTWVideoTrackIdentifier *id = [RCTConvert RCTTWVideoTrackIdentifier:json]; 66 | 67 | [videoModule addParticipantView:view.subviews[0] sid:id.participantSid trackSid:id.videoTrackSid]; 68 | } 69 | } 70 | 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /ios/RCTTWSerializable.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTTWSerialization.h 3 | // Black 4 | // 5 | // Created by Martín Fernández on 6/13/17. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @protocol RCTTWSerializable 13 | 14 | - (id)toJSON; 15 | 16 | @end 17 | 18 | @interface TVIParticipant(RCTTWSerializable) 19 | @end 20 | 21 | @interface TVITrackPublication(RCTTWSerializable) 22 | @end 23 | -------------------------------------------------------------------------------- /ios/RCTTWSerializable.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTTWSerialization.m 3 | // Black 4 | // 5 | // Created by Martín Fernández on 6/13/17. 6 | // 7 | // 8 | 9 | #import "RCTTWSerializable.h" 10 | 11 | @implementation TVIParticipant(RCTTWSerializable) 12 | 13 | - (id)toJSON { 14 | return @{ 15 | @"sid": self.sid, 16 | @"identity": self.identity 17 | }; 18 | } 19 | 20 | @end 21 | 22 | @implementation TVITrackPublication(RCTTWSerializable) 23 | 24 | - (id)toJSON { 25 | return @{ 26 | @"trackSid": self.trackSid, 27 | @"trackName": self.trackName, 28 | @"enabled": @(self.trackEnabled) 29 | }; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /ios/RCTTWVideoModule.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTTWVideoModule.h 3 | // Black 4 | // 5 | // Created by Martín Fernández on 6/13/17. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import 13 | 14 | @interface RCTTWVideoModule : RCTEventEmitter 15 | 16 | - (void)addLocalView:(TVIVideoView *)view; 17 | - (void)removeLocalView:(TVIVideoView *)view; 18 | - (void)addParticipantView:(TVIVideoView *)view sid:(NSString *)sid trackSid:(NSString *)trackSid; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ios/RNTwilioVideoWebRTC.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 73814A34203776CD00E8C67D /* RCTTWLocalVideoViewManager.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 738F9D69203760FF00058972 /* RCTTWLocalVideoViewManager.h */; }; 11 | 73814A35203776CD00E8C67D /* RCTTWRemoteVideoViewManager.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 738F9D6A203760FF00058972 /* RCTTWRemoteVideoViewManager.h */; }; 12 | 73814A36203776CD00E8C67D /* RCTTWSerializable.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 738F9D65203760FF00058972 /* RCTTWSerializable.h */; }; 13 | 73814A37203776CD00E8C67D /* RCTTWVideoModule.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 738F9D63203760FF00058972 /* RCTTWVideoModule.h */; }; 14 | 738F9D6B203760FF00058972 /* RCTTWSerializable.m in Sources */ = {isa = PBXBuildFile; fileRef = 738F9D64203760FF00058972 /* RCTTWSerializable.m */; }; 15 | 738F9D6C203760FF00058972 /* RCTTWRemoteVideoViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 738F9D66203760FF00058972 /* RCTTWRemoteVideoViewManager.m */; }; 16 | 738F9D6D203760FF00058972 /* RCTTWLocalVideoViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 738F9D67203760FF00058972 /* RCTTWLocalVideoViewManager.m */; }; 17 | 738F9D6E203760FF00058972 /* RCTTWVideoModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 738F9D68203760FF00058972 /* RCTTWVideoModule.m */; }; 18 | 73AC89CA20377EA700038475 /* TwilioVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73AC89C920377EA700038475 /* TwilioVideo.framework */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | 738F9D532037604000058972 /* CopyFiles */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 2147483647; 25 | dstPath = "include/$(PRODUCT_NAME)"; 26 | dstSubfolderSpec = 16; 27 | files = ( 28 | 73814A34203776CD00E8C67D /* RCTTWLocalVideoViewManager.h in CopyFiles */, 29 | 73814A35203776CD00E8C67D /* RCTTWRemoteVideoViewManager.h in CopyFiles */, 30 | 73814A36203776CD00E8C67D /* RCTTWSerializable.h in CopyFiles */, 31 | 73814A37203776CD00E8C67D /* RCTTWVideoModule.h in CopyFiles */, 32 | ); 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXCopyFilesBuildPhase section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 738F9D552037604000058972 /* libRNTwilioVideoWebRTC.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNTwilioVideoWebRTC.a; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 738F9D63203760FF00058972 /* RCTTWVideoModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTWVideoModule.h; sourceTree = SOURCE_ROOT; }; 40 | 738F9D64203760FF00058972 /* RCTTWSerializable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTWSerializable.m; sourceTree = SOURCE_ROOT; }; 41 | 738F9D65203760FF00058972 /* RCTTWSerializable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTWSerializable.h; sourceTree = SOURCE_ROOT; }; 42 | 738F9D66203760FF00058972 /* RCTTWRemoteVideoViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTWRemoteVideoViewManager.m; sourceTree = SOURCE_ROOT; }; 43 | 738F9D67203760FF00058972 /* RCTTWLocalVideoViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTWLocalVideoViewManager.m; sourceTree = SOURCE_ROOT; }; 44 | 738F9D68203760FF00058972 /* RCTTWVideoModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTWVideoModule.m; sourceTree = SOURCE_ROOT; }; 45 | 738F9D69203760FF00058972 /* RCTTWLocalVideoViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTWLocalVideoViewManager.h; sourceTree = SOURCE_ROOT; }; 46 | 738F9D6A203760FF00058972 /* RCTTWRemoteVideoViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTWRemoteVideoViewManager.h; sourceTree = SOURCE_ROOT; }; 47 | 73AC89C920377EA700038475 /* TwilioVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = TwilioVideo.framework; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | 738F9D522037604000058972 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | 73AC89CA20377EA700038475 /* TwilioVideo.framework in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 738F9D4C2037604000058972 = { 63 | isa = PBXGroup; 64 | children = ( 65 | 738F9D572037604000058972 /* RNTwilioVideoWebRTC */, 66 | 738F9D562037604000058972 /* Products */, 67 | 73C838D4203766CB00DAFEE1 /* Frameworks */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | 738F9D562037604000058972 /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 738F9D552037604000058972 /* libRNTwilioVideoWebRTC.a */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | 738F9D572037604000058972 /* RNTwilioVideoWebRTC */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 738F9D69203760FF00058972 /* RCTTWLocalVideoViewManager.h */, 83 | 738F9D67203760FF00058972 /* RCTTWLocalVideoViewManager.m */, 84 | 738F9D6A203760FF00058972 /* RCTTWRemoteVideoViewManager.h */, 85 | 738F9D66203760FF00058972 /* RCTTWRemoteVideoViewManager.m */, 86 | 738F9D65203760FF00058972 /* RCTTWSerializable.h */, 87 | 738F9D64203760FF00058972 /* RCTTWSerializable.m */, 88 | 738F9D63203760FF00058972 /* RCTTWVideoModule.h */, 89 | 738F9D68203760FF00058972 /* RCTTWVideoModule.m */, 90 | ); 91 | name = RNTwilioVideoWebRTC; 92 | sourceTree = ""; 93 | }; 94 | 73C838D4203766CB00DAFEE1 /* Frameworks */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 73AC89C920377EA700038475 /* TwilioVideo.framework */, 98 | ); 99 | name = Frameworks; 100 | sourceTree = ""; 101 | }; 102 | /* End PBXGroup section */ 103 | 104 | /* Begin PBXNativeTarget section */ 105 | 738F9D542037604000058972 /* RNTwilioVideoWebRTC */ = { 106 | isa = PBXNativeTarget; 107 | buildConfigurationList = 738F9D5E2037604000058972 /* Build configuration list for PBXNativeTarget "RNTwilioVideoWebRTC" */; 108 | buildPhases = ( 109 | 738F9D512037604000058972 /* Sources */, 110 | 738F9D522037604000058972 /* Frameworks */, 111 | 738F9D532037604000058972 /* CopyFiles */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = RNTwilioVideoWebRTC; 118 | productName = RNTwilioVideoWebRTC; 119 | productReference = 738F9D552037604000058972 /* libRNTwilioVideoWebRTC.a */; 120 | productType = "com.apple.product-type.library.static"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | 738F9D4D2037604000058972 /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | LastUpgradeCheck = 0920; 129 | ORGANIZATIONNAME = Employ; 130 | TargetAttributes = { 131 | 738F9D542037604000058972 = { 132 | CreatedOnToolsVersion = 9.2; 133 | ProvisioningStyle = Automatic; 134 | }; 135 | }; 136 | }; 137 | buildConfigurationList = 738F9D502037604000058972 /* Build configuration list for PBXProject "RNTwilioVideoWebRTC" */; 138 | compatibilityVersion = "Xcode 8.0"; 139 | developmentRegion = en; 140 | hasScannedForEncodings = 0; 141 | knownRegions = ( 142 | en, 143 | ); 144 | mainGroup = 738F9D4C2037604000058972; 145 | productRefGroup = 738F9D562037604000058972 /* Products */; 146 | projectDirPath = ""; 147 | projectRoot = ""; 148 | targets = ( 149 | 738F9D542037604000058972 /* RNTwilioVideoWebRTC */, 150 | ); 151 | }; 152 | /* End PBXProject section */ 153 | 154 | /* Begin PBXSourcesBuildPhase section */ 155 | 738F9D512037604000058972 /* Sources */ = { 156 | isa = PBXSourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | 738F9D6E203760FF00058972 /* RCTTWVideoModule.m in Sources */, 160 | 738F9D6C203760FF00058972 /* RCTTWRemoteVideoViewManager.m in Sources */, 161 | 738F9D6B203760FF00058972 /* RCTTWSerializable.m in Sources */, 162 | 738F9D6D203760FF00058972 /* RCTTWLocalVideoViewManager.m in Sources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXSourcesBuildPhase section */ 167 | 168 | /* Begin XCBuildConfiguration section */ 169 | 738F9D5C2037604000058972 /* Debug */ = { 170 | isa = XCBuildConfiguration; 171 | buildSettings = { 172 | ALWAYS_SEARCH_USER_PATHS = NO; 173 | CLANG_ANALYZER_NONNULL = YES; 174 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 176 | CLANG_CXX_LIBRARY = "libc++"; 177 | CLANG_ENABLE_MODULES = YES; 178 | CLANG_ENABLE_OBJC_ARC = YES; 179 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_COMMA = YES; 182 | CLANG_WARN_CONSTANT_CONVERSION = YES; 183 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 184 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 185 | CLANG_WARN_EMPTY_BODY = YES; 186 | CLANG_WARN_ENUM_CONVERSION = YES; 187 | CLANG_WARN_INFINITE_RECURSION = YES; 188 | CLANG_WARN_INT_CONVERSION = YES; 189 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 190 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 191 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 192 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 193 | CLANG_WARN_STRICT_PROTOTYPES = YES; 194 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 195 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 196 | CLANG_WARN_UNREACHABLE_CODE = YES; 197 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 198 | CODE_SIGN_IDENTITY = "iPhone Developer"; 199 | COPY_PHASE_STRIP = NO; 200 | DEBUG_INFORMATION_FORMAT = dwarf; 201 | ENABLE_STRICT_OBJC_MSGSEND = YES; 202 | ENABLE_TESTABILITY = YES; 203 | GCC_C_LANGUAGE_STANDARD = gnu11; 204 | GCC_DYNAMIC_NO_PIC = NO; 205 | GCC_NO_COMMON_BLOCKS = YES; 206 | GCC_OPTIMIZATION_LEVEL = 0; 207 | GCC_PREPROCESSOR_DEFINITIONS = ( 208 | "DEBUG=1", 209 | "$(inherited)", 210 | ); 211 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 212 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 213 | GCC_WARN_UNDECLARED_SELECTOR = YES; 214 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 215 | GCC_WARN_UNUSED_FUNCTION = YES; 216 | GCC_WARN_UNUSED_VARIABLE = YES; 217 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 218 | MTL_ENABLE_DEBUG_INFO = YES; 219 | ONLY_ACTIVE_ARCH = YES; 220 | SDKROOT = iphoneos; 221 | }; 222 | name = Debug; 223 | }; 224 | 738F9D5D2037604000058972 /* Release */ = { 225 | isa = XCBuildConfiguration; 226 | buildSettings = { 227 | ALWAYS_SEARCH_USER_PATHS = NO; 228 | CLANG_ANALYZER_NONNULL = YES; 229 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 230 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 231 | CLANG_CXX_LIBRARY = "libc++"; 232 | CLANG_ENABLE_MODULES = YES; 233 | CLANG_ENABLE_OBJC_ARC = YES; 234 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 235 | CLANG_WARN_BOOL_CONVERSION = YES; 236 | CLANG_WARN_COMMA = YES; 237 | CLANG_WARN_CONSTANT_CONVERSION = YES; 238 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 239 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 240 | CLANG_WARN_EMPTY_BODY = YES; 241 | CLANG_WARN_ENUM_CONVERSION = YES; 242 | CLANG_WARN_INFINITE_RECURSION = YES; 243 | CLANG_WARN_INT_CONVERSION = YES; 244 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 245 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 246 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 247 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 248 | CLANG_WARN_STRICT_PROTOTYPES = YES; 249 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 250 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 251 | CLANG_WARN_UNREACHABLE_CODE = YES; 252 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 253 | CODE_SIGN_IDENTITY = "iPhone Developer"; 254 | COPY_PHASE_STRIP = NO; 255 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 256 | ENABLE_NS_ASSERTIONS = NO; 257 | ENABLE_STRICT_OBJC_MSGSEND = YES; 258 | GCC_C_LANGUAGE_STANDARD = gnu11; 259 | GCC_NO_COMMON_BLOCKS = YES; 260 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 261 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 262 | GCC_WARN_UNDECLARED_SELECTOR = YES; 263 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 264 | GCC_WARN_UNUSED_FUNCTION = YES; 265 | GCC_WARN_UNUSED_VARIABLE = YES; 266 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 267 | MTL_ENABLE_DEBUG_INFO = NO; 268 | SDKROOT = iphoneos; 269 | VALIDATE_PRODUCT = YES; 270 | }; 271 | name = Release; 272 | }; 273 | 738F9D5F2037604000058972 /* Debug */ = { 274 | isa = XCBuildConfiguration; 275 | buildSettings = { 276 | CODE_SIGN_STYLE = Automatic; 277 | FRAMEWORK_SEARCH_PATHS = ( 278 | "$(inherited)", 279 | "$(PROJECT_DIR)", 280 | "$(PROJECT_DIR)/../../../ios/Frameworks/**", 281 | "$(PROJECT_DIR)/../../../ios/Pods/**", 282 | "$(PROJECT_DIR)/../../../ios/Carthage/Build/**", 283 | ); 284 | HEADER_SEARCH_PATHS = ( 285 | "$(inherited)", 286 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 287 | "${SRCROOT}/../../../ios/Pods/TwilioVideo/TwilioVideo/**", 288 | "$(SRCROOT)/../../../ios/Carthage/Build/**", 289 | ); 290 | OTHER_LDFLAGS = "-ObjC"; 291 | PRODUCT_NAME = "$(TARGET_NAME)"; 292 | SKIP_INSTALL = YES; 293 | TARGETED_DEVICE_FAMILY = "1,2"; 294 | }; 295 | name = Debug; 296 | }; 297 | 738F9D602037604000058972 /* Release */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | CODE_SIGN_STYLE = Automatic; 301 | FRAMEWORK_SEARCH_PATHS = ( 302 | "$(inherited)", 303 | "$(PROJECT_DIR)", 304 | "$(PROJECT_DIR)/../../../ios/Frameworks/**", 305 | "$(PROJECT_DIR)/../../../ios/Pods/**", 306 | "$(PROJECT_DIR)/../../../ios/Carthage/Build/**", 307 | ); 308 | HEADER_SEARCH_PATHS = ( 309 | "$(inherited)", 310 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 311 | "${SRCROOT}/../../../ios/Pods/TwilioVideo/TwilioVideo/**", 312 | "$(SRCROOT)/../../../ios/Carthage/Build/**", 313 | ); 314 | OTHER_LDFLAGS = "-ObjC"; 315 | PRODUCT_NAME = "$(TARGET_NAME)"; 316 | SKIP_INSTALL = YES; 317 | TARGETED_DEVICE_FAMILY = "1,2"; 318 | }; 319 | name = Release; 320 | }; 321 | /* End XCBuildConfiguration section */ 322 | 323 | /* Begin XCConfigurationList section */ 324 | 738F9D502037604000058972 /* Build configuration list for PBXProject "RNTwilioVideoWebRTC" */ = { 325 | isa = XCConfigurationList; 326 | buildConfigurations = ( 327 | 738F9D5C2037604000058972 /* Debug */, 328 | 738F9D5D2037604000058972 /* Release */, 329 | ); 330 | defaultConfigurationIsVisible = 0; 331 | defaultConfigurationName = Release; 332 | }; 333 | 738F9D5E2037604000058972 /* Build configuration list for PBXNativeTarget "RNTwilioVideoWebRTC" */ = { 334 | isa = XCConfigurationList; 335 | buildConfigurations = ( 336 | 738F9D5F2037604000058972 /* Debug */, 337 | 738F9D602037604000058972 /* Release */, 338 | ); 339 | defaultConfigurationIsVisible = 0; 340 | defaultConfigurationName = Release; 341 | }; 342 | /* End XCConfigurationList section */ 343 | }; 344 | rootObject = 738F9D4D2037604000058972 /* Project object */; 345 | } 346 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | ### Steps to reproduce 2 | 1. 3 | 2. 4 | 3. 5 | 6 | ### Expected behaviour 7 | Tell us what should happen 8 | 9 | ### Actual behaviour 10 | Tell us what happens instead 11 | 12 | ### Environment 13 | - **Node.js version**: 14 | - **React Native version**: 15 | - **React Native platform + platform version**: iOS 9.0 16 | 17 | ### react-native-twilio-video-webrtc 18 | **Version**: npm version or "master" 19 | -------------------------------------------------------------------------------- /jest/react-native-twilio-video-webrtc-mock.js: -------------------------------------------------------------------------------- 1 | 2 | class TwilioVideoMock extends Component { 3 | connect = () => {} 4 | 5 | disconnect = () => {} 6 | 7 | setLocalAudioEnabled = () => {} 8 | 9 | setLocalVideoEnabled = () => {} 10 | 11 | toggleSoundSetup = () => {} 12 | 13 | flipCamera = () => {} 14 | 15 | setRemoteAudioPlayback = () => {} 16 | 17 | getStats = () => {} 18 | 19 | render() { 20 | return (

); 21 | } 22 | } 23 | 24 | class TwilioVideoLocalViewMock extends Component { 25 | render() { 26 | return (
); 27 | } 28 | } 29 | 30 | class TwilioVideoParticipantViewMock extends Component { 31 | render() { 32 | return (
); 33 | } 34 | } 35 | 36 | const asMock = { 37 | TwilioVideo: TwilioVideoMock, 38 | TwilioVideoLocalView: TwilioVideoLocalViewMock, 39 | TwilioVideoParticipantView: TwilioVideoParticipantViewMock, 40 | }; 41 | 42 | export default asMock; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-twilio-video-webrtc", 3 | "repository": { 4 | "type": "git", 5 | "url": "https://github.com/blackuy/react-native-twilio-video-webrtc.git" 6 | }, 7 | "homepage": "https://github.com/blackuy/react-native-twilio-video-webrtc", 8 | "version": "3.2.1", 9 | "description": "Twilio Video WebRTC for React Native.", 10 | "main": "index.js", 11 | "scripts": { 12 | "build": "expo-module build", 13 | "clean": "expo-module clean", 14 | "test": "expo-module test", 15 | "lint": "prettier -c 'src/**/*.js'", 16 | "lint:fix": "prettier -w 'src/**/*.js'", 17 | "ci": "yarn lint" 18 | }, 19 | "standard": { 20 | "parser": "babel-eslint" 21 | }, 22 | "keywords": [ 23 | "react-native" 24 | ], 25 | "author": "Gaston Morixe ", 26 | "contributors": [ 27 | "Martin Fernandez " 28 | ], 29 | "license": "MIT", 30 | "devDependencies": { 31 | "@types/react": "^16.9.32", 32 | "@types/react-native": "^0.62.1", 33 | "babel-eslint": "^7.2.3", 34 | "del": "^3.0.0", 35 | "doctoc": "^1.3.0", 36 | "expo": "^47.0.0", 37 | "expo-build-properties": "^0.5.1", 38 | "expo-module-scripts": "^3.0.7", 39 | "fancy-log": "^2.0.0", 40 | "gulp": "^4.0.2", 41 | "gulp-concat": "^2.6.1", 42 | "gulp-load-plugins": "^1.5.0", 43 | "gulp-react-docs": "^1.0.1", 44 | "gulp-tap": "^1.0.1", 45 | "prettier": "^2.8.8", 46 | "react-docgen": "^2.15.0", 47 | "semver": "5.7.2", 48 | "tough-cookie": "4.1.4", 49 | "word-wrap": "^1.2.5" 50 | }, 51 | "peerDependencies": { 52 | "expo": ">=47.0.0", 53 | "expo-build-properties": ">=0.5.1" 54 | }, 55 | "peerDependenciesMeta": { 56 | "expo": { 57 | "optional": true 58 | } 59 | }, 60 | "dependencies": { 61 | "prop-types": "^15.5.10" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /plugin/__tests__/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackuy/react-native-twilio-video-webrtc/721af58f8e5e6bf6d263d7fad105f66235e66fc8/plugin/__tests__/.gitkeep -------------------------------------------------------------------------------- /plugin/build/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ConfigPlugin } from "expo/config-plugins"; 2 | export type WithTwilioVideoWebRtcProps = { 3 | cameraPermission?: string; 4 | microphonePermission?: string; 5 | }; 6 | declare const _default: ConfigPlugin; 7 | export default _default; 8 | -------------------------------------------------------------------------------- /plugin/build/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const config_plugins_1 = require("expo/config-plugins"); 7 | const withAndroidTwilioVideoWebrtc_1 = __importDefault(require("./withAndroidTwilioVideoWebrtc")); 8 | const withIosTwilioVideoWebrtc_1 = __importDefault(require("./withIosTwilioVideoWebrtc")); 9 | const pkg = require("react-native-twilio-video-webrtc/package.json"); 10 | const withTwilioVideoWebrtc = (config, props) => { 11 | config = (0, withAndroidTwilioVideoWebrtc_1.default)(config); 12 | config = (0, withIosTwilioVideoWebrtc_1.default)(config, props); 13 | return config; 14 | }; 15 | exports.default = (0, config_plugins_1.createRunOncePlugin)(withTwilioVideoWebrtc, pkg.name, pkg.version); 16 | -------------------------------------------------------------------------------- /plugin/build/withAndroidTwilioVideoWebrtc.d.ts: -------------------------------------------------------------------------------- 1 | import { ConfigPlugin } from "expo/config-plugins"; 2 | declare const withAndroidTwilioVideoWebrtc: ConfigPlugin; 3 | export default withAndroidTwilioVideoWebrtc; 4 | -------------------------------------------------------------------------------- /plugin/build/withAndroidTwilioVideoWebrtc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const config_plugins_1 = require("expo/config-plugins"); 4 | const expo_build_properties_1 = require("expo-build-properties"); 5 | const { withPermissions: withAndroidPermissions } = config_plugins_1.AndroidConfig.Permissions; 6 | const withAndroidTwilioVideoWebrtc = (config) => { 7 | // 1. Permissions 8 | config = withAndroidPermissions(config, [ 9 | "android.permission.CAMERA", 10 | "android.permission.RECORD_AUDIO", 11 | "android.permission.MODIFY_AUDIO_SETTINGS", 12 | ]); 13 | // 2. Java 1.8 compatibility 14 | config = (0, config_plugins_1.withAppBuildGradle)(config, (conf) => { 15 | if (conf.modResults.contents.includes(`compileOptions`)) { 16 | return conf; 17 | } 18 | conf.modResults.contents = conf.modResults.contents.replace("android {", ` 19 | android { 20 | compileOptions { 21 | sourceCompatibility 1.8 22 | targetCompatibility 1.8 23 | } 24 | `); 25 | return conf; 26 | }); 27 | // 3. Proguard rules 28 | config = (0, expo_build_properties_1.withBuildProperties)(config, { 29 | android: { 30 | extraProguardRules: ` 31 | -keep class com.twilio.** { *; } 32 | -keep class tvi.webrtc.** { *; } 33 | `, 34 | }, 35 | }); 36 | return config; 37 | }; 38 | exports.default = withAndroidTwilioVideoWebrtc; 39 | -------------------------------------------------------------------------------- /plugin/build/withIosTwilioVideoWebrtc.d.ts: -------------------------------------------------------------------------------- 1 | import { ConfigPlugin } from "expo/config-plugins"; 2 | import { WithTwilioVideoWebRtcProps } from "."; 3 | declare const withIosTwilioVideoWebrtc: ConfigPlugin; 4 | export default withIosTwilioVideoWebrtc; 5 | -------------------------------------------------------------------------------- /plugin/build/withIosTwilioVideoWebrtc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const config_plugins_1 = require("expo/config-plugins"); 4 | const addPermission = (conf, permission, message) => { 5 | if (!conf.modResults[permission] && !!message) { 6 | conf.modResults[permission] = message; 7 | } 8 | }; 9 | const withIosTwilioVideoWebrtc = (config, { cameraPermission, microphonePermission }) => { 10 | // 1. Deployment target (iOS 11.0+) 11 | // config = withBuildProperties(config, { 12 | // ios: { 13 | // deploymentTarget: "11.0", 14 | // }, 15 | // }); 16 | // 2. Camera / Microphone Permission on Info.plist 17 | config = (0, config_plugins_1.withInfoPlist)(config, (conf) => { 18 | addPermission(conf, "NSCameraUsageDescription", cameraPermission); 19 | addPermission(conf, "NSMicrophoneUsageDescription", microphonePermission); 20 | return conf; 21 | }); 22 | return config; 23 | }; 24 | exports.default = withIosTwilioVideoWebrtc; 25 | -------------------------------------------------------------------------------- /plugin/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("expo-module-scripts/jest-preset-plugin"); 2 | -------------------------------------------------------------------------------- /plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import { ConfigPlugin, createRunOncePlugin } from "expo/config-plugins"; 2 | 3 | import withAndroidTwilioVideoWebrtc from "./withAndroidTwilioVideoWebrtc"; 4 | import withIosTwilioVideoWebrtc from "./withIosTwilioVideoWebrtc"; 5 | 6 | const pkg = require("react-native-twilio-video-webrtc/package.json"); 7 | 8 | export type WithTwilioVideoWebRtcProps = { 9 | cameraPermission?: string; 10 | microphonePermission?: string; 11 | }; 12 | 13 | const withTwilioVideoWebrtc: ConfigPlugin = ( 14 | config, 15 | props 16 | ) => { 17 | config = withAndroidTwilioVideoWebrtc(config); 18 | config = withIosTwilioVideoWebrtc(config, props); 19 | 20 | return config; 21 | }; 22 | 23 | export default createRunOncePlugin( 24 | withTwilioVideoWebrtc, 25 | pkg.name, 26 | pkg.version 27 | ); 28 | -------------------------------------------------------------------------------- /plugin/src/withAndroidTwilioVideoWebrtc.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AndroidConfig, 3 | ConfigPlugin, 4 | ExportedConfigWithProps, 5 | withAppBuildGradle, 6 | } from "expo/config-plugins"; 7 | import { withBuildProperties } from "expo-build-properties"; 8 | 9 | const { withPermissions: withAndroidPermissions } = AndroidConfig.Permissions; 10 | 11 | const withAndroidTwilioVideoWebrtc: ConfigPlugin = (config) => { 12 | // 1. Permissions 13 | config = withAndroidPermissions(config, [ 14 | "android.permission.CAMERA", 15 | "android.permission.RECORD_AUDIO", 16 | "android.permission.MODIFY_AUDIO_SETTINGS", 17 | ]); 18 | 19 | // 2. Java 1.8 compatibility 20 | config = withAppBuildGradle(config, (conf: ExportedConfigWithProps) => { 21 | if (conf.modResults.contents.includes(`compileOptions`)) { 22 | return conf; 23 | } 24 | 25 | conf.modResults.contents = conf.modResults.contents.replace( 26 | "android {", 27 | ` 28 | android { 29 | compileOptions { 30 | sourceCompatibility 1.8 31 | targetCompatibility 1.8 32 | } 33 | ` 34 | ); 35 | 36 | return conf; 37 | }); 38 | 39 | // 3. Proguard rules 40 | config = withBuildProperties(config, { 41 | android: { 42 | extraProguardRules: ` 43 | -keep class com.twilio.** { *; } 44 | -keep class tvi.webrtc.** { *; } 45 | `, 46 | }, 47 | }); 48 | 49 | return config; 50 | }; 51 | 52 | export default withAndroidTwilioVideoWebrtc; 53 | -------------------------------------------------------------------------------- /plugin/src/withIosTwilioVideoWebrtc.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConfigPlugin, 3 | ExportedConfigWithProps, 4 | withInfoPlist, 5 | } from "expo/config-plugins"; 6 | import { withBuildProperties } from "expo-build-properties"; 7 | 8 | import { WithTwilioVideoWebRtcProps } from "."; 9 | 10 | const addPermission = ( 11 | conf: ExportedConfigWithProps, 12 | permission: string, 13 | message?: string 14 | ) => { 15 | if (!conf.modResults[permission] && !!message) { 16 | conf.modResults[permission] = message; 17 | } 18 | }; 19 | 20 | const withIosTwilioVideoWebrtc: ConfigPlugin = ( 21 | config, 22 | { cameraPermission, microphonePermission } 23 | ) => { 24 | // 1. Deployment target (iOS 11.0+) 25 | // config = withBuildProperties(config, { 26 | // ios: { 27 | // deploymentTarget: "11.0", 28 | // }, 29 | // }); 30 | 31 | // 2. Camera / Microphone Permission on Info.plist 32 | config = withInfoPlist(config, (conf: ExportedConfigWithProps) => { 33 | addPermission(conf, "NSCameraUsageDescription", cameraPermission); 34 | addPermission(conf, "NSMicrophoneUsageDescription", microphonePermission); 35 | 36 | return conf; 37 | }); 38 | 39 | return config; 40 | }; 41 | 42 | export default withIosTwilioVideoWebrtc; 43 | -------------------------------------------------------------------------------- /plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo-module-scripts/tsconfig.plugin", 3 | "compilerOptions": { 4 | "outDir": "build", 5 | "rootDir": "src" 6 | }, 7 | "include": ["./src"], 8 | "exclude": ["**/__mocks__/*", "**/__tests__/*"] 9 | } 10 | -------------------------------------------------------------------------------- /react-native-twilio-video-webrtc.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'react-native-twilio-video-webrtc' 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.description = package['description'] 10 | s.license = package['license'] 11 | s.author = package['author'] 12 | s.homepage = package['homepage'] 13 | s.source = { git: 'https://github.com/blackuy/react-native-twilio-video-web-rtc', tag: s.version } 14 | 15 | s.requires_arc = true 16 | s.platform = :ios, '10.0' 17 | 18 | s.preserve_paths = 'LICENSE', 'README.md', 'package.json', 'index.js' 19 | s.source_files = 'ios/*.{h,m}' 20 | 21 | s.dependency 'React' 22 | s.dependency 'TwilioVideo', '~> 4.6' 23 | end 24 | -------------------------------------------------------------------------------- /src/TwilioVideo.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Component to orchestrate the Twilio Video connection and the various video 3 | * views. 4 | * 5 | * Authors: 6 | * Ralph Pina 7 | * Jonathan Chang 8 | */ 9 | 10 | import { 11 | Platform, 12 | UIManager, 13 | View, 14 | findNodeHandle, 15 | requireNativeComponent, 16 | } from "react-native"; 17 | import React, { Component } from "react"; 18 | 19 | import PropTypes from "prop-types"; 20 | 21 | const propTypes = { 22 | ...View.propTypes, 23 | /** 24 | * Callback that is called when camera source changes 25 | */ 26 | onCameraSwitched: PropTypes.func, 27 | 28 | /** 29 | * Callback that is called when video is toggled. 30 | */ 31 | onVideoChanged: PropTypes.func, 32 | 33 | /** 34 | * Callback that is called when a audio is toggled. 35 | */ 36 | onAudioChanged: PropTypes.func, 37 | 38 | /** 39 | * Callback that is called when user is connected to a room. 40 | */ 41 | onRoomDidConnect: PropTypes.func, 42 | 43 | /** 44 | * Callback that is called when connecting to room fails. 45 | */ 46 | onRoomDidFailToConnect: PropTypes.func, 47 | 48 | /** 49 | * Callback that is called when user is disconnected from room. 50 | */ 51 | onRoomDidDisconnect: PropTypes.func, 52 | 53 | /** 54 | * Called when a new data track has been added 55 | * 56 | * @param {{participant, track}} 57 | */ 58 | onParticipantAddedDataTrack: PropTypes.func, 59 | 60 | /** 61 | * Called when a data track has been removed 62 | * 63 | * @param {{participant, track}} 64 | */ 65 | onParticipantRemovedDataTrack: PropTypes.func, 66 | 67 | /** 68 | * Called when an dataTrack receives a message 69 | * 70 | * @param {{message}} 71 | */ 72 | onDataTrackMessageReceived: PropTypes.func, 73 | 74 | /** 75 | * Called when a new video track has been added 76 | * 77 | * @param {{participant, track, enabled}} 78 | */ 79 | onParticipantAddedVideoTrack: PropTypes.func, 80 | 81 | /** 82 | * Called when a video track has been removed 83 | * 84 | * @param {{participant, track}} 85 | */ 86 | onParticipantRemovedVideoTrack: PropTypes.func, 87 | 88 | /** 89 | * Called when a new audio track has been added 90 | * 91 | * @param {{participant, track}} 92 | */ 93 | onParticipantAddedAudioTrack: PropTypes.func, 94 | 95 | /** 96 | * Called when a audio track has been removed 97 | * 98 | * @param {{participant, track}} 99 | */ 100 | onParticipantRemovedAudioTrack: PropTypes.func, 101 | 102 | /** 103 | * Callback called a participant enters a room. 104 | */ 105 | onRoomParticipantDidConnect: PropTypes.func, 106 | 107 | /** 108 | * Callback that is called when a participant exits a room. 109 | */ 110 | onRoomParticipantDidDisconnect: PropTypes.func, 111 | /** 112 | * Called when a video track has been enabled. 113 | * 114 | * @param {{participant, track}} 115 | */ 116 | onParticipantEnabledVideoTrack: PropTypes.func, 117 | /** 118 | * Called when a video track has been disabled. 119 | * 120 | * @param {{participant, track}} 121 | */ 122 | onParticipantDisabledVideoTrack: PropTypes.func, 123 | /** 124 | * Called when an audio track has been enabled. 125 | * 126 | * @param {{participant, track}} 127 | */ 128 | onParticipantEnabledAudioTrack: PropTypes.func, 129 | /** 130 | * Called when an audio track has been disabled. 131 | * 132 | * @param {{participant, track}} 133 | */ 134 | onParticipantDisabledAudioTrack: PropTypes.func, 135 | /** 136 | * Callback that is called when stats are received (after calling getStats) 137 | */ 138 | onStatsReceived: PropTypes.func, 139 | /** 140 | * Callback that is called when network quality levels are changed (only if enableNetworkQualityReporting in connect is set to true) 141 | */ 142 | onNetworkQualityLevelsChanged: PropTypes.func, 143 | /** 144 | * Called when dominant speaker changes 145 | * @param {{ participant, room }} dominant participant and room 146 | */ 147 | onDominantSpeakerDidChange: PropTypes.func, 148 | /** 149 | * Callback that is called after determining what codecs are supported 150 | */ 151 | onLocalParticipantSupportedCodecs: PropTypes.func, 152 | }; 153 | 154 | const nativeEvents = { 155 | connectToRoom: 1, 156 | disconnect: 2, 157 | switchCamera: 3, 158 | toggleVideo: 4, 159 | toggleSound: 5, 160 | getStats: 6, 161 | disableOpenSLES: 7, 162 | toggleSoundSetup: 8, 163 | toggleRemoteSound: 9, 164 | releaseResource: 10, 165 | toggleBluetoothHeadset: 11, 166 | sendString: 12, 167 | publishVideo: 13, 168 | publishAudio: 14, 169 | setRemoteAudioPlayback: 15, 170 | }; 171 | 172 | class CustomTwilioVideoView extends Component { 173 | connect({ 174 | roomName, 175 | accessToken, 176 | cameraType = "front", 177 | enableAudio = true, 178 | enableVideo = true, 179 | enableRemoteAudio = true, 180 | enableNetworkQualityReporting = false, 181 | dominantSpeakerEnabled = false, 182 | maintainVideoTrackInBackground = false, 183 | encodingParameters = {}, 184 | }) { 185 | this.runCommand(nativeEvents.connectToRoom, [ 186 | roomName, 187 | accessToken, 188 | enableAudio, 189 | enableVideo, 190 | enableRemoteAudio, 191 | enableNetworkQualityReporting, 192 | dominantSpeakerEnabled, 193 | maintainVideoTrackInBackground, 194 | cameraType, 195 | encodingParameters, 196 | ]); 197 | } 198 | 199 | sendString(message) { 200 | this.runCommand(nativeEvents.sendString, [message]); 201 | } 202 | 203 | publishLocalAudio() { 204 | this.runCommand(nativeEvents.publishAudio, [true]); 205 | } 206 | 207 | publishLocalVideo() { 208 | this.runCommand(nativeEvents.publishVideo, [true]); 209 | } 210 | 211 | unpublishLocalAudio() { 212 | this.runCommand(nativeEvents.publishAudio, [false]); 213 | } 214 | 215 | unpublishLocalVideo() { 216 | this.runCommand(nativeEvents.publishVideo, [false]); 217 | } 218 | 219 | disconnect() { 220 | this.runCommand(nativeEvents.disconnect, []); 221 | } 222 | 223 | componentWillUnmount() { 224 | this.runCommand(nativeEvents.releaseResource, []); 225 | } 226 | 227 | flipCamera() { 228 | this.runCommand(nativeEvents.switchCamera, []); 229 | } 230 | 231 | setLocalVideoEnabled(enabled) { 232 | this.runCommand(nativeEvents.toggleVideo, [enabled]); 233 | return Promise.resolve(enabled); 234 | } 235 | 236 | setLocalAudioEnabled(enabled) { 237 | this.runCommand(nativeEvents.toggleSound, [enabled]); 238 | return Promise.resolve(enabled); 239 | } 240 | 241 | setRemoteAudioEnabled(enabled) { 242 | this.runCommand(nativeEvents.toggleRemoteSound, [enabled]); 243 | return Promise.resolve(enabled); 244 | } 245 | 246 | setBluetoothHeadsetConnected(enabled) { 247 | this.runCommand(nativeEvents.toggleBluetoothHeadset, [enabled]); 248 | return Promise.resolve(enabled); 249 | } 250 | 251 | setRemoteAudioPlayback({ participantSid, enabled }) { 252 | this.runCommand(nativeEvents.setRemoteAudioPlayback, [ 253 | participantSid, 254 | enabled, 255 | ]); 256 | } 257 | 258 | getStats() { 259 | this.runCommand(nativeEvents.getStats, []); 260 | } 261 | 262 | disableOpenSLES() { 263 | this.runCommand(nativeEvents.disableOpenSLES, []); 264 | } 265 | 266 | toggleSoundSetup(speaker) { 267 | this.runCommand(nativeEvents.toggleSoundSetup, [speaker]); 268 | } 269 | 270 | runCommand(event, args) { 271 | switch (Platform.OS) { 272 | case "android": 273 | UIManager.dispatchViewManagerCommand( 274 | findNodeHandle(this.refs.videoView), 275 | event, 276 | args 277 | ); 278 | break; 279 | default: 280 | break; 281 | } 282 | } 283 | 284 | buildNativeEventWrappers() { 285 | return [ 286 | "onCameraSwitched", 287 | "onVideoChanged", 288 | "onAudioChanged", 289 | "onRoomDidConnect", 290 | "onRoomDidFailToConnect", 291 | "onRoomDidDisconnect", 292 | "onParticipantAddedDataTrack", 293 | "onParticipantRemovedDataTrack", 294 | "onDataTrackMessageReceived", 295 | "onParticipantAddedVideoTrack", 296 | "onParticipantRemovedVideoTrack", 297 | "onParticipantAddedAudioTrack", 298 | "onParticipantRemovedAudioTrack", 299 | "onRoomParticipantDidConnect", 300 | "onRoomParticipantDidDisconnect", 301 | "onParticipantEnabledVideoTrack", 302 | "onParticipantDisabledVideoTrack", 303 | "onParticipantEnabledAudioTrack", 304 | "onParticipantDisabledAudioTrack", 305 | "onStatsReceived", 306 | "onNetworkQualityLevelsChanged", 307 | "onDominantSpeakerDidChange", 308 | "onLocalParticipantSupportedCodecs", 309 | ].reduce((wrappedEvents, eventName) => { 310 | if (this.props[eventName]) { 311 | return { 312 | ...wrappedEvents, 313 | [eventName]: (data) => this.props[eventName](data.nativeEvent), 314 | }; 315 | } 316 | return wrappedEvents; 317 | }, {}); 318 | } 319 | 320 | render() { 321 | return ( 322 | 327 | ); 328 | } 329 | } 330 | 331 | CustomTwilioVideoView.propTypes = propTypes; 332 | 333 | const NativeCustomTwilioVideoView = requireNativeComponent( 334 | "RNCustomTwilioVideoView", 335 | CustomTwilioVideoView 336 | ); 337 | 338 | module.exports = CustomTwilioVideoView; 339 | -------------------------------------------------------------------------------- /src/TwilioVideo.ios.js: -------------------------------------------------------------------------------- 1 | // 2 | // TwilioVideo.js 3 | // Black 4 | // 5 | // Created by Martín Fernández on 6/13/17. 6 | // 7 | // 8 | 9 | import { Component } from "react"; 10 | import PropTypes from "prop-types"; 11 | import { NativeModules, NativeEventEmitter, View } from "react-native"; 12 | 13 | const { TWVideoModule } = NativeModules; 14 | 15 | export default class TwilioVideo extends Component { 16 | static propTypes = { 17 | /** 18 | * Called when the room has connected 19 | * 20 | * @param {{roomName, participants}} 21 | */ 22 | onRoomDidConnect: PropTypes.func, 23 | /** 24 | * Called when the room has disconnected 25 | * 26 | * @param {{roomName, error}} 27 | */ 28 | onRoomDidDisconnect: PropTypes.func, 29 | /** 30 | * Called when connection with room failed 31 | * 32 | * @param {{roomName, error}} 33 | */ 34 | onRoomDidFailToConnect: PropTypes.func, 35 | /** 36 | * Called when a new participant has connected 37 | * 38 | * @param {{roomName, participant}} 39 | */ 40 | onRoomParticipantDidConnect: PropTypes.func, 41 | /** 42 | * Called when a participant has disconnected 43 | * 44 | * @param {{roomName, participant}} 45 | */ 46 | onRoomParticipantDidDisconnect: PropTypes.func, 47 | /** 48 | * Called when a new video track has been added 49 | * 50 | * @param {{participant, track, enabled}} 51 | */ 52 | onParticipantAddedVideoTrack: PropTypes.func, 53 | /** 54 | * Called when a video track has been removed 55 | * 56 | * @param {{participant, track}} 57 | */ 58 | onParticipantRemovedVideoTrack: PropTypes.func, 59 | /** 60 | * Called when a new data track has been added 61 | * 62 | * @param {{participant, track}} 63 | */ 64 | onParticipantAddedDataTrack: PropTypes.func, 65 | /** 66 | * Called when a data track has been removed 67 | * 68 | * @param {{participant, track}} 69 | */ 70 | onParticipantRemovedDataTrack: PropTypes.func, 71 | /** 72 | * Called when a new audio track has been added 73 | * 74 | * @param {{participant, track}} 75 | */ 76 | onParticipantAddedAudioTrack: PropTypes.func, 77 | /** 78 | * Called when a audio track has been removed 79 | * 80 | * @param {{participant, track}} 81 | */ 82 | onParticipantRemovedAudioTrack: PropTypes.func, 83 | /** 84 | * Called when a video track has been enabled. 85 | * 86 | * @param {{participant, track}} 87 | */ 88 | onParticipantEnabledVideoTrack: PropTypes.func, 89 | /** 90 | * Called when a video track has been disabled. 91 | * 92 | * @param {{participant, track}} 93 | */ 94 | onParticipantDisabledVideoTrack: PropTypes.func, 95 | /** 96 | * Called when an audio track has been enabled. 97 | * 98 | * @param {{participant, track}} 99 | */ 100 | onParticipantEnabledAudioTrack: PropTypes.func, 101 | /** 102 | * Called when an audio track has been disabled. 103 | * 104 | * @param {{participant, track}} 105 | */ 106 | onParticipantDisabledAudioTrack: PropTypes.func, 107 | /** 108 | * Called when an dataTrack receives a message 109 | * 110 | * @param {{message}} 111 | */ 112 | onDataTrackMessageReceived: PropTypes.func, 113 | /** 114 | * Called when the camera has started 115 | * 116 | */ 117 | onCameraDidStart: PropTypes.func, 118 | /** 119 | * Called when the camera has been interrupted 120 | * 121 | */ 122 | onCameraWasInterrupted: PropTypes.func, 123 | /** 124 | * Called when the camera interruption has ended 125 | * 126 | */ 127 | onCameraInterruptionEnded: PropTypes.func, 128 | /** 129 | * Called when the camera has stopped runing with an error 130 | * 131 | * @param {{error}} The error message description 132 | */ 133 | onCameraDidStopRunning: PropTypes.func, 134 | /** 135 | * Called when stats are received (after calling getStats) 136 | * 137 | */ 138 | onStatsReceived: PropTypes.func, 139 | /** 140 | * Called when the network quality levels of a participant have changed (only if enableNetworkQualityReporting is set to True when connecting) 141 | * 142 | */ 143 | onNetworkQualityLevelsChanged: PropTypes.func, 144 | /** 145 | * Called when dominant speaker changes 146 | * @param {{ participant, room }} dominant participant 147 | */ 148 | onDominantSpeakerDidChange: PropTypes.func, 149 | /** 150 | * Whether or not video should be automatically initialized upon mounting 151 | * of this component. Defaults to true. If set to false, any use of the 152 | * camera will require calling `_startLocalVideo`. 153 | */ 154 | autoInitializeCamera: PropTypes.bool, 155 | ...View.propTypes, 156 | }; 157 | 158 | constructor(props) { 159 | super(props); 160 | 161 | this._subscriptions = []; 162 | this._eventEmitter = new NativeEventEmitter(TWVideoModule); 163 | } 164 | 165 | componentDidMount() { 166 | this._registerEvents(); 167 | if (this.props.autoInitializeCamera !== false) { 168 | this._startLocalVideo(); 169 | } 170 | this._startLocalAudio(); 171 | } 172 | 173 | componentWillUnmount() { 174 | this._unregisterEvents(); 175 | this._stopLocalVideo(); 176 | this._stopLocalAudio(); 177 | } 178 | 179 | /** 180 | * Locally mute/ unmute all remote audio tracks from a given participant 181 | */ 182 | setRemoteAudioPlayback({ participantSid, enabled }) { 183 | TWVideoModule.setRemoteAudioPlayback(participantSid, enabled); 184 | } 185 | 186 | setRemoteAudioEnabled(enabled) { 187 | return Promise.resolve(enabled); 188 | } 189 | 190 | setBluetoothHeadsetConnected(enabled) { 191 | return Promise.resolve(enabled); 192 | } 193 | 194 | /** 195 | * Enable or disable local video 196 | */ 197 | setLocalVideoEnabled(enabled) { 198 | return TWVideoModule.setLocalVideoEnabled(enabled); 199 | } 200 | 201 | /** 202 | * Enable or disable local audio 203 | */ 204 | setLocalAudioEnabled(enabled) { 205 | return TWVideoModule.setLocalAudioEnabled(enabled); 206 | } 207 | 208 | /** 209 | * Filp between the front and back camera 210 | */ 211 | flipCamera() { 212 | TWVideoModule.flipCamera(); 213 | } 214 | 215 | /** 216 | * Toggle screen sharing 217 | */ 218 | toggleScreenSharing(status) { 219 | TWVideoModule.toggleScreenSharing(status); 220 | } 221 | 222 | /** 223 | * Toggle audio setup from speaker (default) and headset 224 | */ 225 | toggleSoundSetup(speaker) { 226 | TWVideoModule.toggleSoundSetup(speaker); 227 | } 228 | 229 | /** 230 | * Get connection stats 231 | */ 232 | getStats() { 233 | TWVideoModule.getStats(); 234 | } 235 | 236 | /** 237 | * Connect to given room name using the JWT access token 238 | * @param {String} roomName The connecting room name 239 | * @param {String} accessToken The Twilio's JWT access token 240 | * @param {String} encodingParameters Control Encoding config 241 | * @param {Boolean} enableNetworkQualityReporting Report network quality of participants 242 | */ 243 | connect({ 244 | roomName, 245 | accessToken, 246 | cameraType = "front", 247 | enableAudio = true, 248 | enableVideo = true, 249 | encodingParameters = null, 250 | enableNetworkQualityReporting = false, 251 | dominantSpeakerEnabled = false, 252 | }) { 253 | TWVideoModule.connect( 254 | accessToken, 255 | roomName, 256 | enableAudio, 257 | enableVideo, 258 | encodingParameters, 259 | enableNetworkQualityReporting, 260 | dominantSpeakerEnabled, 261 | cameraType 262 | ); 263 | } 264 | 265 | /** 266 | * Disconnect from current room 267 | */ 268 | disconnect() { 269 | TWVideoModule.disconnect(); 270 | } 271 | 272 | /** 273 | * Publish a local audio track 274 | */ 275 | publishLocalAudio() { 276 | TWVideoModule.publishLocalAudio(); 277 | } 278 | 279 | /** 280 | * Publish a local video track 281 | */ 282 | publishLocalVideo() { 283 | TWVideoModule.publishLocalVideo(); 284 | } 285 | 286 | /** 287 | * Unpublish a local audio track 288 | */ 289 | unpublishLocalAudio() { 290 | TWVideoModule.unpublishLocalAudio(); 291 | } 292 | 293 | /** 294 | * Unpublish a local video track 295 | */ 296 | unpublishLocalVideo() { 297 | TWVideoModule.unpublishLocalVideo(); 298 | } 299 | 300 | /** 301 | * SendString to datatrack 302 | * @param {String} message The message string to send 303 | */ 304 | sendString(message) { 305 | TWVideoModule.sendString(message); 306 | } 307 | 308 | _startLocalVideo() { 309 | TWVideoModule.startLocalVideo(); 310 | } 311 | 312 | _stopLocalVideo() { 313 | TWVideoModule.stopLocalVideo(); 314 | } 315 | 316 | _startLocalAudio() { 317 | TWVideoModule.startLocalAudio(); 318 | } 319 | 320 | _stopLocalAudio() { 321 | TWVideoModule.stopLocalAudio(); 322 | } 323 | 324 | _unregisterEvents() { 325 | TWVideoModule.changeListenerStatus(false); 326 | this._subscriptions.forEach((e) => e.remove()); 327 | this._subscriptions = []; 328 | } 329 | 330 | _registerEvents() { 331 | TWVideoModule.changeListenerStatus(true); 332 | this._subscriptions = [ 333 | this._eventEmitter.addListener("roomDidConnect", (data) => { 334 | if (this.props.onRoomDidConnect) { 335 | this.props.onRoomDidConnect(data); 336 | } 337 | }), 338 | this._eventEmitter.addListener("roomDidDisconnect", (data) => { 339 | if (this.props.onRoomDidDisconnect) { 340 | this.props.onRoomDidDisconnect(data); 341 | } 342 | }), 343 | this._eventEmitter.addListener("roomDidFailToConnect", (data) => { 344 | if (this.props.onRoomDidFailToConnect) { 345 | this.props.onRoomDidFailToConnect(data); 346 | } 347 | }), 348 | this._eventEmitter.addListener("roomParticipantDidConnect", (data) => { 349 | if (this.props.onRoomParticipantDidConnect) { 350 | this.props.onRoomParticipantDidConnect(data); 351 | } 352 | }), 353 | this._eventEmitter.addListener("roomParticipantDidDisconnect", (data) => { 354 | if (this.props.onRoomParticipantDidDisconnect) { 355 | this.props.onRoomParticipantDidDisconnect(data); 356 | } 357 | }), 358 | this._eventEmitter.addListener("participantAddedVideoTrack", (data) => { 359 | if (this.props.onParticipantAddedVideoTrack) { 360 | this.props.onParticipantAddedVideoTrack(data); 361 | } 362 | }), 363 | this._eventEmitter.addListener("participantAddedDataTrack", (data) => { 364 | if (this.props.onParticipantAddedDataTrack) { 365 | this.props.onParticipantAddedDataTrack(data); 366 | } 367 | }), 368 | this._eventEmitter.addListener("participantRemovedDataTrack", (data) => { 369 | if (this.props.onParticipantRemovedDataTrack) { 370 | this.props.onParticipantRemovedDataTrack(data); 371 | } 372 | }), 373 | this._eventEmitter.addListener("participantRemovedVideoTrack", (data) => { 374 | if (this.props.onParticipantRemovedVideoTrack) { 375 | this.props.onParticipantRemovedVideoTrack(data); 376 | } 377 | }), 378 | this._eventEmitter.addListener("participantAddedAudioTrack", (data) => { 379 | if (this.props.onParticipantAddedAudioTrack) { 380 | this.props.onParticipantAddedAudioTrack(data); 381 | } 382 | }), 383 | this._eventEmitter.addListener("participantRemovedAudioTrack", (data) => { 384 | if (this.props.onParticipantRemovedAudioTrack) { 385 | this.props.onParticipantRemovedAudioTrack(data); 386 | } 387 | }), 388 | this._eventEmitter.addListener("participantEnabledVideoTrack", (data) => { 389 | if (this.props.onParticipantEnabledVideoTrack) { 390 | this.props.onParticipantEnabledVideoTrack(data); 391 | } 392 | }), 393 | this._eventEmitter.addListener( 394 | "participantDisabledVideoTrack", 395 | (data) => { 396 | if (this.props.onParticipantDisabledVideoTrack) { 397 | this.props.onParticipantDisabledVideoTrack(data); 398 | } 399 | } 400 | ), 401 | this._eventEmitter.addListener("participantEnabledAudioTrack", (data) => { 402 | if (this.props.onParticipantEnabledAudioTrack) { 403 | this.props.onParticipantEnabledAudioTrack(data); 404 | } 405 | }), 406 | this._eventEmitter.addListener( 407 | "participantDisabledAudioTrack", 408 | (data) => { 409 | if (this.props.onParticipantDisabledAudioTrack) { 410 | this.props.onParticipantDisabledAudioTrack(data); 411 | } 412 | } 413 | ), 414 | this._eventEmitter.addListener("dataTrackMessageReceived", (data) => { 415 | if (this.props.onDataTrackMessageReceived) { 416 | this.props.onDataTrackMessageReceived(data); 417 | } 418 | }), 419 | this._eventEmitter.addListener("cameraDidStart", (data) => { 420 | if (this.props.onCameraDidStart) { 421 | this.props.onCameraDidStart(data); 422 | } 423 | }), 424 | this._eventEmitter.addListener("cameraWasInterrupted", (data) => { 425 | if (this.props.onCameraWasInterrupted) { 426 | this.props.onCameraWasInterrupted(data); 427 | } 428 | }), 429 | this._eventEmitter.addListener("cameraInterruptionEnded", (data) => { 430 | if (this.props.onCameraInterruptionEnded) { 431 | this.props.onCameraInterruptionEnded(data); 432 | } 433 | }), 434 | this._eventEmitter.addListener("cameraDidStopRunning", (data) => { 435 | if (this.props.onCameraDidStopRunning) { 436 | this.props.onCameraDidStopRunning(data); 437 | } 438 | }), 439 | this._eventEmitter.addListener("statsReceived", (data) => { 440 | if (this.props.onStatsReceived) { 441 | this.props.onStatsReceived(data); 442 | } 443 | }), 444 | this._eventEmitter.addListener("networkQualityLevelsChanged", (data) => { 445 | if (this.props.onNetworkQualityLevelsChanged) { 446 | this.props.onNetworkQualityLevelsChanged(data); 447 | } 448 | }), 449 | this._eventEmitter.addListener("onDominantSpeakerDidChange", (data) => { 450 | if (this.props.onDominantSpeakerDidChange) { 451 | this.props.onDominantSpeakerDidChange(data); 452 | } 453 | }), 454 | ]; 455 | } 456 | 457 | render() { 458 | return this.props.children || null; 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /src/TwilioVideoLocalView.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for Twilio Video local views. 3 | * 4 | * Authors: 5 | * Jonathan Chang 6 | */ 7 | 8 | import { requireNativeComponent, View } from "react-native"; 9 | import React from "react"; 10 | import PropTypes from "prop-types"; 11 | 12 | const propTypes = { 13 | ...View.propTypes, 14 | // Whether to apply Z ordering to this view. Setting this to true will cause 15 | // this view to appear above other Twilio Video views. 16 | applyZOrder: PropTypes.bool, 17 | /** 18 | * How the video stream should be scaled to fit its 19 | * container. 20 | */ 21 | scaleType: PropTypes.oneOf(["fit", "fill"]), 22 | }; 23 | 24 | class TwilioVideoPreview extends React.Component { 25 | render() { 26 | return ; 27 | } 28 | } 29 | 30 | TwilioVideoPreview.propTypes = propTypes; 31 | 32 | const NativeTwilioVideoPreview = requireNativeComponent( 33 | "RNTwilioVideoPreview", 34 | TwilioVideoPreview 35 | ); 36 | 37 | module.exports = TwilioVideoPreview; 38 | -------------------------------------------------------------------------------- /src/TwilioVideoLocalView.ios.js: -------------------------------------------------------------------------------- 1 | // 2 | // TwilioVideoLocalView.js 3 | // Black 4 | // 5 | // Created by Martín Fernández on 6/13/17. 6 | // 7 | // 8 | 9 | import React, { Component } from "react"; 10 | import PropTypes from "prop-types"; 11 | import { requireNativeComponent } from "react-native"; 12 | 13 | class TwilioVideoLocalView extends Component { 14 | static propTypes = { 15 | /** 16 | * Indicate if video feed is enabled. 17 | */ 18 | enabled: PropTypes.bool.isRequired, 19 | /** 20 | * How the video stream should be scaled to fit its 21 | * container. 22 | */ 23 | scaleType: PropTypes.oneOf(["fit", "fill"]), 24 | }; 25 | 26 | render() { 27 | const scalesType = this.props.scaleType === "fit" ? 1 : 2; 28 | return ( 29 | 30 | {this.props.children} 31 | 32 | ); 33 | } 34 | } 35 | 36 | const RCTTWLocalVideoView = requireNativeComponent( 37 | "RCTTWLocalVideoView", 38 | TwilioVideoLocalView 39 | ); 40 | 41 | module.exports = TwilioVideoLocalView; 42 | -------------------------------------------------------------------------------- /src/TwilioVideoParticipantView.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Component for Twilio Video participant views. 3 | * 4 | * Authors: 5 | * Jonathan Chang 6 | */ 7 | 8 | import { requireNativeComponent } from "react-native"; 9 | import PropTypes from "prop-types"; 10 | import React from "react"; 11 | 12 | class TwilioRemotePreview extends React.Component { 13 | static propTypes = { 14 | trackIdentifier: PropTypes.shape({ 15 | /** 16 | * The participant's video track you want to render in the view. 17 | */ 18 | videoTrackSid: PropTypes.string.isRequired, 19 | }), 20 | onFrameDimensionsChanged: PropTypes.func, 21 | trackSid: PropTypes.string, 22 | renderToHardwareTextureAndroid: PropTypes.string, 23 | onLayout: PropTypes.string, 24 | accessibilityLiveRegion: PropTypes.string, 25 | accessibilityComponentType: PropTypes.string, 26 | importantForAccessibility: PropTypes.string, 27 | accessibilityLabel: PropTypes.string, 28 | nativeID: PropTypes.string, 29 | testID: PropTypes.string, 30 | // Whether to apply Z ordering to this view. Setting this to true will cause 31 | // this view to appear above other Twilio Video views. 32 | applyZOrder: PropTypes.bool, 33 | }; 34 | 35 | buildNativeEventWrappers() { 36 | return ["onFrameDimensionsChanged"].reduce((wrappedEvents, eventName) => { 37 | if (this.props[eventName]) { 38 | return { 39 | ...wrappedEvents, 40 | [eventName]: (data) => this.props[eventName](data.nativeEvent), 41 | }; 42 | } 43 | return wrappedEvents; 44 | }, {}); 45 | } 46 | 47 | render() { 48 | const { trackIdentifier } = this.props; 49 | return ( 50 | 55 | ); 56 | } 57 | } 58 | 59 | const NativeTwilioRemotePreview = requireNativeComponent( 60 | "RNTwilioRemotePreview", 61 | TwilioRemotePreview 62 | ); 63 | 64 | module.exports = TwilioRemotePreview; 65 | -------------------------------------------------------------------------------- /src/TwilioVideoParticipantView.ios.js: -------------------------------------------------------------------------------- 1 | // 2 | // TwilioVideoParticipantView.js 3 | // Black 4 | // 5 | // Created by Martín Fernández on 6/13/17. 6 | // 7 | // 8 | 9 | import React, { Component } from "react"; 10 | import PropTypes from "prop-types"; 11 | import { requireNativeComponent } from "react-native"; 12 | 13 | class TwilioVideoParticipantView extends Component { 14 | static propTypes = { 15 | trackIdentifier: PropTypes.shape({ 16 | /** 17 | * The participant sid. 18 | */ 19 | participantSid: PropTypes.string.isRequired, 20 | /** 21 | * The participant's video track sid you want to render in the view. 22 | */ 23 | videoTrackSid: PropTypes.string.isRequired, 24 | }), 25 | }; 26 | 27 | render() { 28 | const scalesType = this.props.scaleType === "fit" ? 1 : 2; 29 | return ( 30 | 31 | {this.props.children} 32 | 33 | ); 34 | } 35 | } 36 | 37 | const RCTTWRemoteVideoView = requireNativeComponent( 38 | "RCTTWRemoteVideoView", 39 | TwilioVideoParticipantView 40 | ); 41 | 42 | module.exports = TwilioVideoParticipantView; 43 | --------------------------------------------------------------------------------