├── App.js ├── README.md ├── android ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── app │ ├── BUCK │ ├── build.gradle │ ├── build_defs.bzl │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── myapp │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── app.json ├── babel.config.js ├── index.js ├── ios ├── Podfile ├── myApp-tvOS │ └── Info.plist ├── myApp-tvOSTests │ └── Info.plist ├── myApp.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── myApp-tvOS.xcscheme │ │ └── myApp.xcscheme ├── myApp │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Info.plist │ └── main.m └── myAppTests │ ├── Info.plist │ └── myAppTests.m ├── janus.js ├── metro.config.js └── package.json /App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | * @flow 7 | */ 8 | 9 | import React, { Fragment, Component } from 'react'; 10 | import { 11 | TouchableOpacity, 12 | Alert, Button, 13 | StyleSheet, 14 | View, 15 | Text, 16 | 17 | } from 'react-native'; 18 | 19 | 20 | import { Janus } from './janus.js'; 21 | 22 | import { 23 | RTCPeerConnection, 24 | RTCIceCandidate, 25 | RTCSessionDescription, 26 | RTCView, 27 | MediaStream, 28 | MediaStreamTrack, 29 | mediaDevices 30 | } from 'react-native-webrtc'; 31 | 32 | import { Dimensions } from 'react-native'; 33 | 34 | 35 | // also support setRemoteDescription, createAnswer, addIceCandidate, onnegotiationneeded, oniceconnectionstatechange, onsignalingstatechange, onaddstream 36 | 37 | const dimensions = Dimensions.get('window') 38 | 39 | const configuration = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] }; 40 | const pc = new RTCPeerConnection(configuration); 41 | let isFront = false; 42 | 43 | //export default 44 | class App extends React.Component { 45 | 46 | 47 | 48 | constructor(props) { 49 | super(props); 50 | 51 | this.state = { streamUrl: null }; 52 | }; 53 | 54 | 55 | componentDidMount() { 56 | console.log('componentDidMount'); 57 | 58 | mediaDevices.enumerateDevices().then(sourceInfos => { 59 | console.log(sourceInfos); 60 | let videoSourceId; 61 | for (let i = 0; i < sourceInfos.length; i++) { 62 | const sourceInfo = sourceInfos[i]; 63 | if (sourceInfo.kind == "videoinput" && sourceInfo.facing == (isFront ? "front" : "back")) { 64 | videoSourceId = sourceInfo.deviceId; 65 | } 66 | } 67 | 68 | mediaDevices.getUserMedia({ 69 | audio: true, 70 | video: { 71 | mandatory: { 72 | minWidth: 500, // Provide your own width, height and frame rate here 73 | minHeight: 1200, 74 | minFrameRate: 60 75 | }, 76 | facingMode: (isFront ? "user" : "environment"), 77 | optional: (videoSourceId ? [{ sourceId: videoSourceId }] : []) 78 | } 79 | }).then(stream => { 80 | // Got stream! 81 | // this.state.stream = stream; 82 | this.setState(previousState => ( 83 | { streamUrl: stream.toURL() } 84 | )) 85 | console.log("Got stream !") 86 | console.log("Stream ID: " + this.state.streamUrl) 87 | }).catch(error => { 88 | // Log error 89 | }); 90 | }); 91 | } 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | render() { 100 | return ( 101 | 102 | {} 103 | 104 | ); 105 | } 106 | 107 | } 108 | 109 | 110 | 111 | pc.createOffer().then(desc => { 112 | pc.setLocalDescription(desc).then(() => { 113 | // Send pc.localDescription to peer 114 | }); 115 | }); 116 | 117 | pc.onicecandidate = function (event) { 118 | // send event.candidate to peer 119 | }; 120 | 121 | var sfutest = null; 122 | let host = "10.1.7.19" 123 | let server = "http://" + host + ":8088/janus" 124 | let backHost = "http://" + host + ":3000/stream" 125 | let pin = null; 126 | let myroom = null; 127 | let myid = null; 128 | let janus = null; 129 | 130 | 131 | Janus.init({ 132 | debug: "all", callback: function () { 133 | if (started) 134 | return; 135 | started = true; 136 | } 137 | }); 138 | 139 | export default class JanusReactNative extends Component { 140 | 141 | constructor(props) { 142 | super(props); 143 | this.state = { 144 | info: 'Initializing', 145 | status: 'init', 146 | roomID: '', 147 | isFront: true, 148 | selfViewSrc: null, 149 | selfViewSrcKey: null, 150 | remoteList: {}, 151 | remoteListPluginHandle: {}, 152 | textRoomConnected: false, 153 | textRoomData: [], 154 | textRoomValue: '', 155 | publish: false, 156 | speaker: false, 157 | audioMute: false, 158 | videoMute: false, 159 | visible: false, 160 | buttonText: "Start for Janus !!!" 161 | }; 162 | this.janusStart.bind(this); 163 | this.onPressButton.bind(this); 164 | } 165 | 166 | 167 | 168 | componentDidMount() { 169 | } 170 | 171 | janusStart = () => { 172 | this.setState({ visible: true }); 173 | janus = new Janus( 174 | { 175 | server: server, 176 | success: () => { 177 | janus.attach( 178 | { 179 | plugin: "janus.plugin.videoroom", 180 | success: (pluginHandle) => { 181 | sfutest = pluginHandle; 182 | this.requestStart().then(this.registerUsername); 183 | // let register = { "request": "join", "room": 1234, "ptype": "publisher", "display": "yanhao", "id": 5035925950 }; 184 | // sfutest.send({ "message": register }); 185 | }, 186 | error: (error) => { 187 | Alert.alert(" -- Error attaching plugin...", error); 188 | }, 189 | consentDialog: (on) => { 190 | }, 191 | mediaState: (medium, on) => { 192 | }, 193 | webrtcState: (on) => { 194 | }, 195 | onmessage: (msg, jsep) => { 196 | var event = msg["videoroom"]; 197 | if (event != undefined && event != null) { 198 | if (event === "joined") { 199 | myid = msg["id"]; 200 | this.publishOwnFeed(true); 201 | this.setState({ visible: false }); 202 | if (msg["publishers"] !== undefined && msg["publishers"] !== null) { 203 | var list = msg["publishers"]; 204 | for (var f in list) { 205 | var id = list[f]["id"]; 206 | var display = list[f]["display"]; 207 | // this.newRemoteFeed(id, display) 208 | } 209 | } 210 | } else if (event === "destroyed") { 211 | } else if (event === "event") { 212 | if (msg["publishers"] !== undefined && msg["publishers"] !== null) { 213 | var list = msg["publishers"]; 214 | for (var f in list) { 215 | let id = list[f]["id"] 216 | let display = list[f]["display"] 217 | // this.newRemoteFeed(id, display) 218 | } 219 | } else if (msg["leaving"] !== undefined && msg["leaving"] !== null) { 220 | var leaving = msg["leaving"]; 221 | var remoteFeed = null; 222 | let numLeaving = parseInt(msg["leaving"]) 223 | if (this.state.remoteList.hasOwnProperty(numLeaving)) { 224 | delete this.state.remoteList.numLeaving 225 | this.setState({ remoteList: this.state.remoteList }) 226 | this.state.remoteListPluginHandle[numLeaving].detach(); 227 | delete this.state.remoteListPluginHandle.numLeaving 228 | } 229 | } else if (msg["unpublished"] !== undefined && msg["unpublished"] !== null) { 230 | var unpublished = msg["unpublished"]; 231 | if (unpublished === 'ok') { 232 | sfutest.hangup(); 233 | return; 234 | } 235 | let numLeaving = parseInt(msg["unpublished"]) 236 | if ('numLeaving' in this.state.remoteList) { 237 | delete this.state.remoteList.numLeaving 238 | this.setState({ remoteList: this.state.remoteList }) 239 | this.state.remoteListPluginHandle[numLeaving].detach(); 240 | delete this.state.remoteListPluginHandle.numLeaving 241 | } 242 | } else if (msg["error"] !== undefined && msg["error"] !== null) { 243 | } 244 | } 245 | } 246 | if (jsep !== undefined && jsep !== null) { 247 | sfutest.handleRemoteJsep({ jsep: jsep }); 248 | } 249 | }, 250 | onlocalstream: (stream) => { 251 | this.setState({ selfViewSrc: stream.toURL() }); 252 | this.setState({ selfViewSrcKey: Math.floor(Math.random() * 1000) }); 253 | this.setState({ status: 'ready', info: 'Please enter or create room ID' }); 254 | }, 255 | onremotestream: (stream) => { 256 | }, 257 | oncleanup: () => { 258 | mystream = null; 259 | } 260 | }); 261 | }, 262 | error: (error) => { 263 | Alert.alert(" Janus Error", error); 264 | }, 265 | destroyed: () => { 266 | // Alert.alert(" Success for End Call "); 267 | this.setState({ publish: false }); 268 | } 269 | }) 270 | } 271 | 272 | async registerUsername() { 273 | console.log("register user name") 274 | var username = 'yanhao'; 275 | 276 | var register = { "request": "join", "room": myroom, "ptype": "publisher", "display": username, "pin": pin, id: myid }; 277 | sfutest.send({ "message": register }); 278 | var bitrate = 2000 * 1024; 279 | sfutest.send({ "message": { "request": "configure", "bitrate": bitrate } }); 280 | } 281 | 282 | publishOwnFeed(useAudio) { 283 | if (!this.state.publish) { 284 | this.setState({ publish: true, buttonText: "Stop"}); 285 | 286 | sfutest.createOffer( 287 | { 288 | media: { audioRecv: false, videoRecv: false, audioSend: useAudio, videoSend: true }, 289 | success: (jsep) => { 290 | console.log("Create offer : success \n") 291 | var publish = { "request": "configure", "audio": useAudio, "video": true, "bitrate": 5000 * 1024 }; 292 | sfutest.send({ "message": publish, "jsep": jsep }); 293 | }, 294 | error: (error) => { 295 | Alert.alert("WebRTC error:", error); 296 | if (useAudio) { 297 | publishOwnFeed(false); 298 | } else { 299 | 300 | } 301 | } 302 | }); 303 | } else { 304 | // this.setState({ publish: false }); 305 | // let unpublish = { "request": "unpublish" }; 306 | // sfutest.send({"message": unpublish}); 307 | } 308 | } 309 | 310 | async requestStart() { 311 | await fetch(backHost, { 312 | cache: "no-cache", 313 | credentials: "omit", 314 | headers: { 315 | Accept: "application/json, text/plain, */*", 316 | "Content-Type": "application/json" 317 | }, 318 | method: "POST", 319 | body: JSON.stringify({ 320 | login: "yanhao", 321 | passwd: "1234", 322 | roomid: 5555, 323 | request: 'publish' 324 | }) 325 | }).then(response => { 326 | return response.json() 327 | }).then(data => { 328 | console.log("parse response") 329 | console.log(data) 330 | if (data.status === "success") { 331 | myroom = data.key.room 332 | pin = data.key.pin 333 | myid = data.key.id 334 | } 335 | }) 336 | } 337 | 338 | unpublishOwnFeed(){ 339 | if (this.state.publish){ 340 | this.setState({buttonText: "Start for Janus !!!"}); 341 | let unpublish = {request: "unpublish"}; 342 | sfutest.send({message: unpublish}); 343 | janus.destroy(); 344 | this.setState({ selfViewSrc: null }); 345 | } 346 | } 347 | 348 | onPressButton = () => { 349 | if (!this.state.publish){ 350 | this.janusStart(); 351 | } 352 | else { 353 | this.unpublishOwnFeed(); 354 | } 355 | } 356 | 357 | 358 | render() { 359 | return ( 360 | 361 | 362 | 363 | {this.state.buttonText} 364 | 365 | 366 | 367 | {this.state.selfViewSrc && 368 | } 371 | 372 | 373 | 374 | 375 | ); 376 | } 377 | } 378 | 379 | const styles = StyleSheet.create({ 380 | container: { 381 | paddingTop: 60, 382 | alignItems: 'center' 383 | }, 384 | button: { 385 | marginBottom: 30, 386 | width: 260, 387 | alignItems: 'center', 388 | backgroundColor: '#2196F3' 389 | }, 390 | buttonText: { 391 | padding: 20, 392 | color: 'white' 393 | } 394 | }); 395 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Janus WebRTC React Native module 2 | 3 | ## Usage 4 | 5 | 1. Follow the official instructions to install [React Native]: 6 | 7 | * Install Node 8.3 or newer 8 | 9 | * Run the command in a shell: 10 | 11 | $ npm install -g react-native-cli 12 | 13 | * Install Android Studio or Xcode for mobile application development 14 | 15 | 2. Clone this project. Install essential node modules with npm tool 16 | 17 | $ npm install 18 | 19 | 3. Check the IP address of your computer. Set the parameter of your server host address 20 | (in `App.js` => `let host = "10.1.7.19"`). 21 | 22 | 4. Connect your device to your computer. Build the app and install it on your mobile device. 23 | The following code is for an android device. 24 | 25 | $ react-native start 26 | $ react-native run-android 27 | 28 | 4. Launch Janus server, backend server and Nginx server. (See [janus-demo-docker]) 29 | 30 | 5. Open this app in your mobile device. Press the button to communicate with Janus server and backend server. 31 | 32 | 6. Check the log of your Docker server to see if connection is successful. 33 | 34 | [React Native]: https://facebook.github.io/react-native/docs/getting-started 35 | 36 | [janus-demo-docker]: https://github.com/MinesNicaicai/janus-demo-docker 37 | 38 | ## Extension 39 | 40 | The `janus.js` and `App.js` are based on a [project] which is no longer maintained and cannot work due to 41 | obsolete APIs. By updating the use of some APIs, this demo can work now but needs amending. 42 | 43 | [project]: https://github.com/atyenoria/react-native-webrtc-janus-gateway 44 | 45 | Please regularly check the updating of the related library [react-native-webrtc] 46 | 47 | [react-native-webrtc]: https://github.com/react-native-webrtc/react-native-webrtc -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir= 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.myapp", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.myapp", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /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 | * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format 22 | * bundleCommand: "ram-bundle", 23 | * 24 | * // whether to bundle JS and assets in debug mode 25 | * bundleInDebug: false, 26 | * 27 | * // whether to bundle JS and assets in release mode 28 | * bundleInRelease: true, 29 | * 30 | * // whether to bundle JS and assets in another build variant (if configured). 31 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 32 | * // The configuration property can be in the following formats 33 | * // 'bundleIn${productFlavor}${buildType}' 34 | * // 'bundleIn${buildType}' 35 | * // bundleInFreeDebug: true, 36 | * // bundleInPaidRelease: true, 37 | * // bundleInBeta: true, 38 | * 39 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 40 | * // for example: to disable dev mode in the staging build type (if configured) 41 | * devDisabledInStaging: true, 42 | * // The configuration property can be in the following formats 43 | * // 'devDisabledIn${productFlavor}${buildType}' 44 | * // 'devDisabledIn${buildType}' 45 | * 46 | * // the root of your project, i.e. where "package.json" lives 47 | * root: "../../", 48 | * 49 | * // where to put the JS bundle asset in debug mode 50 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 51 | * 52 | * // where to put the JS bundle asset in release mode 53 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 54 | * 55 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 56 | * // require('./image.png')), in debug mode 57 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 58 | * 59 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 60 | * // require('./image.png')), in release mode 61 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 62 | * 63 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 64 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 65 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 66 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 67 | * // for example, you might want to remove it from here. 68 | * inputExcludes: ["android/**", "ios/**"], 69 | * 70 | * // override which node gets called and with what additional arguments 71 | * nodeExecutableAndArgs: ["node"], 72 | * 73 | * // supply additional arguments to the packager 74 | * extraPackagerArgs: [] 75 | * ] 76 | */ 77 | 78 | project.ext.react = [ 79 | entryFile: "index.js", 80 | enableHermes: false, // clean and rebuild if changing 81 | ] 82 | 83 | apply from: "../../node_modules/react-native/react.gradle" 84 | 85 | /** 86 | * Set this to true to create two separate APKs instead of one: 87 | * - An APK that only works on ARM devices 88 | * - An APK that only works on x86 devices 89 | * The advantage is the size of the APK is reduced by about 4MB. 90 | * Upload all the APKs to the Play Store and people will download 91 | * the correct one based on the CPU architecture of their device. 92 | */ 93 | def enableSeparateBuildPerCPUArchitecture = false 94 | 95 | /** 96 | * Run Proguard to shrink the Java bytecode in release builds. 97 | */ 98 | def enableProguardInReleaseBuilds = false 99 | 100 | /** 101 | * The preferred build flavor of JavaScriptCore. 102 | * 103 | * For example, to use the international variant, you can use: 104 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 105 | * 106 | * The international variant includes ICU i18n library and necessary data 107 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 108 | * give correct results when using with locales other than en-US. Note that 109 | * this variant is about 6MiB larger per architecture than default. 110 | */ 111 | def jscFlavor = 'org.webkit:android-jsc:+' 112 | 113 | /** 114 | * Whether to enable the Hermes VM. 115 | * 116 | * This should be set on project.ext.react and mirrored here. If it is not set 117 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode 118 | * and the benefits of using Hermes will therefore be sharply reduced. 119 | */ 120 | def enableHermes = project.ext.react.get("enableHermes", false); 121 | 122 | android { 123 | compileSdkVersion rootProject.ext.compileSdkVersion 124 | 125 | compileOptions { 126 | sourceCompatibility JavaVersion.VERSION_1_8 127 | targetCompatibility JavaVersion.VERSION_1_8 128 | } 129 | 130 | defaultConfig { 131 | applicationId "com.myapp" 132 | minSdkVersion rootProject.ext.minSdkVersion 133 | targetSdkVersion rootProject.ext.targetSdkVersion 134 | versionCode 1 135 | versionName "1.0" 136 | } 137 | splits { 138 | abi { 139 | reset() 140 | enable enableSeparateBuildPerCPUArchitecture 141 | universalApk false // If true, also generate a universal APK 142 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 143 | } 144 | } 145 | signingConfigs { 146 | debug { 147 | storeFile file('debug.keystore') 148 | storePassword 'android' 149 | keyAlias 'androiddebugkey' 150 | keyPassword 'android' 151 | } 152 | } 153 | buildTypes { 154 | debug { 155 | signingConfig signingConfigs.debug 156 | } 157 | release { 158 | // Caution! In production, you need to generate your own keystore file. 159 | // see https://facebook.github.io/react-native/docs/signed-apk-android. 160 | signingConfig signingConfigs.debug 161 | minifyEnabled enableProguardInReleaseBuilds 162 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 163 | } 164 | } 165 | // applicationVariants are e.g. debug, release 166 | applicationVariants.all { variant -> 167 | variant.outputs.each { output -> 168 | // For each separate APK per architecture, set a unique version code as described here: 169 | // https://developer.android.com/studio/build/configure-apk-splits.html 170 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] 171 | def abi = output.getFilter(OutputFile.ABI) 172 | if (abi != null) { // null for the universal-debug, universal-release variants 173 | output.versionCodeOverride = 174 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 175 | } 176 | 177 | } 178 | } 179 | 180 | packagingOptions { 181 | pickFirst '**/armeabi-v7a/libc++_shared.so' 182 | pickFirst '**/x86/libc++_shared.so' 183 | pickFirst '**/arm64-v8a/libc++_shared.so' 184 | pickFirst '**/x86_64/libc++_shared.so' 185 | pickFirst '**/x86/libjsc.so' 186 | pickFirst '**/armeabi-v7a/libjsc.so' 187 | } 188 | } 189 | 190 | dependencies { 191 | implementation fileTree(dir: "libs", include: ["*.jar"]) 192 | implementation "com.facebook.react:react-native:+" // From node_modules 193 | 194 | if (enableHermes) { 195 | def hermesPath = "../../node_modules/hermesvm/android/"; 196 | debugImplementation files(hermesPath + "hermes-debug.aar") 197 | releaseImplementation files(hermesPath + "hermes-release.aar") 198 | } else { 199 | implementation jscFlavor 200 | } 201 | } 202 | 203 | // Run this once to be able to run the application with BUCK 204 | // puts all compile dependencies into folder libs for BUCK to use 205 | task copyDownloadableDepsToLibs(type: Copy) { 206 | from configurations.compile 207 | into 'libs' 208 | } 209 | 210 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 211 | -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/myapp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.myapp; 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 "myApp"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/myapp/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.myapp; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.facebook.react.PackageList; 7 | import com.facebook.hermes.reactexecutor.HermesExecutorFactory; 8 | import com.facebook.react.bridge.JavaScriptExecutorFactory; 9 | import com.facebook.react.ReactApplication; 10 | import com.facebook.react.ReactNativeHost; 11 | import com.facebook.react.ReactPackage; 12 | import com.facebook.soloader.SoLoader; 13 | import com.oney.WebRTCModule.WebRTCModulePackage; 14 | 15 | import java.util.List; 16 | 17 | public class MainApplication extends Application implements ReactApplication { 18 | 19 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 20 | @Override 21 | public boolean getUseDeveloperSupport() { 22 | return BuildConfig.DEBUG; 23 | } 24 | 25 | @Override 26 | protected List getPackages() { 27 | @SuppressWarnings("UnnecessaryLocalVariable") 28 | List packages = new PackageList(this).getPackages(); 29 | // Packages that cannot be autolinked yet can be added manually here, for example: 30 | // packages.add(new MyReactNativePackage()); 31 | return packages; 32 | } 33 | 34 | @Override 35 | protected String getJSMainModuleName() { 36 | return "index"; 37 | } 38 | }; 39 | 40 | @Override 41 | public ReactNativeHost getReactNativeHost() { 42 | return mReactNativeHost; 43 | } 44 | 45 | @Override 46 | public void onCreate() { 47 | super.onCreate(); 48 | SoLoader.init(this, /* native exopackage */ false); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | myApp 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "28.0.3" 6 | minSdkVersion = 16 7 | compileSdkVersion = 28 8 | targetSdkVersion = 28 9 | supportLibVersion = "28.0.0" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath("com.android.tools.build:gradle:3.4.1") 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | mavenLocal() 26 | maven { 27 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 28 | url("$rootDir/../node_modules/react-native/android") 29 | } 30 | maven { 31 | // Android JSC is installed from npm 32 | url("$rootDir/../node_modules/jsc-android/dist") 33 | } 34 | 35 | google() 36 | jcenter() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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 | android.enableJetifier=true 22 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-yanhao/janus-webrtc-react-native/0cce2d5cb9440ff43dffb8f45058a234d5a237eb/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin, switch paths to Windows format before running java 129 | if $cygwin ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem http://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'myApp' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':WebRTCModule', ':app' 4 | project(':WebRTCModule').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android') -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myApp", 3 | "displayName": "myApp" 4 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | target 'myApp' do 5 | # Pods for myApp 6 | pod 'React', :path => '../node_modules/react-native/' 7 | pod 'React-Core', :path => '../node_modules/react-native/React' 8 | pod 'React-DevSupport', :path => '../node_modules/react-native/React' 9 | pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS' 10 | pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' 11 | pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' 12 | pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' 13 | pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' 14 | pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' 15 | pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings' 16 | pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text' 17 | pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration' 18 | pod 'React-RCTWebSocket', :path => '../node_modules/react-native/Libraries/WebSocket' 19 | 20 | pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' 21 | pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi' 22 | pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor' 23 | pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector' 24 | pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga' 25 | 26 | pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' 27 | pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' 28 | pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' 29 | 30 | target 'myAppTests' do 31 | inherit! :search_paths 32 | # Pods for testing 33 | end 34 | 35 | use_native_modules! 36 | end 37 | 38 | target 'myApp-tvOS' do 39 | # Pods for myApp-tvOS 40 | 41 | target 'myApp-tvOSTests' do 42 | inherit! :search_paths 43 | # Pods for testing 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /ios/myApp-tvOS/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSExceptionDomains 28 | 29 | localhost 30 | 31 | NSExceptionAllowsInsecureHTTPLoads 32 | 33 | 34 | 35 | 36 | NSLocationWhenInUseUsageDescription 37 | 38 | UILaunchStoryboardName 39 | LaunchScreen 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UIViewControllerBasedStatusBarAppearance 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ios/myApp-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 | -------------------------------------------------------------------------------- /ios/myApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 00E356F31AD99517003FC87E /* myAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* myAppTests.m */; }; 11 | 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 12 | 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13 | 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 14 | 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 15 | 2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 16 | 2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 17 | 2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 18 | 2DCD954D1E0B4F2C00145EB5 /* myAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* myAppTests.m */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 13B07F861A680F5B00A75B9A; 27 | remoteInfo = myApp; 28 | }; 29 | 2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 2D02E47A1E0B4A5D006451C7; 34 | remoteInfo = "myApp-tvOS"; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 40 | 00E356EE1AD99517003FC87E /* myAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = myAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 00E356F21AD99517003FC87E /* myAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = myAppTests.m; sourceTree = ""; }; 43 | 13B07F961A680F5B00A75B9A /* myApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = myApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = myApp/AppDelegate.h; sourceTree = ""; }; 45 | 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = myApp/AppDelegate.m; sourceTree = ""; }; 46 | 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 47 | 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = myApp/Images.xcassets; sourceTree = ""; }; 48 | 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = myApp/Info.plist; sourceTree = ""; }; 49 | 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = myApp/main.m; sourceTree = ""; }; 50 | 2D02E47B1E0B4A5D006451C7 /* myApp-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "myApp-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 2D02E4901E0B4A5D006451C7 /* myApp-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "myApp-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 53 | ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 00E356EB1AD99517003FC87E /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | 2D02E4781E0B4A5D006451C7 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | 2D02E48D1E0B4A5D006451C7 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 00E356EF1AD99517003FC87E /* myAppTests */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 00E356F21AD99517003FC87E /* myAppTests.m */, 92 | 00E356F01AD99517003FC87E /* Supporting Files */, 93 | ); 94 | path = myAppTests; 95 | sourceTree = ""; 96 | }; 97 | 00E356F01AD99517003FC87E /* Supporting Files */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 00E356F11AD99517003FC87E /* Info.plist */, 101 | ); 102 | name = "Supporting Files"; 103 | sourceTree = ""; 104 | }; 105 | 13B07FAE1A68108700A75B9A /* myApp */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 109 | 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 110 | 13B07FB01A68108700A75B9A /* AppDelegate.m */, 111 | 13B07FB51A68108700A75B9A /* Images.xcassets */, 112 | 13B07FB61A68108700A75B9A /* Info.plist */, 113 | 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, 114 | 13B07FB71A68108700A75B9A /* main.m */, 115 | ); 116 | name = myApp; 117 | sourceTree = ""; 118 | }; 119 | 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | ED297162215061F000B7C4FE /* JavaScriptCore.framework */, 123 | ED2971642150620600B7C4FE /* JavaScriptCore.framework */, 124 | ); 125 | name = Frameworks; 126 | sourceTree = ""; 127 | }; 128 | 832341AE1AAA6A7D00B99B32 /* Libraries */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | ); 132 | name = Libraries; 133 | sourceTree = ""; 134 | }; 135 | 83CBB9F61A601CBA00E9B192 = { 136 | isa = PBXGroup; 137 | children = ( 138 | 13B07FAE1A68108700A75B9A /* myApp */, 139 | 832341AE1AAA6A7D00B99B32 /* Libraries */, 140 | 00E356EF1AD99517003FC87E /* myAppTests */, 141 | 83CBBA001A601CBA00E9B192 /* Products */, 142 | 2D16E6871FA4F8E400B85C8A /* Frameworks */, 143 | ); 144 | indentWidth = 2; 145 | sourceTree = ""; 146 | tabWidth = 2; 147 | usesTabs = 0; 148 | }; 149 | 83CBBA001A601CBA00E9B192 /* Products */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 13B07F961A680F5B00A75B9A /* myApp.app */, 153 | 00E356EE1AD99517003FC87E /* myAppTests.xctest */, 154 | 2D02E47B1E0B4A5D006451C7 /* myApp-tvOS.app */, 155 | 2D02E4901E0B4A5D006451C7 /* myApp-tvOSTests.xctest */, 156 | ); 157 | name = Products; 158 | sourceTree = ""; 159 | }; 160 | /* End PBXGroup section */ 161 | 162 | /* Begin PBXNativeTarget section */ 163 | 00E356ED1AD99517003FC87E /* myAppTests */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "myAppTests" */; 166 | buildPhases = ( 167 | 00E356EA1AD99517003FC87E /* Sources */, 168 | 00E356EB1AD99517003FC87E /* Frameworks */, 169 | 00E356EC1AD99517003FC87E /* Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | 00E356F51AD99517003FC87E /* PBXTargetDependency */, 175 | ); 176 | name = myAppTests; 177 | productName = myAppTests; 178 | productReference = 00E356EE1AD99517003FC87E /* myAppTests.xctest */; 179 | productType = "com.apple.product-type.bundle.unit-test"; 180 | }; 181 | 13B07F861A680F5B00A75B9A /* myApp */ = { 182 | isa = PBXNativeTarget; 183 | buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "myApp" */; 184 | buildPhases = ( 185 | FD10A7F022414F080027D42C /* Start Packager */, 186 | 13B07F871A680F5B00A75B9A /* Sources */, 187 | 13B07F8C1A680F5B00A75B9A /* Frameworks */, 188 | 13B07F8E1A680F5B00A75B9A /* Resources */, 189 | 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | ); 195 | name = myApp; 196 | productName = "myApp"; 197 | productReference = 13B07F961A680F5B00A75B9A /* myApp.app */; 198 | productType = "com.apple.product-type.application"; 199 | }; 200 | 2D02E47A1E0B4A5D006451C7 /* myApp-tvOS */ = { 201 | isa = PBXNativeTarget; 202 | buildConfigurationList = 2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "myApp-tvOS" */; 203 | buildPhases = ( 204 | FD10A7F122414F3F0027D42C /* Start Packager */, 205 | 2D02E4771E0B4A5D006451C7 /* Sources */, 206 | 2D02E4781E0B4A5D006451C7 /* Frameworks */, 207 | 2D02E4791E0B4A5D006451C7 /* Resources */, 208 | 2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */, 209 | ); 210 | buildRules = ( 211 | ); 212 | dependencies = ( 213 | ); 214 | name = "myApp-tvOS"; 215 | productName = "myApp-tvOS"; 216 | productReference = 2D02E47B1E0B4A5D006451C7 /* myApp-tvOS.app */; 217 | productType = "com.apple.product-type.application"; 218 | }; 219 | 2D02E48F1E0B4A5D006451C7 /* myApp-tvOSTests */ = { 220 | isa = PBXNativeTarget; 221 | buildConfigurationList = 2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "myApp-tvOSTests" */; 222 | buildPhases = ( 223 | 2D02E48C1E0B4A5D006451C7 /* Sources */, 224 | 2D02E48D1E0B4A5D006451C7 /* Frameworks */, 225 | 2D02E48E1E0B4A5D006451C7 /* Resources */, 226 | ); 227 | buildRules = ( 228 | ); 229 | dependencies = ( 230 | 2D02E4921E0B4A5D006451C7 /* PBXTargetDependency */, 231 | ); 232 | name = "myApp-tvOSTests"; 233 | productName = "myApp-tvOSTests"; 234 | productReference = 2D02E4901E0B4A5D006451C7 /* myApp-tvOSTests.xctest */; 235 | productType = "com.apple.product-type.bundle.unit-test"; 236 | }; 237 | /* End PBXNativeTarget section */ 238 | 239 | /* Begin PBXProject section */ 240 | 83CBB9F71A601CBA00E9B192 /* Project object */ = { 241 | isa = PBXProject; 242 | attributes = { 243 | LastUpgradeCheck = 0940; 244 | ORGANIZATIONNAME = Facebook; 245 | TargetAttributes = { 246 | 00E356ED1AD99517003FC87E = { 247 | CreatedOnToolsVersion = 6.2; 248 | TestTargetID = 13B07F861A680F5B00A75B9A; 249 | }; 250 | 2D02E47A1E0B4A5D006451C7 = { 251 | CreatedOnToolsVersion = 8.2.1; 252 | ProvisioningStyle = Automatic; 253 | }; 254 | 2D02E48F1E0B4A5D006451C7 = { 255 | CreatedOnToolsVersion = 8.2.1; 256 | ProvisioningStyle = Automatic; 257 | TestTargetID = 2D02E47A1E0B4A5D006451C7; 258 | }; 259 | }; 260 | }; 261 | buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "myApp" */; 262 | compatibilityVersion = "Xcode 3.2"; 263 | developmentRegion = English; 264 | hasScannedForEncodings = 0; 265 | knownRegions = ( 266 | en, 267 | Base, 268 | ); 269 | mainGroup = 83CBB9F61A601CBA00E9B192; 270 | productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; 271 | projectDirPath = ""; 272 | projectRoot = ""; 273 | targets = ( 274 | 13B07F861A680F5B00A75B9A /* myApp */, 275 | 00E356ED1AD99517003FC87E /* myAppTests */, 276 | 2D02E47A1E0B4A5D006451C7 /* myApp-tvOS */, 277 | 2D02E48F1E0B4A5D006451C7 /* myApp-tvOSTests */, 278 | ); 279 | }; 280 | /* End PBXProject section */ 281 | 282 | /* Begin PBXResourcesBuildPhase section */ 283 | 00E356EC1AD99517003FC87E /* Resources */ = { 284 | isa = PBXResourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | 13B07F8E1A680F5B00A75B9A /* Resources */ = { 291 | isa = PBXResourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 295 | 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | 2D02E4791E0B4A5D006451C7 /* Resources */ = { 300 | isa = PBXResourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | 2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | 2D02E48E1E0B4A5D006451C7 /* Resources */ = { 308 | isa = PBXResourcesBuildPhase; 309 | buildActionMask = 2147483647; 310 | files = ( 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | /* End PBXResourcesBuildPhase section */ 315 | 316 | /* Begin PBXShellScriptBuildPhase section */ 317 | 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { 318 | isa = PBXShellScriptBuildPhase; 319 | buildActionMask = 2147483647; 320 | files = ( 321 | ); 322 | inputPaths = ( 323 | ); 324 | name = "Bundle React Native code and images"; 325 | outputPaths = ( 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | shellPath = /bin/sh; 329 | shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; 330 | }; 331 | 2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */ = { 332 | isa = PBXShellScriptBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | ); 336 | inputPaths = ( 337 | ); 338 | name = "Bundle React Native Code And Images"; 339 | outputPaths = ( 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | shellPath = /bin/sh; 343 | shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; 344 | }; 345 | FD10A7F022414F080027D42C /* Start Packager */ = { 346 | isa = PBXShellScriptBuildPhase; 347 | buildActionMask = 2147483647; 348 | files = ( 349 | ); 350 | inputFileListPaths = ( 351 | ); 352 | inputPaths = ( 353 | ); 354 | name = "Start Packager"; 355 | outputFileListPaths = ( 356 | ); 357 | outputPaths = ( 358 | ); 359 | runOnlyForDeploymentPostprocessing = 0; 360 | shellPath = /bin/sh; 361 | shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; 362 | showEnvVarsInLog = 0; 363 | }; 364 | FD10A7F122414F3F0027D42C /* Start Packager */ = { 365 | isa = PBXShellScriptBuildPhase; 366 | buildActionMask = 2147483647; 367 | files = ( 368 | ); 369 | inputFileListPaths = ( 370 | ); 371 | inputPaths = ( 372 | ); 373 | name = "Start Packager"; 374 | outputFileListPaths = ( 375 | ); 376 | outputPaths = ( 377 | ); 378 | runOnlyForDeploymentPostprocessing = 0; 379 | shellPath = /bin/sh; 380 | shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; 381 | showEnvVarsInLog = 0; 382 | }; 383 | /* End PBXShellScriptBuildPhase section */ 384 | 385 | /* Begin PBXSourcesBuildPhase section */ 386 | 00E356EA1AD99517003FC87E /* Sources */ = { 387 | isa = PBXSourcesBuildPhase; 388 | buildActionMask = 2147483647; 389 | files = ( 390 | 00E356F31AD99517003FC87E /* myAppTests.m in Sources */, 391 | ); 392 | runOnlyForDeploymentPostprocessing = 0; 393 | }; 394 | 13B07F871A680F5B00A75B9A /* Sources */ = { 395 | isa = PBXSourcesBuildPhase; 396 | buildActionMask = 2147483647; 397 | files = ( 398 | 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, 399 | 13B07FC11A68108700A75B9A /* main.m in Sources */, 400 | ); 401 | runOnlyForDeploymentPostprocessing = 0; 402 | }; 403 | 2D02E4771E0B4A5D006451C7 /* Sources */ = { 404 | isa = PBXSourcesBuildPhase; 405 | buildActionMask = 2147483647; 406 | files = ( 407 | 2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */, 408 | 2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */, 409 | ); 410 | runOnlyForDeploymentPostprocessing = 0; 411 | }; 412 | 2D02E48C1E0B4A5D006451C7 /* Sources */ = { 413 | isa = PBXSourcesBuildPhase; 414 | buildActionMask = 2147483647; 415 | files = ( 416 | 2DCD954D1E0B4F2C00145EB5 /* myAppTests.m in Sources */, 417 | ); 418 | runOnlyForDeploymentPostprocessing = 0; 419 | }; 420 | /* End PBXSourcesBuildPhase section */ 421 | 422 | /* Begin PBXTargetDependency section */ 423 | 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { 424 | isa = PBXTargetDependency; 425 | target = 13B07F861A680F5B00A75B9A /* myApp */; 426 | targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; 427 | }; 428 | 2D02E4921E0B4A5D006451C7 /* PBXTargetDependency */ = { 429 | isa = PBXTargetDependency; 430 | target = 2D02E47A1E0B4A5D006451C7 /* myApp-tvOS */; 431 | targetProxy = 2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */; 432 | }; 433 | /* End PBXTargetDependency section */ 434 | 435 | /* Begin PBXVariantGroup section */ 436 | 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { 437 | isa = PBXVariantGroup; 438 | children = ( 439 | 13B07FB21A68108700A75B9A /* Base */, 440 | ); 441 | name = LaunchScreen.xib; 442 | path = myApp; 443 | sourceTree = ""; 444 | }; 445 | /* End PBXVariantGroup section */ 446 | 447 | /* Begin XCBuildConfiguration section */ 448 | 00E356F61AD99517003FC87E /* Debug */ = { 449 | isa = XCBuildConfiguration; 450 | buildSettings = { 451 | BUNDLE_LOADER = "$(TEST_HOST)"; 452 | GCC_PREPROCESSOR_DEFINITIONS = ( 453 | "DEBUG=1", 454 | "$(inherited)", 455 | ); 456 | INFOPLIST_FILE = myAppTests/Info.plist; 457 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 458 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 459 | OTHER_LDFLAGS = ( 460 | "-ObjC", 461 | "-lc++", 462 | "$(inherited)", 463 | ); 464 | PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/myApp.app/myApp"; 467 | }; 468 | name = Debug; 469 | }; 470 | 00E356F71AD99517003FC87E /* Release */ = { 471 | isa = XCBuildConfiguration; 472 | buildSettings = { 473 | BUNDLE_LOADER = "$(TEST_HOST)"; 474 | COPY_PHASE_STRIP = NO; 475 | INFOPLIST_FILE = myAppTests/Info.plist; 476 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 477 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 478 | OTHER_LDFLAGS = ( 479 | "-ObjC", 480 | "-lc++", 481 | "$(inherited)", 482 | ); 483 | PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; 484 | PRODUCT_NAME = "$(TARGET_NAME)"; 485 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/myApp.app/myApp"; 486 | }; 487 | name = Release; 488 | }; 489 | 13B07F941A680F5B00A75B9A /* Debug */ = { 490 | isa = XCBuildConfiguration; 491 | buildSettings = { 492 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 493 | CURRENT_PROJECT_VERSION = 1; 494 | DEAD_CODE_STRIPPING = NO; 495 | INFOPLIST_FILE = myApp/Info.plist; 496 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 497 | OTHER_LDFLAGS = ( 498 | "$(inherited)", 499 | "-ObjC", 500 | "-lc++", 501 | ); 502 | PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; 503 | PRODUCT_NAME = myApp; 504 | VERSIONING_SYSTEM = "apple-generic"; 505 | }; 506 | name = Debug; 507 | }; 508 | 13B07F951A680F5B00A75B9A /* Release */ = { 509 | isa = XCBuildConfiguration; 510 | buildSettings = { 511 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 512 | CURRENT_PROJECT_VERSION = 1; 513 | INFOPLIST_FILE = myApp/Info.plist; 514 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 515 | OTHER_LDFLAGS = ( 516 | "$(inherited)", 517 | "-ObjC", 518 | "-lc++", 519 | ); 520 | PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; 521 | PRODUCT_NAME = myApp; 522 | VERSIONING_SYSTEM = "apple-generic"; 523 | }; 524 | name = Release; 525 | }; 526 | 2D02E4971E0B4A5E006451C7 /* Debug */ = { 527 | isa = XCBuildConfiguration; 528 | buildSettings = { 529 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 530 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 531 | CLANG_ANALYZER_NONNULL = YES; 532 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 533 | CLANG_WARN_INFINITE_RECURSION = YES; 534 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 535 | DEBUG_INFORMATION_FORMAT = dwarf; 536 | ENABLE_TESTABILITY = YES; 537 | GCC_NO_COMMON_BLOCKS = YES; 538 | INFOPLIST_FILE = "myApp-tvOS/Info.plist"; 539 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 540 | OTHER_LDFLAGS = ( 541 | "$(inherited)", 542 | "-ObjC", 543 | "-lc++", 544 | ); 545 | PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.myApp-tvOS"; 546 | PRODUCT_NAME = "$(TARGET_NAME)"; 547 | SDKROOT = appletvos; 548 | TARGETED_DEVICE_FAMILY = 3; 549 | TVOS_DEPLOYMENT_TARGET = 9.2; 550 | }; 551 | name = Debug; 552 | }; 553 | 2D02E4981E0B4A5E006451C7 /* Release */ = { 554 | isa = XCBuildConfiguration; 555 | buildSettings = { 556 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 557 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 558 | CLANG_ANALYZER_NONNULL = YES; 559 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 560 | CLANG_WARN_INFINITE_RECURSION = YES; 561 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 562 | COPY_PHASE_STRIP = NO; 563 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 564 | GCC_NO_COMMON_BLOCKS = YES; 565 | INFOPLIST_FILE = "myApp-tvOS/Info.plist"; 566 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 567 | OTHER_LDFLAGS = ( 568 | "$(inherited)", 569 | "-ObjC", 570 | "-lc++", 571 | ); 572 | PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.myApp-tvOS"; 573 | PRODUCT_NAME = "$(TARGET_NAME)"; 574 | SDKROOT = appletvos; 575 | TARGETED_DEVICE_FAMILY = 3; 576 | TVOS_DEPLOYMENT_TARGET = 9.2; 577 | }; 578 | name = Release; 579 | }; 580 | 2D02E4991E0B4A5E006451C7 /* Debug */ = { 581 | isa = XCBuildConfiguration; 582 | buildSettings = { 583 | BUNDLE_LOADER = "$(TEST_HOST)"; 584 | CLANG_ANALYZER_NONNULL = YES; 585 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 586 | CLANG_WARN_INFINITE_RECURSION = YES; 587 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 588 | DEBUG_INFORMATION_FORMAT = dwarf; 589 | ENABLE_TESTABILITY = YES; 590 | GCC_NO_COMMON_BLOCKS = YES; 591 | INFOPLIST_FILE = "myApp-tvOSTests/Info.plist"; 592 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 593 | OTHER_LDFLAGS = ( 594 | "$(inherited)", 595 | "-ObjC", 596 | "-lc++", 597 | ); 598 | PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.myApp-tvOSTests"; 599 | PRODUCT_NAME = "$(TARGET_NAME)"; 600 | SDKROOT = appletvos; 601 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/myApp-tvOS.app/myApp-tvOS"; 602 | TVOS_DEPLOYMENT_TARGET = 10.1; 603 | }; 604 | name = Debug; 605 | }; 606 | 2D02E49A1E0B4A5E006451C7 /* Release */ = { 607 | isa = XCBuildConfiguration; 608 | buildSettings = { 609 | BUNDLE_LOADER = "$(TEST_HOST)"; 610 | CLANG_ANALYZER_NONNULL = YES; 611 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 612 | CLANG_WARN_INFINITE_RECURSION = YES; 613 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 614 | COPY_PHASE_STRIP = NO; 615 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 616 | GCC_NO_COMMON_BLOCKS = YES; 617 | INFOPLIST_FILE = "myApp-tvOSTests/Info.plist"; 618 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 619 | OTHER_LDFLAGS = ( 620 | "$(inherited)", 621 | "-ObjC", 622 | "-lc++", 623 | ); 624 | PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.myApp-tvOSTests"; 625 | PRODUCT_NAME = "$(TARGET_NAME)"; 626 | SDKROOT = appletvos; 627 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/myApp-tvOS.app/myApp-tvOS"; 628 | TVOS_DEPLOYMENT_TARGET = 10.1; 629 | }; 630 | name = Release; 631 | }; 632 | 83CBBA201A601CBA00E9B192 /* Debug */ = { 633 | isa = XCBuildConfiguration; 634 | buildSettings = { 635 | ALWAYS_SEARCH_USER_PATHS = NO; 636 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 637 | CLANG_CXX_LIBRARY = "libc++"; 638 | CLANG_ENABLE_MODULES = YES; 639 | CLANG_ENABLE_OBJC_ARC = YES; 640 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 641 | CLANG_WARN_BOOL_CONVERSION = YES; 642 | CLANG_WARN_COMMA = YES; 643 | CLANG_WARN_CONSTANT_CONVERSION = YES; 644 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 645 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 646 | CLANG_WARN_EMPTY_BODY = YES; 647 | CLANG_WARN_ENUM_CONVERSION = YES; 648 | CLANG_WARN_INFINITE_RECURSION = YES; 649 | CLANG_WARN_INT_CONVERSION = YES; 650 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 651 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 652 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 653 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 654 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 655 | CLANG_WARN_STRICT_PROTOTYPES = YES; 656 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 657 | CLANG_WARN_UNREACHABLE_CODE = YES; 658 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 659 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 660 | COPY_PHASE_STRIP = NO; 661 | ENABLE_STRICT_OBJC_MSGSEND = YES; 662 | ENABLE_TESTABILITY = YES; 663 | GCC_C_LANGUAGE_STANDARD = gnu99; 664 | GCC_DYNAMIC_NO_PIC = NO; 665 | GCC_NO_COMMON_BLOCKS = YES; 666 | GCC_OPTIMIZATION_LEVEL = 0; 667 | GCC_PREPROCESSOR_DEFINITIONS = ( 668 | "DEBUG=1", 669 | "$(inherited)", 670 | ); 671 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 672 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 673 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 674 | GCC_WARN_UNDECLARED_SELECTOR = YES; 675 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 676 | GCC_WARN_UNUSED_FUNCTION = YES; 677 | GCC_WARN_UNUSED_VARIABLE = YES; 678 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 679 | MTL_ENABLE_DEBUG_INFO = YES; 680 | ONLY_ACTIVE_ARCH = YES; 681 | SDKROOT = iphoneos; 682 | }; 683 | name = Debug; 684 | }; 685 | 83CBBA211A601CBA00E9B192 /* Release */ = { 686 | isa = XCBuildConfiguration; 687 | buildSettings = { 688 | ALWAYS_SEARCH_USER_PATHS = NO; 689 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 690 | CLANG_CXX_LIBRARY = "libc++"; 691 | CLANG_ENABLE_MODULES = YES; 692 | CLANG_ENABLE_OBJC_ARC = YES; 693 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 694 | CLANG_WARN_BOOL_CONVERSION = YES; 695 | CLANG_WARN_COMMA = YES; 696 | CLANG_WARN_CONSTANT_CONVERSION = YES; 697 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 698 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 699 | CLANG_WARN_EMPTY_BODY = YES; 700 | CLANG_WARN_ENUM_CONVERSION = YES; 701 | CLANG_WARN_INFINITE_RECURSION = YES; 702 | CLANG_WARN_INT_CONVERSION = YES; 703 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 704 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 705 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 706 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 707 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 708 | CLANG_WARN_STRICT_PROTOTYPES = YES; 709 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 710 | CLANG_WARN_UNREACHABLE_CODE = YES; 711 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 712 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 713 | COPY_PHASE_STRIP = YES; 714 | ENABLE_NS_ASSERTIONS = NO; 715 | ENABLE_STRICT_OBJC_MSGSEND = YES; 716 | GCC_C_LANGUAGE_STANDARD = gnu99; 717 | GCC_NO_COMMON_BLOCKS = YES; 718 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 719 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 720 | GCC_WARN_UNDECLARED_SELECTOR = YES; 721 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 722 | GCC_WARN_UNUSED_FUNCTION = YES; 723 | GCC_WARN_UNUSED_VARIABLE = YES; 724 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 725 | MTL_ENABLE_DEBUG_INFO = NO; 726 | SDKROOT = iphoneos; 727 | VALIDATE_PRODUCT = YES; 728 | }; 729 | name = Release; 730 | }; 731 | /* End XCBuildConfiguration section */ 732 | 733 | /* Begin XCConfigurationList section */ 734 | 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "myAppTests" */ = { 735 | isa = XCConfigurationList; 736 | buildConfigurations = ( 737 | 00E356F61AD99517003FC87E /* Debug */, 738 | 00E356F71AD99517003FC87E /* Release */, 739 | ); 740 | defaultConfigurationIsVisible = 0; 741 | defaultConfigurationName = Release; 742 | }; 743 | 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "myApp" */ = { 744 | isa = XCConfigurationList; 745 | buildConfigurations = ( 746 | 13B07F941A680F5B00A75B9A /* Debug */, 747 | 13B07F951A680F5B00A75B9A /* Release */, 748 | ); 749 | defaultConfigurationIsVisible = 0; 750 | defaultConfigurationName = Release; 751 | }; 752 | 2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "myApp-tvOS" */ = { 753 | isa = XCConfigurationList; 754 | buildConfigurations = ( 755 | 2D02E4971E0B4A5E006451C7 /* Debug */, 756 | 2D02E4981E0B4A5E006451C7 /* Release */, 757 | ); 758 | defaultConfigurationIsVisible = 0; 759 | defaultConfigurationName = Release; 760 | }; 761 | 2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "myApp-tvOSTests" */ = { 762 | isa = XCConfigurationList; 763 | buildConfigurations = ( 764 | 2D02E4991E0B4A5E006451C7 /* Debug */, 765 | 2D02E49A1E0B4A5E006451C7 /* Release */, 766 | ); 767 | defaultConfigurationIsVisible = 0; 768 | defaultConfigurationName = Release; 769 | }; 770 | 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "myApp" */ = { 771 | isa = XCConfigurationList; 772 | buildConfigurations = ( 773 | 83CBBA201A601CBA00E9B192 /* Debug */, 774 | 83CBBA211A601CBA00E9B192 /* Release */, 775 | ); 776 | defaultConfigurationIsVisible = 0; 777 | defaultConfigurationName = Release; 778 | }; 779 | /* End XCConfigurationList section */ 780 | }; 781 | rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; 782 | } 783 | -------------------------------------------------------------------------------- /ios/myApp.xcodeproj/xcshareddata/xcschemes/myApp-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 | -------------------------------------------------------------------------------- /ios/myApp.xcodeproj/xcshareddata/xcschemes/myApp.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 | -------------------------------------------------------------------------------- /ios/myApp/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (nonatomic, strong) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ios/myApp/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | #import 11 | #import 12 | #import 13 | 14 | @implementation AppDelegate 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 19 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 20 | moduleName:@"myApp" 21 | initialProperties:nil]; 22 | 23 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 24 | 25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 26 | UIViewController *rootViewController = [UIViewController new]; 27 | rootViewController.view = rootView; 28 | self.window.rootViewController = rootViewController; 29 | [self.window makeKeyAndVisible]; 30 | return YES; 31 | } 32 | 33 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 34 | { 35 | #if DEBUG 36 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 37 | #else 38 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 39 | #endif 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ios/myApp/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 | -------------------------------------------------------------------------------- /ios/myApp/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 | } -------------------------------------------------------------------------------- /ios/myApp/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/myApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | myApp 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSExceptionDomains 32 | 33 | localhost 34 | 35 | NSExceptionAllowsInsecureHTTPLoads 36 | 37 | 38 | 39 | 40 | NSLocationWhenInUseUsageDescription 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UIViewControllerBasedStatusBarAppearance 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /ios/myApp/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/myAppTests/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 | -------------------------------------------------------------------------------- /ios/myAppTests/myAppTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | #import 12 | #import 13 | 14 | #define TIMEOUT_SECONDS 600 15 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 16 | 17 | @interface myAppTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation myAppTests 22 | 23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 24 | { 25 | if (test(view)) { 26 | return YES; 27 | } 28 | for (UIView *subview in [view subviews]) { 29 | if ([self findSubviewInView:subview matching:test]) { 30 | return YES; 31 | } 32 | } 33 | return NO; 34 | } 35 | 36 | - (void)testRendersWelcomeScreen 37 | { 38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 40 | BOOL foundElement = NO; 41 | 42 | __block NSString *redboxError = nil; 43 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 44 | if (level >= RCTLogLevelError) { 45 | redboxError = message; 46 | } 47 | }); 48 | 49 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 50 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 51 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 52 | 53 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 54 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 55 | return YES; 56 | } 57 | return NO; 58 | }]; 59 | } 60 | 61 | RCTSetLogFunction(RCTDefaultLogFunction); 62 | 63 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 64 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 65 | } 66 | 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /janus.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 Meetecho 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the "Software"), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. */ 23 | 24 | import { 25 | RTCPeerConnection, 26 | RTCMediaStream, 27 | RTCIceCandidate, 28 | RTCSessionDescription, 29 | RTCView, 30 | mediaDevices, 31 | } from 'react-native-webrtc'; 32 | 33 | import { 34 | Alert 35 | } from 'react-native'; 36 | 37 | 38 | // List of sessions 39 | Janus.sessions = {}; 40 | 41 | // Screensharing Chrome Extension ID 42 | Janus.extensionId = "hapfgfdkleiggjjpfpenajgdnfckjpaj"; 43 | 44 | 45 | Janus.noop = function () { }; 46 | 47 | // Initialization 48 | Janus.init = function (options) { 49 | options = options || {}; 50 | options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop; 51 | if (Janus.initDone === true) { 52 | // Already initialized 53 | options.callback(); 54 | } else { 55 | if (typeof console == "undefined" || typeof console.log == "undefined") 56 | console = { log: function () { } }; 57 | // Console logging (all debugging disabled by default) 58 | Janus.trace = Janus.noop; 59 | Janus.debug = Janus.noop; 60 | Janus.log = Janus.noop; 61 | Janus.warn = Janus.noop; 62 | Janus.error = Janus.noop; 63 | if (options.debug === true || options.debug === "all") { 64 | // Enable all debugging levels 65 | Janus.trace = console.trace.bind(console); 66 | Janus.debug = console.debug.bind(console); 67 | Janus.log = console.log.bind(console); 68 | Janus.warn = console.warn.bind(console); 69 | Janus.error = console.error.bind(console); 70 | } else if (Array.isArray(options.debug)) { 71 | for (var i in options.debug) { 72 | var d = options.debug[i]; 73 | switch (d) { 74 | case "trace": 75 | Janus.trace = console.trace.bind(console); 76 | break; 77 | case "debug": 78 | Janus.debug = console.debug.bind(console); 79 | break; 80 | case "log": 81 | Janus.log = console.log.bind(console); 82 | break; 83 | case "warn": 84 | Janus.warn = console.warn.bind(console); 85 | break; 86 | case "error": 87 | Janus.error = console.error.bind(console); 88 | break; 89 | default: 90 | console.error("Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'log', warn', 'error')"); 91 | break; 92 | } 93 | } 94 | } 95 | Janus.log("Initializing library"); 96 | // Helper method to enumerate devices 97 | Janus.listDevices = function (callback) { 98 | callback = (typeof callback == "function") ? callback : Janus.noop; 99 | if (navigator.mediaDevices) { 100 | getUserMedia({ audio: true, video: true }, function (stream) { 101 | navigator.mediaDevices.enumerateDevices().then(function (devices) { 102 | Janus.debug(devices); 103 | callback(devices); 104 | // Get rid of the now useless stream 105 | try { 106 | stream.stop(); 107 | } catch (e) { } 108 | try { 109 | var tracks = stream.getTracks(); 110 | for (var i in tracks) { 111 | var mst = tracks[i]; 112 | if (mst !== null && mst !== undefined) 113 | mst.stop(); 114 | } 115 | } catch (e) { } 116 | }); 117 | }, function (err) { 118 | Janus.error(err); 119 | callback([]); 120 | }); 121 | } else { 122 | Janus.warn("navigator.mediaDevices unavailable"); 123 | callback([]); 124 | } 125 | } 126 | // Prepare a helper method to send AJAX requests in a syntax similar to jQuery (at least for what we care) 127 | Janus.ajax = function (params) { 128 | // Check params 129 | if (params === null || params === undefined) 130 | return; 131 | params.success = (typeof params.success == "function") ? params.success : Janus.noop; 132 | params.error = (typeof params.error == "function") ? params.error : Janus.noop; 133 | // Make sure there's an URL 134 | if (params.url === null || params.url === undefined) { 135 | Janus.error('Missing url', params.url); 136 | params.error(null, -1, 'Missing url'); 137 | return; 138 | } 139 | // Validate async 140 | params.async = (params.async === null || params.async === undefined) ? true : (params.async === true); 141 | Janus.log(params); 142 | // IE doesn't even know what WebRTC is, so no polyfill needed 143 | var XHR = new XMLHttpRequest(); 144 | XHR.open(params.type, params.url, params.async); 145 | if (params.contentType !== null && params.contentType !== undefined) 146 | XHR.setRequestHeader('Content-type', params.contentType); 147 | if (params.async) { 148 | XHR.onreadystatechange = function () { 149 | if (XHR.readyState != 4) 150 | return; 151 | if (XHR.status !== 200) { 152 | // Got an error? 153 | if (XHR.status === 0) 154 | XHR.status = "error"; 155 | params.error(XHR, XHR.status, ""); 156 | return; 157 | } 158 | // Got payload 159 | params.success(JSON.parse(XHR.responseText)); 160 | }; 161 | } 162 | try { 163 | XHR.send(params.data); 164 | if (!params.async) { 165 | if (XHR.status !== 200) { 166 | // Got an error? 167 | if (XHR.status === 0) 168 | XHR.status = "error"; 169 | params.error(XHR, XHR.status, ""); 170 | return; 171 | } 172 | // Got payload 173 | params.success(JSON.parse(XHR.responseText)); 174 | } 175 | } catch (e) { 176 | // Something broke up 177 | params.error(XHR, 'error', ''); 178 | }; 179 | }; 180 | // Detect tab close 181 | window.onbeforeunload = function () { 182 | Janus.log("Closing window"); 183 | for (var s in Janus.sessions) { 184 | if (Janus.sessions[s] !== null && Janus.sessions[s] !== undefined && 185 | Janus.sessions[s].destroyOnUnload) { 186 | Janus.log("Destroying session " + s); 187 | Janus.sessions[s].destroy(); 188 | } 189 | } 190 | } 191 | 192 | Janus.initDone = true; 193 | // addJsList(["adapter.js"]); // We may need others in the future 194 | } 195 | }; 196 | 197 | // Helper method to check whether WebRTC is supported by this browser 198 | Janus.isWebrtcSupported = function () { 199 | return window.RTCPeerConnection && window.getUserMedia; 200 | }; 201 | 202 | 203 | // Janus session object 204 | export function Janus(gatewayCallbacks) { 205 | if (Janus.initDone === undefined) { 206 | gatewayCallbacks.error("Library not initialized"); 207 | return {}; 208 | } 209 | Janus.log("Library initialized: " + Janus.initDone); 210 | gatewayCallbacks = gatewayCallbacks || {}; 211 | gatewayCallbacks.success = (typeof gatewayCallbacks.success == "function") ? gatewayCallbacks.success : Janus.noop; 212 | gatewayCallbacks.error = (typeof gatewayCallbacks.error == "function") ? gatewayCallbacks.error : Janus.noop; 213 | gatewayCallbacks.destroyed = (typeof gatewayCallbacks.destroyed == "function") ? gatewayCallbacks.destroyed : Janus.noop; 214 | if (gatewayCallbacks.server === null || gatewayCallbacks.server === undefined) { 215 | gatewayCallbacks.error("Invalid gateway url"); 216 | return {}; 217 | } 218 | var localstream = null; 219 | var websockets = false; 220 | var ws = null; 221 | var wsHandlers = {}; 222 | var wsKeepaliveTimeoutId = null; 223 | 224 | var servers = null, serversIndex = 0; 225 | var server = gatewayCallbacks.server; 226 | var camera_front = gatewayCallbacks.camera_front; 227 | if (Array.isArray(server)) { 228 | Janus.log("Multiple servers provided (" + server.length + "), will use the first that works"); 229 | server = null; 230 | servers = gatewayCallbacks.server; 231 | Janus.debug(servers); 232 | } else { 233 | if (server.indexOf("ws") === 0) { 234 | websockets = true; 235 | Janus.log("Using WebSockets to contact Janus: " + server); 236 | } else { 237 | websockets = false; 238 | Janus.log("Using REST API to contact Janus: " + server); 239 | } 240 | } 241 | var iceServers = gatewayCallbacks.iceServers; 242 | if (iceServers === undefined || iceServers === null) 243 | iceServers = [{ "url": "stun:stun.l.google.com:19302" }]; 244 | // Whether IPv6 candidates should be gathered 245 | var ipv6Support = gatewayCallbacks.ipv6; 246 | if (ipv6Support === undefined || ipv6Support === null) 247 | ipv6Support = false; 248 | // Optional max events 249 | var maxev = null; 250 | if (gatewayCallbacks.max_poll_events !== undefined && gatewayCallbacks.max_poll_events !== null) 251 | maxev = gatewayCallbacks.max_poll_events; 252 | if (maxev < 1) 253 | maxev = 1; 254 | // Token to use (only if the token based authentication mechanism is enabled) 255 | var token = null; 256 | if (gatewayCallbacks.token !== undefined && gatewayCallbacks.token !== null) 257 | token = gatewayCallbacks.token; 258 | // API secret to use (only if the shared API secret is enabled) 259 | var apisecret = null; 260 | if (gatewayCallbacks.apisecret !== undefined && gatewayCallbacks.apisecret !== null) 261 | apisecret = gatewayCallbacks.apisecret; 262 | // Whether we should destroy this session when onbeforeunload is called 263 | this.destroyOnUnload = true; 264 | if (gatewayCallbacks.destroyOnUnload !== undefined && gatewayCallbacks.destroyOnUnload !== null) 265 | this.destroyOnUnload = (gatewayCallbacks.destroyOnUnload === true); 266 | 267 | var connected = false; 268 | var sessionId = null; 269 | var pluginHandles = {}; 270 | var that = this; 271 | var retries = 0; 272 | var transactions = {}; 273 | console.log("sample") 274 | createSession(gatewayCallbacks); 275 | 276 | // Public methods 277 | this.getServer = function () { return server; }; 278 | this.isConnected = function () { return connected; }; 279 | this.getSessionId = function () { return sessionId; }; 280 | this.destroy = function (callbacks) { destroySession(callbacks); }; 281 | this.attach = function (callbacks) { createHandle(callbacks); }; 282 | 283 | // Private method to create random identifiers (e.g., transaction) 284 | function randomString(len) { 285 | charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 286 | var randomString = ''; 287 | for (var i = 0; i < len; i++) { 288 | var randomPoz = Math.floor(Math.random() * charSet.length); 289 | randomString += charSet.substring(randomPoz, randomPoz + 1); 290 | } 291 | return randomString; 292 | } 293 | 294 | function eventHandler() { 295 | if (sessionId == null) 296 | return; 297 | Janus.debug('Long poll...'); 298 | if (!connected) { 299 | Janus.warn("Is the gateway down? (connected=false)"); 300 | return; 301 | } 302 | var longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime(); 303 | if (maxev !== undefined && maxev !== null) 304 | longpoll = longpoll + "&maxev=" + maxev; 305 | if (token !== null && token !== undefined) 306 | longpoll = longpoll + "&token=" + token; 307 | if (apisecret !== null && apisecret !== undefined) 308 | longpoll = longpoll + "&apisecret=" + apisecret; 309 | Janus.ajax({ 310 | type: 'GET', 311 | url: longpoll, 312 | cache: false, 313 | timeout: 1000, // FIXME 314 | success: handleEvent, 315 | sucess: function (json) { 316 | console.log(json) 317 | }, 318 | error: function (XMLHttpRequest, textStatus, errorThrown) { 319 | console.log(XMLHttpRequest) 320 | Janus.error(textStatus + ": " + errorThrown); 321 | //~ clearTimeout(timeoutTimer); 322 | retries++; 323 | if (retries > 1) { 324 | // Did we just lose the gateway? :-( 325 | connected = false; 326 | gatewayCallbacks.error("Lost connection to the gateway (is it down?)"); 327 | return; 328 | } 329 | eventHandler(); 330 | }, 331 | dataType: "json" 332 | }); 333 | } 334 | 335 | // Private event handler: this will trigger plugin callbacks, if set 336 | function handleEvent(json) { 337 | retries = 0; 338 | if (!websockets && sessionId !== undefined && sessionId !== null) 339 | setTimeout(eventHandler, 200); 340 | Janus.debug("Got event on session " + sessionId); 341 | Janus.debug(json); 342 | if (!websockets && Array.isArray(json)) { 343 | // We got an array: it means we passed a maxev > 1, iterate on all objects 344 | for (var i = 0; i < json.length; i++) { 345 | handleEvent(json[i]); 346 | } 347 | return; 348 | } 349 | if (json["janus"] === "keepalive") { 350 | // Nothing happened 351 | return; 352 | } else if (json["janus"] === "ack") { 353 | // Just an ack, we can probably ignore 354 | var transaction = json["transaction"]; 355 | if (transaction !== null && transaction !== undefined) { 356 | var reportSuccess = transactions[transaction]; 357 | if (reportSuccess !== null && reportSuccess !== undefined) { 358 | reportSuccess(json); 359 | } 360 | delete transactions[transaction]; 361 | } 362 | return; 363 | } else if (json["janus"] === "success") { 364 | // Success! 365 | var transaction = json["transaction"]; 366 | if (transaction !== null && transaction !== undefined) { 367 | var reportSuccess = transactions[transaction]; 368 | if (reportSuccess !== null && reportSuccess !== undefined) { 369 | reportSuccess(json); 370 | } 371 | delete transactions[transaction]; 372 | } 373 | return; 374 | } else if (json["janus"] === "webrtcup") { 375 | // The PeerConnection with the gateway is up! Notify this 376 | var sender = json["sender"]; 377 | if (sender === undefined || sender === null) { 378 | Janus.warn("Missing sender..."); 379 | return; 380 | } 381 | var pluginHandle = pluginHandles[sender]; 382 | if (pluginHandle === undefined || pluginHandle === null) { 383 | Janus.warn("This handle is not attached to this session"); 384 | return; 385 | } 386 | pluginHandle.webrtcState(true); 387 | return; 388 | } else if (json["janus"] === "hangup") { 389 | // A plugin asked the core to hangup a PeerConnection on one of our handles 390 | var sender = json["sender"]; 391 | if (sender === undefined || sender === null) { 392 | Janus.warn("Missing sender..."); 393 | return; 394 | } 395 | var pluginHandle = pluginHandles[sender]; 396 | if (pluginHandle === undefined || pluginHandle === null) { 397 | Janus.warn("This handle is not attached to this session"); 398 | return; 399 | } 400 | pluginHandle.webrtcState(false); 401 | pluginHandle.hangup(); 402 | } else if (json["janus"] === "detached") { 403 | // A plugin asked the core to detach one of our handles 404 | var sender = json["sender"]; 405 | if (sender === undefined || sender === null) { 406 | Janus.warn("Missing sender..."); 407 | return; 408 | } 409 | var pluginHandle = pluginHandles[sender]; 410 | if (pluginHandle === undefined || pluginHandle === null) { 411 | // Don't warn here because destroyHandle causes this situation. 412 | return; 413 | } 414 | pluginHandle.ondetached(); 415 | pluginHandle.detach(); 416 | } else if (json["janus"] === "media") { 417 | // Media started/stopped flowing 418 | var sender = json["sender"]; 419 | if (sender === undefined || sender === null) { 420 | Janus.warn("Missing sender..."); 421 | return; 422 | } 423 | var pluginHandle = pluginHandles[sender]; 424 | if (pluginHandle === undefined || pluginHandle === null) { 425 | Janus.warn("This handle is not attached to this session"); 426 | return; 427 | } 428 | pluginHandle.mediaState(json["type"], json["receiving"]); 429 | } else if (json["janus"] === "error") { 430 | // Oops, something wrong happened 431 | // Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME 432 | var transaction = json["transaction"]; 433 | if (transaction !== null && transaction !== undefined) { 434 | var reportSuccess = transactions[transaction]; 435 | if (reportSuccess !== null && reportSuccess !== undefined) { 436 | reportSuccess(json); 437 | } 438 | delete transactions[transaction]; 439 | } 440 | return; 441 | } else if (json["janus"] === "event") { 442 | var sender = json["sender"]; 443 | if (sender === undefined || sender === null) { 444 | Janus.warn("Missing sender..."); 445 | return; 446 | } 447 | var plugindata = json["plugindata"]; 448 | if (plugindata === undefined || plugindata === null) { 449 | Janus.warn("Missing plugindata..."); 450 | return; 451 | } 452 | Janus.debug(" -- Event is coming from " + sender + " (" + plugindata["plugin"] + ")"); 453 | var data = plugindata["data"]; 454 | Janus.debug(data); 455 | var pluginHandle = pluginHandles[sender]; 456 | if (pluginHandle === undefined || pluginHandle === null) { 457 | Janus.warn("This handle is not attached to this session"); 458 | return; 459 | } 460 | var jsep = json["jsep"]; 461 | if (jsep !== undefined && jsep !== null) { 462 | Janus.debug("Handling SDP as well..."); 463 | Janus.debug(jsep); 464 | } 465 | var callback = pluginHandle.onmessage; 466 | if (callback !== null && callback !== undefined) { 467 | Janus.debug("Notifying application..."); 468 | // Send to callback specified when attaching plugin handle 469 | callback(data, jsep); 470 | } else { 471 | // Send to generic callback (?) 472 | Janus.debug("No provided notification callback"); 473 | } 474 | } else { 475 | Janus.warn("Unknown message '" + json["janus"] + "'"); 476 | } 477 | } 478 | 479 | // Private helper to send keep-alive messages on WebSockets 480 | function keepAlive() { 481 | if (server === null || !websockets || !connected) 482 | return; 483 | wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000); 484 | var request = { "janus": "keepalive", "session_id": sessionId, "transaction": randomString(12) }; 485 | if (token !== null && token !== undefined) 486 | request["token"] = token; 487 | if (apisecret !== null && apisecret !== undefined) 488 | request["apisecret"] = apisecret; 489 | ws.send(JSON.stringify(request)); 490 | } 491 | 492 | // Private method to create a session 493 | function createSession(callbacks) { 494 | var transaction = randomString(12); 495 | var request = { "janus": "create", "transaction": transaction }; 496 | if (token !== null && token !== undefined) 497 | request["token"] = token; 498 | if (apisecret !== null && apisecret !== undefined) 499 | request["apisecret"] = apisecret; 500 | if (server === null && Array.isArray(servers)) { 501 | // We still need to find a working server from the list we were given 502 | server = servers[serversIndex]; 503 | if (server.indexOf("ws") === 0) { 504 | websockets = true; 505 | Alert.alert("Server #" + (serversIndex + 1) + ": trying WebSockets to contact Janus (" + server + ")"); 506 | } else { 507 | websockets = false; 508 | Alert.alert("Server #" + (serversIndex + 1) + ": trying REST API to contact Janus (" + server + ")"); 509 | } 510 | } 511 | if (websockets) { 512 | ws = new WebSocket(server, 'janus-protocol'); 513 | wsHandlers = { 514 | 'error': function () { 515 | Alert.alert("Error connecting to the Janus WebSockets server... " + server); 516 | if (Array.isArray(servers)) { 517 | serversIndex++; 518 | if (serversIndex == servers.length) { 519 | // We tried all the servers the user gave us and they all failed 520 | callbacks.error("Error connecting to any of the provided Janus servers: Is the gateway down?"); 521 | return; 522 | } 523 | // Let's try the next server 524 | server = null; 525 | setTimeout(function () { 526 | createSession(callbacks); 527 | }, 200); 528 | return; 529 | } 530 | callbacks.error("Error connecting to the Janus WebSockets server: Is the gateway down?"); 531 | }, 532 | 533 | 'open': function () { 534 | // We need to be notified about the success 535 | transactions[transaction] = function (json) { 536 | Janus.debug(json); 537 | if (json["janus"] !== "success") { 538 | Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME 539 | callbacks.error(json["error"].reason); 540 | return; 541 | } 542 | wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000); 543 | connected = true; 544 | sessionId = json.data["id"]; 545 | Janus.log("Created session: " + sessionId); 546 | Janus.sessions[sessionId] = that; 547 | callbacks.success(); 548 | }; 549 | ws.send(JSON.stringify(request)); 550 | }, 551 | 552 | 'message': function (event) { 553 | handleEvent(JSON.parse(event.data)); 554 | }, 555 | 556 | 'close': function () { 557 | if (server === null || !connected) { 558 | return; 559 | } 560 | connected = false; 561 | // FIXME What if this is called when the page is closed? 562 | gatewayCallbacks.error("Lost connection to the gateway (is it down?)"); 563 | } 564 | }; 565 | 566 | for (var eventName in wsHandlers) { 567 | ws.addEventListener(eventName, wsHandlers[eventName]); 568 | } 569 | 570 | return; 571 | } 572 | console.log("Janus.ajax({})") 573 | console.log(server) 574 | console.log(JSON.stringify(request)) 575 | Janus.ajax({ 576 | type: 'POST', 577 | url: server, 578 | cache: false, 579 | contentType: "application/json", 580 | data: JSON.stringify(request), 581 | success: function (json) { 582 | Janus.debug(json); 583 | if (json["janus"] !== "success") { 584 | Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME 585 | callbacks.error(json["error"].reason); 586 | return; 587 | } 588 | connected = true; 589 | sessionId = json.data["id"]; 590 | Janus.log("Created session: " + sessionId); 591 | Janus.sessions[sessionId] = that; 592 | eventHandler(); 593 | callbacks.success(); 594 | }, 595 | error: function (XMLHttpRequest, textStatus, errorThrown) { 596 | console.log("*********error***********") 597 | console.log(XMLHttpRequest) 598 | console.log(textStatus) 599 | console.log(errorThrown) 600 | // Janus.error(textStatus + ": " + errorThrown); // FIXME 601 | if (Array.isArray(servers)) { 602 | serversIndex++; 603 | if (serversIndex == servers.length) { 604 | // We tried all the servers the user gave us and they all failed 605 | callbacks.error("Error connecting to any of the provided Janus servers: Is the gateway down?"); 606 | return; 607 | } 608 | // Let's try the next server 609 | server = null; 610 | setTimeout(function () { createSession(callbacks); }, 200); 611 | return; 612 | } 613 | if (errorThrown === "") 614 | callbacks.error(textStatus + ": Is the gateway down?"); 615 | else 616 | callbacks.error(textStatus + ": " + errorThrown); 617 | }, 618 | dataType: "json" 619 | }); 620 | } 621 | 622 | // Private method to destroy a session 623 | function destroySession(callbacks, syncRequest) { 624 | syncRequest = (syncRequest === true); 625 | Janus.log("Destroying session " + sessionId + " (sync=" + syncRequest + ")"); 626 | callbacks = callbacks || {}; 627 | // FIXME This method triggers a success even when we fail 628 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 629 | if (!connected) { 630 | Janus.warn("Is the gateway down? (connected=false)"); 631 | callbacks.success(); 632 | return; 633 | } 634 | if (sessionId === undefined || sessionId === null) { 635 | Janus.warn("No session to destroy"); 636 | callbacks.success(); 637 | gatewayCallbacks.destroyed(); 638 | return; 639 | } 640 | delete Janus.sessions[sessionId]; 641 | // Destroy all handles first 642 | for (var ph in pluginHandles) { 643 | var phv = pluginHandles[ph]; 644 | Janus.log("Destroying handle " + phv.id + " (" + phv.plugin + ")"); 645 | destroyHandle(phv.id, null, syncRequest); 646 | } 647 | // Ok, go on 648 | var request = { "janus": "destroy", "transaction": randomString(12) }; 649 | if (token !== null && token !== undefined) 650 | request["token"] = token; 651 | if (apisecret !== null && apisecret !== undefined) 652 | request["apisecret"] = apisecret; 653 | if (websockets) { 654 | request["session_id"] = sessionId; 655 | 656 | var unbindWebSocket = function () { 657 | for (var eventName in wsHandlers) { 658 | ws.removeEventListener(eventName, wsHandlers[eventName]); 659 | } 660 | ws.removeEventListener('message', onUnbindMessage); 661 | ws.removeEventListener('error', onUnbindError); 662 | if (wsKeepaliveTimeoutId) { 663 | clearTimeout(wsKeepaliveTimeoutId); 664 | } 665 | }; 666 | 667 | var onUnbindMessage = function (event) { 668 | var data = JSON.parse(event.data); 669 | if (data.session_id == request.session_id && data.transaction == request.transaction) { 670 | unbindWebSocket(); 671 | callbacks.success(); 672 | gatewayCallbacks.destroyed(); 673 | } 674 | }; 675 | var onUnbindError = function (event) { 676 | unbindWebSocket(); 677 | callbacks.error("Failed to destroy the gateway: Is the gateway down?"); 678 | gatewayCallbacks.destroyed(); 679 | }; 680 | 681 | ws.addEventListener('message', onUnbindMessage); 682 | ws.addEventListener('error', onUnbindError); 683 | 684 | ws.send(JSON.stringify(request)); 685 | return; 686 | } 687 | console.log("sessionId") 688 | console.log(sessionId) 689 | console.log("server") 690 | console.log(server) 691 | Janus.ajax({ 692 | type: 'POST', 693 | url: server + "/" + sessionId, 694 | async: true, // Sometimes we need false here, or destroying in onbeforeunload won't work 695 | cache: false, 696 | contentType: "application/json", 697 | data: JSON.stringify(request), 698 | success: function (json) { 699 | Janus.log("Destroyed session:"); 700 | Janus.debug(json); 701 | sessionId = null; 702 | connected = false; 703 | if (json["janus"] !== "success") { 704 | Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME 705 | } 706 | callbacks.success(); 707 | gatewayCallbacks.destroyed(); 708 | }, 709 | error: function (XMLHttpRequest, textStatus, errorThrown) { 710 | Janus.error(textStatus + ": " + errorThrown); // FIXME 711 | // Reset everything anyway 712 | sessionId = null; 713 | connected = false; 714 | callbacks.success(); 715 | gatewayCallbacks.destroyed(); 716 | }, 717 | dataType: "json" 718 | }); 719 | } 720 | 721 | // Private method to create a plugin handle 722 | function createHandle(callbacks) { 723 | callbacks = callbacks || {}; 724 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 725 | callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; 726 | callbacks.consentDialog = (typeof callbacks.consentDialog == "function") ? callbacks.consentDialog : Janus.noop; 727 | callbacks.mediaState = (typeof callbacks.mediaState == "function") ? callbacks.mediaState : Janus.noop; 728 | callbacks.webrtcState = (typeof callbacks.webrtcState == "function") ? callbacks.webrtcState : Janus.noop; 729 | callbacks.onmessage = (typeof callbacks.onmessage == "function") ? callbacks.onmessage : Janus.noop; 730 | callbacks.onlocalstream = (typeof callbacks.onlocalstream == "function") ? callbacks.onlocalstream : Janus.noop; 731 | callbacks.onremotestream = (typeof callbacks.onremotestream == "function") ? callbacks.onremotestream : Janus.noop; 732 | callbacks.ondata = (typeof callbacks.ondata == "function") ? callbacks.ondata : Janus.noop; 733 | callbacks.ondataopen = (typeof callbacks.ondataopen == "function") ? callbacks.ondataopen : Janus.noop; 734 | callbacks.oncleanup = (typeof callbacks.oncleanup == "function") ? callbacks.oncleanup : Janus.noop; 735 | callbacks.ondetached = (typeof callbacks.ondetached == "function") ? callbacks.ondetached : Janus.noop; 736 | if (!connected) { 737 | Janus.warn("Is the gateway down? (connected=false)"); 738 | callbacks.error("Is the gateway down? (connected=false)"); 739 | return; 740 | } 741 | var plugin = callbacks.plugin; 742 | if (plugin === undefined || plugin === null) { 743 | Janus.error("Invalid plugin"); 744 | callbacks.error("Invalid plugin"); 745 | return; 746 | } 747 | var transaction = randomString(12); 748 | var request = { "janus": "attach", "plugin": plugin, "transaction": transaction }; 749 | if (token !== null && token !== undefined) 750 | request["token"] = token; 751 | if (apisecret !== null && apisecret !== undefined) 752 | request["apisecret"] = apisecret; 753 | if (websockets) { 754 | transactions[transaction] = function (json) { 755 | Janus.debug(json); 756 | if (json["janus"] !== "success") { 757 | Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME 758 | callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason); 759 | return; 760 | } 761 | var handleId = json.data["id"]; 762 | Janus.log("Created handle: " + handleId); 763 | var pluginHandle = 764 | { 765 | session: that, 766 | plugin: plugin, 767 | id: handleId, 768 | webrtcStuff: { 769 | started: false, 770 | myStream: null, 771 | streamExternal: false, 772 | remoteStream: null, 773 | mySdp: null, 774 | pc: null, 775 | dataChannel: null, 776 | dtmfSender: null, 777 | trickle: true, 778 | iceDone: false, 779 | sdpSent: false, 780 | volume: { 781 | value: null, 782 | timer: null 783 | }, 784 | bitrate: { 785 | value: null, 786 | bsnow: null, 787 | bsbefore: null, 788 | tsnow: null, 789 | tsbefore: null, 790 | timer: null 791 | } 792 | }, 793 | getId: function () { return handleId; }, 794 | getPlugin: function () { return plugin; }, 795 | getVolume: function () { return getVolume(handleId); }, 796 | isAudioMuted: function () { return isMuted(handleId, false); }, 797 | muteAudio: function () { return mute(handleId, false, true); }, 798 | unmuteAudio: function () { return mute(handleId, false, false); }, 799 | isVideoMuted: function () { return isMuted(handleId, true); }, 800 | muteVideo: function () { return mute(handleId, true, true); }, 801 | unmuteVideo: function () { return mute(handleId, true, false); }, 802 | getBitrate: function () { return getBitrate(handleId); }, 803 | changeLocalCamera: function (isFront) { return changeLocalCamera(handleId, isFront); }, 804 | send: function (callbacks) { sendMessage(handleId, callbacks); }, 805 | data: function (callbacks) { sendData(handleId, callbacks); }, 806 | dtmf: function (callbacks) { sendDtmf(handleId, callbacks); }, 807 | consentDialog: callbacks.consentDialog, 808 | mediaState: callbacks.mediaState, 809 | webrtcState: callbacks.webrtcState, 810 | onmessage: callbacks.onmessage, 811 | createOffer: function (callbacks) { prepareWebrtc(handleId, callbacks); }, 812 | createAnswer: function (callbacks) { prepareWebrtc(handleId, callbacks); }, 813 | handleRemoteJsep: function (callbacks) { prepareWebrtcPeer(handleId, callbacks); }, 814 | onlocalstream: callbacks.onlocalstream, 815 | onremotestream: callbacks.onremotestream, 816 | ondata: callbacks.ondata, 817 | ondataopen: callbacks.ondataopen, 818 | oncleanup: callbacks.oncleanup, 819 | ondetached: callbacks.ondetached, 820 | hangup: function (sendRequest) { cleanupWebrtc(handleId, sendRequest === true); }, 821 | detach: function (callbacks) { destroyHandle(handleId, callbacks); } 822 | } 823 | pluginHandles[handleId] = pluginHandle; 824 | callbacks.success(pluginHandle); 825 | }; 826 | request["session_id"] = sessionId; 827 | ws.send(JSON.stringify(request)); 828 | return; 829 | } 830 | Janus.ajax({ 831 | type: 'POST', 832 | url: server + "/" + sessionId, 833 | cache: false, 834 | contentType: "application/json", 835 | data: JSON.stringify(request), 836 | success: function (json) { 837 | Janus.debug(json); 838 | if (json["janus"] !== "success") { 839 | Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME 840 | callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason); 841 | return; 842 | } 843 | var handleId = json.data["id"]; 844 | Janus.log("Created handle: " + handleId); 845 | var pluginHandle = 846 | { 847 | session: that, 848 | plugin: plugin, 849 | id: handleId, 850 | webrtcStuff: { 851 | started: false, 852 | myStream: null, 853 | streamExternal: false, 854 | remoteStream: null, 855 | mySdp: null, 856 | pc: null, 857 | dataChannel: null, 858 | dtmfSender: null, 859 | trickle: true, 860 | iceDone: false, 861 | sdpSent: false, 862 | volume: { 863 | value: null, 864 | timer: null 865 | }, 866 | bitrate: { 867 | value: null, 868 | bsnow: null, 869 | bsbefore: null, 870 | tsnow: null, 871 | tsbefore: null, 872 | timer: null 873 | } 874 | }, 875 | getId: function () { return handleId; }, 876 | getPlugin: function () { return plugin; }, 877 | getVolume: function () { return getVolume(handleId); }, 878 | isAudioMuted: function () { return isMuted(handleId, false); }, 879 | muteAudio: function () { return mute(handleId, false, true); }, 880 | unmuteAudio: function () { return mute(handleId, false, false); }, 881 | isVideoMuted: function () { return isMuted(handleId, true); }, 882 | muteVideo: function () { return mute(handleId, true, true); }, 883 | unmuteVideo: function () { return mute(handleId, true, false); }, 884 | getBitrate: function () { return getBitrate(handleId); }, 885 | changeLocalCamera: function (isFront) { return changeLocalCamera(handleId, isFront); }, 886 | send: function (callbacks) { sendMessage(handleId, callbacks); }, 887 | data: function (callbacks) { sendData(handleId, callbacks); }, 888 | dtmf: function (callbacks) { sendDtmf(handleId, callbacks); }, 889 | consentDialog: callbacks.consentDialog, 890 | mediaState: callbacks.mediaState, 891 | webrtcState: callbacks.webrtcState, 892 | onmessage: callbacks.onmessage, 893 | createOffer: function (callbacks) { prepareWebrtc(handleId, callbacks); }, 894 | createAnswer: function (callbacks) { prepareWebrtc(handleId, callbacks); }, 895 | handleRemoteJsep: function (callbacks) { prepareWebrtcPeer(handleId, callbacks); }, 896 | onlocalstream: callbacks.onlocalstream, 897 | onremotestream: callbacks.onremotestream, 898 | ondata: callbacks.ondata, 899 | ondataopen: callbacks.ondataopen, 900 | oncleanup: callbacks.oncleanup, 901 | ondetached: callbacks.ondetached, 902 | hangup: function (sendRequest) { cleanupWebrtc(handleId, sendRequest === true); }, 903 | detach: function (callbacks) { destroyHandle(handleId, callbacks); } 904 | } 905 | pluginHandles[handleId] = pluginHandle; 906 | callbacks.success(pluginHandle); 907 | }, 908 | error: function (XMLHttpRequest, textStatus, errorThrown) { 909 | Janus.error(textStatus + ": " + errorThrown); // FIXME 910 | }, 911 | dataType: "json" 912 | }); 913 | } 914 | 915 | // Private method to send a message 916 | function sendMessage(handleId, callbacks) { 917 | callbacks = callbacks || {}; 918 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 919 | callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; 920 | if (!connected) { 921 | Janus.warn("Is the gateway down? (connected=false)"); 922 | callbacks.error("Is the gateway down? (connected=false)"); 923 | return; 924 | } 925 | var message = callbacks.message; 926 | var jsep = callbacks.jsep; 927 | var transaction = randomString(12); 928 | var request = { "janus": "message", "body": message, "transaction": transaction }; 929 | if (token !== null && token !== undefined) 930 | request["token"] = token; 931 | if (apisecret !== null && apisecret !== undefined) 932 | request["apisecret"] = apisecret; 933 | if (jsep !== null && jsep !== undefined) 934 | request.jsep = jsep; 935 | Janus.debug("Sending message to plugin (handle=" + handleId + "):"); 936 | Janus.debug(request); 937 | if (websockets) { 938 | request["session_id"] = sessionId; 939 | request["handle_id"] = handleId; 940 | transactions[transaction] = function (json) { 941 | Janus.debug("Message sent!"); 942 | Janus.debug(json); 943 | if (json["janus"] === "success") { 944 | // We got a success, must have been a synchronous transaction 945 | var plugindata = json["plugindata"]; 946 | if (plugindata === undefined || plugindata === null) { 947 | Janus.warn("Request succeeded, but missing plugindata..."); 948 | callbacks.success(); 949 | return; 950 | } 951 | Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")"); 952 | var data = plugindata["data"]; 953 | Janus.debug(data); 954 | callbacks.success(data); 955 | return; 956 | } else if (json["janus"] !== "ack") { 957 | // Not a success and not an ack, must be an error 958 | if (json["error"] !== undefined && json["error"] !== null) { 959 | Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME 960 | callbacks.error(json["error"].code + " " + json["error"].reason); 961 | } else { 962 | Janus.error("Unknown error"); // FIXME 963 | callbacks.error("Unknown error"); 964 | } 965 | return; 966 | } 967 | // If we got here, the plugin decided to handle the request asynchronously 968 | callbacks.success(); 969 | }; 970 | ws.send(JSON.stringify(request)); 971 | return; 972 | } 973 | Janus.ajax({ 974 | type: 'POST', 975 | url: server + "/" + sessionId + "/" + handleId, 976 | cache: false, 977 | contentType: "application/json", 978 | data: JSON.stringify(request), 979 | success: function (json) { 980 | Janus.debug("Message sent!"); 981 | Janus.debug(json); 982 | if (json["janus"] === "success") { 983 | // We got a success, must have been a synchronous transaction 984 | var plugindata = json["plugindata"]; 985 | if (plugindata === undefined || plugindata === null) { 986 | Janus.warn("Request succeeded, but missing plugindata..."); 987 | callbacks.success(); 988 | return; 989 | } 990 | Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")"); 991 | var data = plugindata["data"]; 992 | Janus.debug(data); 993 | callbacks.success(data); 994 | return; 995 | } else if (json["janus"] !== "ack") { 996 | // Not a success and not an ack, must be an error 997 | if (json["error"] !== undefined && json["error"] !== null) { 998 | Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME 999 | callbacks.error(json["error"].code + " " + json["error"].reason); 1000 | } else { 1001 | Janus.error("Unknown error"); // FIXME 1002 | callbacks.error("Unknown error"); 1003 | } 1004 | return; 1005 | } 1006 | // If we got here, the plugin decided to handle the request asynchronously 1007 | callbacks.success(); 1008 | }, 1009 | error: function (XMLHttpRequest, textStatus, errorThrown) { 1010 | Janus.error(textStatus + ": " + errorThrown); // FIXME 1011 | callbacks.error(textStatus + ": " + errorThrown); 1012 | }, 1013 | dataType: "json" 1014 | }); 1015 | } 1016 | 1017 | // Private method to send a trickle candidate 1018 | function sendTrickleCandidate(handleId, candidate) { 1019 | if (!connected) { 1020 | Janus.warn("Is the gateway down? (connected=false)"); 1021 | return; 1022 | } 1023 | var request = { "janus": "trickle", "candidate": candidate, "transaction": randomString(12) }; 1024 | if (token !== null && token !== undefined) 1025 | request["token"] = token; 1026 | if (apisecret !== null && apisecret !== undefined) 1027 | request["apisecret"] = apisecret; 1028 | Janus.debug("Sending trickle candidate (handle=" + handleId + "):"); 1029 | Janus.debug(request); 1030 | if (websockets) { 1031 | request["session_id"] = sessionId; 1032 | request["handle_id"] = handleId; 1033 | ws.send(JSON.stringify(request)); 1034 | return; 1035 | } 1036 | Janus.ajax({ 1037 | type: 'POST', 1038 | url: server + "/" + sessionId + "/" + handleId, 1039 | cache: false, 1040 | contentType: "application/json", 1041 | data: JSON.stringify(request), 1042 | success: function (json) { 1043 | Janus.debug("Candidate sent!"); 1044 | Janus.debug(json); 1045 | if (json["janus"] !== "ack") { 1046 | Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME 1047 | return; 1048 | } 1049 | }, 1050 | error: function (XMLHttpRequest, textStatus, errorThrown) { 1051 | Janus.error(textStatus + ": " + errorThrown); // FIXME 1052 | }, 1053 | dataType: "json" 1054 | }); 1055 | } 1056 | 1057 | // Private method to send a data channel message 1058 | function sendData(handleId, callbacks) { 1059 | callbacks = callbacks || {}; 1060 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 1061 | callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; 1062 | var pluginHandle = pluginHandles[handleId]; 1063 | if (pluginHandle === null || pluginHandle === undefined || 1064 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1065 | Janus.warn("Invalid handle"); 1066 | callbacks.error("Invalid handle"); 1067 | return; 1068 | } 1069 | var config = pluginHandle.webrtcStuff; 1070 | var text = callbacks.text; 1071 | if (text === null || text === undefined) { 1072 | Janus.warn("Invalid text"); 1073 | callbacks.error("Invalid text"); 1074 | return; 1075 | } 1076 | Janus.log("Sending string on data channel: " + text); 1077 | config.dataChannel.send(text); 1078 | callbacks.success(); 1079 | } 1080 | 1081 | // Private method to send a DTMF tone 1082 | function sendDtmf(handleId, callbacks) { 1083 | callbacks = callbacks || {}; 1084 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 1085 | callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; 1086 | var pluginHandle = pluginHandles[handleId]; 1087 | if (pluginHandle === null || pluginHandle === undefined || 1088 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1089 | Janus.warn("Invalid handle"); 1090 | callbacks.error("Invalid handle"); 1091 | return; 1092 | } 1093 | var config = pluginHandle.webrtcStuff; 1094 | if (config.dtmfSender === null || config.dtmfSender === undefined) { 1095 | // Create the DTMF sender, if possible 1096 | if (config.myStream !== undefined && config.myStream !== null) { 1097 | var tracks = config.myStream.getAudioTracks(); 1098 | if (tracks !== null && tracks !== undefined && tracks.length > 0) { 1099 | var local_audio_track = tracks[0]; 1100 | config.dtmfSender = config.pc.createDTMFSender(local_audio_track); 1101 | Janus.log("Created DTMF Sender"); 1102 | config.dtmfSender.ontonechange = function (tone) { Janus.debug("Sent DTMF tone: " + tone.tone); }; 1103 | } 1104 | } 1105 | if (config.dtmfSender === null || config.dtmfSender === undefined) { 1106 | Janus.warn("Invalid DTMF configuration"); 1107 | callbacks.error("Invalid DTMF configuration"); 1108 | return; 1109 | } 1110 | } 1111 | var dtmf = callbacks.dtmf; 1112 | if (dtmf === null || dtmf === undefined) { 1113 | Janus.warn("Invalid DTMF parameters"); 1114 | callbacks.error("Invalid DTMF parameters"); 1115 | return; 1116 | } 1117 | var tones = dtmf.tones; 1118 | if (tones === null || tones === undefined) { 1119 | Janus.warn("Invalid DTMF string"); 1120 | callbacks.error("Invalid DTMF string"); 1121 | return; 1122 | } 1123 | var duration = dtmf.duration; 1124 | if (duration === null || duration === undefined) 1125 | duration = 500; // We choose 500ms as the default duration for a tone 1126 | var gap = dtmf.gap; 1127 | if (gap === null || gap === undefined) 1128 | gap = 50; // We choose 50ms as the default gap between tones 1129 | Janus.debug("Sending DTMF string " + tones + " (duration " + duration + "ms, gap " + gap + "ms"); 1130 | config.dtmfSender.insertDTMF(tones, duration, gap); 1131 | } 1132 | 1133 | // Private method to destroy a plugin handle 1134 | function destroyHandle(handleId, callbacks, syncRequest) { 1135 | syncRequest = false; 1136 | Janus.log("Destroying handle " + handleId + " (sync=" + syncRequest + ")"); 1137 | callbacks = callbacks || {}; 1138 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 1139 | callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; 1140 | cleanupWebrtc(handleId); 1141 | if (!connected) { 1142 | Janus.warn("Is the gateway down? (connected=false)"); 1143 | callbacks.error("Is the gateway down? (connected=false)"); 1144 | return; 1145 | } 1146 | var request = { "janus": "detach", "transaction": randomString(12) }; 1147 | if (token !== null && token !== undefined) 1148 | request["token"] = token; 1149 | if (apisecret !== null && apisecret !== undefined) 1150 | request["apisecret"] = apisecret; 1151 | if (websockets) { 1152 | request["session_id"] = sessionId; 1153 | request["handle_id"] = handleId; 1154 | ws.send(JSON.stringify(request)); 1155 | delete pluginHandles[handleId]; 1156 | callbacks.success(); 1157 | return; 1158 | } 1159 | Janus.ajax({ 1160 | type: 'POST', 1161 | url: server + "/" + sessionId + "/" + handleId, 1162 | async: true, // Sometimes we need false here, or destroying in onbeforeunload won't work 1163 | cache: false, 1164 | contentType: "application/json", 1165 | data: JSON.stringify(request), 1166 | success: function (json) { 1167 | Janus.log("Destroyed handle:"); 1168 | Janus.debug(json); 1169 | if (json["janus"] !== "success") { 1170 | Janus.log("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME 1171 | } 1172 | delete pluginHandles[handleId]; 1173 | callbacks.success(); 1174 | }, 1175 | error: function (XMLHttpRequest, textStatus, errorThrown) { 1176 | Janus.error(textStatus + ": " + errorThrown); // FIXME 1177 | // We cleanup anyway 1178 | delete pluginHandles[handleId]; 1179 | callbacks.success(); 1180 | }, 1181 | dataType: "json" 1182 | }); 1183 | } 1184 | 1185 | 1186 | 1187 | 1188 | // WebRTC stuff 1189 | function changeLocalCamera(handleId) { 1190 | localstream._tracks[1]._switchCamera() 1191 | } 1192 | 1193 | function streamsDone(handleId, jsep, media, callbacks, stream) { 1194 | var pluginHandle = pluginHandles[handleId]; 1195 | if (pluginHandle === null || pluginHandle === undefined || 1196 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1197 | Janus.warn("Invalid handle"); 1198 | callbacks.error("Invalid handle"); 1199 | return; 1200 | } 1201 | var config = pluginHandle.webrtcStuff; 1202 | console.log("streamsDone:", stream) 1203 | Janus.debug("streamsDone:", stream); 1204 | config.myStream = stream; 1205 | var pc_config = { "iceServers": iceServers }; 1206 | //~ var pc_constraints = {'mandatory': {'MozDontOfferDataChannel':true}}; 1207 | var pc_constraints = { 1208 | "optional": [{ "DtlsSrtpKeyAgreement": true }] 1209 | }; 1210 | if (ipv6Support === true) { 1211 | // FIXME This is only supported in Chrome right now 1212 | // For support in Firefox track this: https://bugzilla.mozilla.org/show_bug.cgi?id=797262 1213 | pc_constraints.optional.push({ "googIPv6": true }); 1214 | } 1215 | Janus.log("Creating PeerConnection"); 1216 | console.log("Creating PeerConnection"); 1217 | Janus.debug(pc_constraints); 1218 | config.pc = new RTCPeerConnection(pc_config, pc_constraints); 1219 | Janus.debug(config.pc); 1220 | console.log(config.pc); 1221 | 1222 | if (config.pc.getStats) { // FIXME 1223 | config.volume.value = 0; 1224 | config.bitrate.value = "0 kbits/sec"; 1225 | } 1226 | Janus.log("Preparing local SDP and gathering candidates (trickle=" + config.trickle + ")"); 1227 | console.log("Preparing local SDP and gathering candidates (trickle=" + config.trickle + ")"); 1228 | 1229 | config.pc.onicecandidate = function (event) { 1230 | // console.log("config.pc.onicecandidate") 1231 | // if (event.candidate == null || 1232 | // (webrtcDetectedBrowser === 'edge' && event.candidate.candidate.indexOf('endOfCandidates') > 0)) { 1233 | // Janus.log("End of candidates."); 1234 | // config.iceDone = true; 1235 | // if(config.trickle === true) { 1236 | // // Notify end of candidates 1237 | // sendTrickleCandidate(handleId, {"completed": true}); 1238 | // } else { 1239 | // // No trickle, time to send the complete SDP (including all candidates) 1240 | // sendSDP(handleId, callbacks); 1241 | // } 1242 | // } else { 1243 | // JSON.stringify doesn't work on some WebRTC objects anymore 1244 | // See https://code.google.com/p/chromium/issues/detail?id=467366 1245 | if (event.candidate) { 1246 | var candidate = { 1247 | "candidate": event.candidate.candidate, 1248 | "sdpMid": event.candidate.sdpMid, 1249 | "sdpMLineIndex": event.candidate.sdpMLineIndex 1250 | }; 1251 | if (config.trickle === true) { 1252 | // Send candidate 1253 | sendTrickleCandidate(handleId, candidate); 1254 | } 1255 | } 1256 | // } 1257 | }; 1258 | 1259 | 1260 | if (stream !== null && stream !== undefined) { 1261 | Janus.log('Adding local stream'); 1262 | console.log('Adding local stream') 1263 | config.pc.addStream(stream); 1264 | console.log('Adding local end') 1265 | pluginHandle.onlocalstream(stream); 1266 | 1267 | localstream = stream 1268 | } 1269 | 1270 | config.pc.onaddstream = function (remoteStream) { 1271 | Janus.log("Handling Remote Stream"); 1272 | console.log("Handling Remote Stream"); 1273 | Janus.debug(remoteStream); 1274 | config.remoteStream = remoteStream; 1275 | pluginHandle.onremotestream(remoteStream.stream); 1276 | }; 1277 | // Any data channel to create? 1278 | if (isDataEnabled(media)) { 1279 | Janus.log("Creating data channel"); 1280 | console.log("Creating data channel"); 1281 | var onDataChannelMessage = function (event) { 1282 | Janus.log('Received message on data channel: ' + event.data); 1283 | pluginHandle.ondata(event.data); // FIXME 1284 | } 1285 | var onDataChannelStateChange = function () { 1286 | var dcState = config.dataChannel !== null ? config.dataChannel.readyState : "null"; 1287 | Janus.log('State change on data channel: ' + dcState); 1288 | if (dcState === 'open') { 1289 | pluginHandle.ondataopen(); // FIXME 1290 | } 1291 | } 1292 | var onDataChannelError = function (error) { 1293 | Janus.error('Got error on data channel:', error); 1294 | // TODO 1295 | } 1296 | // Until we implement the proxying of open requests within the Janus core, we open a channel ourselves whatever the case 1297 | config.dataChannel = config.pc.createDataChannel("JanusDataChannel", { ordered: false }); // FIXME Add options (ordered, maxRetransmits, etc.) 1298 | config.dataChannel.onmessage = onDataChannelMessage; 1299 | config.dataChannel.onopen = onDataChannelStateChange; 1300 | config.dataChannel.onclose = onDataChannelStateChange; 1301 | config.dataChannel.onerror = onDataChannelError; 1302 | } 1303 | // Create offer/answer now 1304 | if (jsep === null || jsep === undefined) { 1305 | console.log("createOffer") 1306 | createOffer(handleId, media, callbacks); 1307 | } else { 1308 | config.pc.setRemoteDescription( 1309 | new RTCSessionDescription(jsep), 1310 | function () { 1311 | Janus.log("Remote description accepted!"); 1312 | createAnswer(handleId, media, callbacks); 1313 | }, callbacks.error); 1314 | } 1315 | } 1316 | 1317 | function prepareWebrtc(handleId, callbacks) { 1318 | 1319 | callbacks = callbacks || {}; 1320 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 1321 | callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError; 1322 | var jsep = callbacks.jsep; 1323 | var media = callbacks.media; 1324 | var pluginHandle = pluginHandles[handleId]; 1325 | if (pluginHandle === null || pluginHandle === undefined || 1326 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1327 | Janus.warn("Invalid handle"); 1328 | callbacks.error("Invalid handle"); 1329 | return; 1330 | } 1331 | var config = pluginHandle.webrtcStuff; 1332 | // Are we updating a session? 1333 | if (config.pc !== undefined && config.pc !== null) { 1334 | Janus.log("Updating existing media session"); 1335 | // Create offer/answer now 1336 | if (jsep === null || jsep === undefined) { 1337 | createOffer(handleId, media, callbacks); 1338 | } else { 1339 | config.pc.setRemoteDescription( 1340 | new RTCSessionDescription(jsep), 1341 | function () { 1342 | Janus.log("Remote description accepted!"); 1343 | createAnswer(handleId, media, callbacks); 1344 | }, callbacks.error); 1345 | } 1346 | return; 1347 | } 1348 | // Was a MediaStream object passed, or do we need to take care of that? 1349 | if (callbacks.stream !== null && callbacks.stream !== undefined) { 1350 | var stream = callbacks.stream; 1351 | Janus.log("MediaStream provided by the application"); 1352 | Janus.debug(stream); 1353 | // Skip the getUserMedia part 1354 | config.streamExternal = true; 1355 | streamsDone(handleId, jsep, media, callbacks, stream); 1356 | return; 1357 | } 1358 | config.trickle = isTrickleEnabled(callbacks.trickle); 1359 | if (isAudioSendEnabled(media) || isVideoSendEnabled(media)) { 1360 | var constraints = { mandatory: {}, optional: [] }; 1361 | pluginHandle.consentDialog(true); 1362 | var audioSupport = isAudioSendEnabled(media); 1363 | if (audioSupport === true && media != undefined && media != null) { 1364 | if (typeof media.audio === 'object') { 1365 | audioSupport = media.audio; 1366 | } 1367 | } 1368 | var videoSupport = isVideoSendEnabled(media); 1369 | if (videoSupport === true && media != undefined && media != null) { 1370 | if (media.video && media.video != 'screen' && media.video != 'window') { 1371 | var width = 0; 1372 | var height = 0, maxHeight = 0; 1373 | if (media.video === 'lowres') { 1374 | // Small resolution, 4:3 1375 | height = 240; 1376 | maxHeight = 240; 1377 | width = 320; 1378 | } else if (media.video === 'lowres-16:9') { 1379 | // Small resolution, 16:9 1380 | height = 180; 1381 | maxHeight = 180; 1382 | width = 320; 1383 | } else if (media.video === 'hires' || media.video === 'hires-16:9') { 1384 | // High resolution is only 16:9 1385 | height = 720; 1386 | maxHeight = 720; 1387 | width = 1280; 1388 | if (navigator.mozGetUserMedia) { 1389 | var firefoxVer = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10); 1390 | if (firefoxVer < 38) { 1391 | // Unless this is and old Firefox, which doesn't support it 1392 | Janus.warn(media.video + " unsupported, falling back to stdres (old Firefox)"); 1393 | height = 480; 1394 | maxHeight = 480; 1395 | width = 640; 1396 | } 1397 | } 1398 | } else if (media.video === 'stdres') { 1399 | // Normal resolution, 4:3 1400 | height = 480; 1401 | maxHeight = 480; 1402 | width = 640; 1403 | } else if (media.video === 'stdres-16:9') { 1404 | // Normal resolution, 16:9 1405 | height = 360; 1406 | maxHeight = 360; 1407 | width = 640; 1408 | } else { 1409 | Janus.log("Default video setting (" + media.video + ") is stdres 4:3"); 1410 | height = 480; 1411 | maxHeight = 480; 1412 | width = 640; 1413 | } 1414 | Janus.log("Adding media constraint " + media.video); 1415 | if (navigator.mozGetUserMedia) { 1416 | var firefoxVer = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10); 1417 | if (firefoxVer < 38) { 1418 | videoSupport = { 1419 | 'require': ['height', 'width'], 1420 | 'height': { 'max': maxHeight, 'min': height }, 1421 | 'width': { 'max': width, 'min': width } 1422 | }; 1423 | } else { 1424 | // http://stackoverflow.com/questions/28282385/webrtc-firefox-constraints/28911694#28911694 1425 | // https://github.com/meetecho/janus-gateway/pull/246 1426 | videoSupport = { 1427 | 'height': { 'ideal': height }, 1428 | 'width': { 'ideal': width } 1429 | }; 1430 | } 1431 | } else { 1432 | videoSupport = { 1433 | 'mandatory': { 1434 | 'maxHeight': maxHeight, 1435 | 'minHeight': height, 1436 | 'maxWidth': width, 1437 | 'minWidth': width 1438 | }, 1439 | 'optional': [] 1440 | }; 1441 | } 1442 | if (typeof media.video === 'object') { 1443 | videoSupport = media.video; 1444 | } 1445 | Janus.debug(videoSupport); 1446 | } else if (media.video === 'screen' || media.video === 'window') { 1447 | // Not a webcam, but screen capture 1448 | if (window.location.protocol !== 'https:') { 1449 | // Screen sharing mandates HTTPS 1450 | Janus.warn("Screen sharing only works on HTTPS, try the https:// version of this page"); 1451 | pluginHandle.consentDialog(false); 1452 | callbacks.error("Screen sharing only works on HTTPS, try the https:// version of this page"); 1453 | return; 1454 | } 1455 | // We're going to try and use the extension for Chrome 34+, the old approach 1456 | // for older versions of Chrome, or the experimental support in Firefox 33+ 1457 | var cache = {}; 1458 | function callbackUserMedia(error, stream) { 1459 | pluginHandle.consentDialog(false); 1460 | if (error) { 1461 | callbacks.error({ code: error.code, name: error.name, message: error.message }); 1462 | } else { 1463 | streamsDone(handleId, jsep, media, callbacks, stream); 1464 | } 1465 | }; 1466 | function getScreenMedia(constraints, gsmCallback) { 1467 | Janus.log("Adding media constraint (screen capture)"); 1468 | Janus.debug(constraints); 1469 | navigator.mediaDevices.getUserMedia(constraints) 1470 | .then(function (stream) { gsmCallback(null, stream); }) 1471 | .catch(function (error) { pluginHandle.consentDialog(false); gsmCallback(error); }); 1472 | }; 1473 | 1474 | if (window.navigator.userAgent.match('Chrome')) { 1475 | var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10); 1476 | var maxver = 33; 1477 | if (window.navigator.userAgent.match('Linux')) 1478 | maxver = 35; // "known" crash in chrome 34 and 35 on linux 1479 | if (chromever >= 26 && chromever <= maxver) { 1480 | // Chrome 26->33 requires some awkward chrome://flags manipulation 1481 | constraints = { 1482 | video: { 1483 | mandatory: { 1484 | googLeakyBucket: true, 1485 | maxWidth: window.screen.width, 1486 | maxHeight: window.screen.height, 1487 | maxFrameRate: 3, 1488 | chromeMediaSource: 'screen' 1489 | } 1490 | }, 1491 | audio: isAudioSendEnabled(media) 1492 | }; 1493 | getScreenMedia(constraints, callbackUserMedia); 1494 | } else { 1495 | // Chrome 34+ requires an extension 1496 | var pending = window.setTimeout( 1497 | function () { 1498 | error = new Error('NavigatorUserMediaError'); 1499 | error.name = 'The required Chrome extension is not installed: click here to install it. (NOTE: this will need you to refresh the page)'; 1500 | pluginHandle.consentDialog(false); 1501 | return callbacks.error(error); 1502 | }, 1000); 1503 | cache[pending] = [callbackUserMedia, null]; 1504 | window.postMessage({ type: 'janusGetScreen', id: pending }, '*'); 1505 | } 1506 | } else if (window.navigator.userAgent.match('Firefox')) { 1507 | var ffver = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10); 1508 | if (ffver >= 33) { 1509 | // Firefox 33+ has experimental support for screen sharing 1510 | constraints = { 1511 | video: { 1512 | mozMediaSource: media.video, 1513 | mediaSource: media.video 1514 | }, 1515 | audio: isAudioSendEnabled(media) 1516 | }; 1517 | getScreenMedia(constraints, function (err, stream) { 1518 | callbackUserMedia(err, stream); 1519 | // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810 1520 | if (!err) { 1521 | var lastTime = stream.currentTime; 1522 | var polly = window.setInterval(function () { 1523 | if (!stream) 1524 | window.clearInterval(polly); 1525 | if (stream.currentTime == lastTime) { 1526 | window.clearInterval(polly); 1527 | if (stream.onended) { 1528 | stream.onended(); 1529 | } 1530 | } 1531 | lastTime = stream.currentTime; 1532 | }, 500); 1533 | } 1534 | }); 1535 | } else { 1536 | var error = new Error('NavigatorUserMediaError'); 1537 | error.name = 'Your version of Firefox does not support screen sharing, please install Firefox 33 (or more recent versions)'; 1538 | pluginHandle.consentDialog(false); 1539 | callbacks.error(error); 1540 | return; 1541 | } 1542 | } 1543 | 1544 | // Wait for events from the Chrome Extension 1545 | window.addEventListener('message', function (event) { 1546 | if (event.origin != window.location.origin) 1547 | return; 1548 | if (event.data.type == 'janusGotScreen' && cache[event.data.id]) { 1549 | var data = cache[event.data.id]; 1550 | var callback = data[0]; 1551 | delete cache[event.data.id]; 1552 | 1553 | if (event.data.sourceId === '') { 1554 | // user canceled 1555 | var error = new Error('NavigatorUserMediaError'); 1556 | error.name = 'You cancelled the request for permission, giving up...'; 1557 | pluginHandle.consentDialog(false); 1558 | callbacks.error(error); 1559 | } else { 1560 | constraints = { 1561 | audio: isAudioSendEnabled(media), 1562 | video: { 1563 | mandatory: { 1564 | chromeMediaSource: 'desktop', 1565 | maxWidth: window.screen.width, 1566 | maxHeight: window.screen.height, 1567 | maxFrameRate: 3 1568 | }, 1569 | optional: [ 1570 | { googLeakyBucket: true }, 1571 | { googTemporalLayeredScreencast: true } 1572 | ] 1573 | } 1574 | }; 1575 | constraints.video.mandatory.chromeMediaSourceId = event.data.sourceId; 1576 | getScreenMedia(constraints, callback); 1577 | } 1578 | } else if (event.data.type == 'janusGetScreenPending') { 1579 | window.clearTimeout(event.data.id); 1580 | } 1581 | }); 1582 | return; 1583 | } 1584 | } 1585 | // If we got here, we're not screensharing 1586 | if (media === null || media === undefined || media.video !== 'screen') { 1587 | // Check whether all media sources are actually available or not 1588 | 1589 | mediaDevices.enumerateDevices().then(devices => { 1590 | console.log(devices); 1591 | mediaDevices.getUserMedia({ 1592 | audio: true, 1593 | video: { 1594 | facingMode: (false ? "user" : "environment"), 1595 | } 1596 | }).then(stream => { 1597 | localStream = stream; 1598 | console.log("Succeeded to get the local camera!"); 1599 | streamsDone(handleId, jsep, media, callbacks, stream); 1600 | }).catch(error => { 1601 | console.log("Failed to get the local camera!"); 1602 | console.log(error); 1603 | }) 1604 | }) 1605 | } 1606 | 1607 | } else { 1608 | // No need to do a getUserMedia, create offer/answer right away 1609 | streamsDone(handleId, jsep, media, callbacks); 1610 | } 1611 | } 1612 | 1613 | function prepareWebrtcPeer(handleId, callbacks) { 1614 | callbacks = callbacks || {}; 1615 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 1616 | callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError; 1617 | var jsep = callbacks.jsep; 1618 | var pluginHandle = pluginHandles[handleId]; 1619 | if (pluginHandle === null || pluginHandle === undefined || 1620 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1621 | Janus.warn("Invalid handle"); 1622 | callbacks.error("Invalid handle"); 1623 | return; 1624 | } 1625 | var config = pluginHandle.webrtcStuff; 1626 | if (jsep !== undefined && jsep !== null) { 1627 | if (config.pc === null) { 1628 | Janus.warn("Wait, no PeerConnection?? if this is an answer, use createAnswer and not handleRemoteJsep"); 1629 | callbacks.error("No PeerConnection: if this is an answer, use createAnswer and not handleRemoteJsep"); 1630 | return; 1631 | } 1632 | config.pc.setRemoteDescription( 1633 | new RTCSessionDescription(jsep), 1634 | function () { 1635 | Janus.log("Remote description accepted!"); 1636 | callbacks.success(); 1637 | }, callbacks.error); 1638 | } else { 1639 | callbacks.error("Invalid JSEP"); 1640 | } 1641 | } 1642 | 1643 | function createOffer(handleId, media, callbacks) { 1644 | callbacks = callbacks || {}; 1645 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 1646 | callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; 1647 | var pluginHandle = pluginHandles[handleId]; 1648 | if (pluginHandle === null || pluginHandle === undefined || 1649 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1650 | Janus.warn("Invalid handle"); 1651 | callbacks.error("Invalid handle"); 1652 | return; 1653 | } 1654 | var config = pluginHandle.webrtcStuff; 1655 | Janus.log("Creating offer (iceDone=" + config.iceDone + ")"); 1656 | // https://code.google.com/p/webrtc/issues/detail?id=3508 1657 | var mediaConstraints = null; 1658 | mediaConstraints = { 1659 | 'mandatory': { 1660 | 'OfferToReceiveAudio': isAudioRecvEnabled(media), 1661 | 'OfferToReceiveVideo': isVideoRecvEnabled(media) 1662 | } 1663 | }; 1664 | // } 1665 | Janus.debug(mediaConstraints); 1666 | config.pc.createOffer().then( 1667 | function (offer) { 1668 | 1669 | Janus.debug(offer); 1670 | if (config.mySdp === null || config.mySdp === undefined) { 1671 | console.log("Setting local description"); 1672 | config.mySdp = offer.sdp; 1673 | // config.pc.setLocalDescription(offer); 1674 | 1675 | config.pc.setLocalDescription(offer, () => { 1676 | console.log('setLocalDescription', config.pc.localDescription); 1677 | }, function (e) { }); 1678 | } 1679 | if (!config.iceDone && !config.trickle) { 1680 | // Don't do anything until we have all candidates 1681 | Janus.log("Waiting for all candidates..."); 1682 | return; 1683 | } 1684 | if (config.sdpSent) { 1685 | Janus.log("Offer already sent, not sending it again"); 1686 | return; 1687 | } 1688 | Janus.log("Offer ready"); 1689 | Janus.debug(callbacks); 1690 | config.sdpSent = true; 1691 | // JSON.stringify doesn't work on some WebRTC objects anymore 1692 | // See https://code.google.com/p/chromium/issues/detail?id=467366 1693 | var jsep = { 1694 | "type": offer.type, 1695 | "sdp": offer.sdp 1696 | }; 1697 | callbacks.success(jsep); 1698 | }).then(function (e) { }); 1699 | } 1700 | 1701 | function createAnswer(handleId, media, callbacks) { 1702 | callbacks = callbacks || {}; 1703 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 1704 | callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; 1705 | var pluginHandle = pluginHandles[handleId]; 1706 | if (pluginHandle === null || pluginHandle === undefined || 1707 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1708 | Janus.warn("Invalid handle"); 1709 | callbacks.error("Invalid handle"); 1710 | return; 1711 | } 1712 | var config = pluginHandle.webrtcStuff; 1713 | Janus.log("Creating answer (iceDone=" + config.iceDone + ")"); 1714 | var mediaConstraints = null; 1715 | // if(webrtcDetectedBrowser == "firefox" || webrtcDetectedBrowser == "edge") { 1716 | // mediaConstraints = { 1717 | // 'offerToReceiveAudio':isAudioRecvEnabled(media), 1718 | // 'offerToReceiveVideo':isVideoRecvEnabled(media) 1719 | // }; 1720 | // } else { 1721 | mediaConstraints = { 1722 | 'mandatory': { 1723 | 'OfferToReceiveAudio': isAudioRecvEnabled(media), 1724 | 'OfferToReceiveVideo': isVideoRecvEnabled(media) 1725 | } 1726 | }; 1727 | // } 1728 | Janus.debug(mediaConstraints); 1729 | config.pc.createAnswer( 1730 | function (answer) { 1731 | Janus.debug(answer); 1732 | if (config.mySdp === null || config.mySdp === undefined) { 1733 | Janus.log("Setting local description"); 1734 | config.mySdp = answer.sdp; 1735 | config.pc.setLocalDescription(answer, () => { 1736 | // console.log('setLocalDescription', config.pc.localDescription); 1737 | }, (error) => { 1738 | console.log(error); 1739 | }, 1740 | ); 1741 | } 1742 | if (!config.iceDone && !config.trickle) { 1743 | // Don't do anything until we have all candidates 1744 | Janus.log("Waiting for all candidates..."); 1745 | return; 1746 | } 1747 | if (config.sdpSent) { // FIXME badly 1748 | Janus.log("Answer already sent, not sending it again"); 1749 | return; 1750 | } 1751 | config.sdpSent = true; 1752 | // JSON.stringify doesn't work on some WebRTC objects anymore 1753 | // See https://code.google.com/p/chromium/issues/detail?id=467366 1754 | var jsep = { 1755 | "type": answer.type, 1756 | "sdp": answer.sdp 1757 | }; 1758 | callbacks.success(jsep); 1759 | }, callbacks.error, mediaConstraints); 1760 | } 1761 | 1762 | function sendSDP(handleId, callbacks) { 1763 | callbacks = callbacks || {}; 1764 | callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; 1765 | callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; 1766 | var pluginHandle = pluginHandles[handleId]; 1767 | if (pluginHandle === null || pluginHandle === undefined || 1768 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1769 | Janus.warn("Invalid handle, not sending anything"); 1770 | return; 1771 | } 1772 | var config = pluginHandle.webrtcStuff; 1773 | Janus.log("Sending offer/answer SDP..."); 1774 | if (config.mySdp === null || config.mySdp === undefined) { 1775 | Janus.warn("Local SDP instance is invalid, not sending anything..."); 1776 | return; 1777 | } 1778 | config.mySdp = { 1779 | "type": config.pc.localDescription.type, 1780 | "sdp": config.pc.localDescription.sdp 1781 | }; 1782 | if (config.sdpSent) { 1783 | Janus.log("Offer/Answer SDP already sent, not sending it again"); 1784 | return; 1785 | } 1786 | if (config.trickle === false) 1787 | config.mySdp["trickle"] = false; 1788 | Janus.debug(callbacks); 1789 | config.sdpSent = true; 1790 | callbacks.success(config.mySdp); 1791 | } 1792 | 1793 | function getVolume(handleId) { 1794 | var pluginHandle = pluginHandles[handleId]; 1795 | if (pluginHandle === null || pluginHandle === undefined || 1796 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1797 | Janus.warn("Invalid handle"); 1798 | return 0; 1799 | } 1800 | var config = pluginHandle.webrtcStuff; 1801 | // Start getting the volume, if getStats is supported 1802 | if (config.pc.getStats && webrtcDetectedBrowser == "chrome") { // FIXME 1803 | if (config.remoteStream === null || config.remoteStream === undefined) { 1804 | Janus.warn("Remote stream unavailable"); 1805 | return 0; 1806 | } 1807 | // http://webrtc.googlecode.com/svn/trunk/samples/js/demos/html/constraints-and-stats.html 1808 | if (config.volume.timer === null || config.volume.timer === undefined) { 1809 | Janus.log("Starting volume monitor"); 1810 | config.volume.timer = setInterval(function () { 1811 | config.pc.getStats(function (stats) { 1812 | var results = stats.result(); 1813 | for (var i = 0; i < results.length; i++) { 1814 | var res = results[i]; 1815 | if (res.type == 'ssrc' && res.stat('audioOutputLevel')) { 1816 | config.volume.value = res.stat('audioOutputLevel'); 1817 | } 1818 | } 1819 | }); 1820 | }, 200); 1821 | return 0; // We don't have a volume to return yet 1822 | } 1823 | return config.volume.value; 1824 | } else { 1825 | Janus.log("Getting the remote volume unsupported by browser"); 1826 | return 0; 1827 | } 1828 | } 1829 | 1830 | function isMuted(handleId, video) { 1831 | var pluginHandle = pluginHandles[handleId]; 1832 | if (pluginHandle === null || pluginHandle === undefined || 1833 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1834 | Janus.warn("Invalid handle"); 1835 | return true; 1836 | } 1837 | var config = pluginHandle.webrtcStuff; 1838 | if (config.pc === null || config.pc === undefined) { 1839 | Janus.warn("Invalid PeerConnection"); 1840 | return true; 1841 | } 1842 | if (config.myStream === undefined || config.myStream === null) { 1843 | Janus.warn("Invalid local MediaStream"); 1844 | return true; 1845 | } 1846 | if (video) { 1847 | // Check video track 1848 | if (config.myStream.getVideoTracks() === null 1849 | || config.myStream.getVideoTracks() === undefined 1850 | || config.myStream.getVideoTracks().length === 0) { 1851 | Janus.warn("No video track"); 1852 | return true; 1853 | } 1854 | return !config.myStream.getVideoTracks()[0].enabled; 1855 | } else { 1856 | // Check audio track 1857 | if (config.myStream.getAudioTracks() === null 1858 | || config.myStream.getAudioTracks() === undefined 1859 | || config.myStream.getAudioTracks().length === 0) { 1860 | Janus.warn("No audio track"); 1861 | return true; 1862 | } 1863 | return !config.myStream.getAudioTracks()[0].enabled; 1864 | } 1865 | } 1866 | 1867 | function mute(handleId, video, mute) { 1868 | var pluginHandle = pluginHandles[handleId]; 1869 | if (pluginHandle === null || pluginHandle === undefined || 1870 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1871 | Janus.warn("Invalid handle"); 1872 | return false; 1873 | } 1874 | var config = pluginHandle.webrtcStuff; 1875 | if (config.pc === null || config.pc === undefined) { 1876 | Janus.warn("Invalid PeerConnection"); 1877 | return false; 1878 | } 1879 | if (config.myStream === undefined || config.myStream === null) { 1880 | Janus.warn("Invalid local MediaStream"); 1881 | return false; 1882 | } 1883 | if (video) { 1884 | // Mute/unmute video track 1885 | if (config.myStream.getVideoTracks() === null 1886 | || config.myStream.getVideoTracks() === undefined 1887 | || config.myStream.getVideoTracks().length === 0) { 1888 | Janus.warn("No video track"); 1889 | return false; 1890 | } 1891 | config.myStream.getVideoTracks()[0].enabled = mute ? false : true; 1892 | return true; 1893 | } else { 1894 | // Mute/unmute audio track 1895 | if (config.myStream.getAudioTracks() === null 1896 | || config.myStream.getAudioTracks() === undefined 1897 | || config.myStream.getAudioTracks().length === 0) { 1898 | Janus.warn("No audio track"); 1899 | return false; 1900 | } 1901 | config.myStream.getAudioTracks()[0].enabled = mute ? false : true; 1902 | return true; 1903 | } 1904 | } 1905 | 1906 | function getBitrate(handleId) { 1907 | var pluginHandle = pluginHandles[handleId]; 1908 | if (pluginHandle === null || pluginHandle === undefined || 1909 | pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) { 1910 | Janus.warn("Invalid handle"); 1911 | return "Invalid handle"; 1912 | } 1913 | var config = pluginHandle.webrtcStuff; 1914 | if (config.pc === null || config.pc === undefined) 1915 | return "Invalid PeerConnection"; 1916 | // Start getting the bitrate, if getStats is supported 1917 | if (true) { 1918 | // Do it the Chrome way 1919 | if (config.remoteStream === null || config.remoteStream === undefined) { 1920 | Janus.warn("Remote stream unavailable"); 1921 | return "Remote stream unavailable"; 1922 | } 1923 | // http://webrtc.googlecode.com/svn/trunk/samples/js/demos/html/constraints-and-stats.html 1924 | if (config.bitrate.timer === null || config.bitrate.timer === undefined) { 1925 | Janus.log("Starting bitrate timer (Chrome)"); 1926 | config.bitrate.timer = setInterval(function () { 1927 | config.pc.getStats(function (stats) { 1928 | var results = stats.result(); 1929 | for (var i = 0; i < results.length; i++) { 1930 | var res = results[i]; 1931 | if (res.type == 'ssrc' && res.stat('googFrameHeightReceived')) { 1932 | config.bitrate.bsnow = res.stat('bytesReceived'); 1933 | config.bitrate.tsnow = res.timestamp; 1934 | if (config.bitrate.bsbefore === null || config.bitrate.tsbefore === null) { 1935 | // Skip this round 1936 | config.bitrate.bsbefore = config.bitrate.bsnow; 1937 | config.bitrate.tsbefore = config.bitrate.tsnow; 1938 | } else { 1939 | // Calculate bitrate 1940 | var bitRate = Math.round((config.bitrate.bsnow - config.bitrate.bsbefore) * 8 / (config.bitrate.tsnow - config.bitrate.tsbefore)); 1941 | config.bitrate.value = bitRate + ' kbits/sec'; 1942 | //~ Janus.log("Estimated bitrate is " + config.bitrate.value); 1943 | config.bitrate.bsbefore = config.bitrate.bsnow; 1944 | config.bitrate.tsbefore = config.bitrate.tsnow; 1945 | } 1946 | } 1947 | } 1948 | }); 1949 | }, 1000); 1950 | return "0 kbits/sec"; // We don't have a bitrate value yet 1951 | } 1952 | return config.bitrate.value; 1953 | } 1954 | } 1955 | 1956 | function webrtcError(error) { 1957 | Janus.error("WebRTC error:", error); 1958 | } 1959 | 1960 | function cleanupWebrtc(handleId, hangupRequest) { 1961 | 1962 | 1963 | Janus.log("Cleaning WebRTC stuff"); 1964 | console.log(handleId) 1965 | console.log(hangupRequest) 1966 | var pluginHandle = pluginHandles[handleId]; 1967 | if (pluginHandle === null || pluginHandle === undefined) { 1968 | // Nothing to clean 1969 | return; 1970 | } 1971 | var config = pluginHandle.webrtcStuff; 1972 | if (config !== null && config !== undefined) { 1973 | if (hangupRequest === true) { 1974 | // Send a hangup request (we don't really care about the response) 1975 | var request = { "janus": "hangup", "transaction": randomString(12) }; 1976 | if (token !== null && token !== undefined) 1977 | request["token"] = token; 1978 | if (apisecret !== null && apisecret !== undefined) 1979 | request["apisecret"] = apisecret; 1980 | Janus.debug("Sending hangup request (handle=" + handleId + "):"); 1981 | Janus.debug(request); 1982 | if (websockets) { 1983 | request["session_id"] = sessionId; 1984 | request["handle_id"] = handleId; 1985 | ws.send(JSON.stringify(request)); 1986 | } else { 1987 | $.ajax({ 1988 | type: 'POST', 1989 | url: server + "/" + sessionId + "/" + handleId, 1990 | cache: false, 1991 | contentType: "application/json", 1992 | data: JSON.stringify(request), 1993 | dataType: "json" 1994 | }); 1995 | } 1996 | } 1997 | // Cleanup stack 1998 | config.remoteStream = null; 1999 | if (config.volume.timer) 2000 | clearInterval(config.volume.timer); 2001 | config.volume.value = null; 2002 | if (config.bitrate.timer) 2003 | clearInterval(config.bitrate.timer); 2004 | config.bitrate.timer = null; 2005 | config.bitrate.bsnow = null; 2006 | config.bitrate.bsbefore = null; 2007 | config.bitrate.tsnow = null; 2008 | config.bitrate.tsbefore = null; 2009 | config.bitrate.value = null; 2010 | try { 2011 | // Try a MediaStream.stop() first 2012 | if (!config.streamExternal && config.myStream !== null && config.myStream !== undefined) { 2013 | // console.log(config) 2014 | Janus.log("Stopping local stream"); 2015 | // config.pc.removeStream(localstream) 2016 | // localstream.release() 2017 | // localstream.stop() 2018 | localStream.getTracks().forEach((t) => { 2019 | localStream.removeTrack(t); 2020 | }); 2021 | localStream.release(); 2022 | // config.myStream.stop(); 2023 | config.myStream.getTracks().forEach(function (track) { track.stop(); }); 2024 | } 2025 | } catch (e) { 2026 | console.log(e) 2027 | // Do nothing if this fails 2028 | } 2029 | try { 2030 | // Try a MediaStreamTrack.stop() for each track as well 2031 | if (!config.streamExternal && config.myStream !== null && config.myStream !== undefined) { 2032 | Janus.log("Stopping local stream tracks"); 2033 | var tracks = config.myStream.getTracks(); 2034 | for (var i in tracks) { 2035 | var mst = tracks[i]; 2036 | Janus.log(mst); 2037 | if (mst !== null && mst !== undefined) 2038 | mst.stop(); 2039 | } 2040 | } 2041 | } catch (e) { 2042 | console.log(e) 2043 | // Do nothing if this fails 2044 | } 2045 | config.streamExternal = false; 2046 | config.myStream = null; 2047 | // Close PeerConnection 2048 | try { 2049 | config.pc.close(); 2050 | } catch (e) { 2051 | // Do nothing 2052 | } 2053 | config.pc = null; 2054 | config.mySdp = null; 2055 | config.iceDone = false; 2056 | config.sdpSent = false; 2057 | config.dataChannel = null; 2058 | config.dtmfSender = null; 2059 | } 2060 | pluginHandle.oncleanup(); 2061 | } 2062 | 2063 | // Helper methods to parse a media object 2064 | function isAudioSendEnabled(media) { 2065 | Janus.debug("isAudioSendEnabled:", media); 2066 | if (media === undefined || media === null) 2067 | return true; // Default 2068 | if (media.audio === false) 2069 | return false; // Generic audio has precedence 2070 | if (media.audioSend === undefined || media.audioSend === null) 2071 | return true; // Default 2072 | return (media.audioSend === true); 2073 | } 2074 | 2075 | function isAudioRecvEnabled(media) { 2076 | Janus.debug("isAudioRecvEnabled:", media); 2077 | if (media === undefined || media === null) 2078 | return true; // Default 2079 | if (media.audio === false) 2080 | return false; // Generic audio has precedence 2081 | if (media.audioRecv === undefined || media.audioRecv === null) 2082 | return true; // Default 2083 | return (media.audioRecv === true); 2084 | } 2085 | 2086 | function isVideoSendEnabled(media) { 2087 | Janus.debug("isVideoSendEnabled:", media); 2088 | // if(webrtcDetectedBrowser == "edge") { 2089 | // Janus.warn("Edge doesn't support compatible video yet"); 2090 | // return false; 2091 | // } 2092 | if (media === undefined || media === null) 2093 | return true; // Default 2094 | if (media.video === false) 2095 | return false; // Generic video has precedence 2096 | if (media.videoSend === undefined || media.videoSend === null) 2097 | return true; // Default 2098 | return (media.videoSend === true); 2099 | } 2100 | 2101 | function isVideoRecvEnabled(media) { 2102 | Janus.debug("isVideoRecvEnabled:", media); 2103 | // if(webrtcDetectedBrowser == "edge") { 2104 | // Janus.warn("Edge doesn't support compatible video yet"); 2105 | // return false; 2106 | // } 2107 | if (media === undefined || media === null) 2108 | return true; // Default 2109 | if (media.video === false) 2110 | return false; // Generic video has precedence 2111 | if (media.videoRecv === undefined || media.videoRecv === null) 2112 | return true; // Default 2113 | return (media.videoRecv === true); 2114 | } 2115 | 2116 | function isDataEnabled(media) { 2117 | Janus.debug("isDataEnabled:", media); 2118 | // if(webrtcDetectedBrowser == "edge") { 2119 | // Janus.warn("Edge doesn't support data channels yet"); 2120 | // return false; 2121 | // } 2122 | if (media === undefined || media === null) 2123 | return false; // Default 2124 | return (media.data === true); 2125 | } 2126 | 2127 | function isTrickleEnabled(trickle) { 2128 | Janus.debug("isTrickleEnabled:", trickle); 2129 | if (trickle === undefined || trickle === null) 2130 | return true; // Default is true 2131 | return (trickle === true); 2132 | } 2133 | }; 2134 | 2135 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: false, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myApp", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "react-native start", 7 | "test": "jest", 8 | "lint": "eslint ." 9 | }, 10 | "dependencies": { 11 | "react": "16.8.6", 12 | "react-native": "0.60.5", 13 | "react-native-webrtc": "^1.75.0" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "7.5.5", 17 | "@babel/runtime": "7.5.5", 18 | "@react-native-community/eslint-config": "0.0.3", 19 | "babel-jest": "24.9.0", 20 | "eslint": "6.2.0", 21 | "jest": "24.9.0", 22 | "metro-react-native-babel-preset": "0.54.1", 23 | "react-test-renderer": "16.8.6" 24 | }, 25 | "jest": { 26 | "preset": "react-native" 27 | } 28 | } 29 | --------------------------------------------------------------------------------