├── .all-contributorsrc ├── .babelrc ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .npmignore ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── rnopentok │ ├── Events.java │ ├── RNOpenTokModule.java │ ├── RNOpenTokPackage.java │ ├── RNOpenTokPublisherView.java │ ├── RNOpenTokPublisherViewManager.java │ ├── RNOpenTokScreenSharingCapturer.java │ ├── RNOpenTokSessionManager.java │ ├── RNOpenTokSubscriberView.java │ ├── RNOpenTokSubscriberViewManager.java │ ├── RNOpenTokView.java │ └── RNOpenTokViewManager.java ├── example ├── App.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 │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── keystores │ │ ├── BUCK │ │ └── debug.keystore.properties │ └── settings.gradle ├── app.json ├── index.js └── ios │ ├── Podfile │ ├── Podfile.lock │ ├── example.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── example-tvOS.xcscheme │ │ └── example.xcscheme │ ├── example.xcworkspace │ └── contents.xcworkspacedata │ └── example │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ └── LaunchScreen.xib │ ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Info.plist │ └── main.m ├── ios ├── RNOpenTok.h ├── RNOpenTok.m ├── RNOpenTok.podspec ├── RNOpenTok.xcodeproj │ └── project.pbxproj ├── RNOpenTokEventEmitter.h ├── RNOpenTokEventEmitter.m ├── RNOpenTokPublisherView.h ├── RNOpenTokPublisherView.m ├── RNOpenTokPublisherViewManager.h ├── RNOpenTokPublisherViewManager.m ├── RNOpenTokScreenSharingCapturer.h ├── RNOpenTokScreenSharingCapturer.m ├── RNOpenTokSessionManager.h ├── RNOpenTokSessionManager.m ├── RNOpenTokSessionObserver.h ├── RNOpenTokSessionObserver.m ├── RNOpenTokSubscriberView.h ├── RNOpenTokSubscriberView.m ├── RNOpenTokSubscriberViewManager.h └── RNOpenTokSubscriberViewManager.m ├── jsconfig.json ├── package.json ├── src ├── NativeEventEmitter.js ├── components │ ├── PublisherView.js │ └── SubscriberView.js ├── index.js └── types.js └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "react-native-opentok", 3 | "projectOwner": "callstack", 4 | "files": [ 5 | "README.md" 6 | ], 7 | "imageSize": 100, 8 | "commit": true, 9 | "contributors": [ 10 | { 11 | "login": "mike866", 12 | "name": "Michał Chudziak", 13 | "avatar_url": "https://avatars0.githubusercontent.com/u/7837457?v=4", 14 | "profile": "https://github.com/mike866", 15 | "contributions": [ 16 | "code" 17 | ] 18 | }, 19 | { 20 | "login": "dratwas", 21 | "name": "Drapich Piotr", 22 | "avatar_url": "https://avatars2.githubusercontent.com/u/16336501?v=4", 23 | "profile": "https://github.com/dratwas", 24 | "contributions": [ 25 | "code" 26 | ] 27 | }, 28 | { 29 | "login": "grabbou", 30 | "name": "Mike Grabowski", 31 | "avatar_url": "https://avatars2.githubusercontent.com/u/2464966?v=4", 32 | "profile": "https://github.com/grabbou", 33 | "contributions": [ 34 | "code" 35 | ] 36 | }, 37 | { 38 | "login": "jukben", 39 | "name": "Jakub Beneš", 40 | "avatar_url": "https://avatars3.githubusercontent.com/u/8135252?v=4", 41 | "profile": "https://jukben.cz", 42 | "contributions": [ 43 | "code" 44 | ] 45 | }, 46 | { 47 | "login": "radko93", 48 | "name": "Radek Czemerys", 49 | "avatar_url": "https://avatars0.githubusercontent.com/u/7029942?v=4", 50 | "profile": "https://twitter.com/radko93", 51 | "contributions": [ 52 | "code" 53 | ] 54 | }, 55 | { 56 | "login": "jacobcabantomski-ct", 57 | "name": "Jacob Caban-Tomski", 58 | "avatar_url": "https://avatars1.githubusercontent.com/u/21041380?v=4", 59 | "profile": "https://github.com/jacobcabantomski-ct", 60 | "contributions": [ 61 | "code" 62 | ] 63 | }, 64 | { 65 | "login": "damiantw", 66 | "name": "damiantw", 67 | "avatar_url": "https://avatars2.githubusercontent.com/u/19997758?v=4", 68 | "profile": "https://github.com/damiantw", 69 | "contributions": [ 70 | "code" 71 | ] 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } 4 | -------------------------------------------------------------------------------- /.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 | 16 | ; Ignore polyfills 17 | .*/Libraries/polyfills/.* 18 | 19 | [include] 20 | 21 | [libs] 22 | node_modules/react-native/Libraries/react-native/react-native-interface.js 23 | node_modules/react-native/flow/ 24 | 25 | [options] 26 | emoji=true 27 | 28 | module.system=haste 29 | 30 | munge_underscores=true 31 | 32 | 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' 33 | 34 | suppress_type=$FlowIssue 35 | suppress_type=$FlowFixMe 36 | suppress_type=$FlowFixMeProps 37 | suppress_type=$FlowFixMeState 38 | suppress_type=$FixMe 39 | 40 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 41 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 42 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 43 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 44 | 45 | unsafe.enable_getters_and_setters=true 46 | 47 | [version] 48 | ^0.53.0 49 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Pods 6 | # 7 | *Pods 8 | 9 | # Xcode 10 | # 11 | build/ 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata 21 | *.xccheckout 22 | *.moved-aside 23 | DerivedData 24 | *.hmap 25 | *.ipa 26 | *.xcuserstate 27 | project.xcworkspace 28 | 29 | # Android/IJ 30 | # 31 | *.iml 32 | .idea 33 | .gradle 34 | local.properties 35 | 36 | # node.js 37 | # 38 | node_modules/ 39 | npm-debug.log 40 | 41 | # BUCK 42 | buck-out/ 43 | \.buckd/ 44 | android/app/libs 45 | android/keystores/debug.keystore 46 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE:** This repository is not maintained anymore, feel free to fork it and modify for your own convinience. If you are interested in maintaining this reposiotry, please reach out to us on Discord: 2 | [![Chat](https://img.shields.io/discord/426714625279524876.svg?style=flat-square&colorB=758ED3)](https://discord.gg/zwR2Cdh) 3 | 4 | # react-native-opentok 5 | 6 | **React Native OpenTok** is wrapper over native [TokBox OpenTok SDK](https://tokbox.com/developer/). The OpenTok platform, developed by TokBox, makes it easy to embed high-quality interactive video, voice, messaging, and screen sharing into web and mobile apps. OpenTok uses WebRTC for audio-video communications 👀🎧. For more info on how OpenTok works, check out [OpenTok Basics](https://tokbox.com/developer/guides/basics/). 7 | 8 | ## Requirements: 9 | - `react-native` >=0.49.3 10 | 11 | Supported OpenTok SDK version: 12 | - `OpenTok SDK` 2.13.+ 13 | 14 | ## Table of contents 15 | - [Installation](#installation) 16 | - [API Reference](#api-reference) 17 | - [Components](#components) 18 | - [Usage](#usage) 19 | 20 | ## Installation 21 | React native OpenTok SDK depends on native OpenTok SDK implementations. You need to integrate OpenTok SDK into your existing application. Following steps needs to be done in order to have library working correctly: 22 | 23 | Add library using `yarn` 📦 (or `npm`): 24 | 25 | ```bash 26 | yarn add react-native-opentok 27 | ``` 28 | 29 | ### iOS 30 | 31 | 1. Install [CocoaPods](https://cocoapods.org/) on your computer. 32 | 2. Within you application `ios/` directory please run `pod init`. 33 | 3. Replace content within your brand-new `Podfile` with: 34 | ```ruby 35 | source 'https://github.com/CocoaPods/Specs.git' 36 | 37 | platform :ios, '9.0' 38 | 39 | target '' do 40 | node_modules_path = '../node_modules' 41 | 42 | pod 'yoga', path: "#{node_modules_path}/react-native/ReactCommon/yoga/yoga.podspec" 43 | pod 'React', path: "#{node_modules_path}/react-native" 44 | 45 | pod 'RNOpenTok', path: "#{node_modules_path}/react-native-opentok/ios" 46 | end 47 | 48 | post_install do |installer| 49 | installer.pods_project.targets.each do |target| 50 | if target.name == "React" 51 | target.remove_from_project 52 | end 53 | end 54 | end 55 | ``` 56 | 4. Run `pod install`. 57 | 5. Open .xcworkspace file (you'll need to use it as a starting file from now on). 58 | 6. Add `OPENTOK_API_KEY` key to your `Info.plist`: 59 | ```xml 60 | OPENTOK_API_KEY 61 | YOUR_API_KEY 62 | NSCameraUsageDescription 63 | ${PRODUCT_NAME} Camera Usage 64 | NSMicrophoneUsageDescription 65 | ${PRODUCT_NAME} Microphone Usage 66 | ``` 67 | 7. Run the project 🎉. 68 | 69 | ### Android 70 | 71 | 1. Run `react-native link`. 72 | 2. Edit your `android/build.gradle` file and update *allprojects* section: 73 | ```gradle 74 | allprojects { 75 | repositories { 76 | ... 77 | // ------------------------------------------------- 78 | // Add this below the existing maven property above 79 | // ------------------------------------------------- 80 | maven { 81 | url "http://tokbox.bintray.com/maven" 82 | } 83 | } 84 | } 85 | ``` 86 | 3. Add `OPENTOK_API_KEY` to your `AndroidManifest.xml`(within `` tag): 87 | ```xml 88 | 89 | ``` 90 | 4. Run the project 🎉. 91 | 92 | ## API Reference 93 | 94 | #### setApiKey(apiKey: string): void 95 | Override Api key. 96 | ```js 97 | OpenTok.setApiKey('YOUR_API_KEY'); 98 | ``` 99 | 100 | #### connect(sessionId: string, token: string): Promise 101 | Connects to choosen session. 102 | ```js 103 | const connectToSession = async () => { 104 | try { 105 | await OpenTok.connect('YOUR_SESSION_ID', 'YOUR_TOKEN'); 106 | } catch (e) { 107 | console.log(e) 108 | } 109 | } 110 | ``` 111 | 112 | #### disconnect(sessionId: string): void 113 | Disconnects from chosen session. 114 | ```js 115 | OpenTok.disconnect('YOUR_SESSION_ID'); 116 | ``` 117 | 118 | #### disconnectAll(): void 119 | Disconnects all available sessions. 120 | ```js 121 | OpenTok.disconnectAll(); 122 | ``` 123 | 124 | #### sendSignal(sessionId: string, type: string, message: string): Promise 125 | Send signal to chosen session. 126 | ```js 127 | const connectToSession = async () => { 128 | try { 129 | await OpenTok.connect('YOUR_SESSION_ID', 'YOUR_TOKEN'); 130 | } catch (e) { 131 | console.log(e) 132 | } 133 | } 134 | ``` 135 | 136 | #### events 137 | Constants for events thrown in app. Available values: 138 | - *ON_SIGNAL_RECEIVED* 139 | - *ON_SESSION_CONNECTION_CREATED* 140 | - *ON_SESSION_CONNECTION_DESTROYED* 141 | - *ON_SESSION_DID_CONNECT* 142 | - *ON_SESSION_DID_DISCONNECT* 143 | - *ON_SESSION_DID_FAIL_WITH_ERROR* 144 | - *ON_SESSION_STREAM_CREATED* 145 | - *ON_SESSION_STREAM_DESTROYED* 146 | - *ERROR_NO_SCREEN_CAPTURE_VIEW* 147 | - *ON_ARCHIVE_STARTED_WITH_ID* 148 | - *ON_ARCHIVE_STOPPED_WITH_ID* 149 | - *ON_SESSION_DID_BEGIN_RECONNECTING* 150 | - *ON_SESSION_DID_RECONNECT* 151 | 152 | #### on(name: string, callback: Function) 153 | Event listener, for events listed above. 154 | ```js 155 | OpenTok.on(OpenTok.events.ON_SIGNAL_RECEIVED, e => console.log(e)); 156 | ``` 157 | 158 | #### removeListener(name: string): void 159 | Removes listener. 160 | ```js 161 | OpenTok.removeListener(OpenTok.events.ON_SIGNAL_RECEIVED); 162 | ``` 163 | 164 | ## Components 165 | 166 | #### Publisher 167 | Component used for publishing the video to the stream. 168 | 169 | Available props: 170 | - `sessionId: string` - ID of the session (you need to connect it before using this component). 171 | - `onPublishStart?: Function` - Invoked when publishing starts. Optional. 172 | - `onPublishStop?: () => void` - Invoked when publishing stops. Optional. 173 | - `onPublishError?: () => void` - Invoked when publish error occurs. Optional. 174 | - `mute?: boolean` - This props tells Publisher if should publish audio as well or not. Optional. Defaults to false. 175 | - `video?: boolean` - This props tells Publisher if should publish video as well or not. Optional. Defaults to true. 176 | - `videoScale?: string` - Whether the video should scale to `fill` the frame or `fit` into the frame. 177 | - `zOrderMediaOverlay?: boolean` - On android, calls SurfaceView.setZOrderMediaOverlay. Optional. Defaults to true. 178 | - `cameraDirection?: string` - Whether the camera should face `front` (towards screen) or `back` (away from screen). 179 | - `screenCapture?: boolean` - Stream screen if `true` instead of camera. 180 | - `screenCaptureSettings?: { fps?: number }` - Screen sharing settings. 181 | - `fps?: number` - Specify frames per second for a stream (default: `15`). 182 | - every [View property](https://facebook.github.io/react-native/docs/viewproptypes.html#props). 183 | 184 | Available methods: 185 | - `switchCamera()`: switches to the next camera. Goes back to first one when out of cameras. Calling this will overwrite `cameraDirection`. 186 | 187 | ```js 188 | import { Publisher } from 'react-native-opentok' 189 | { console.log('started')}} 193 | /> 194 | ``` 195 | 196 | #### Subscriber 197 | Component used for subscribing to the stream. 198 | 199 | Available props: 200 | - `sessionId: string` - ID of the session (you need to connect it before using this component). 201 | - `onSubscribeStart?: Function` - Invoked when stream starts. Optional. 202 | - `onSubscribeStop?: () => void` - Invoked when stream stops. Optional. 203 | - `onSubscribeError?: () => void` - Invoked when subscribing error occurs. Optional. 204 | - `mute?: boolean` - This props tells Subscriber if should subscribe audio as well or not. Optional. Defaults to false. 205 | - `video?: boolean` - This props tells Subscriber if should subscribe video as well or not. Optional. Defaults to true. 206 | - `videoScale?: string` - Whether the video should scale to `fill` the frame or `fit` into the frame. 207 | - `zOrderMediaOverlay?: boolean` - On android, calls SurfaceView.setZOrderMediaOverlay. Optional. Defaults to true. 208 | - every [View property](https://facebook.github.io/react-native/docs/viewproptypes.html#props). 209 | 210 | ```js 211 | import { Subscriber } from 'react-native-opentok' 212 | 213 | { console.log('started')}} 217 | /> 218 | ``` 219 | 220 | #### ScreenCapture 221 | Component used for capturing a stream of it's children for screen sharing. 222 | 223 | Everything inside this component will be streamed as long as `` has `screenCapture` prop set to `true`. 224 | 225 | Available props: 226 | - every [View property](https://facebook.github.io/react-native/docs/viewproptypes.html#props) except `nativeID`. 227 | 228 | ```js 229 | import { Publisher, ScreenCapture } from 'react-native-opentok'; 230 | 231 | 232 | {/* some children */} 233 | 234 | 235 | ``` 236 | 237 | ## Usage 238 | 239 | Simply import the library and use methods/components listed above. 240 | 241 | ```js 242 | import OpenTok from 'react-native-opentok'; 243 | ``` 244 | 245 | Check out [example project](https://github.com/callstack/react-native-opentok/tree/master/example). 246 | 247 | ### Contributors 248 | 249 | 250 | 251 | | [
Michał Chudziak](https://github.com/mike866)
[💻](https://github.com/callstack/react-native-opentok/commits?author=mike866 "Code") | [
Drapich Piotr](https://github.com/dratwas)
[💻](https://github.com/callstack/react-native-opentok/commits?author=dratwas "Code") | [
Mike Grabowski](https://github.com/grabbou)
[💻](https://github.com/callstack/react-native-opentok/commits?author=grabbou "Code") | [
Jakub Beneš](https://jukben.cz)
[💻](https://github.com/callstack/react-native-opentok/commits?author=jukben "Code") | [
Radek Czemerys](https://twitter.com/radko93)
[💻](https://github.com/callstack/react-native-opentok/commits?author=radko93 "Code") | [
Jacob Caban-Tomski](https://github.com/jacobcabantomski-ct)
[💻](https://github.com/callstack/react-native-opentok/commits?author=jacobcabantomski-ct "Code") | [
damiantw](https://github.com/damiantw)
[💻](https://github.com/callstack/react-native-opentok/commits?author=damiantw "Code") | 252 | | :---: | :---: | :---: | :---: | :---: | :---: | :---: | 253 | 254 | 255 | ### Credits 256 | 257 | Thanks to [TokBox](https://tokbox.com/) for native SDKs development. 258 | 259 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! 260 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.1' 9 | } 10 | } 11 | 12 | apply plugin: 'com.android.library' 13 | 14 | android { 15 | compileSdkVersion 23 16 | buildToolsVersion "23.0.1" 17 | 18 | defaultConfig { 19 | minSdkVersion 16 20 | targetSdkVersion 22 21 | versionCode 1 22 | versionName "1.0" 23 | } 24 | lintOptions { 25 | abortOnError false 26 | } 27 | } 28 | 29 | repositories { 30 | mavenCentral() 31 | } 32 | 33 | dependencies { 34 | compile 'com.facebook.react:react-native:+' 35 | compile 'com.opentok.android:opentok-android-sdk:2.13.+' 36 | } 37 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/Events.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | enum Events { 4 | EVENT_PUBLISH_START("onPublishStart"), 5 | EVENT_PUBLISH_STOP("onPublishStop"), 6 | EVENT_PUBLISH_ERROR("onPublishError"), 7 | EVENT_SUBSCRIBE_START("onSubscribeStart"), 8 | EVENT_SUBSCRIBE_STOP("onSubscribeStop"), 9 | EVENT_SUBSCRIBE_ERROR("onSubscribeError"), 10 | EVENT_ON_SIGNAL_RECEIVED("onSignalReceived"), 11 | ON_ARCHIVE_STARTED_WITH_ID("onArchiveStartedWithId"), 12 | ON_ARCHIVE_STOPPED_WITH_ID("onArchiveStoppedWithId"), 13 | ON_SESSION_CONNECTION_CREATED("onSessionConnectionCreated"), 14 | ON_SESSION_CONNECTION_DESTROYED("onSessionConnectionDestroyed"), 15 | ON_SESSION_DID_RECONNECTING("onSessionDidReconnect"), 16 | ON_SESSION_DID_BEGIN_RECONNECTING("onSessionDidBeginReconnecting"), 17 | ON_SESSION_DID_CONNECT("onSessionDidConnect"), 18 | ON_SESSION_DID_DISCONNECT("onSessionDidDisconnect"), 19 | ON_SESSION_DID_FAIL_WITH_ERROR("onSessionDidFailWithError"), 20 | ON_SESSION_STREAM_CREATED("onSessionStreamCreated"), 21 | ON_SESSION_STREAM_DESTROYED("onSessionStreamDestroyed"), 22 | ERROR_NO_SCREEN_CAPTURE_VIEW("errorNoScreenCaptureView"); 23 | 24 | 25 | private final String mName; 26 | 27 | Events(final String name) { 28 | mName = name; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return mName; 34 | } 35 | } -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/RNOpenTokModule.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | import com.facebook.react.bridge.ReactApplicationContext; 4 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 5 | import com.facebook.react.bridge.ReactMethod; 6 | import com.facebook.react.bridge.Promise; 7 | import com.opentok.android.Session; 8 | 9 | public class RNOpenTokModule extends ReactContextBaseJavaModule { 10 | public static final String REACT_CLASS = "RNOpenTok"; 11 | private static ReactApplicationContext reactContext = null; 12 | 13 | public RNOpenTokModule(ReactApplicationContext context) { 14 | super(context); 15 | 16 | RNOpenTokSessionManager.initSessionManager(context); 17 | reactContext = context; 18 | } 19 | 20 | @Override 21 | public String getName() { 22 | return REACT_CLASS; 23 | } 24 | 25 | @ReactMethod 26 | public void setApiKey(String apiKey) { 27 | RNOpenTokSessionManager.getSessionManager().setApiKey(apiKey); 28 | } 29 | 30 | @ReactMethod 31 | public void connect(String sessionId, String token, Promise promise) { 32 | Session session = RNOpenTokSessionManager.getSessionManager().connectToSession(sessionId, token); 33 | session.setSessionListener(RNOpenTokSessionManager.getSessionManager()); 34 | session.setSignalListener(RNOpenTokSessionManager.getSessionManager()); 35 | session.setReconnectionListener(RNOpenTokSessionManager.getSessionManager()); 36 | session.setArchiveListener(RNOpenTokSessionManager.getSessionManager()); 37 | promise.resolve(Boolean.valueOf(true)); 38 | } 39 | 40 | @ReactMethod 41 | public void disconnect(String sessionId) { 42 | RNOpenTokSessionManager.getSessionManager().disconnectSession(sessionId); 43 | } 44 | 45 | @ReactMethod 46 | public void disconnectAll() { 47 | RNOpenTokSessionManager.getSessionManager().disconnectAllSessions(); 48 | } 49 | 50 | @ReactMethod 51 | public void sendSignal(String sessionId, String type, String data, Promise promise) { 52 | Session session = RNOpenTokSessionManager.getSessionManager().getSession(sessionId); 53 | 54 | session.sendSignal(type, data); 55 | promise.resolve(Boolean.valueOf(true)); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/RNOpenTokPackage.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.uimanager.ViewManager; 11 | import com.facebook.react.bridge.JavaScriptModule; 12 | 13 | public class RNOpenTokPackage implements ReactPackage { 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactContext) { 16 | return Arrays.asList( 17 | new RNOpenTokModule(reactContext) 18 | ); 19 | } 20 | 21 | @Override 22 | public List createViewManagers(ReactApplicationContext reactContext) { 23 | return Arrays.asList( 24 | new RNOpenTokSubscriberViewManager(), 25 | new RNOpenTokPublisherViewManager() 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/RNOpenTokPublisherView.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.WritableMap; 5 | import com.facebook.react.bridge.ReadableMap; 6 | import com.facebook.react.uimanager.ThemedReactContext; 7 | import com.facebook.react.uimanager.util.ReactFindViewUtil; 8 | import com.opentok.android.BaseVideoCapturer; 9 | import com.opentok.android.OpentokError; 10 | import com.opentok.android.Session; 11 | import com.opentok.android.Stream; 12 | import com.opentok.android.Publisher; 13 | import com.opentok.android.PublisherKit; 14 | 15 | import android.hardware.Camera; 16 | import android.support.annotation.Nullable; 17 | import android.view.View; 18 | 19 | public class RNOpenTokPublisherView extends RNOpenTokView implements PublisherKit.PublisherListener { 20 | private Publisher mPublisher; 21 | private Boolean mAudioEnabled; 22 | private Boolean mVideoEnabled; 23 | private Boolean mScreenCapture; 24 | private CameraDirection mCameraDirection; 25 | private ReadableMap mScreenCaptureSettings; 26 | 27 | public enum CameraDirection { 28 | BACK, 29 | FRONT, 30 | }; 31 | 32 | public RNOpenTokPublisherView(ThemedReactContext context) { 33 | super(context); 34 | } 35 | 36 | @Override 37 | public void onAttachedToWindow() { 38 | super.onAttachedToWindow(); 39 | RNOpenTokSessionManager.getSessionManager().setPublisherListener(mSessionId, this); 40 | } 41 | 42 | @Override 43 | protected void onDetachedFromWindow() { 44 | super.onDetachedFromWindow(); 45 | RNOpenTokSessionManager.getSessionManager().removePublisherListener(mSessionId); 46 | } 47 | 48 | public void setAudio(Boolean enabled) { 49 | if (mPublisher != null) { 50 | mPublisher.setPublishAudio(enabled); 51 | } 52 | 53 | mAudioEnabled = enabled; 54 | } 55 | 56 | public void setVideo(Boolean enabled) { 57 | if (mPublisher != null) { 58 | mPublisher.setPublishVideo(enabled); 59 | } 60 | 61 | mVideoEnabled = enabled; 62 | } 63 | 64 | public void cycleCamera() { 65 | if (mPublisher != null) { 66 | mPublisher.cycleCamera(); 67 | } 68 | } 69 | 70 | public void setCameraDirection(CameraDirection cameraDirection) { 71 | mCameraDirection = cameraDirection; 72 | if (mPublisher != null) { 73 | updateCameraDirection(); 74 | } 75 | } 76 | 77 | private void updateCameraDirection() { 78 | if (mPublisher.getCapturer() instanceof BaseVideoCapturer.CaptureSwitch) { 79 | ((BaseVideoCapturer.CaptureSwitch)mPublisher.getCapturer()).swapCamera(getCameraIndex(mCameraDirection)); 80 | } 81 | } 82 | 83 | private static int getCameraIndex(CameraDirection cameraDirection) { 84 | int facing; 85 | switch (cameraDirection) { 86 | case BACK: facing = Camera.CameraInfo.CAMERA_FACING_BACK; break; 87 | case FRONT: facing = Camera.CameraInfo.CAMERA_FACING_FRONT; break; 88 | default: throw new IllegalArgumentException("Invalid cameraDirection value"); 89 | } 90 | 91 | int numCameras = Camera.getNumberOfCameras(); 92 | for (int i = 0; i < numCameras; i++) { 93 | Camera.CameraInfo info = new Camera.CameraInfo(); 94 | Camera.getCameraInfo(i, info); 95 | if (info.facing == facing) { 96 | return i; 97 | } 98 | } 99 | 100 | throw new MissingCameraException("Cannot find camera facing " + cameraDirection); 101 | } 102 | 103 | public void setScreenCapture(Boolean enabled) { 104 | mScreenCapture = enabled; 105 | 106 | // @TODO: recreate publisher on change 107 | // if (mPublisher != null) { 108 | // Session session = RNOpenTokSessionManager.getSessionManager().getSession(mSessionId); 109 | // session.unpublish(mPublisher); 110 | // RNOpenTokSessionManager.getSessionManager().removePublisherListener(mSessionId); 111 | // cleanUpPublisher(); 112 | // startPublishing(); 113 | // } 114 | } 115 | 116 | public void setScreenCaptureSettings(@Nullable ReadableMap screenCaptureSettings) { 117 | mScreenCaptureSettings = screenCaptureSettings; 118 | } 119 | 120 | private void startPublishing() { 121 | Publisher.Builder builder = new Publisher.Builder(getContext()); 122 | builder.renderer(getVideoRenderer()); 123 | 124 | if (mScreenCapture) { 125 | View captureView = ReactFindViewUtil.findView(this.getRootView(), "RN_OPENTOK_SCREEN_CAPTURE_VIEW"); 126 | if (captureView == null) { 127 | sendEvent(Events.ERROR_NO_SCREEN_CAPTURE_VIEW, null); 128 | return; 129 | } 130 | builder.capturer(new RNOpenTokScreenSharingCapturer(captureView, mScreenCaptureSettings)); 131 | } 132 | 133 | mPublisher = builder.build(); 134 | mPublisher.setPublisherListener(this); 135 | mPublisher.setPublishAudio(mAudioEnabled); 136 | mPublisher.setPublishVideo(mVideoEnabled); 137 | 138 | if (mScreenCapture) { 139 | mPublisher.setPublisherVideoType(PublisherKit.PublisherKitVideoType.PublisherKitVideoTypeScreen); 140 | mPublisher.setAudioFallbackEnabled(false); 141 | } 142 | 143 | if (mCameraDirection != null) { 144 | updateCameraDirection(); 145 | } 146 | 147 | Session session = RNOpenTokSessionManager.getSessionManager().getSession(mSessionId); 148 | session.publish(mPublisher); 149 | 150 | attachVideoView(); 151 | } 152 | 153 | private void cleanUpPublisher() { 154 | detachVideoView(); 155 | mPublisher = null; 156 | } 157 | 158 | public void onConnected(Session session) { 159 | startPublishing(); 160 | } 161 | 162 | /** Publisher listener **/ 163 | 164 | @Override 165 | public void onStreamCreated(PublisherKit publisherKit, Stream stream) { 166 | sendEvent(Events.EVENT_PUBLISH_START, Arguments.createMap()); 167 | } 168 | 169 | @Override 170 | public void onStreamDestroyed(PublisherKit publisherKit, Stream stream) { 171 | sendEvent(Events.EVENT_PUBLISH_STOP, Arguments.createMap()); 172 | cleanUpPublisher(); 173 | } 174 | 175 | @Override 176 | public void onError(PublisherKit publisherKit, OpentokError opentokError) { 177 | WritableMap payload = Arguments.createMap(); 178 | payload.putString("connectionId", opentokError.toString()); 179 | 180 | sendEvent(Events.EVENT_PUBLISH_ERROR, payload); 181 | cleanUpPublisher(); 182 | } 183 | 184 | private static class MissingCameraException extends RuntimeException { 185 | MissingCameraException(String message) { 186 | super(message); 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/RNOpenTokPublisherViewManager.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | import android.support.annotation.Nullable; 4 | 5 | import com.facebook.react.uimanager.ThemedReactContext; 6 | import com.facebook.react.uimanager.annotations.ReactProp; 7 | import com.facebook.react.bridge.ReadableMap; 8 | 9 | 10 | public class RNOpenTokPublisherViewManager extends RNOpenTokViewManager { 11 | @Override 12 | public String getName() { 13 | return "RNOpenTokPublisherView"; 14 | } 15 | 16 | @Override 17 | protected RNOpenTokPublisherView createViewInstance(ThemedReactContext reactContext) { 18 | return new RNOpenTokPublisherView(reactContext); 19 | } 20 | 21 | @ReactProp(name = "mute") 22 | public void setMute(RNOpenTokPublisherView view, Boolean mute) { 23 | view.setAudio(!mute); 24 | } 25 | 26 | @ReactProp(name = "video") 27 | public void setVideo(RNOpenTokPublisherView view, Boolean video) { 28 | view.setVideo(video); 29 | } 30 | 31 | @ReactProp(name = "camera") 32 | public void setCamera(RNOpenTokPublisherView view, Integer camera) { 33 | if (camera != 0) { 34 | view.cycleCamera(); 35 | } 36 | } 37 | 38 | @ReactProp(name = "cameraDirection") 39 | public void setCameraDirection(RNOpenTokPublisherView view, String cameraDirection) { 40 | view.setCameraDirection(RNOpenTokPublisherView.CameraDirection.valueOf(cameraDirection.toUpperCase())); 41 | } 42 | 43 | @ReactProp(name = "screenCapture") 44 | public void setScreenCapture(RNOpenTokPublisherView view, Boolean screenCapture) { 45 | view.setScreenCapture(screenCapture); 46 | } 47 | 48 | @ReactProp(name = "screenCaptureSettings") 49 | public void setScreenCaptureSettings(RNOpenTokPublisherView view, @Nullable ReadableMap screenCaptureSettings) { 50 | view.setScreenCaptureSettings(screenCaptureSettings); 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/RNOpenTokScreenSharingCapturer.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | import android.os.Handler; 6 | import android.view.View; 7 | import android.support.annotation.Nullable; 8 | 9 | import com.opentok.android.BaseVideoCapturer; 10 | import com.facebook.react.bridge.ReadableMap; 11 | 12 | 13 | public class RNOpenTokScreenSharingCapturer extends BaseVideoCapturer { 14 | 15 | private boolean capturing = false; 16 | private View mView; 17 | 18 | private int fps = 15; 19 | private int width = 0; 20 | private int height = 0; 21 | 22 | private int[] frame; 23 | private Bitmap bmp; 24 | private Canvas canvas; 25 | 26 | private Handler mHandler = new Handler(); 27 | 28 | private Runnable newFrame = new Runnable() { 29 | @Override 30 | public void run() { 31 | if (capturing) { 32 | int width = mView.getWidth(); 33 | int height = mView.getHeight(); 34 | 35 | if ( 36 | frame == null || 37 | RNOpenTokScreenSharingCapturer.this.width != width || 38 | RNOpenTokScreenSharingCapturer.this.height != height 39 | ) { 40 | RNOpenTokScreenSharingCapturer.this.width = width; 41 | RNOpenTokScreenSharingCapturer.this.height = height; 42 | 43 | if (bmp != null) { 44 | bmp.recycle(); 45 | bmp = null; 46 | } 47 | 48 | bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 49 | 50 | canvas = new Canvas(bmp); 51 | frame = new int[width * height]; 52 | } 53 | canvas.save(Canvas.MATRIX_SAVE_FLAG); 54 | canvas.translate(-mView.getScrollX(), -mView.getScrollY()); 55 | mView.draw(canvas); 56 | bmp.getPixels(frame, 0, width, 0, 0, width, height); 57 | 58 | provideIntArrayFrame(frame, ARGB, width, height, 0, false); 59 | 60 | canvas.restore(); 61 | 62 | mHandler.postDelayed(newFrame, 1000 / fps); 63 | 64 | } 65 | } 66 | }; 67 | 68 | public RNOpenTokScreenSharingCapturer(View view, @Nullable ReadableMap captureSettings) { 69 | mView = view; 70 | if (captureSettings != null) { 71 | fps = captureSettings.hasKey("fps") ? captureSettings.getInt("fps") : fps; 72 | width = captureSettings.hasKey("width") ? captureSettings.getInt("width") : width; 73 | height = captureSettings.hasKey("height") ? captureSettings.getInt("height") : height; 74 | } 75 | } 76 | 77 | @Override 78 | public void init() { 79 | 80 | } 81 | 82 | @Override 83 | public int startCapture() { 84 | capturing = true; 85 | mHandler.postDelayed(newFrame, 1000 / fps); 86 | return 0; 87 | } 88 | 89 | @Override 90 | public int stopCapture() { 91 | capturing = false; 92 | mHandler.removeCallbacks(newFrame); 93 | return 0; 94 | } 95 | 96 | @Override 97 | public boolean isCaptureStarted() { 98 | return capturing; 99 | } 100 | 101 | @Override 102 | public CaptureSettings getCaptureSettings() { 103 | CaptureSettings settings = new CaptureSettings(); 104 | settings.fps = fps; 105 | settings.width = width; 106 | settings.height = height; 107 | settings.format = ARGB; 108 | return settings; 109 | } 110 | 111 | @Override 112 | public void destroy() { 113 | 114 | } 115 | 116 | @Override 117 | public void onPause() { 118 | 119 | } 120 | 121 | @Override 122 | public void onResume() { 123 | 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/RNOpenTokSessionManager.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | import android.content.pm.ApplicationInfo; 4 | import android.content.pm.PackageManager; 5 | 6 | import com.facebook.react.bridge.Arguments; 7 | import com.facebook.react.bridge.WritableMap; 8 | import com.facebook.react.modules.core.DeviceEventManagerModule; 9 | import com.opentok.android.Connection; 10 | import com.opentok.android.OpentokError; 11 | import com.opentok.android.Session; 12 | 13 | import com.facebook.react.bridge.ReactApplicationContext; 14 | import com.opentok.android.Stream; 15 | 16 | import java.util.HashMap; 17 | 18 | 19 | public class RNOpenTokSessionManager implements Session.SessionListener, Session.SignalListener, Session.ReconnectionListener, Session.ArchiveListener{ 20 | private static RNOpenTokSessionManager instance = null; 21 | private ReactApplicationContext mContext; 22 | private String mApiKey; 23 | private HashMap mSessions; 24 | private HashMap mSubscribers; 25 | private HashMap mPublishers; 26 | 27 | private RNOpenTokSessionManager(ReactApplicationContext context, String apiKey) { 28 | this.mSessions = new HashMap<>(); 29 | this.mSubscribers = new HashMap(); 30 | this.mPublishers = new HashMap(); 31 | this.mApiKey = apiKey; 32 | this.mContext = context; 33 | } 34 | 35 | static RNOpenTokSessionManager initSessionManager(ReactApplicationContext context) { 36 | if (instance == null) { 37 | String apiKey = ""; 38 | ApplicationInfo ai = null; 39 | try { 40 | ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); 41 | apiKey = ai.metaData.get("OPENTOK_API_KEY").toString(); 42 | } catch (PackageManager.NameNotFoundException | NullPointerException e) { 43 | e.printStackTrace(); 44 | } 45 | instance = new RNOpenTokSessionManager(context, apiKey); 46 | } 47 | return instance; 48 | } 49 | 50 | static RNOpenTokSessionManager getSessionManager() { 51 | return RNOpenTokSessionManager.initSessionManager(null); 52 | } 53 | 54 | public void setApiKey(String apiKey) { 55 | this.mApiKey = apiKey; 56 | } 57 | 58 | public Session connectToSession(String sessionId, String token) { 59 | Session session = new Session(this.mContext, this.mApiKey, sessionId); 60 | session.connect(token); 61 | this.mSessions.put(sessionId, session); 62 | 63 | return session; 64 | } 65 | 66 | public Session getSession(String sessionId) { 67 | return this.mSessions.get(sessionId); 68 | } 69 | 70 | public void disconnectSession(String sessionId) { 71 | Session session = this.mSessions.get(sessionId); 72 | 73 | if (session != null) { 74 | session.disconnect(); 75 | this.mSessions.remove(sessionId); 76 | } 77 | } 78 | 79 | public void disconnectAllSessions() { 80 | for (Session session: this.mSessions.values()) { 81 | session.disconnect(); 82 | } 83 | this.mSessions.clear(); 84 | } 85 | 86 | public void setSubscriberListener (String sessionId, RNOpenTokSubscriberView subscriber) { 87 | this.mSubscribers.put(sessionId, subscriber); 88 | } 89 | 90 | public void removeSubscriberListener (String sessionId) { 91 | this.mSubscribers.remove(sessionId); 92 | } 93 | 94 | public void setPublisherListener (String sessionId, RNOpenTokPublisherView publisher) { 95 | this.mPublishers.put(sessionId, publisher); 96 | } 97 | 98 | public void removePublisherListener (String sessionId) { 99 | this.mPublishers.remove(sessionId); 100 | } 101 | 102 | @Override 103 | public void onConnected(Session session) { 104 | RNOpenTokPublisherView publisherView = this.mPublishers.get(session.getSessionId()); 105 | if( publisherView != null) { 106 | publisherView.onConnected(session); 107 | } 108 | WritableMap payload = Arguments.createMap(); 109 | payload.putString("sessionId", session.getSessionId()); 110 | 111 | mContext 112 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 113 | .emit(Events.ON_SESSION_DID_CONNECT.toString(), payload); 114 | } 115 | 116 | @Override 117 | public void onDisconnected(Session session) { 118 | WritableMap payload = Arguments.createMap(); 119 | payload.putString("sessionId", session.getSessionId()); 120 | 121 | mContext 122 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 123 | .emit(Events.ON_SESSION_DID_DISCONNECT.toString(), payload); 124 | } 125 | 126 | @Override 127 | public void onStreamReceived(Session session, Stream stream) { 128 | RNOpenTokSubscriberView subscriberView = this.mSubscribers.get(session.getSessionId()); 129 | if( subscriberView != null) { 130 | subscriberView.onStreamReceived(session, stream); 131 | } 132 | WritableMap payload = Arguments.createMap(); 133 | payload.putString("sessionId", session.getSessionId()); 134 | payload.putString("streamId", stream.getStreamId()); 135 | 136 | mContext 137 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 138 | .emit(Events.ON_SESSION_STREAM_CREATED.toString(), payload); 139 | } 140 | 141 | @Override 142 | public void onStreamDropped(Session session, Stream stream) { 143 | RNOpenTokSubscriberView subscriberView = this.mSubscribers.get(session.getSessionId()); 144 | if( subscriberView != null) { 145 | subscriberView.onStreamDropped(session, stream); 146 | } 147 | WritableMap payload = Arguments.createMap(); 148 | payload.putString("sessionId", session.getSessionId()); 149 | payload.putString("streamId", stream.getStreamId()); 150 | 151 | mContext 152 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 153 | .emit(Events.ON_SESSION_STREAM_DESTROYED.toString(), payload); 154 | } 155 | 156 | @Override 157 | public void onError(Session session, OpentokError opentokError) { 158 | WritableMap payload = Arguments.createMap(); 159 | payload.putString("sessionId", session.getSessionId()); 160 | payload.putString("error", opentokError.getMessage()); 161 | 162 | mContext 163 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 164 | .emit(Events.ON_SESSION_DID_FAIL_WITH_ERROR.toString(), payload); 165 | } 166 | 167 | @Override 168 | public void onSignalReceived(Session session, String type, String data, Connection connection) { 169 | WritableMap payload = Arguments.createMap(); 170 | payload.putString("sessionId", session.getSessionId()); 171 | payload.putString("type", type); 172 | payload.putString("data", data); 173 | 174 | mContext 175 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 176 | .emit(Events.EVENT_ON_SIGNAL_RECEIVED.toString(), payload); 177 | } 178 | 179 | @Override 180 | public void onReconnected(Session session) { 181 | WritableMap payload = Arguments.createMap(); 182 | payload.putString("sessionId", session.getSessionId()); 183 | mContext 184 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 185 | .emit(Events.ON_SESSION_DID_BEGIN_RECONNECTING.toString(), payload); 186 | } 187 | 188 | @Override 189 | public void onReconnecting(Session session) { 190 | WritableMap payload = Arguments.createMap(); 191 | payload.putString("sessionId", session.getSessionId()); 192 | mContext 193 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 194 | .emit(Events.ON_SESSION_DID_RECONNECTING.toString(), payload); 195 | } 196 | 197 | @Override 198 | public void onArchiveStarted(Session session, String id, String name) { 199 | WritableMap payload = Arguments.createMap(); 200 | payload.putString("sessionId", session.getSessionId()); 201 | payload.putString("id", id); 202 | payload.putString("name", name); 203 | mContext 204 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 205 | .emit(Events.ON_ARCHIVE_STARTED_WITH_ID.toString(), payload); 206 | } 207 | 208 | @Override 209 | public void onArchiveStopped(Session session, String id) { 210 | WritableMap payload = Arguments.createMap(); 211 | payload.putString("sessionId", session.getSessionId()); 212 | payload.putString("id", id); 213 | mContext 214 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 215 | .emit(Events.ON_ARCHIVE_STOPPED_WITH_ID.toString(), payload); 216 | } 217 | } -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/RNOpenTokSubscriberView.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.WritableMap; 5 | import com.facebook.react.uimanager.ThemedReactContext; 6 | import com.opentok.android.OpentokError; 7 | import com.opentok.android.Session; 8 | import com.opentok.android.Stream; 9 | import com.opentok.android.Subscriber; 10 | import com.opentok.android.SubscriberKit; 11 | 12 | public class RNOpenTokSubscriberView extends RNOpenTokView implements SubscriberKit.SubscriberListener { 13 | private Subscriber mSubscriber; 14 | private Boolean mAudioEnabled; 15 | private Boolean mVideoEnabled; 16 | 17 | public RNOpenTokSubscriberView(ThemedReactContext context) { 18 | super(context); 19 | } 20 | 21 | @Override 22 | public void onAttachedToWindow() { 23 | super.onAttachedToWindow(); 24 | RNOpenTokSessionManager.getSessionManager().setSubscriberListener(mSessionId, this); 25 | } 26 | 27 | public void setAudio(Boolean enabled) { 28 | if (mSubscriber != null) { 29 | mSubscriber.setSubscribeToAudio(enabled); 30 | } 31 | 32 | mAudioEnabled = enabled; 33 | } 34 | 35 | public void setVideo(Boolean enabled) { 36 | if (mSubscriber != null) { 37 | mSubscriber.setSubscribeToVideo(enabled); 38 | } 39 | 40 | mVideoEnabled = enabled; 41 | } 42 | 43 | @Override 44 | protected void onDetachedFromWindow() { 45 | super.onDetachedFromWindow(); 46 | RNOpenTokSessionManager.getSessionManager().removeSubscriberListener(mSessionId); 47 | } 48 | 49 | private void startSubscribing(Stream stream) { 50 | Subscriber.Builder builder = new Subscriber.Builder(getContext(), stream); 51 | builder.renderer(getVideoRenderer()); 52 | 53 | mSubscriber = builder.build(); 54 | mSubscriber.setSubscriberListener(this); 55 | mSubscriber.setSubscribeToAudio(mAudioEnabled); 56 | mSubscriber.setSubscribeToVideo(mVideoEnabled); 57 | 58 | Session session = RNOpenTokSessionManager.getSessionManager().getSession(mSessionId); 59 | session.subscribe(mSubscriber); 60 | 61 | attachVideoView(); 62 | } 63 | 64 | private void cleanUpSubscriber() { 65 | detachVideoView(); 66 | mSubscriber = null; 67 | } 68 | 69 | public void onStreamReceived(Session session, Stream stream) { 70 | if (mSubscriber == null) { 71 | startSubscribing(stream); 72 | sendEvent(Events.EVENT_SUBSCRIBE_START, Arguments.createMap()); 73 | } 74 | } 75 | 76 | public void onStreamDropped(Session session, Stream stream) { 77 | sendEvent(Events.EVENT_SUBSCRIBE_STOP, Arguments.createMap()); 78 | } 79 | 80 | /** Subscribe listener **/ 81 | 82 | @Override 83 | public void onConnected(SubscriberKit subscriberKit) {} 84 | 85 | @Override 86 | public void onDisconnected(SubscriberKit subscriberKit) {} 87 | 88 | @Override 89 | public void onError(SubscriberKit subscriberKit, OpentokError opentokError) { 90 | WritableMap payload = Arguments.createMap(); 91 | payload.putString("connectionId", opentokError.toString()); 92 | 93 | sendEvent(Events.EVENT_SUBSCRIBE_ERROR, payload); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/RNOpenTokSubscriberViewManager.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | import com.facebook.react.uimanager.ThemedReactContext; 4 | import com.facebook.react.uimanager.annotations.ReactProp; 5 | 6 | public class RNOpenTokSubscriberViewManager extends RNOpenTokViewManager { 7 | @Override 8 | public String getName() { 9 | return "RNOpenTokSubscriberView"; 10 | } 11 | 12 | @Override 13 | protected RNOpenTokSubscriberView createViewInstance(ThemedReactContext reactContext) { 14 | return new RNOpenTokSubscriberView(reactContext); 15 | } 16 | 17 | @ReactProp(name = "mute") 18 | public void setMute(RNOpenTokSubscriberView view, Boolean mute) { 19 | view.setAudio(!mute); 20 | } 21 | 22 | @ReactProp(name = "video") 23 | public void setVideo(RNOpenTokSubscriberView view, Boolean video) { 24 | view.setVideo(video); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/RNOpenTokView.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | import android.view.SurfaceView; 4 | import android.widget.FrameLayout; 5 | 6 | import com.facebook.react.bridge.WritableMap; 7 | import com.facebook.react.modules.core.DeviceEventManagerModule; 8 | import com.facebook.react.uimanager.ThemedReactContext; 9 | import com.opentok.android.BaseVideoRenderer; 10 | import com.opentok.android.VideoRenderFactory; 11 | 12 | public class RNOpenTokView extends FrameLayout { 13 | protected String mSessionId; 14 | private static ThemedReactContext reactContext = null; 15 | private BaseVideoRenderer renderer; 16 | 17 | public enum VideoScale { 18 | FILL, 19 | FIT, 20 | }; 21 | 22 | public RNOpenTokView(ThemedReactContext context) { 23 | super(context); 24 | 25 | reactContext = context; 26 | 27 | renderer = VideoRenderFactory.constructRenderer(getContext()); 28 | } 29 | 30 | public void setSessionId(String sessionId) { 31 | mSessionId = sessionId; 32 | } 33 | 34 | protected BaseVideoRenderer getVideoRenderer() { 35 | return renderer; 36 | } 37 | 38 | protected void attachVideoView() { 39 | addView(renderer.getView(), new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 40 | requestLayout(); 41 | } 42 | 43 | protected void detachVideoView() { 44 | removeView(renderer.getView()); 45 | } 46 | 47 | public void setVideoScale(VideoScale scaleType) { 48 | String value; 49 | 50 | switch (scaleType) { 51 | case FILL: value = BaseVideoRenderer.STYLE_VIDEO_FILL; break; 52 | case FIT: value = BaseVideoRenderer.STYLE_VIDEO_FIT; break; 53 | default: throw new IllegalArgumentException("Invalid VideoScale value"); 54 | } 55 | 56 | renderer.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, value); 57 | } 58 | 59 | public void setZOrderMediaOverlay(boolean value) { 60 | if (renderer.getView() instanceof SurfaceView) { 61 | ((SurfaceView)renderer.getView()).setZOrderMediaOverlay(value); 62 | int index = indexOfChild(renderer.getView()); 63 | if (index != -1) { 64 | removeViewAt(index); 65 | attachVideoView(); 66 | } 67 | } 68 | } 69 | 70 | protected void sendEvent(Events event, WritableMap payload) { 71 | reactContext 72 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 73 | .emit(event.toString(), payload); 74 | } 75 | 76 | /** View methods **/ 77 | 78 | @Override 79 | public void requestLayout() { 80 | super.requestLayout(); 81 | post(mLayoutRunnable); 82 | } 83 | 84 | private final Runnable mLayoutRunnable = new Runnable() { 85 | @Override 86 | public void run() { 87 | measure( 88 | MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), 89 | MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); 90 | layout(getLeft(), getTop(), getRight(), getBottom()); 91 | } 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnopentok/RNOpenTokViewManager.java: -------------------------------------------------------------------------------- 1 | package com.rnopentok; 2 | 3 | import com.facebook.react.uimanager.SimpleViewManager; 4 | import com.facebook.react.uimanager.annotations.ReactProp; 5 | 6 | /* Left here for future prop implementations. */ 7 | abstract class RNOpenTokViewManager extends SimpleViewManager { 8 | @ReactProp(name = "sessionId") 9 | public void setSessionId(T view, String sessionId) { 10 | view.setSessionId(sessionId); 11 | } 12 | 13 | @ReactProp(name = "videoScale") 14 | public void setVideoScale(T view, String videoScale) { 15 | view.setVideoScale(RNOpenTokView.VideoScale.valueOf(videoScale.toUpperCase())); 16 | } 17 | 18 | @ReactProp(name = "zOrderMediaOverlay") 19 | public void setZOrderMediaOverlay(T view, boolean value) { 20 | view.setZOrderMediaOverlay(value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/App.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, { Component } from 'react'; 4 | import { AppRegistry, StyleSheet, Button, View } from 'react-native'; 5 | 6 | import OpenTok, { Publisher } from "react-native-opentok"; // eslint-disable-line 7 | 8 | import type { Ref } from 'react'; 9 | 10 | const sessionId = 'YOUR_SESSION_ID'; 11 | const token = 'YOUR_TOKEN'; 12 | 13 | export default class App extends Component<{}> { 14 | /* $FlowFixMe we ignore the fact that componentWillMount shouldn't be async. Just for example purposes */ 15 | async componentWillMount() { 16 | await OpenTok.connect(sessionId, token); 17 | OpenTok.on(OpenTok.events.ON_SIGNAL_RECEIVED, e => console.log(e)); 18 | } 19 | 20 | ref: Ref; 21 | 22 | render() { 23 | return ( 24 | 25 |