├── .gitignore ├── README.md ├── art ├── cordova-rtc-logo.png ├── icon-white.png ├── icon.png ├── jssip_freeswitch_1.png ├── jssip_freeswitch_2.png └── local_1.png ├── config.xml ├── hooks └── README.md ├── iosrtc.sh ├── package-lock.json ├── package.json ├── res ├── README.md ├── icon │ └── ios │ │ ├── AppIcon24x24@2x.png │ │ ├── AppIcon27.5x27.5@2x.png │ │ ├── AppIcon29x29@2x.png │ │ ├── AppIcon29x29@3x.png │ │ ├── AppIcon40x40@2x.png │ │ ├── AppIcon44x44@2x.png │ │ ├── AppIcon86x86@2x.png │ │ ├── AppIcon98x98@2x.png │ │ ├── icon-1024.png │ │ ├── icon-20.png │ │ ├── icon-20@2x.png │ │ ├── icon-20@3x.png │ │ ├── icon-40.png │ │ ├── icon-40@2x.png │ │ ├── icon-50.png │ │ ├── icon-50@2x.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-72.png │ │ ├── icon-72@2x.png │ │ ├── icon-76.png │ │ ├── icon-76@2x.png │ │ ├── icon-83.5@2x.png │ │ ├── icon-small.png │ │ ├── icon-small@2x.png │ │ ├── icon-small@3x.png │ │ ├── icon.png │ │ └── icon@2x.png └── screen │ └── ios │ ├── Default-2436h.png │ ├── Default-568h@2x~iphone.png │ ├── Default-667h.png │ ├── Default-736h.png │ ├── Default-Landscape-2436h.png │ ├── Default-Landscape-736h.png │ ├── Default-Landscape@2x~ipad.png │ ├── Default-Landscape~ipad.png │ ├── Default-Portrait@2x~ipad.png │ ├── Default-Portrait~ipad.png │ ├── Default@2x~iphone.png │ └── Default~iphone.png └── www ├── assets └── Sunset.mp4 ├── css └── index.css ├── img └── logo.png ├── index.html ├── js ├── common.js ├── index-easyrtc.js ├── index-janus.js ├── index-jssip.js ├── index-local.js ├── index-twilio.js └── index-websocket.js └── lib └── ios-websocket-hack.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /TODO 3 | /NO_GIT/ 4 | .DS_Store 5 | /plugins/ 6 | /platforms/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![cordova-rtc-logo](./art/cordova-rtc-logo.png) 2 | 3 | # cordova-plugin-iosrtc-sample 4 | 5 | Basic sample application using cordova-plugin-iosrtc. 6 | 7 | ![capture](./art/jssip_freeswitch_1.png) 8 | 9 | ## Requirements 10 | 11 | In order to make this Cordova plugin run into a iOS application some requirements must be satisfied in both development computer and target devices: 12 | 13 | * Xcode >= 11.1 (11A1027) 14 | * iOS >= 10.2 (run on lower versions at your own risk, don't report issues) 15 | * `swift-version` => 4.2 16 | * `cordova` >= 7.1.0 17 | * `cordova-ios` >= 4.5.1 18 | 19 | ### Third-Party Library Examples 20 | 21 | * WebRTC W3C v1.0.0 22 | * WebRTC.framework => M69 23 | * WebSocket 24 | * Janus => 0.7.4 25 | * JSSip => 3.1.2 26 | * Sip.js => 0.15.6 27 | * Twilio => 2.0.0 28 | * Open-Easyrtc => 2.0.5 29 | 30 | ## Author 31 | 32 | [Harold Thetiot](https://sylaps.com) 33 | 34 | 35 | ### Maintainers 36 | 37 | * [Harold Thetiot](https://sylaps.com) 38 | 39 | ## License 40 | 41 | [MIT](./LICENSE) :) 42 | -------------------------------------------------------------------------------- /art/cordova-rtc-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/art/cordova-rtc-logo.png -------------------------------------------------------------------------------- /art/icon-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/art/icon-white.png -------------------------------------------------------------------------------- /art/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/art/icon.png -------------------------------------------------------------------------------- /art/jssip_freeswitch_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/art/jssip_freeswitch_1.png -------------------------------------------------------------------------------- /art/jssip_freeswitch_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/art/jssip_freeswitch_2.png -------------------------------------------------------------------------------- /art/local_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/art/local_1.png -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cordova iOSRTC Sample 4 | 5 | A sample Apache Cordova iOSRTC application that use cordova-plugin-iosrtc 6 | 7 | 8 | Cordova-RTC Team 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | This application requires access to the devices camera to record and send the users camera image via video chat functionality 95 | 96 | 97 | TThis application requires access to the devices image library to display an image as the users profile picture and/or send images via message chat functionality 98 | 99 | 100 | This application requires access to the devices microphone to record and send the users voice message via video/voice chat functionality 101 | 102 | 103 | This application requires access to the bluetooth wireless headphones and microphone to broadcast or record and send the users voice message via video/voice chat functionality 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /hooks/README.md: -------------------------------------------------------------------------------- 1 | 21 | # Cordova Hooks 22 | 23 | Cordova Hooks represent special scripts which could be added by application and plugin developers or even by your own build system to customize cordova commands. See Hooks Guide for more details: http://cordova.apache.org/docs/en/edge/guide_appdev_hooks_index.md.html#Hooks%20Guide. 24 | -------------------------------------------------------------------------------- /iosrtc.sh: -------------------------------------------------------------------------------- 1 | 2 | # Change test version for iOSRTCApp 3 | cordova plugin remove cordova-plugin-iosrtc --verbose 4 | #cordova plugin add file://./../cordova-plugin-iosrtc --verbose 5 | cordova plugin add https://github.com/cordova-rtc/cordova-plugin-iosrtc#master --verbose 6 | #cordova plugin add https://github.com/cordova-rtc/cordova-plugin-iosrtc#bugs/RTCDispatcherTypeMainGeneratingDeviceOrientationNotifications --verbose 7 | #cordova plugin add https://github.com/cordova-rtc/cordova-plugin-iosrtc#6.0.13-RC2 --verbose 8 | #cordova plugin add https://github.com/cordova-rtc/cordova-plugin-iosrtc#bugs/ontrack --verbose 9 | #cordova plugin add https://github.com/cordova-rtc/cordova-plugin-iosrtc#bugs/getStats --verbose 10 | #cordova plugin add https://github.com/cordova-rtc/cordova-plugin-iosrtc#bugs/Blob --verbose 11 | #cordova platform remove ios --no-save 12 | #cordova platform add ios@latest --no-save 13 | cordova prepare ios 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-iosrtc-sample", 3 | "displayName": "cordovaPluginIosrtcSample", 4 | "version": "1.0.0", 5 | "description": "A sample Apache Cordova application that responds to the deviceready event.", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Apache Cordova Team", 11 | "license": "Apache-2.0", 12 | "cordova": { 13 | "plugins": { 14 | "cordova-plugin-whitelist": {}, 15 | "cordova.plugins.diagnostic": {}, 16 | "cordova-plugin-statusbar": {}, 17 | "cordova-plugin-background-mode": {}, 18 | "cordova-plugin-ipad-multitasking": {}, 19 | "cordova-custom-config": {}, 20 | "cordova-plugin-iosrtc": { 21 | "MANUAL_INIT_AUDIO_DEVICE": "FALSE" 22 | } 23 | }, 24 | "platforms": [ 25 | "android", 26 | "browser", 27 | "ios" 28 | ] 29 | }, 30 | "devDependencies": { 31 | "cordova": "^10.0.0", 32 | "cordova-android": "^8.1.0", 33 | "cordova-browser": "^6.0.0", 34 | "cordova-custom-config": "^5.1.0", 35 | "cordova-plugin-background-mode": "^0.7.3", 36 | "cordova-plugin-device": "^2.0.3", 37 | "cordova-plugin-ipad-multitasking": "^0.1.1", 38 | "cordova-plugin-statusbar": "^2.4.3", 39 | "cordova-plugin-whitelist": "^1.3.4", 40 | "cordova.plugins.diagnostic": "^7.1.1", 41 | "cordova-ios": "^6.1.1", 42 | "cordova-plugin-iosrtc": "git+https://github.com/cordova-rtc/cordova-plugin-iosrtc.git#master", 43 | "plugin": "^0.3.3" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /res/README.md: -------------------------------------------------------------------------------- 1 | 21 | 22 | Note that these image resources are not copied into a project when a project 23 | is created with the CLI. Although there are default image resources in a 24 | newly-created project, those come from the platform-specific project template, 25 | which can generally be found in the platform's `template` directory. Until 26 | icon and splashscreen support is added to the CLI, these image resources 27 | aren't used directly. 28 | 29 | See https://issues.apache.org/jira/browse/CB-5145 30 | -------------------------------------------------------------------------------- /res/icon/ios/AppIcon24x24@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/AppIcon24x24@2x.png -------------------------------------------------------------------------------- /res/icon/ios/AppIcon27.5x27.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/AppIcon27.5x27.5@2x.png -------------------------------------------------------------------------------- /res/icon/ios/AppIcon29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/AppIcon29x29@2x.png -------------------------------------------------------------------------------- /res/icon/ios/AppIcon29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/AppIcon29x29@3x.png -------------------------------------------------------------------------------- /res/icon/ios/AppIcon40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/AppIcon40x40@2x.png -------------------------------------------------------------------------------- /res/icon/ios/AppIcon44x44@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/AppIcon44x44@2x.png -------------------------------------------------------------------------------- /res/icon/ios/AppIcon86x86@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/AppIcon86x86@2x.png -------------------------------------------------------------------------------- /res/icon/ios/AppIcon98x98@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/AppIcon98x98@2x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-1024.png -------------------------------------------------------------------------------- /res/icon/ios/icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-20.png -------------------------------------------------------------------------------- /res/icon/ios/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-20@2x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-20@3x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-40.png -------------------------------------------------------------------------------- /res/icon/ios/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-40@2x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-50.png -------------------------------------------------------------------------------- /res/icon/ios/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-50@2x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-60@2x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-60@3x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-72.png -------------------------------------------------------------------------------- /res/icon/ios/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-72@2x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-76.png -------------------------------------------------------------------------------- /res/icon/ios/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-76@2x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-83.5@2x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-small.png -------------------------------------------------------------------------------- /res/icon/ios/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-small@2x.png -------------------------------------------------------------------------------- /res/icon/ios/icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon-small@3x.png -------------------------------------------------------------------------------- /res/icon/ios/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon.png -------------------------------------------------------------------------------- /res/icon/ios/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/icon/ios/icon@2x.png -------------------------------------------------------------------------------- /res/screen/ios/Default-2436h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default-2436h.png -------------------------------------------------------------------------------- /res/screen/ios/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /res/screen/ios/Default-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default-667h.png -------------------------------------------------------------------------------- /res/screen/ios/Default-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default-736h.png -------------------------------------------------------------------------------- /res/screen/ios/Default-Landscape-2436h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default-Landscape-2436h.png -------------------------------------------------------------------------------- /res/screen/ios/Default-Landscape-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default-Landscape-736h.png -------------------------------------------------------------------------------- /res/screen/ios/Default-Landscape@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default-Landscape@2x~ipad.png -------------------------------------------------------------------------------- /res/screen/ios/Default-Landscape~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default-Landscape~ipad.png -------------------------------------------------------------------------------- /res/screen/ios/Default-Portrait@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default-Portrait@2x~ipad.png -------------------------------------------------------------------------------- /res/screen/ios/Default-Portrait~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default-Portrait~ipad.png -------------------------------------------------------------------------------- /res/screen/ios/Default@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default@2x~iphone.png -------------------------------------------------------------------------------- /res/screen/ios/Default~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/res/screen/ios/Default~iphone.png -------------------------------------------------------------------------------- /www/assets/Sunset.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/www/assets/Sunset.mp4 -------------------------------------------------------------------------------- /www/css/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. 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, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | * { 20 | -webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */ 21 | } 22 | 23 | html { 24 | height:100%; 25 | width:100%; 26 | -webkit-text-size-adjust: none; 27 | touch-action: manipulation; 28 | } 29 | 30 | body { 31 | -webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */ 32 | -webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */ 33 | -webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */ 34 | font-family: "RobotoDraft", "Roboto", "Helvetica", sans-serif; 35 | font-size:12px; 36 | height:100%; 37 | width:100%; 38 | margin:0px; 39 | padding:0px; 40 | } 41 | 42 | /* Portrait layout (default) */ 43 | .app { 44 | display: flex; 45 | position: relative; 46 | height: 100%; 47 | width: 100%; 48 | background-color: #333; 49 | color: #fff; 50 | } 51 | 52 | .hidden { 53 | display: none !important; 54 | } 55 | 56 | video { 57 | display: flex; 58 | width: 100%; 59 | height: 100%; 60 | max-width: 100%; 61 | max-width: 100%; 62 | } 63 | 64 | .remote-stream { 65 | position: absolute; 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | flex: 1 1 100%; 70 | width: 100%; 71 | height: 100%; 72 | max-height: 100%; 73 | } 74 | 75 | .remote-stream video { 76 | object-fit: cover; 77 | } 78 | 79 | .remote-stream video.video-only { 80 | object-fit: contain; 81 | } 82 | 83 | .local-stream { 84 | position: absolute; 85 | display: flex; 86 | align-items: center; 87 | justify-content: center; 88 | box-sizing: border-box; 89 | right: 0; 90 | padding: 10px 10px 0 0; 91 | width: 33%; 92 | height: 25%; 93 | } 94 | 95 | .local-stream video { 96 | object-fit: cover; 97 | border-radius: 10px; 98 | z-index: 10; 99 | } 100 | 101 | .local-stream video { 102 | -webkit-transform: scaleX(-1); 103 | -moz-transform: scaleX(-1); 104 | -ms-transform: scaleX(-1); 105 | -o-transform: scaleX(-1); 106 | transform: scaleX(-1); 107 | } 108 | 109 | /* Button */ 110 | 111 | .btn { 112 | border: none; 113 | background: transparent; 114 | } 115 | 116 | .btn.btn-round { 117 | box-shadow: 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12); 118 | box-sizing: border-box; 119 | position: relative; 120 | -webkit-user-select: none; 121 | -moz-user-select: none; 122 | -ms-user-select: none; 123 | user-select: none; 124 | cursor: pointer; 125 | border: none; 126 | -webkit-tap-highlight-color: transparent; 127 | display: inline-block; 128 | white-space: nowrap; 129 | text-decoration: none; 130 | vertical-align: baseline; 131 | text-align: center; 132 | margin: 0; 133 | min-width: 64px; 134 | line-height: 36px; 135 | padding: 0 16px; 136 | border-radius: 4px; 137 | overflow: visible; 138 | transition: background .4s cubic-bezier(.25,.8,.25,1),box-shadow 280ms cubic-bezier(.4,0,.2,1); 139 | min-width: 0; 140 | border-radius: 50%; 141 | width: 48px; 142 | height: 48px; 143 | padding: 0; 144 | flex-shrink: 0; 145 | background-color: rgba(149, 151, 150, 0.53); 146 | line-height: 0px; 147 | } 148 | 149 | .btn.btn-active{ 150 | background-color: #01579b; 151 | } 152 | 153 | 154 | .btn.btn-danger { 155 | background-color: #f44336; 156 | } 157 | 158 | .btn.btn-call { 159 | background-color: #4caf50; 160 | width: 56px; 161 | height: 56px; 162 | } 163 | 164 | .btn.btn-settings { 165 | background-color: #f44336; 166 | width: 84px; 167 | height: 84px; 168 | } 169 | 170 | .btn.btn-hangup { 171 | width: 56px; 172 | height: 56px; 173 | } 174 | 175 | .btn.btn-report { 176 | width: 56px; 177 | height: 56px; 178 | background-color: #ffb300; 179 | box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); 180 | } 181 | 182 | .btn i { 183 | color: #fff; 184 | } 185 | 186 | /* */ 187 | 188 | .notice { 189 | z-index: 200; 190 | padding: 10px; 191 | box-sizing: border-box; 192 | background-color: rgba(51, 51, 51, .5); 193 | border-radius: 10px; 194 | position: absolute; 195 | } 196 | 197 | /* Controls */ 198 | 199 | .controls { 200 | z-index: 100; 201 | padding: 10px; 202 | box-sizing: border-box; 203 | } 204 | 205 | .top-controls { 206 | position: absolute; 207 | display: flex; 208 | flex-direction: column; 209 | top: 0; 210 | } 211 | 212 | .top-controls .btn{ 213 | margin: 10px 0; 214 | } 215 | 216 | .footer-controls { 217 | position: absolute; 218 | bottom: 0; 219 | width: 100%; 220 | display: flex; 221 | align-items: center; 222 | justify-content: center; 223 | } 224 | 225 | .footer-controls .btn{ 226 | margin: 0 10px; 227 | } 228 | 229 | .local-controls { 230 | position: absolute; 231 | top: 26%; 232 | width: 33%; 233 | right: 0; 234 | display: flex; 235 | align-items: center; 236 | justify-content: center; 237 | } 238 | 239 | .report-controls { 240 | position: absolute; 241 | } 242 | 243 | .report-controls { 244 | position: absolute; 245 | top: 65vh; 246 | right: 0; 247 | padding-right: 0; 248 | } 249 | 250 | /* Landscape layout (with min-width) */ 251 | @media screen and (min-aspect-ratio: 1/1) and (min-width:400px) { 252 | .report-controls { 253 | top: 75vh; 254 | } 255 | 256 | .local-stream { 257 | width: 25%; 258 | height: 33%; 259 | } 260 | 261 | .local-controls { 262 | top: 34%; 263 | width: 25%; 264 | } 265 | } 266 | 267 | .material-icons { 268 | font-family: 'Material Icons'; 269 | font-weight: normal; 270 | font-style: normal; 271 | font-size: 24px; /* Preferred icon size */ 272 | display: inline-block; 273 | line-height: 1; 274 | text-transform: none; 275 | letter-spacing: normal; 276 | word-wrap: normal; 277 | white-space: nowrap; 278 | direction: ltr; 279 | 280 | /* Support for all WebKit browsers. */ 281 | -webkit-font-smoothing: antialiased; 282 | /* Support for Safari and Chrome. */ 283 | text-rendering: optimizeLegibility; 284 | 285 | /* Support for Firefox. */ 286 | -moz-osx-font-smoothing: grayscale; 287 | 288 | /* Support for IE. */ 289 | font-feature-settings: 'liga'; 290 | } 291 | 292 | .material-icons { 293 | color: white; 294 | } 295 | -------------------------------------------------------------------------------- /www/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordova-rtc/cordova-plugin-iosrtc-sample/6b75a940059e8c83c885cd6a52e456338a009dda/www/img/logo.png -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Cordova-RTC 14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 | Press Call button to start the call. 23 |
24 |
25 | 45 |
46 | 47 |
48 |
49 | 52 |
53 |
54 | 57 | 60 | 63 | 66 | 69 |
70 |
71 | 74 |
75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /www/js/common.js: -------------------------------------------------------------------------------- 1 | // Config 2 | // Note: Change to match your adapter version 3 | 4 | var adapterVersion = 'latest'; 5 | var adapterUrl = "https://webrtc.github.io/adapter/adapter-" + adapterVersion + ".js"; 6 | 7 | // Detect if current enviroment is cordova 8 | var isCordova = !document.URL.includes('http'); 9 | 10 | // 11 | // Container 12 | // 13 | 14 | var appContainer = document.querySelector('.app'); 15 | 16 | // 17 | // Utils 18 | // 19 | 20 | function uuid4() { 21 | function hex(s, b) { 22 | return s + 23 | (b >>> 4).toString(16) + // high nibble 24 | (b & 0b1111).toString(16); // low nibble 25 | } 26 | 27 | var r = crypto.getRandomValues(new Uint8Array(16)); 28 | 29 | r[6] = r[6] >>> 4 | 0b01000000; // Set type 4: 0100 30 | r[8] = r[8] >>> 3 | 0b10000000; // Set variant: 100 31 | 32 | return r.slice(0, 4).reduce(hex, '') + 33 | r.slice(4, 6).reduce(hex, '-') + 34 | r.slice(6, 8).reduce(hex, '-') + 35 | r.slice(8, 10).reduce(hex, '-') + 36 | r.slice(10, 16).reduce(hex, '-'); 37 | } 38 | 39 | function loadScript(scriptUrl) { 40 | return new Promise(function(resolve, reject) { 41 | // load adapter.js 42 | var script = document.createElement("script"); 43 | script.type = "text/javascript"; 44 | script.src = scriptUrl; 45 | script.async = false; 46 | document.getElementsByTagName("head")[0].appendChild(script); 47 | script.onload = function() { 48 | console.debug('loadScript.loaded', script.src); 49 | resolve(); 50 | }; 51 | }); 52 | } 53 | 54 | // 55 | // Event debug 56 | // 57 | 58 | var excludeEvents = [ 59 | 'ontimeupdate', 'onprogress', 60 | 'onmousemove', 'onmouseover', 'onmouseout', 'onmouseenter', 'onmouseleave', 61 | 'onpointermove', 'onpointerover', 'onpointerenter', 'onpointerrawupdate', 'onpointerout', 'onpointerleave', 62 | 'onpointerdown', 'onmousedown', 'onclick', 'onmouseup', 'onpointerup' 63 | ]; 64 | 65 | function TestListenEvents(eventTarget) { 66 | 67 | if (eventTarget.TestListenEvents) { 68 | return; 69 | } 70 | eventTarget.TestListenEvents = true; 71 | 72 | function eventTriggered(event) { 73 | console.debug('event.triggered', event, eventTarget); 74 | } 75 | 76 | for(var eventName in eventTarget) { 77 | if (eventName.search('on') === 0 && excludeEvents.indexOf(eventName) == -1) { 78 | eventTarget.addEventListener(eventName.slice(2), eventTriggered.bind(eventTarget, eventName)); 79 | } 80 | } 81 | } 82 | 83 | // 84 | // MediaStreams 85 | // 86 | 87 | var localStream, localVideoEl, localDeviceId; 88 | function TestGetUserMedia(deviceId) { 89 | 90 | if (!deviceId) { 91 | return navigator.mediaDevices.enumerateDevices().then(function(devices) { 92 | var newDevice = devices.filter(function(device) { 93 | return device.kind === 'videoinput'; 94 | }).find(function(device, idx) { 95 | return device.deviceId !== 'default'; 96 | }); 97 | 98 | localDeviceId = newDevice ? newDevice.deviceId : null; 99 | return TestGetUserMedia(localDeviceId || 'default'); 100 | }); 101 | } 102 | 103 | console.debug('TestGetUserMedia', deviceId); 104 | return navigator.mediaDevices.getUserMedia({ 105 | /* 106 | video: true, 107 | */ 108 | video: { 109 | deviceId: deviceId, 110 | 111 | /* 112 | width: { 113 | max: 1280, 114 | min: 640 115 | }, 116 | height: { 117 | max: 720, 118 | min: 480 119 | }, 120 | */ 121 | /* 122 | height: { 123 | ideal: 240, 124 | min: 180, 125 | max: 480 126 | }, 127 | */ 128 | //facingMode: 'environment' 129 | //height: 480, 130 | //width: 1280, 131 | //height: 720, 132 | //width: 1280, 133 | //height: 960, 134 | //aspectRatio: 16/9, 135 | //aspectRatio: 11/9, 136 | //aspectRatio: 4/3, 137 | //frameRate:{ min: 30.0, max: 30.0 } 138 | }, 139 | audio: true 140 | /* 141 | video: { 142 | // Test Back Camera 143 | //deviceId: 'com.apple.avfoundation.avcapturedevice.built-in_video:0' 144 | //sourceId: 'com.apple.avfoundation.avcapturedevice.built-in_video:0' 145 | deviceId: { 146 | exact: 'com.apple.avfoundation.avcapturedevice.built-in_video:0' 147 | } 148 | }, 149 | audio: { 150 | deviceId: { 151 | exact: 'Built-In Microphone' 152 | } 153 | }*/ 154 | }).then(function(stream) { 155 | 156 | console.debug('getUserMedia.stream', stream); 157 | console.debug('getUserMedia.stream.getTracks', stream.getTracks()); 158 | 159 | TestSetLocalStream(stream); 160 | 161 | // Test mute at Start 162 | /* 163 | localStream.getAudioTracks().forEach(function (track) { 164 | track.enabled = false; 165 | }); 166 | */ 167 | return localStream; 168 | 169 | }).catch(function(err) { 170 | console.error('getUserMediaError', err, err.stack); 171 | }); 172 | } 173 | 174 | function TestGetDisplayMedia() { 175 | console.debug('getDisplayMedia'); 176 | return navigator.mediaDevices.getDisplayMedia({}).then(function(stream) { 177 | 178 | console.debug('getDisplayMedia.stream', stream); 179 | console.debug('getDisplayMedia.stream.getTracks', stream.getTracks()); 180 | 181 | TestSetLocalStream(stream); 182 | 183 | // Test mute at Start 184 | /* 185 | localStream.getAudioTracks().forEach(function (track) { 186 | track.enabled = false; 187 | }); 188 | */ 189 | 190 | return localStream; 191 | 192 | }).catch(function(err) { 193 | console.error('getDisplayMedia', err, err.stack); 194 | }); 195 | } 196 | 197 | function TestSetLocalStream(localStreamMedia) { 198 | 199 | localVideoEl = appContainer.querySelector('.local-video'); 200 | 201 | // Note: Expose for debug 202 | localStream = localStreamMedia; 203 | 204 | // Listen to all events 205 | TestListenEvents(localStream); 206 | TestListenEvents(localVideoEl); 207 | 208 | // Attach local stream to video element 209 | localVideoEl.srcObject = localStream; 210 | } 211 | 212 | var peerVideoEl, peerVideoElLoader, peerStream; 213 | 214 | function TestSetPeerStreamLoading(loaded) { 215 | console.debug('TestSetPeerStreamLoading', loaded); 216 | 217 | peerVideoEl = appContainer.querySelector('.remote-video'); 218 | peerVideoElLoader = appContainer.querySelector('.remote-video-loader'); 219 | 220 | if (!loaded) { 221 | peerVideoElLoader.classList.remove('hidden'); 222 | peerVideoEl.classList.add('hidden'); 223 | } else { 224 | peerVideoElLoader.classList.add('hidden'); 225 | peerVideoEl.classList.remove('hidden'); 226 | 227 | if (isCordova) { 228 | appContainer.style.background = "transparent"; 229 | } 230 | } 231 | } 232 | 233 | function TestSetPeerTracks(peerConnection) { 234 | var peerStreamMedia = new MediaStream(); 235 | peerConnection.getReceivers().forEach(function (receiver) { 236 | console.debug('peerStreamMedia.addTrack', receiver, peerStreamMedia); 237 | peerStreamMedia.addTrack(receiver.track); 238 | }); 239 | 240 | TestSetPeerStream(peerStreamMedia); 241 | } 242 | 243 | function TestSetPeerStream(peerStreamMedia) { 244 | 245 | peerVideoEl = appContainer.querySelector('.remote-video'); 246 | 247 | TestSetPeerStreamLoading(true); 248 | peerVideoEl.removeEventListener('canplay', TestSetPeerStreamLoading); 249 | peerVideoEl.addEventListener('canplay', TestSetPeerStreamLoading); 250 | 251 | // Note: Expose for debug 252 | peerStream = peerStreamMedia; 253 | 254 | // Listen to all events 255 | TestListenEvents(peerStream); 256 | TestListenEvents(peerVideoEl); 257 | 258 | // Attach peer stream to video element 259 | peerVideoEl.srcObject = peerStream; 260 | 261 | // Display 262 | peerVideoEl.classList.remove('hidden'); 263 | 264 | function isVideoOnly() { 265 | // Handle video only display 266 | if (peerStreamMedia.getAudioTracks().length) { 267 | peerVideoEl.classList.remove('video-only'); 268 | } else { 269 | peerVideoEl.classList.add('video-only'); 270 | } 271 | }; 272 | 273 | isVideoOnly(); 274 | 275 | peerStreamMedia.addEventListener('addtrack', isVideoOnly); 276 | peerStreamMedia.addEventListener('removetrack', isVideoOnly); 277 | 278 | if (isCordova) { 279 | appContainer.style.background = "transparent"; 280 | } 281 | } 282 | 283 | function TestStopLocalMediaStream(localStream) { 284 | 285 | // Stop previous stream tracks 286 | if (localStream) { 287 | localStream.getTracks().forEach(function(track) { 288 | track.stop(); 289 | }); 290 | 291 | if (localVideoEl.srcObject === localStream) { 292 | localVideoEl.srcObject = null; 293 | } 294 | } 295 | } 296 | 297 | function TestSwitchCamera() { 298 | 299 | if (localStream) { 300 | TestStopLocalMediaStream(localStream); 301 | } 302 | 303 | return navigator.mediaDevices.enumerateDevices().then(function(devices) { 304 | var idx = 0; 305 | var newDevice = devices.filter(function(device) { 306 | return device.kind === 'videoinput'; 307 | }).find(function(device, idx) { 308 | return device.deviceId !== localDeviceId; 309 | }); 310 | 311 | 312 | localDeviceId = newDevice ? newDevice.deviceId : null; 313 | 314 | console.debug('TestSwitchCamera', localDeviceId); 315 | 316 | if (localDeviceId) { 317 | return TestGetUserMedia(localDeviceId); 318 | } else if (typeof navigator.mediaDevices.getDisplayMedia !== 'undefined') { 319 | return TestGetDisplayMedia(); 320 | } 321 | 322 | }, function(err) { 323 | console.error('enumerateDevices.err', err); 324 | }); 325 | } 326 | 327 | function TestAddStreamToPeerConnection(peerConnection, localStream) { 328 | try { 329 | if (!isCordova && typeof peerConnection.addStream === 'function') { 330 | peerConnection.addStream(localStream); 331 | } else { 332 | var localPeerStream = new MediaStream(); 333 | localStream.getTracks().forEach(function(track) { 334 | console.debug('peerConnection.addTrack', peerConnection, track); 335 | peerConnection.addTrack(track, localPeerStream); 336 | }); 337 | } 338 | } catch (err) { 339 | console.error('TestAddStreamToPeerConnection.err', err); 340 | } 341 | } 342 | 343 | function TestRemoveStreamToPeerConnection(peerConnection, localStream) { 344 | try { 345 | if (typeof peerConnection.removeStream === 'function') { 346 | peerConnection.removeStream(localStream); 347 | } else { 348 | peerConnection.getSenders().forEach(function(track) { 349 | console.debug('peerConnection.removeTrack', peerConnection, track); 350 | peerConnection.removeTrack(track); 351 | }); 352 | } 353 | } catch (err) { 354 | console.error('TestRemoveStreamToPeerConnection.err', err); 355 | } 356 | } 357 | 358 | // 359 | // PeerConnections 360 | // 361 | 362 | var peerConnections = {}; 363 | 364 | function TestHangupRTCPeerConnection(targetId, peerConnection) { 365 | peerConnection = peerConnection || peerConnections[targetId]; 366 | console.debug('TestHangupRTCPeerConnection', targetId, peerConnection); 367 | delete peerConnections[targetId]; 368 | delete peerConnectionsCandicates[targetId]; 369 | peerConnection.close(); 370 | 371 | if (Object.keys(peerConnections).length === 0) { 372 | TestControlsClosingCall(); 373 | } 374 | } 375 | 376 | function TestHangupRTCPeerConnections(peerConnections) { 377 | Object.keys(peerConnections).forEach(function(targetId) { 378 | var peerConnection = peerConnections[targetId]; 379 | TestHangupRTCPeerConnection(targetId, peerConnection); 380 | }); 381 | } 382 | 383 | function selectControlByName(name) { 384 | return appContainer.querySelector('.controls .btn[name=' + name + ']'); 385 | } 386 | 387 | function TestControlsIncomingCall() { 388 | selectControlByName('call_remote').classList.add('hidden'); 389 | selectControlByName('hangup_remote').classList.remove('hidden'); 390 | appContainer.querySelector('.notice-alone').classList.add('hidden'); 391 | 392 | if (isCordova) { 393 | appContainer.style.background = "transparent"; 394 | } 395 | } 396 | 397 | function TestControlsOutgoingCall() { 398 | selectControlByName('call_remote').classList.add('hidden'); 399 | selectControlByName('hangup_remote').classList.remove('hidden'); 400 | appContainer.querySelector('.notice-alone').classList.add('hidden'); 401 | 402 | if (isCordova) { 403 | appContainer.style.background = "transparent"; 404 | } 405 | } 406 | 407 | function TestControlsClosingCall() { 408 | selectControlByName('hangup_remote').classList.add('hidden'); 409 | selectControlByName('call_remote').classList.remove('hidden'); 410 | appContainer.querySelector('.notice-alone').classList.remove('hidden'); 411 | 412 | 413 | peerVideoEl = appContainer.querySelector('.remote-video'); 414 | peerVideoElLoader = appContainer.querySelector('.remote-video-loader'); 415 | 416 | 417 | if (peerVideoEl) { 418 | peerVideoEl.classList.add('hidden'); 419 | peerVideoEl.srcObject = null; 420 | } 421 | 422 | if (peerVideoElLoader) { 423 | peerVideoElLoader.classList.add('hidden'); 424 | } 425 | 426 | if (isCordova) { 427 | appContainer.style.background = ""; 428 | } 429 | } 430 | 431 | function TestControls() { 432 | 433 | navigator.mediaDevices.enumerateDevices().then(function(devices) { 434 | console.debug('enumerateDevices', devices); 435 | var canSwitchDevice = devices.filter(function(device) { 436 | return device.kind === 'videoinput'; 437 | }).length > 1; 438 | 439 | 440 | if (!canSwitchDevice && typeof navigator.mediaDevices.getDisplayMedia === 'undefined') { 441 | selectControlByName('switch_camera').classList.add('hidden'); 442 | } 443 | }); 444 | 445 | if (!isCordova || typeof cordova.plugins.iosrtc.selectAudioOutput === 'undefined') { 446 | selectControlByName('speaker').classList.add('hidden'); 447 | selectControlByName('earpiece').classList.add('hidden'); 448 | } 449 | 450 | if (!isCordova || typeof cordova.plugins.iosrtc.selectAudioOutput === 'undefined') { 451 | selectControlByName('speaker').classList.add('hidden'); 452 | selectControlByName('earpiece').classList.add('hidden'); 453 | } 454 | 455 | if (!isCordova || typeof cordova.plugins.iosrtc.turnOnSpeaker === 'undefined') { 456 | selectControlByName('speaker').classList.add('hidden'); 457 | } 458 | 459 | function handleControlsEvent(event) { 460 | var targetEl = event.target; 461 | 462 | if (!targetEl.classList.contains('btn')) { 463 | targetEl = targetEl.closest('.btn'); 464 | } 465 | 466 | if (targetEl && targetEl.classList.contains('btn')) { 467 | var actionName = targetEl.getAttribute('name'); 468 | switch (actionName) { 469 | case 'mic_on': 470 | selectControlByName('mic_on').classList.add('hidden'); 471 | selectControlByName('mic_off').classList.remove('hidden'); 472 | localStream.getAudioTracks().forEach(function(track) { 473 | track.enabled = true; 474 | }); 475 | break; 476 | case 'mic_off': 477 | selectControlByName('mic_off').classList.add('hidden'); 478 | selectControlByName('mic_on').classList.remove('hidden'); 479 | localStream.getAudioTracks().forEach(function(track) { 480 | track.enabled = false; 481 | }); 482 | break; 483 | case 'hangup_remote': 484 | 485 | TestControlsClosingCall(); 486 | 487 | Object.values(peerConnections).forEach(function(peerConnection) { 488 | TestRemoveStreamToPeerConnection(peerConnection, localStream); 489 | }); 490 | 491 | TestHangupRTCPeerConnections(peerConnections); 492 | break; 493 | case 'call_remote': 494 | TestControlsIncomingCall(); 495 | 496 | TestHangupRTCPeerConnections(peerConnections); 497 | 498 | TestRTCPeerConnection(localStream); 499 | break; 500 | case 'camera_on': 501 | selectControlByName('camera_on').classList.add('hidden'); 502 | selectControlByName('camera_off').classList.remove('hidden'); 503 | localVideoEl.classList.remove('hidden'); 504 | localStream.getVideoTracks().forEach(function(track) { 505 | track.enabled = true; 506 | }); 507 | break; 508 | case 'camera_off': 509 | selectControlByName('camera_off').classList.add('hidden'); 510 | selectControlByName('camera_on').classList.remove('hidden'); 511 | localVideoEl.classList.add('hidden'); 512 | localStream.getVideoTracks().forEach(function(track) { 513 | track.enabled = false; 514 | }); 515 | break; 516 | case 'switch_camera': 517 | selectControlByName('switch_camera').classList.toggle('btn-active'); 518 | 519 | 520 | localVideoEl.classList.remove('hidden'); 521 | localVideoEl.srcObject = null; 522 | /* 523 | localStream.getTracks().forEach(function (track) { 524 | localStream.removeTrack(track); 525 | }); 526 | */ 527 | 528 | var oldLocalStream = localStream; 529 | TestSwitchCamera().then(function() { 530 | 531 | if (typeof pc1 !== 'undefined' && typeof pc2 !== 'undefined') { 532 | 533 | pc1.createOffer({ 534 | iceRestart: true 535 | }).then(function(desc) { 536 | 537 | TestRemoveStreamToPeerConnection(pc1, oldLocalStream); 538 | TestAddStreamToPeerConnection(pc1, localStream); 539 | 540 | return pc1.setLocalDescription(desc).then(function() { 541 | return pc2.setRemoteDescription(desc).then(function() { 542 | return pc2.createAnswer(answerConstraints).then(function(desc) { 543 | return pc2.setLocalDescription(desc).then(function() { 544 | return pc1.setRemoteDescription(desc); 545 | }); 546 | }); 547 | }); 548 | }); 549 | }).catch(function(err) { 550 | console.error('TestCallAwnserPeerError', err); 551 | }); 552 | 553 | } else { 554 | Object.keys(peerConnections).forEach(function(targetPeerId) { 555 | var peerConnection = peerConnections[targetPeerId]; 556 | TestRemoveStreamToPeerConnection(peerConnection, oldLocalStream); 557 | TestAddStreamToPeerConnection(peerConnection, localStream); 558 | return peerConnection.createOffer({ 559 | iceRestart: true 560 | }).then(function(desc) { 561 | return peerConnection.setLocalDescription(desc).then(function() { 562 | webSocketSendMessage({ 563 | source: peerId, 564 | target: targetPeerId, 565 | type: desc.type, 566 | sdp: desc.sdp 567 | }); 568 | }); 569 | }); 570 | }); 571 | } 572 | 573 | 574 | }); 575 | break; 576 | case 'speaker': 577 | selectControlByName('earpiece').classList.remove('btn-active'); 578 | selectControlByName('speaker').classList.add('btn-active'); 579 | cordova.plugins.iosrtc.turnOnSpeaker(true); 580 | break; 581 | case 'earpiece': 582 | selectControlByName('speaker').classList.remove('btn-active'); 583 | selectControlByName('earpiece').classList.add('btn-active'); 584 | cordova.plugins.iosrtc.selectAudioOutput('earpiece'); 585 | break; 586 | case 'mute_remote': 587 | selectControlByName('mute_remote').classList.add('hidden'); 588 | selectControlByName('unmute_remote').classList.remove('hidden'); 589 | peerStream.getAudioTracks().forEach(function(track) { 590 | if (track.kind == 'audio') { 591 | track.enabled = false; 592 | } 593 | }); 594 | break; 595 | case 'unmute_remote': 596 | selectControlByName('unmute_remote').classList.add('hidden'); 597 | selectControlByName('mute_remote').classList.remove('hidden'); 598 | peerStream.getAudioTracks().forEach(function(track) { 599 | if (track.kind == 'audio') { 600 | track.enabled = true; 601 | } 602 | }); 603 | break; 604 | case 'report': 605 | window.location = 'https://github.com/cordova-rtc/cordova-plugin-iosrtc/issues/new'; 606 | break; 607 | 608 | default: 609 | console.error('Unknow button name', targetEl); 610 | } 611 | } 612 | } 613 | 614 | appContainer.addEventListener('click', handleControlsEvent, false); 615 | } 616 | 617 | function TestIosRTCSample(event) { 618 | 619 | if (isCordova == false) { 620 | TestControls(); 621 | 622 | loadScript(adapterUrl).then(function() { 623 | return TestGetUserMedia().then(function(localStream) { 624 | return TestRTCPeerConnection(localStream); 625 | }); 626 | }).catch(function (err) { 627 | console.error(err); 628 | }); 629 | 630 | } else { 631 | document.addEventListener('deviceready', function() { 632 | 633 | // Init cordova plugins 634 | if (window.device && window.device.platform == 'iOS') { 635 | 636 | var cordova = window.cordova; 637 | 638 | // Expose WebRTC Globals 639 | if (cordova && cordova.plugins && cordova.plugins.iosrtc) { 640 | 641 | //cordova.plugins.iosrtc.debug.enable('*', true); 642 | 643 | cordova.plugins.iosrtc.registerGlobals(); 644 | 645 | cordova.plugins.iosrtc.turnOnSpeaker(true); 646 | 647 | // Implement iosrtc HTML over video trick 648 | document.documentElement.style.background = "transparent"; 649 | document.body.style.background = "transparent"; 650 | //appContainer.style.background = "transparent"; 651 | appContainer.querySelector('.remote-video').style.zIndex = '-1'; 652 | } 653 | 654 | // Enable Background audio 655 | if (cordova && cordova.plugins && cordova.plugins.backgroundMode) { 656 | cordova.plugins.backgroundMode.enable(); 657 | } 658 | } 659 | 660 | TestControls(); 661 | loadScript(adapterUrl).then(function () { 662 | return TestGetUserMedia().then(function(localStream) { 663 | return TestRTCPeerConnection(localStream); 664 | }); 665 | }).catch(function (err) { 666 | console.error(err); 667 | }); 668 | }); 669 | } 670 | } 671 | 672 | if (document.readyState === "complete" || document.readyState === "loaded") { 673 | TestIosRTCSample(); 674 | } else { 675 | window.addEventListener("DOMContentLoaded", TestIosRTCSample); 676 | } 677 | -------------------------------------------------------------------------------- /www/js/index-easyrtc.js: -------------------------------------------------------------------------------- 1 | const EASYRTC_SERVER = 'https://example.com'; 2 | const EASYRTC_APP_NAME = 'default'; 3 | const EASYRTC_ROOM_NAME = 'test'; 4 | 5 | function TestRTCPeerConnection() { 6 | return loadScript('https://unpkg.com/socket.io-client@2.2.0/dist/socket.io.js').then(function () { 7 | return loadScript('https://unpkg.com/open-easyrtc@2.0.5/api/easyrtc.js').then(function () { 8 | return joinRoom({ 9 | server: EASYRTC_SERVER, 10 | appName: EASYRTC_APP_NAME, 11 | roomName: EASYRTC_ROOM_NAME 12 | }); 13 | }); 14 | }); 15 | } 16 | 17 | function joinRoom(config) { 18 | // Set server socket url 19 | easyrtc.setSocketUrl(config.server); 20 | 21 | // Handle peer stream 22 | easyrtc.setStreamAcceptor(function (socketId, stream, streamName) { 23 | TestSetPeerStream(stream); 24 | }); 25 | 26 | // Register local stream 27 | easyrtc.register3rdPartyLocalMediaStream(localStream, 'default'); 28 | 29 | // Connect to easyrtc server 30 | return new Promise(function (resolve, reject) { 31 | easyrtc.connect(config.appName, resolve, reject); 32 | }).then(function () { 33 | return new Promise(function (resolve, reject) { 34 | easyrtc.joinRoom(config.roomName, {}, resolve, reject); 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /www/js/index-janus.js: -------------------------------------------------------------------------------- 1 | /* global RTCPeerConnection */ 2 | // jshint unused:false 3 | 4 | // Config 5 | // Note: Change to match your Janus server 6 | var server = "https://janus.conf.meetecho.com/janus"; 7 | 8 | // Note: Override common.js adapterVersion and adapterUrl 9 | var adapterVersion = 'latest'; 10 | var adapterUrl = "https://webrtc.github.io/adapter/adapter-" + adapterVersion + ".js"; 11 | 12 | // 13 | // Test RTCPeerConnection 14 | // 15 | 16 | var janus = null, 17 | echotest = null; 18 | 19 | function TestRTCPeerConnection() { 20 | return loadScript('https://janus.conf.meetecho.com/janus.js').then(function() { 21 | 22 | // Make sure the browser supports WebRTC 23 | if (!Janus.isWebrtcSupported()) { 24 | alert("No WebRTC support... "); 25 | return; 26 | } 27 | 28 | var opaqueId = "echotest-" + Janus.randomString(12); 29 | 30 | // Initialize the library (all console debuggers enabled) 31 | Janus.init({ 32 | debug: "all", 33 | callback: function() { 34 | 35 | 36 | // TODO overide unifiedPlan from Janus.init 37 | // Janus.unifiedPlan = false; 38 | 39 | // Create session 40 | janus = new Janus({ 41 | server: server, 42 | iceServers: [ 43 | { 44 | url: "stun:stun.stunprotocol.org" 45 | } 46 | ], 47 | success: function() { 48 | // Attach to echo test plugin 49 | janus.attach({ 50 | plugin: "janus.plugin.echotest", 51 | opaqueId: opaqueId, 52 | success: function(pluginHandle) { 53 | echotest = pluginHandle; 54 | Janus.log("Plugin attached! (" + echotest.getPlugin() + ", id=" + echotest.getId() + ")"); 55 | 56 | // Negotiate WebRTC 57 | var body = { 58 | "audio": true, 59 | "video": true 60 | }; 61 | Janus.debug("Sending message (" + JSON.stringify(body) + ")"); 62 | echotest.send({ 63 | "message": body 64 | }); 65 | 66 | Janus.debug("Trying a createOffer too (audio/video sendrecv)"); 67 | echotest.createOffer({ 68 | // No media provided: by default, it's sendrecv for audio and video 69 | //media: { data: false }, // Let's negotiate data channels as well 70 | stream: localStream, 71 | success: function(jsep) { 72 | Janus.debug("Got SDP!"); 73 | Janus.debug(jsep); 74 | echotest.send({ 75 | "message": body, 76 | "jsep": jsep 77 | }); 78 | }, 79 | error: function(error) { 80 | Janus.error("WebRTC error:", error); 81 | } 82 | }); 83 | }, 84 | error: function(error) { 85 | console.error(" -- Error attaching plugin...", error); 86 | }, 87 | consentDialog: function(on) { 88 | Janus.debug("Consent dialog should be " + (on ? "on" : "off") + " now"); 89 | }, 90 | iceState: function(state) { 91 | Janus.log("ICE state changed to " + state); 92 | }, 93 | mediaState: function(medium, on) { 94 | Janus.log("Janus " + (on ? "started" : "stopped") + " receiving our " + medium); 95 | }, 96 | webrtcState: function(on) { 97 | Janus.log("Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " now"); 98 | }, 99 | slowLink: function(uplink, nacks) { 100 | Janus.warn("Janus reports problems " + (uplink ? "sending" : "receiving") + 101 | " packets on this PeerConnection (" + nacks + " NACKs/s " + (uplink ? "received" : "sent") + ")"); 102 | }, 103 | onmessage: function(msg, jsep) { 104 | Janus.debug(" ::: Got a message :::"); 105 | Janus.debug(msg); 106 | if (jsep !== undefined && jsep !== null) { 107 | Janus.debug("Handling SDP as well..."); 108 | Janus.debug(jsep); 109 | echotest.handleRemoteJsep({ 110 | jsep: jsep 111 | }); 112 | } 113 | }, 114 | onlocalstream: function(stream) { 115 | Janus.debug(" ::: Got a local stream :::"); 116 | Janus.debug(stream); 117 | 118 | localVideoEl = appContainer.querySelector('.local-video'); 119 | 120 | // Note: Expose for debug 121 | localStream = stream; 122 | 123 | // Attach local stream to video element 124 | localVideoEl.srcObject = localStream; 125 | }, 126 | onremotestream: function(stream) { 127 | Janus.debug(" ::: Got a remote stream :::"); 128 | Janus.debug(stream); 129 | TestSetPeerStream(stream); 130 | }, 131 | ondataopen: function(data) { 132 | Janus.log("The DataChannel is available!"); 133 | }, 134 | ondata: function(data) { 135 | Janus.debug("We got data from the DataChannel! " + data); 136 | }, 137 | oncleanup: function() { 138 | Janus.log(" ::: Got a cleanup notification :::"); 139 | } 140 | }); 141 | }, 142 | error: function(error) { 143 | console.error('Janus.error', error); 144 | } 145 | }); 146 | } 147 | }); 148 | }); 149 | } -------------------------------------------------------------------------------- /www/js/index-jssip.js: -------------------------------------------------------------------------------- 1 | /* global RTCPeerConnection */ 2 | // jshint unused:false 3 | 4 | // Config 5 | // Note: Change to match your SIP server and user credentials. 6 | 7 | const FREESWITCH_URL = 'sip.example.com'; 8 | const FREESWITCH_PORT = '7443'; 9 | const FREESWITCH_USERNAME = 'test'; 10 | const FREESWITCH_CREDENTIALS = 'test'; 11 | 12 | var sipConfig = { 13 | display_name: 'iOSRTC', 14 | server: 'wss://' + FREESWITCH_URL + ':7443', 15 | uri: 'sip:' + FREESWITCH_USERNAME + '@' + FREESWITCH_URL, 16 | password: FREESWITCH_CREDENTIALS, 17 | authorization_user: null, 18 | realm: FREESWITCH_URL 19 | }; 20 | 21 | // Set debug 22 | window.localStorage.setItem('debug', '* -engine* -socket* *ERROR* *WARN*'); 23 | 24 | // Note: Select JsSip Implementation (JSSip and SIP.js supported). 25 | var jsSipUrl = "https://cdnjs.cloudflare.com/ajax/libs/jssip/3.1.2/jssip.min.js"; 26 | //var jsSipUrl = "https://sipjs.com/download/sip-0.15.6.min.js"; 27 | 28 | 29 | // 30 | // Test RTCPeerConnection 31 | // 32 | 33 | var peerConnectionsCandicates = []; 34 | 35 | var webSocket, 36 | webSocketChannel = 'iosrtc', 37 | webSocketRoomID = 'jssip'; 38 | 39 | var mediaConstraints = { 40 | video: true, 41 | audio: true 42 | }; 43 | 44 | function webSocketSendMessage(msg) { 45 | console.log('webSocketSendMessage', msg); 46 | webSocket.send(JSON.stringify(msg)); 47 | } 48 | 49 | var sipUserAgent, sipSession; 50 | 51 | function TestCallOfferPeer(callUri) { 52 | 53 | callUri = callUri || '3500'; 54 | // 666, 9196 55 | 56 | console.log('jssip.call', callUri); 57 | 58 | var call; 59 | if (typeof sipUserAgent.invite === 'function') { 60 | call = sipUserAgent.invite.bind(sipUserAgent); 61 | } else { 62 | call = sipUserAgent.call.bind(sipUserAgent); 63 | } 64 | 65 | sipSession = call(callUri, { 66 | //mediaStream: localStream, 67 | // JSSIP 68 | mediaConstraints: mediaConstraints, 69 | eventHandlers: { 70 | 'confirmed': function(data) { 71 | console.info('dialCall.confirmed', data); 72 | }, 73 | 'progress': function(data) { 74 | console.info('dialCall.progress', data); 75 | }, 76 | 'failed': function(data) { 77 | console.info('dialCall.failed', data); 78 | }, 79 | 'ended': function(data) { 80 | console.info('dialCall.ended', data); 81 | } 82 | }, 83 | // Sip.JS 84 | sessionDescriptionHandlerOptions: { 85 | constraints: mediaConstraints 86 | }, 87 | // Common 88 | pcConfig: { 89 | sdpSemantics: 'plan-b', 90 | tcpMuxPolicy: 'negotiate', 91 | bundlePolicy: 'balanced', 92 | iceServers: [ 93 | { 94 | urls: "stun:sip.example.com:3478" 95 | }, 96 | { 97 | urls: "turn:sip.example.com:5349", 98 | username: "turnuser", 99 | credential: "turnpwd" 100 | }, { 101 | urls: "turn:sip.example.com:5349?transport=tcp", 102 | username: "turnuser", 103 | credential: "turnpwd" 104 | } 105 | ] 106 | } 107 | }); 108 | 109 | sipSession.on('ended', function(e) { 110 | console.debug('sipSession.ended', e); 111 | }); 112 | 113 | // Sip.js 114 | if (!isJsSip) { 115 | 116 | sipSession.on('trackAdded', function(e) { 117 | 118 | console.debug('sipSession.trackAdded', e); 119 | 120 | var peerConnection = sipSession.sessionDescriptionHandler.peerConnection; 121 | 122 | // Save peerConnection 123 | peerConnections[sipSession.id] = peerConnection; 124 | 125 | peerConnection.addEventListener('addstream', function(e) { 126 | console.debug('peerConnection.addStream', e); 127 | TestSetPeerStream(e.stream); 128 | }); 129 | 130 | // Gets local tracks 131 | var localStream = new MediaStream(); 132 | peerConnection.getSenders().forEach(function(sender) { 133 | localStream.addTrack(sender.track || sender); 134 | }); 135 | 136 | TestSetLocalStream(localStream); 137 | }); 138 | } 139 | } 140 | 141 | function TestCallAwnserPeer() { 142 | 143 | sipSession.answer({ 144 | //mediaStream: localStream, 145 | // JSSIP 146 | mediaConstraints: mediaConstraints, 147 | // Sip.JS 148 | media: mediaConstraints, 149 | // Common 150 | pcConfig: peerConnectionConfig, 151 | rtcConstraints: { 152 | mandatory: { 153 | OfferToReceiveVideo: true, 154 | OfferToReceiveAudio: true 155 | } 156 | } 157 | }); 158 | } 159 | 160 | function PatchPromiseToCallback(object, prototypeMethod) { 161 | 162 | var originalMethod = object.prototype[prototypeMethod]; 163 | 164 | object.prototype[prototypeMethod] = function(arg) { 165 | var success, failure, 166 | args = Array.prototype.slice.call(arguments); 167 | 168 | console.log('PatchPromiseToCallback', prototypeMethod, args); 169 | 170 | var finalArgs = []; 171 | args.forEach(function(arg, idx) { 172 | if (typeof arg === 'function') { 173 | if (!success) { 174 | success = arg; 175 | } else { 176 | failure = arg; 177 | } 178 | } else { 179 | finalArgs.push(arg); 180 | } 181 | }); 182 | 183 | return originalMethod.apply(this, finalArgs).then(success).catch(failure); 184 | }; 185 | } 186 | 187 | var isJsSip; 188 | function TestRTCPeerConnection() { 189 | 190 | loadScript(jsSipUrl).then(function() { 191 | 192 | isJsSip = typeof JsSIP !== 'undefined'; 193 | 194 | var socket; 195 | if (typeof JsSIP === 'undefined') { 196 | JsSIP = SIP; 197 | } else { 198 | socket = new JsSIP.WebSocketInterface(sipConfig.server); 199 | } 200 | 201 | //JsSIP.debug.enable('JsSIP:*'); 202 | 203 | sipUserAgent = new JsSIP.UA({ 204 | display_name: sipConfig.display_name, 205 | connection_recovery_min_interval: 10, 206 | connection_recovery_max_interval: 60, 207 | sockets: [socket], 208 | session_timers: false, 209 | use_preloaded_route: false, 210 | uri: sipConfig.uri, 211 | password: sipConfig.password, 212 | authorization_user: sipConfig.authorization_user, 213 | realm: sipConfig.realm, 214 | // Sip.JS 215 | hackWssInTransport: true, 216 | authorizationUser: sipConfig.authorization_user, 217 | transportOptions: { 218 | wsServers: sipConfig.server, 219 | }, 220 | allowLegacyNotifications: true, 221 | displayName: 'iOSRTC' 222 | }); 223 | 224 | sipUserAgent.on('registrationFailed', function(e) { 225 | console.log('jssip.registrationFailed', e); 226 | }); 227 | 228 | sipUserAgent.on("registered", function(e) { 229 | console.log('jssip.registered', e); 230 | TestCallOfferPeer(); 231 | }); 232 | 233 | // JSSIP 234 | if (isJsSip) { 235 | 236 | sipUserAgent.on('unregistered', function(e) { 237 | console.log('jssip.unregistered', e); 238 | 239 | }); 240 | 241 | sipUserAgent.on('disconnected', function(e) { 242 | console.log('jssip.disconnected', e); 243 | 244 | }); 245 | 246 | sipUserAgent.on("newRTCSession", function(e) { 247 | console.log('jssip.newRTCSession', e); 248 | 249 | var session = e.session; // outgoing call session here 250 | 251 | if (session.direction === 'incoming') { 252 | 253 | session.answer({ 254 | // JSSIP 255 | //mediaStream: localStream, 256 | mediaConstraints: mediaConstraints, 257 | // Sip.JS 258 | sessionDescriptionHandlerOptions: { 259 | constraints: mediaConstraints 260 | }, 261 | rtcOfferConstraints: { 262 | OfferToReceiveAudio: true, 263 | OfferToReceiveVideo: false 264 | }, 265 | rtcAnswerConstraints: { 266 | OfferToSendAudio: false, 267 | OfferToSendVideo: true 268 | } 269 | }); 270 | 271 | peerConnection = session.connection; 272 | 273 | peerConnection.addEventListener('addstream', function(e) { 274 | console.debug('peerConnection.addStream', e); 275 | TestSetPeerStream(e.stream); 276 | }); 277 | 278 | } else if (session.direction === 'outgoing') { 279 | 280 | peerConnection = session.connection; 281 | 282 | peerConnection.addEventListener('addstream', function(e) { 283 | console.debug('peerConnection.addStream', e); 284 | TestSetPeerStream(e.stream); 285 | }); 286 | } 287 | 288 | // Save peerConnection 289 | peerConnections[session.id] = peerConnection; 290 | 291 | // Gets local tracks 292 | session.on('connecting', function () { 293 | 294 | var localStream = new MediaStream(); 295 | peerConnection.getSenders().forEach(function(sender) { 296 | localStream.addTrack(sender.track || sender); 297 | }); 298 | 299 | TestSetLocalStream(localStream); 300 | }); 301 | }); 302 | } 303 | 304 | sipUserAgent.start(); 305 | 306 | }); 307 | } 308 | -------------------------------------------------------------------------------- /www/js/index-local.js: -------------------------------------------------------------------------------- 1 | /* global RTCPeerConnection */ 2 | // jshint unused:false 3 | 4 | // 5 | // Test RTCPeerConnection 6 | // 7 | 8 | var peerConnectionConfig = { 9 | offerToReceiveVideo: true, 10 | offerToReceiveAudio: true, 11 | //iceTransportPolicy: 'relay', 12 | //sdpSemantics: 'unified-plan', 13 | //sdpSemantics: 'plan-b', 14 | //bundlePolicy: 'max-compat', 15 | //rtcpMuxPolicy: 'negotiate', 16 | iceServers: [ 17 | { 18 | url: "stun:stun.stunprotocol.org" 19 | } 20 | ] 21 | }; 22 | 23 | var peerId = uuid4(), 24 | peerConnections = {}, 25 | peerConnectionsCandicates = {}; 26 | 27 | var offerConstraints = { 28 | offerToReceiveAudio: true, 29 | offerToReceiveVideo: true 30 | }; 31 | 32 | var answerConstraints = { 33 | //iceRestart: true 34 | }; 35 | 36 | var pc1, pc2; 37 | 38 | 39 | var useTrackEvent = false; //Object.getOwnPropertyDescriptors(RTCPeerConnection.prototype).ontrack; 40 | 41 | function TestRTCPeerConnection(localStream) { 42 | 43 | // Current you cannot reuse previous RTCPeerConnection 44 | pc1 = new RTCPeerConnection(peerConnectionConfig); 45 | pc2 = new RTCPeerConnection(peerConnectionConfig); 46 | 47 | peerConnections.pc1 = pc1; 48 | peerConnections.pc2 = pc2; 49 | 50 | if (useTrackEvent) { 51 | 52 | localStream.getTracks().forEach(function (track) { 53 | console.log('addTrack', track); 54 | pc1.addTrack(track); 55 | }); 56 | 57 | // Note: Deprecated but supported 58 | } else { 59 | 60 | pc1.addStream(localStream); 61 | 62 | // Note: Deprecated Test removeStream 63 | // pc1.removeStream(pc1.getLocalStreams()[0]);< 64 | } 65 | 66 | function onAddIceCandidate(pc, can) { 67 | console.log('addIceCandidate', pc, can); 68 | return can && pc.addIceCandidate(can).catch(function(err) { 69 | console.log('addIceCandidateError', err); 70 | }); 71 | } 72 | 73 | pc1.addEventListener('icecandidate', function(e) { 74 | onAddIceCandidate(pc2, e.candidate); 75 | }); 76 | 77 | pc2.addEventListener('icecandidate', function(e) { 78 | onAddIceCandidate(pc1, e.candidate); 79 | }); 80 | 81 | if (useTrackEvent) { 82 | 83 | var peerStream; 84 | 85 | pc2.addEventListener('track', function(e) { 86 | console.log('pc2.track', e); 87 | var peerStream = e.streams[0] || new MediaStream(); 88 | TestSetPeerStream(peerStream); 89 | peerStream.addTrack(e.track); 90 | }); 91 | } else { 92 | 93 | pc2.addEventListener('addstream', function(e) { 94 | console.log('pc2.addStream', e); 95 | TestSetPeerStream(e.stream); 96 | }); 97 | } 98 | 99 | pc2.addEventListener('removestream', function(e) { 100 | console.log('pc2.removeStream', e); 101 | }); 102 | 103 | pc1.addEventListener('iceconnectionstatechange', function(e) { 104 | console.log('pc1.iceConnectionState', e, pc1.iceConnectionState); 105 | 106 | if (pc1.iceConnectionState === 'completed') { 107 | console.log('pc1.getSenders', pc1.getSenders()); 108 | console.log('pc2.getReceivers', pc2.getReceivers()); 109 | } 110 | }); 111 | 112 | pc1.addEventListener('icegatheringstatechange', function(e) { 113 | console.log('pc1.iceGatheringStateChange', e); 114 | }); 115 | 116 | // https://stackoverflow.com/questions/48963787/failed-to-set-local-answer-sdp-called-in-wrong-state-kstable 117 | // https://bugs.chromium.org/p/chromium/issues/detail?id=740501 118 | var isNegotiating = false; 119 | pc1.addEventListener('signalingstatechange', function(e) { 120 | console.log('pc1.signalingstatechange', e); 121 | isNegotiating = (pc1.signalingState !== "stable"); 122 | }); 123 | 124 | pc1.addEventListener('negotiationneeded', function(e) { 125 | 126 | if (isNegotiating) { 127 | // Should not trigger on iosrtc cause of PluginRTCPeerConnection.swift fix 128 | console.log("pc1.negotiatioNeeded", "SKIP nested negotiations"); 129 | return; 130 | } 131 | isNegotiating = true; 132 | 133 | console.log('pc1.negotiatioNeeded', e); 134 | 135 | return pc1.createOffer().then(function(d) { 136 | var desc = { 137 | type: d.type, 138 | sdp: d.sdp 139 | }; 140 | console.log('pc1.setLocalDescription', desc); 141 | return pc1.setLocalDescription(desc); 142 | }).then(function() { 143 | var desc = { 144 | type: pc1.localDescription.type, 145 | sdp: pc1.localDescription.sdp 146 | }; 147 | console.log('pc2.setLocalDescription', desc); 148 | return pc2.setRemoteDescription(desc); 149 | }).then(function() { 150 | console.log('pc2.createAnswer'); 151 | return pc2.createAnswer(); 152 | }).then(function(d) { 153 | var desc = { 154 | type: d.type, 155 | sdp: d.sdp 156 | }; 157 | console.log('pc2.setLocalDescription', desc); 158 | return pc2.setLocalDescription(d); 159 | }).then(function() { 160 | var desc = { 161 | type: pc2.localDescription.type, 162 | sdp: pc2.localDescription.sdp 163 | }; 164 | console.log('pc1.setRemoteDescription', desc); 165 | return pc1.setRemoteDescription(desc); 166 | }).then(function() { 167 | TestControlsOutgoingCall(); 168 | }).catch(function(err) { 169 | console.log('pc1.createOfferError', err); 170 | }); 171 | }); 172 | } -------------------------------------------------------------------------------- /www/js/index-twilio.js: -------------------------------------------------------------------------------- 1 | /* jshint esversion: 6 */ 2 | const TWILIO_TOKEN = ''; 3 | const TWILIO_ROOM = 'test'; 4 | const TWILIO_API_URL = 'https://example.com'; 5 | const TWILIO_VERSION = '2.4.0'; 6 | 7 | function TestRTCPeerConnection(localStream) { 8 | 9 | // Patch MediaStreamTrack with clone 10 | MediaStreamTrack.prototype.clone = function () { 11 | return this; 12 | }; 13 | 14 | return loadScript('https://media.twiliocdn.com/sdk/js/video/releases/' + TWILIO_VERSION + '/twilio-video.js').then(function () { 15 | return getToken().then(function (token) { 16 | return joinRoom(localStream, { 17 | token: TWILIO_TOKEN, 18 | room: TWILIO_ROOM 19 | }); 20 | }); 21 | }); 22 | } 23 | 24 | function getToken() { 25 | if (TWILIO_TOKEN && TWILIO_ROOM) { 26 | return Promise.resolve({ 27 | token: TWILIO_TOKEN, 28 | room: TWILIO_ROOM 29 | }); 30 | } 31 | 32 | // Example via fetch with XSRF TOKEN 33 | fetch(TWILIO_API_URL + "/auth/session", { 34 | method: 'get' 35 | }).then(function (res) { 36 | var xsrf = res.headers.get('X-XSRF-TOKEN'); 37 | var opts = { 38 | room: TWILIO_ROOM, 39 | peerId: 'User-' + Date.now() 40 | }; 41 | fetch(TWILIO_API_URL + "/api/twiml/room/token", { 42 | method: 'post', 43 | body: JSON.stringify(opts), 44 | headers: { 45 | 'X-XSRF-TOKEN': xsrf, 46 | 'Accept': 'application/json', 47 | 'Content-Type': 'application/json' 48 | } 49 | }).then(function (res) { 50 | return res.json(); 51 | }); 52 | }); 53 | } 54 | 55 | function _TestGetUserMedia(deviceId) { 56 | Video.createLocalTracks().then(tracks => { 57 | var localMediaContainer = document.querySelector('.local-stream'); 58 | tracks.forEach(function(track) { 59 | localMediaContainer.appendChild(track.attach()); 60 | }); 61 | }); 62 | } 63 | 64 | function joinRoom(localStream, config) { 65 | 66 | var Video = Twilio.Video; 67 | var audioTracks = localStream.getAudioTracks().map(track => new Video.LocalAudioTrack(track)); 68 | var videoTracks = localStream.getVideoTracks().map(track => new Video.LocalVideoTrack(track)); 69 | var tracks = audioTracks.concat(videoTracks); 70 | 71 | Video.connect(config.token, { 72 | name: config.room, 73 | tracks: tracks, 74 | sdpSemantics: 'plan-b', 75 | bundlePolicy: 'max-compat' 76 | }).then(room => { 77 | console.log(`Successfully joined a Room: ${room}`); 78 | 79 | // Attach the Tracks of the Room's Participants. 80 | var remoteMediaContainer = document.querySelector('.remote-stream'); 81 | room.participants.forEach(function(participant) { 82 | console.log("Already in Room: '" + participant.identity + "'"); 83 | participantConnected(participant, remoteMediaContainer); 84 | }); 85 | 86 | room.on('participantConnected', participant => { 87 | console.log(`A remote Participant connected: ${participant}`); 88 | participantConnected(participant); 89 | }); 90 | 91 | room.on('participantDisconnected', participant => { 92 | console.log(`A remote Participant connected: ${participant}`); 93 | participantDisconnected(participant); 94 | }); 95 | 96 | }, error => { 97 | console.error(`Unable to connect to Room: ${error.message}`); 98 | }); 99 | 100 | 101 | function participantConnected(participant) { 102 | console.log('Participant "%s" connected', participant.identity); 103 | var div = document.createElement('div'); 104 | div.id = participant.sid; 105 | participant.on('trackSubscribed', (track) => { 106 | trackSubscribed(div, track); 107 | }); 108 | participant.on('trackUnsubscribed', trackUnsubscribed); 109 | participant.tracks.forEach(publication => { 110 | if (publication.isSubscribed) { 111 | trackSubscribed(div, publication.track); 112 | } 113 | }); 114 | 115 | var remoteMediaContainer = document.querySelector('.remote-stream'); 116 | remoteMediaContainer.appendChild(div); 117 | } 118 | 119 | function participantDisconnected(participant) { 120 | console.log('Participant "%s" disconnected', participant.identity); 121 | 122 | var div = document.getElementById(participant.sid); 123 | if (div) { 124 | div.remove(); 125 | } 126 | } 127 | 128 | function trackSubscribed(div, track) { 129 | div.appendChild(track.attach()); 130 | } 131 | 132 | function trackUnsubscribed(track) { 133 | track.detach().forEach(element => element.remove()); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /www/js/index-websocket.js: -------------------------------------------------------------------------------- 1 | /* global RTCPeerConnection */ 2 | const WEBSOCKET_SERVER = 'connect.websocket.in/v3'; 3 | const WEBSOCKET_CHANNEL = '1'; 4 | const WEBSOCKET_TOKEN = "ySGGACsat7vGEyQdDwrFSu0AoWCae2uNsZe418d7eh6AfJDr9U9nUcgp5MtS"; 5 | 6 | // 7 | // RTCPeerConnection Config 8 | // Note: Change to match your TURN and other webRTC settings. 9 | // 10 | 11 | var peerConnectionConfig = { 12 | offerToReceiveVideo: true, 13 | offerToReceiveAudio: true, 14 | //iceTransportPolicy: 'relay', 15 | //sdpSemantics: 'unified-plan', 16 | sdpSemantics: 'plan-b', 17 | bundlePolicy: 'max-compat', 18 | rtcpMuxPolicy: 'negotiate', 19 | iceServers: [ 20 | { 21 | urls: "stun:stun.stunprotocol.org" 22 | } 23 | ] 24 | }; 25 | 26 | var peerId = uuid4(), 27 | peerConnectionsCandicates = {}; 28 | 29 | var optionalsConstraints = [ 30 | {"DtlsSrtpKeyAgreement": true}, 31 | {"googImprovedWifiBwe": false}, 32 | {"googDscp": false}, 33 | {"googCpuOveruseDetection": false} 34 | ]; 35 | 36 | var offerConstraints = { 37 | offerToReceiveAudio: true, 38 | offerToReceiveVideo: true, 39 | optionals: optionalsConstraints, 40 | }; 41 | 42 | var answerConstraints = { 43 | optional: optionalsConstraints, 44 | //iceRestart: true 45 | }; 46 | 47 | // 48 | // WebSocket Signaling 49 | // 50 | 51 | var webSocket, 52 | webSocketChannel = WEBSOCKET_CHANNEL, 53 | webSocketToken = WEBSOCKET_TOKEN, 54 | webSocketHostname = WEBSOCKET_SERVER; 55 | 56 | function webSocketSendMessage(msg) { 57 | console.debug('webSocketSendMessage', msg); 58 | webSocket.send(JSON.stringify(msg)); 59 | } 60 | 61 | function notifyWebSocketRoom() { 62 | webSocket.send(JSON.stringify({ 63 | source: peerId, 64 | type: 'peer' 65 | })); 66 | } 67 | 68 | function joinWebSocketRoom() { 69 | 70 | var webSocketUrl = 'wss://' + webSocketHostname + '/' + webSocketChannel + 71 | '?apiKey=' + webSocketToken; 72 | 73 | webSocket = new WebSocket(webSocketUrl); 74 | 75 | webSocket.onopen = function(e) { 76 | console.debug('socket.open', e); 77 | notifyWebSocketRoom(); 78 | }; 79 | 80 | webSocket.onclose = function(e) { 81 | webSocket = null; 82 | console.debug('socket.close', e); 83 | TestHangupRTCPeerConnections(peerConnections); 84 | //TestRTCPeerConnection(); 85 | }; 86 | 87 | webSocket.onmessage = function(e) { 88 | console.debug('socket.message', e); 89 | var data = JSON.parse(e.data); 90 | if (data.type == 'offer' && data.target === peerId) { 91 | TestCallAwnserPeer(data.source, { 92 | type: data.type, 93 | sdp: data.sdp 94 | }); 95 | TestControlsOutgoingCall(); 96 | } else if (data.type == 'answer' && data.target === peerId) { 97 | if (peerConnections[data.source]) { 98 | TestCallAcceptedByPeer(data.source, { 99 | type: data.type, 100 | sdp: data.sdp 101 | }); 102 | TestControlsIncomingCall(); 103 | } else { 104 | console.error('TestCallAcceptedByPeer.err', 'Invalid peer', data.source); 105 | } 106 | } else if (data.type == 'candidate' && data.target === peerId) { 107 | TestReceiveCandicate(data.source, data.candidate); 108 | } else if (data.type == 'peer') { 109 | TestCallOfferPeer(data.source); 110 | } else if (data.type == 'accepted') { 111 | 112 | } else if (data.type == 'rejected') { 113 | TestHangupRTCPeerConnection(data.source); 114 | } else if (data.type == 'closed') { 115 | TestHangupRTCPeerConnection(data.source); 116 | } 117 | }; 118 | 119 | webSocket.onerror = function(e) { 120 | console.error('socket.error', e); 121 | }; 122 | } 123 | 124 | // 125 | // 126 | // 127 | 128 | var preferredCodec;// = 'VP8'; 129 | 130 | // Find the line in sdpLines that starts with |prefix|, and, if specified, 131 | // contains |substr| (case-insensitive search). 132 | function findLine(sdpLines, prefix, substr) { 133 | return findLineInRange(sdpLines, 0, -1, prefix, substr); 134 | } 135 | 136 | // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix| 137 | // and, if specified, contains |substr| (case-insensitive search). 138 | function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { 139 | var realEndLine = endLine !== -1 ? endLine : sdpLines.length; 140 | for (var i = startLine; i < realEndLine; ++i) { 141 | if (sdpLines[i].indexOf(prefix) === 0) { 142 | if (!substr || 143 | sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { 144 | return i; 145 | } 146 | } 147 | } 148 | return null; 149 | } 150 | 151 | // Gets the codec payload type from sdp lines. 152 | function getCodecPayloadType(sdpLines, codec) { 153 | var index = findLine(sdpLines, 'a=rtpmap', codec); 154 | return index ? getCodecPayloadTypeFromLine(sdpLines[index]) : null; 155 | } 156 | 157 | // Gets the codec payload type from an a=rtpmap:X line. 158 | function getCodecPayloadTypeFromLine(sdpLine) { 159 | var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); 160 | var result = sdpLine.match(pattern); 161 | return (result && result.length === 2) ? result[1] : null; 162 | } 163 | 164 | // Returns a new m= line with the specified codec as the first one. 165 | function setDefaultCodec(mLine, payload) { 166 | var elements = mLine.split(' '); 167 | 168 | // Just copy the first three parameters; codec order starts on fourth. 169 | var newLine = elements.slice(0, 3); 170 | 171 | // Put target payload first and copy in the rest. 172 | newLine.push(payload); 173 | for (var i = 3; i < elements.length; i++) { 174 | if (elements[i] !== payload) { 175 | newLine.push(elements[i]); 176 | } 177 | } 178 | return newLine.join(' '); 179 | } 180 | 181 | // Sets |codec| as the default |type| codec if it's present. 182 | // The format of |codec| is 'NAME/RATE', e.g. 'opus/48000'. 183 | function maybePreferCodec(sdp, type, dir, codec) { 184 | var str = type + ' ' + dir + ' codec'; 185 | if (!codec) { 186 | console.info('No preference on ' + str + '.'); 187 | return sdp; 188 | } 189 | 190 | console.info('Prefer ' + str + ': ' + codec); 191 | 192 | var sdpLines = sdp.split('\r\n'); 193 | 194 | // Search for m line. 195 | var mLineIndex = findLine(sdpLines, 'm=', type); 196 | if (mLineIndex === null) { 197 | return sdp; 198 | } 199 | 200 | // If the codec is available, set it as the default in m line. 201 | var payload = getCodecPayloadType(sdpLines, codec); 202 | if (payload) { 203 | sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], payload); 204 | } 205 | 206 | sdp = sdpLines.join('\r\n'); 207 | return sdp; 208 | } 209 | 210 | // Promotes |audioSendCodec| to be the first in the m=audio line, if set. 211 | function maybePreferAudioSendCodec(sdp, params) { 212 | return maybePreferCodec(sdp, 'audio', 'send', params.audioSendCodec); 213 | } 214 | 215 | // Promotes |audioRecvCodec| to be the first in the m=audio line, if set. 216 | function maybePreferAudioReceiveCodec(sdp, params) { 217 | return maybePreferCodec(sdp, 'audio', 'receive', params.audioRecvCodec); 218 | } 219 | 220 | // Promotes |videoSendCodec| to be the first in the m=audio line, if set. 221 | function maybePreferVideoSendCodec(sdp, params) { 222 | return maybePreferCodec(sdp, 'video', 'send', params.videoSendCodec); 223 | } 224 | 225 | // Promotes |videoRecvCodec| to be the first in the m=audio line, if set. 226 | function maybePreferVideoReceiveCodec(sdp, params) { 227 | return maybePreferCodec(sdp, 'video', 'receive', params.videoRecvCodec); 228 | } 229 | 230 | 231 | // 232 | // WebRTC Signaling 233 | // 234 | 235 | function TestCallOfferPeer(targetPeerId, constraints) { 236 | var peerConnection = peerConnections[targetPeerId] || new RTCPeerConnection(peerConnectionConfig); 237 | 238 | console.debug('TestCallOfferPeer', targetPeerId, peerConnection.connectionState); 239 | if (!peerConnection.TestListenEvents) { 240 | peerConnection.TestListenEvents = true; 241 | peerConnections[targetPeerId] = peerConnection; 242 | TestRemoveStreamToPeerConnection(peerConnection, localStream); 243 | TestAddStreamToPeerConnection(peerConnection, localStream); 244 | TestListenPeerConnection(targetPeerId, peerConnection); 245 | } 246 | 247 | return peerConnection.createOffer(constraints || offerConstraints).then(function(desc) { 248 | 249 | if (preferredCodec) { 250 | desc.sdp = maybePreferVideoReceiveCodec(desc.sdp, { 251 | videoRecvCodec: preferredCodec 252 | }); 253 | 254 | desc.sdp = maybePreferVideoSendCodec(desc.sdp, { 255 | videoSendCodec: preferredCodec 256 | }); 257 | } 258 | 259 | return peerConnection.setLocalDescription(desc).then(function() { 260 | webSocketSendMessage({ 261 | source: peerId, 262 | target: targetPeerId, 263 | type: desc.type, 264 | sdp: desc.sdp 265 | }); 266 | }); 267 | }).catch(function(err) { 268 | console.error('TestCallOfferPeerError', err.message, err); 269 | TestHangupRTCPeerConnection(targetPeerId, peerConnection); 270 | }); 271 | } 272 | 273 | function TestCallAwnserPeer(targetPeerId, desc, constraints) { 274 | var peerConnection = peerConnections[targetPeerId] || new RTCPeerConnection(peerConnectionConfig); 275 | 276 | console.debug('TestCallAwnserPeer', targetPeerId, peerConnection.connectionState); 277 | if (!peerConnection.TestListenEvents) { 278 | peerConnection.TestListenEvents = true; 279 | peerConnections[targetPeerId] = peerConnection; 280 | TestRemoveStreamToPeerConnection(peerConnection, localStream); 281 | TestAddStreamToPeerConnection(peerConnection, localStream); 282 | TestListenPeerConnection(targetPeerId, peerConnection); 283 | } 284 | 285 | return peerConnection.setRemoteDescription(desc).then(function() { 286 | return peerConnection.createAnswer(constraints || answerConstraints).then(function(desc) { 287 | 288 | if (preferredCodec) { 289 | desc.sdp = maybePreferVideoReceiveCodec(desc.sdp, { 290 | videoRecvCodec: preferredCodec 291 | }); 292 | 293 | desc.sdp = maybePreferVideoSendCodec(desc.sdp, { 294 | videoSendCodec: preferredCodec 295 | }); 296 | } 297 | 298 | return peerConnection.setLocalDescription(desc).then(function() { 299 | 300 | webSocketSendMessage({ 301 | source: peerId, 302 | target: targetPeerId, 303 | type: desc.type, 304 | sdp: desc.sdp 305 | }); 306 | }); 307 | }); 308 | }).catch(function(err) { 309 | console.error('TestCallAwnserPeerError', err); 310 | TestHangupRTCPeerConnection(targetPeerId, peerConnection); 311 | }); 312 | } 313 | 314 | function TestProcessCandicate(targetPeerId) { 315 | var peerConnection = peerConnections[targetPeerId]; 316 | if (peerConnection && peerConnectionsCandicates[targetPeerId]) { 317 | peerConnectionsCandicates[targetPeerId].forEach(function(can) { 318 | console.debug('peerConnection.addIceCandidate', peerConnection.signalingState, can); 319 | peerConnection.addIceCandidate(can).catch(function(err) { 320 | console.error('peerConnection.addIceCandidateError', err.message, err); 321 | }); 322 | }); 323 | peerConnectionsCandicates[targetPeerId].length = 0; 324 | } 325 | } 326 | 327 | function TestReceiveCandicate(targetPeerId, can) { 328 | var peerConnectionCandicate = peerConnectionsCandicates[targetPeerId] = peerConnectionsCandicates[targetPeerId] || []; 329 | peerConnectionCandicate.push(can); 330 | TestProcessCandicate(targetPeerId); 331 | } 332 | 333 | function TestListenPeerConnection(targetPeerId, peerConnection) { 334 | 335 | var isNegotiating = false; 336 | var disconnectedTimer; 337 | peerConnection.addEventListener('icecandidate', function(e) { 338 | var candidate = e.candidate; 339 | console.debug('peerConnection.icecandidate', peerConnection, candidate); 340 | 341 | if (candidate && isNegotiating) { 342 | webSocketSendMessage({ 343 | source: peerId, 344 | target: targetPeerId, 345 | type: 'candidate', 346 | candidate: candidate 347 | }); 348 | } 349 | }); 350 | 351 | peerConnection.addEventListener('open', function(e) { 352 | console.debug('peerConnection.open', e); 353 | TestSetPeerStreamLoading(false); 354 | }); 355 | 356 | peerConnection.addEventListener('close', function(e) { 357 | console.debug('peerConnection.close', e); 358 | TestHangupRTCPeerConnection(targetPeerId, peerConnection); 359 | }); 360 | 361 | peerConnection.addEventListener('track', function(e) { 362 | console.debug('peerConnection.addTrack', e); 363 | }); 364 | 365 | peerConnection.addEventListener('addstream', function(e) { 366 | console.debug('peerConnection.addStream', e); 367 | TestSetPeerStream(e.stream); 368 | }); 369 | 370 | peerConnection.addEventListener('icegatheringstatechange', function(e) { 371 | console.debug('peerConnection.iceGatheringStateChange', e); 372 | }); 373 | 374 | // https://stackoverflow.com/questions/48963787/failed-to-set-local-answer-sdp-called-in-wrong-state-kstable 375 | // https://bugs.chromium.org/p/chromium/issues/detail?id=740501 376 | peerConnection.addEventListener('signalingstatechange', function(e) { 377 | console.debug('peerConnection.signalingstatechange', peerConnection.signalingState, e); 378 | 379 | clearTimeout(disconnectedTimer); 380 | isNegotiating = (peerConnection.signalingState !== "stable" && peerConnection.signalingState !== "closed"); 381 | 382 | if (peerConnection.signalingState === 'closed') { 383 | TestHangupRTCPeerConnection(targetPeerId, peerConnection); 384 | } 385 | }); 386 | 387 | peerConnection.addEventListener('negotiationneeded', function(e) { 388 | console.debug('peerConnection.negotiationneeded', e); 389 | // TODO https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/negotiationneeded_event 390 | }); 391 | } 392 | 393 | function TestCallAcceptedByPeer(targetPeerId, desc) { 394 | var peerConnection = peerConnections[targetPeerId]; 395 | return peerConnection.setRemoteDescription(desc).then(function() { 396 | webSocketSendMessage({ 397 | source: peerId, 398 | target: targetPeerId, 399 | type: 'accepted' 400 | }); 401 | }).catch(function(err) { 402 | console.error('TestCallAcceptedByPeerError', err.message, err); 403 | webSocketSendMessage({ 404 | source: peerId, 405 | target: targetPeerId, 406 | type: 'rejected' 407 | }); 408 | }); 409 | } 410 | 411 | 412 | function TestRTCPeerConnection() { 413 | if (webSocket) { 414 | notifyWebSocketRoom(); 415 | } else { 416 | joinWebSocketRoom(); 417 | } 418 | } -------------------------------------------------------------------------------- /www/lib/ios-websocket-hack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * iOS Safari browser makes the Cordova app crash when any plugin method is called 3 | * within a WebSocket event (such as onopen, onmessage, etc). This happens randomly. 4 | * This script overrides the native WebSocket class by making all the events and 5 | * methods to be fired within a setTimeout so event listeners are not fired in the 6 | * buggy WebSocket context. 7 | * 8 | * The issue is described here: 9 | * https://github.com/cordova-rtc/cordova-plugin-iosrtc/issues/12 10 | * 11 | * USAGE: 12 | * 13 | * Just load this script in the HTML of your Cordova iOS app within the first 14 | *