├── .gitignore ├── LICENSE ├── README.md ├── Vagrantfile ├── archi.png ├── architecture.svg ├── config.js ├── destroy.bat ├── doc ├── Kurento-build.md └── letsencrypt_cert.md ├── drachtio.conf.xml ├── init.sh ├── install.sh ├── install_drachtio.sh ├── keys ├── README.md ├── server.crt ├── server.csr └── server.key ├── kurento-sip-gw.service ├── mediastack.js ├── my-kurento-sip-gw.service ├── package.json ├── run.bat ├── runDrachtio.sh ├── server.js ├── sipstack.js ├── ssh.bat ├── static ├── bower.json ├── css │ ├── kurento.css │ └── style.css ├── img │ ├── giphy.gif │ ├── ic_perm_identity_black_24px.svg │ ├── ic_phone_black_24px.svg │ ├── images.jpg │ ├── kurento.png │ ├── logo.png │ ├── logo2.png │ ├── naevatec.png │ ├── pipeline.png │ ├── spinner.gif │ ├── transparent-1px.png │ ├── urjc.gif │ └── webrtc.png ├── index.html ├── index_kurento.html └── js │ ├── DTMFSenderInband.js │ ├── config_client.js │ ├── index.js │ └── index_app.js ├── stop.bat └── sync.sh /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vagrant 3 | drachtio-server 4 | static/bower_components 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Synopsis 2 | 3 | This project is a basic WebRTC to SIP gateway. It uses [drachtio](https://github.com/davehorton/drachtio) for SIP signaling and [Kurento](https://www.kurento.org/) for the Media Server. 4 | This node application was made using [Kurent-tutorial-node/helloworld](https://github.com/Kurento/kurento-tutorial-node/tree/master/kurento-hello-world). 5 | Html client UI is made using Login/Logout Animation Concept by Nikolay Talanov 6 | http://codepen.io/suez/pen/dPqxoM. 7 | 8 | ## Motivation 9 | 10 | This gateway was made to easily connect a browser to any classic SIP endpoint like Softphone, PABX or MCU. 11 | It was firstly designed to work with Asterisk and it works with it. 12 | 13 | ## Architecture 14 | ![Kurento-SIP-GW architecture](https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/master/archi.png "Kurento-SIP-GW architecture") 15 | 16 | ## Installation on Ubuntu 18.04 17 | 18 | First, install [Kurento-media-server](https://github.com/Kurento/kurento-media-server) (with a coturn server it's better) : 19 | 20 | ```bash 21 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 5AFA7A83 22 | source /etc/upstream-release/lsb-release 2>/dev/null || source /etc/lsb-release 23 | 24 | sudo tee "/etc/apt/sources.list.d/kurento.list" >/dev/null < 2 | 3 | 4 | 18 | 20 | 27 | 33 | 34 | 41 | 47 | 48 | 55 | 61 | 62 | 69 | 75 | 76 | 83 | 89 | 90 | 97 | 103 | 104 | 111 | 117 | 118 | 125 | 131 | 132 | 139 | 145 | 146 | 153 | 159 | 160 | 161 | 179 | 181 | 182 | 184 | image/svg+xml 185 | 187 | 188 | 189 | 190 | 191 | 196 | 204 | 212 | NodeJSServer 227 | 230 | 238 | WebRTC ClientWebsocket Client 253 | 254 | 262 | 270 | Http Server 281 | WebSocket Server 292 | 300 | DrachtioSIPServer 319 | 324 | Websocket Channel 336 | 342 | 345 | 353 | KURENTOMediaServer 372 | 375 | 383 | WebRTCEndpoint 394 | 395 | 398 | 406 | RTPEndpoint 417 | 418 | 419 | 422 | 425 | 433 | SIP EndPointAsteriskLinphoneOpenMCU 456 | 457 | 462 | 467 | 473 | 474 | WebRTC Media Channel 486 | Kurento JSON API 498 | Drachtio API 510 | SIP 522 | 528 | RTP Media Channel 540 | Kurento-SIP-GW 552 | 553 | 554 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | 3 | // Server exposeed external public IP used for SIP sdp generation. 4 | config.serverPublicIP = "192.168.0.17"; 5 | 6 | // Kurento WebRTC media server specific option 7 | config.kurento = { 8 | as_uri: 'https://localhost:8443/', 9 | ws_uri: 'ws://localhost:8888/kurento' 10 | }; 11 | 12 | // Drachtio SIP server specific options 13 | config.drachtio = { 14 | host: '127.0.0.1', 15 | port: 9022, 16 | secret: 'cymru', 17 | methods: ['invite','bye','option'], 18 | } 19 | 20 | // Limite max call time to 60 seconds 21 | config.maxCallSeconds = 60; 22 | 23 | // Maximum concurrent calls supported by the server, every new startcalls will be rejected after this limit 24 | config.maxConcurentCalls = 1; 25 | 26 | module.exports = config; 27 | -------------------------------------------------------------------------------- /destroy.bat: -------------------------------------------------------------------------------- 1 | vagrant destroy 2 | pause 3 | -------------------------------------------------------------------------------- /doc/Kurento-build.md: -------------------------------------------------------------------------------- 1 | ## Contribute to DTMF in Kurento 2 | 3 | In order to add RFC 4733 DTMF sending support we need to add it at Kurento base RTP element level 4 | 5 | ## Get Kurento source 6 | 7 | This gateway was made to easily connect a browser to any classic SIP endpoint like Softphone, PABX or MCU. 8 | It was firstly designed to work with Asterisk and it works with it. 9 | 10 | Kurento media server is buil of 4 differents part : 11 | 12 | * kms-media-server 13 | * kms-core 14 | * kms-element 15 | * kms-filter 16 | 17 | 18 | ```bash 19 | git clone https://github.com/Kurento/kurento-media-server.git 20 | 21 | 22 | ``` 23 | 24 | 25 | 26 | ## Build desire Kurento part 27 | 28 | 29 | ## Test it with our Gateway 30 | -------------------------------------------------------------------------------- /doc/letsencrypt_cert.md: -------------------------------------------------------------------------------- 1 | # Generate letsencrypt certificat using certbot 2 | 3 | 1. Install certbot 4 | ```bash 5 | $ sudo apt-get update 6 | $ sudo apt-get install software-properties-common 7 | $ sudo add-apt-repository ppa:certbot/certbot 8 | $ sudo apt-get update 9 | $ sudo apt-get install certbot 10 | ``` 11 | 12 | 2. Generate nitial certificate 13 | ```bash 14 | sudo certbot certonly --standalone -d example.com -d www.example.com 15 | ``` 16 | 17 | 3. Renew your certificate 18 | ```bash 19 | sudo certbot renew --dry-run add it to cron 20 | ``` 21 | -------------------------------------------------------------------------------- /drachtio.conf.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 127.0.0.1 5 | 6 | 82 | 83 | 89 | 90 | 91 | 92 | sip:*;transport=udp,tcp 93 | 94 | 100 | 101 | 102 | 103 | 108 | 109 | 110 | 111 | 119 | 120 | 123 | 124 | 132 | 133 | 136 | 137 | 138 | 141 | 142 | 147 | 148 |
149 | sip-cli 150 | sipcli 151 | friendly-scanner 152 |
153 |
154 | sipvicious 155 |
156 |
157 |
158 | 159 | 160 | false 161 | 162 | 163 | 127.0.0.1 164 | 165 | 166 | 167 | 168 | 169 | 172 | 173 | 174 | 181 | 182 | 183 | 184 | /var/log/drachtio/drachtio.log 185 | /var/log/drachtio/archive 186 | 100 187 | 20 188 | true 189 | 190 | 191 | 192 | 3 193 | 194 | 195 | info 196 | 197 | 198 |
199 | -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Start kurento" 4 | sudo service kurento-media-server-6.0 start 5 | 6 | echo "Start Dracthio" 7 | sudo service drachtio-init-script start 8 | 9 | echo "Done" 10 | 11 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Kurent SIP gateway basic sh script installation for Ubuntu 16" 4 | echo "Start" 5 | 6 | echo "Installation of basic element" 7 | 8 | sudo apt update 9 | 10 | sudo apt-get -y install git nodejs autoconf automake libtool-bin gcc g++ libcurl4-openssl-dev libssl-dev openh264-gst-plugins-bad-1.5 11 | 12 | echo "Installation of Kurento" 13 | # Import the Kurento repository signing key 14 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 5AFA7A83 15 | DISTRIB_CODENAME="bionic" 16 | # Get Ubuntu version definitions 17 | source /etc/upstream-release/lsb-release 2>/dev/null || source /etc/lsb-release 18 | 19 | # Add the repository to Apt 20 | sudo tee "/etc/apt/sources.list.d/kurento.list" >/dev/null < my-kurento-sip-gw.service 54 | cp my-kurento-sip-gw.service /etc/systemd/system 55 | systemctl daemon-reload 56 | 57 | 58 | echo "end" 59 | -------------------------------------------------------------------------------- /install_drachtio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Start" 4 | 5 | 6 | #echo "Installation of coturn" 7 | #sudo apt-get install coturn 8 | 9 | echo "Installation of Drachtio" 10 | git clone --depth=50 --branch=develop git://github.com/davehorton/drachtio-server.git 11 | cd drachtio-server 12 | git submodule update --init --recursive 13 | ./bootstrap.sh 14 | mkdir build && cd build 15 | ../configure CPPFLAGS='-DNDEBUG' 16 | make 17 | sudo make install 18 | # Put Drachtio as a Deamon service 19 | cp drachtio-server.service /etc/systemd/system 20 | 21 | cd ../.. 22 | cp drachtio-conf.xml /etc 23 | 24 | echo "Installation of node modules" 25 | npm install 26 | -------------------------------------------------------------------------------- /keys/README.md: -------------------------------------------------------------------------------- 1 | [![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0) 2 | [![Documentation badge](https://readthedocs.org/projects/fiware-orion/badge/?version=latest)](http://doc-kurento.readthedocs.org/en/latest/) 3 | [![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/fiware/stream-oriented-kurento/) 4 | [![Support badge]( https://img.shields.io/badge/support-sof-yellowgreen.svg)](http://stackoverflow.com/questions/tagged/kurento) 5 | 6 | This folder contains a dummy self-signed certificate only for demo purposses, 7 | **DON'T USE IT IN PRODUCTION**. 8 | -------------------------------------------------------------------------------- /keys/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDBjCCAe4CCQCuf5QfyX2oDDANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB 3 | VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 4 | cyBQdHkgTHRkMB4XDTE0MDkyOTA5NDczNVoXDTE1MDkyOTA5NDczNVowRTELMAkG 5 | A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 6 | IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB 7 | AMJOyOHJ+rJWJEQ7P7kKoWa31ff7hKNZxF6sYE5lFi3pBYWIY6kTN/iUaxJLROFo 8 | FhoC/M/STY76rIryix474v/6cRoG8N+GQBEn4IAP1UitWzVO6pVvBaIt5IKlhhfm 9 | YA1IMweCd03vLcaHTddNmFDBTks7QDwfenTaR5VjKYc3OtEhcG8dgLAnOjbbk2Hr 10 | 8wter2IeNgkhya3zyoXnTLT8m8IMg2mQaJs62Xlo9gs56urvVDWG4rhdGybj1uwU 11 | ZiDYyP4CFCUHS6UVt12vADP8vjbwmss2ScGsIf0NjaU+MpSdEbB82z4b2NiN8Wq+ 12 | rFA/JbvyeoWWHMoa7wkVs1MCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYLRwV9fo 13 | AOhJfeK199Tv6oXoNSSSe10pVLnYxPcczCVQ4b9SomKFJFbmwtPVGi6w3m+8mV7F 14 | 9I2WKyeBHzmzfW2utZNupVybxgzEjuFLOVytSPdsB+DcJomOi8W/Cf2Vk8Wykb/t 15 | Ctr1gfOcI8rwEGKxm279spBs0u1snzoLyoimbMbiXbC82j1IiN3Jus08U07m/j7N 16 | hRBCpeHjUHT3CRpvYyTRnt+AyBd8BiyJB7nWmcNI1DksXPfehd62MAFS9e1ZE+dH 17 | Aavg/U8VpS7pcCQcPJvIJ2hehrt8L6kUk3YUYqZ0OeRZK27f2R5+wFlDF33esm3N 18 | dCSsLJlXyqAQFg== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /keys/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx 3 | ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN 4 | AQEBBQADggEPADCCAQoCggEBAMJOyOHJ+rJWJEQ7P7kKoWa31ff7hKNZxF6sYE5l 5 | Fi3pBYWIY6kTN/iUaxJLROFoFhoC/M/STY76rIryix474v/6cRoG8N+GQBEn4IAP 6 | 1UitWzVO6pVvBaIt5IKlhhfmYA1IMweCd03vLcaHTddNmFDBTks7QDwfenTaR5Vj 7 | KYc3OtEhcG8dgLAnOjbbk2Hr8wter2IeNgkhya3zyoXnTLT8m8IMg2mQaJs62Xlo 8 | 9gs56urvVDWG4rhdGybj1uwUZiDYyP4CFCUHS6UVt12vADP8vjbwmss2ScGsIf0N 9 | jaU+MpSdEbB82z4b2NiN8Wq+rFA/JbvyeoWWHMoa7wkVs1MCAwEAAaAAMA0GCSqG 10 | SIb3DQEBCwUAA4IBAQBMszYHMpklgTF/3h1zAzKXUD9NrtZp8eWhL06nwVjQX8Ai 11 | EaCUiW0ypstokWcH9+30chd2OD++67NbxYUEucH8HrKpOoy6gs5L/mqgQ9Npz3OT 12 | TB1HI4kGtpVuUQ5D7L0596tKzMX/CgW/hRcHWl+PDkwGhQs1qZcJ8QN+YP6AkRrO 13 | 5sDdDB/BLrB9PtBQbPrYIQcHQ7ooYWz/G+goqRxzZ6rt0aU2uAB6l7c82ADLAqFJ 14 | qlw+xqVzEETVfqM5TXKK/wV3hgm4oSX5Q4SHLKF94ODOkWcnV4nfIKz7y+5XcQ3p 15 | PrGimI1br07okC5rO9cgLCR0Ks20PPFcM0FvInW/ 16 | -----END CERTIFICATE REQUEST----- 17 | -------------------------------------------------------------------------------- /keys/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAwk7I4cn6slYkRDs/uQqhZrfV9/uEo1nEXqxgTmUWLekFhYhj 3 | qRM3+JRrEktE4WgWGgL8z9JNjvqsivKLHjvi//pxGgbw34ZAESfggA/VSK1bNU7q 4 | lW8Foi3kgqWGF+ZgDUgzB4J3Te8txodN102YUMFOSztAPB96dNpHlWMphzc60SFw 5 | bx2AsCc6NtuTYevzC16vYh42CSHJrfPKhedMtPybwgyDaZBomzrZeWj2Cznq6u9U 6 | NYbiuF0bJuPW7BRmINjI/gIUJQdLpRW3Xa8AM/y+NvCayzZJwawh/Q2NpT4ylJ0R 7 | sHzbPhvY2I3xar6sUD8lu/J6hZYcyhrvCRWzUwIDAQABAoIBACwt56TW3MZxqZtN 8 | 8WYsUZheUispJ/ZQMcLo5JjOiSV1Jwk+gpJtyTse291z+bxagzP02/CQu4u32UVa 9 | cmE0cp+LHO4zB8964dREwdm8P91fdS6Au/uwG5LNZniCFCQZAFvkv52Ef4XbzQen 10 | uf4rKWerHBck6K0C5z/sZXxE6KtScE2ZLUmkhO0nkHM6MA6gFk2OMnB+oDTOWWPt 11 | 1mlreQlzuMYG/D4axviRYrOSYCE5Qu1SOw/DEOLQqqeBjQrKtAyOlFHZsIR6lBfe 12 | KHMChPUcYIwaowt2DcqH/A+AFXRtaifa6DvH8Yul+2vAp47UEpaenVfM5bpN33XV 13 | EzerjtECgYEA+xiXzblek67iQgRpc9eHSoqs4iRLhae8s8kpAG51Jz46Je+Dmium 14 | XV769oiUGUxBeoUb7ryW+4MOzHJaA1BfGejQSvwLIB9e4cnikqnAArcqbcAcOCL1 15 | aYYDiSmSmN/AokNZlPKEBFXP9bzXrU9smQJWNTHlcRl7JXfnwF+jwNsCgYEAxhpE 16 | SBr9vlUVHNh/S6C5i80NIYg6jCy2FgsmuzEqmcqV0pTyzegmq8bru+QmuvoUj2o4 17 | nVv4J9d1fLF6ECUVk9aK8UdJOOB6hAfurOdJCArgrsY/9t4uDzXfbPCdfSNQITE0 18 | XgeNGQX1EzvwwkBmyZKk0kLIr3syP8ZCWfXDROkCgYBR+dF1pJMv++R6UR5sZ20P 19 | 9P5ERj0xwXVl7MKqFWXCDhrFz9BTQPTrftrIKgbPy4mFCnf4FTHlov/t11dzxYWG 20 | 2+9Ey8yGDDfZ1yNVZn39ZPdBJXsRCLi+XrZAzYXCyyoEz6ArdJGNKMbgH2r6dfeq 21 | bIzgiQ2zQvJlZSQQNiksCQKBgCgwzAmU8EXdHRttEOZXBU3HnBJhgP9PUuHGAWWY 22 | 4/uvjhXbAiekIbRX9xt3fiQQ+HrgIfxK3F246K0TlKAR5f7IWAf7Xm+bmz+OHG4X 23 | vklTa6IJtpBvIwkS9PE1H75zm54gTW+GOKoK+12bm4zNZA0hIy9FPVHcvKUTpAJ8 24 | SdGBAoGAHLtJnB1NO4EgO6WtLQMXt7HrIbup8eZi8/82gC3422C+ooKIrYQ07qSw 25 | nBOO/G0OB4yd6vCE2x5+TWSSCYGgG5A8aIv5qP76RP4hovGHxG/y2tfotw5UuOrh 26 | nFWlTP4Urs8PeykvK9ao8r/T8BnPIC16U6ENYvAc0mRlFA2j1GA= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /kurento-sip-gw.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Kurent-sip-gw 3 | After=syslog.target network.target local-fs.target 4 | 5 | [Service] 6 | ; service 7 | WorkingDirectory=SERVERPATH 8 | ExecStart=/usr/bin/node server.js 9 | TimeoutSec=15s 10 | Restart=always 11 | ; exec 12 | User=root 13 | Group=daemon 14 | LimitCORE=infinity 15 | LimitNOFILE=100000 16 | LimitNPROC=60000 17 | ;LimitSTACK=240 18 | LimitRTPRIO=infinity 19 | LimitRTTIME=7000000 20 | IOSchedulingClass=realtime 21 | IOSchedulingPriority=2 22 | CPUSchedulingPolicy=rr 23 | CPUSchedulingPriority=89 24 | UMask=0007 25 | 26 | [Install] 27 | WantedBy=multi-user.target 28 | -------------------------------------------------------------------------------- /mediastack.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | /* 4 | * Kurento media Stack 5 | */ 6 | 7 | var kurento = require('kurento-client'); 8 | var transform = require('sdp-transform'); 9 | var config = require('./config'); 10 | 11 | 12 | 13 | var MediaStack = function () { 14 | MediaStack.id ="bob"; 15 | MediaStack.sessions = {}; 16 | MediaStack.sip = null; 17 | MediaStack.candidatesQueue = {}; 18 | MediaStack.kurentoClient = null; 19 | }; 20 | 21 | MediaStack.prototype.init = function (sip){ 22 | MediaStack.sip = sip; 23 | } 24 | 25 | MediaStack.prototype.start = function (sessionId, ws, from,to, sdpOffer, options,callback) { 26 | if (!sessionId) { 27 | return callback('Cannot use undefined sessionId'); 28 | } 29 | MediaStack.candidatesQueue[sessionId] = []; 30 | 31 | MediaStack.sessions[sessionId]={ 32 | 'ws': ws 33 | }; 34 | 35 | console.log(sessionId +"Concurent calls : " + Object.keys(MediaStack.sessions).length +"/"+ config.maxConcurentCalls + util.inspect(MediaStack.sessions) ); 36 | if(Object.keys(MediaStack.sessions).length > config.maxConcurentCalls){ 37 | 38 | return callback('Unable to start call due to server concurrent capacity limit'); 39 | } 40 | getKurentoClient(function(error, kurentoClient) { 41 | if (error) { 42 | return callback(error); 43 | } 44 | 45 | kurentoClient.create('MediaPipeline', function(error, pipeline) { 46 | 47 | if (error) { 48 | return callback(error); 49 | } 50 | 51 | createMediaElements(sessionId,pipeline,ws,from,to,options, function(error, webRtcEndpoint,rtpEndpoint) { 52 | if (error) { 53 | pipeline.release(); 54 | return callback(error); 55 | } 56 | console.log("Collect Candidates"); 57 | if (MediaStack.candidatesQueue[sessionId]) { 58 | while(MediaStack.candidatesQueue[sessionId].length) { 59 | var candidate = MediaStack.candidatesQueue[sessionId].shift(); 60 | webRtcEndpoint.addIceCandidate(candidate); 61 | } 62 | } 63 | console.log("connect media element"); 64 | connectMediaElements(webRtcEndpoint,rtpEndpoint, function(error) { 65 | if (error) { 66 | pipeline.release(); 67 | return callback(error); 68 | } 69 | 70 | webRtcEndpoint.on('OnIceCandidate', function(event) { 71 | var candidate = kurento.getComplexType('IceCandidate')(event.candidate); 72 | ws.send(JSON.stringify({ 73 | id : 'iceCandidate', 74 | candidate : candidate 75 | })); 76 | }); 77 | 78 | webRtcEndpoint.processOffer(sdpOffer, function(error, sdpAnswer) { 79 | console.log("Sdp Answer WebRTC Endpoint " + sdpAnswer); 80 | if (error) { 81 | pipeline.release(); 82 | return callback(error); 83 | } 84 | MediaStack.sessions[sessionId].pipeline = pipeline; 85 | MediaStack.sessions[sessionId].webRtcEndpoint = webRtcEndpoint; 86 | MediaStack.sessions[sessionId].rtpEndpoint = rtpEndpoint; 87 | return callback(null, sdpAnswer); 88 | }); 89 | 90 | webRtcEndpoint.gatherCandidates(function(error) { 91 | if (error) { 92 | return callback(error); 93 | } 94 | }); 95 | }); 96 | }); 97 | }); 98 | }); 99 | } 100 | 101 | // Recover kurentoClient for the first time. 102 | function getKurentoClient(callback) { 103 | console.log("Get Kurento Client "); 104 | if (MediaStack.kurentoClient) { 105 | console.log(" Kurento Client not null "); 106 | return callback(null, MediaStack.kurentoClient); 107 | } 108 | 109 | kurento(config.kurento.ws_uri, function(error, _kurentoClient) { 110 | if (error) { 111 | console.log("Could not find media server at address " + config.kurento.ws_uri); 112 | return callback("Could not find media server at address" + config.kurento.ws_uri 113 | + ". Exiting with error " + error); 114 | } 115 | MediaStack.kurentoClient = _kurentoClient; 116 | console.log(" Call Abck Kurento CLient "); 117 | callback(null, MediaStack.kurentoClient); 118 | }); 119 | } 120 | 121 | function getIPAddress() { 122 | return config.serverPublicIP; 123 | } 124 | 125 | function replace_ip(sdp, ip) { 126 | if (!ip) 127 | ip = getIPAddress(); 128 | console.log("IP " + ip); 129 | console.log("sdp init : "+sdp); 130 | 131 | var sdpObject = transform.parse(sdp); 132 | sdpObject.origin.address = ip; 133 | sdpObject.connection.ip = ip; 134 | var sdpResult = transform.write(sdpObject); 135 | console.log("sdp result : "+sdpResult); 136 | return sdpResult; 137 | } 138 | 139 | function mungleSDP(sdp){ 140 | mugleSdp = sdp; 141 | var mugleSdp = sdp.replace(new RegExp("RTP/AVPF", "g"), "RTP/AVP"); 142 | var h264Payload = MediaStack.sip.getH264Payload(sdp); 143 | mugleSdp+="a=fmtp:"+h264Payload+" profile-level-id=42801F\n"; 144 | return mugleSdp; 145 | } 146 | 147 | function mungleSDP2(sdp){ 148 | mugleSdp = sdp; 149 | var mugleSdp = sdp.replace(new RegExp("RTP/AVPF", "g"), "RTP/AVP"); 150 | var h264Payload = MediaStack.sip.getH264Payload(sdp); 151 | return mugleSdp; 152 | } 153 | 154 | function prettyJSON(obj) { 155 | console.log(JSON.stringify(obj, null, 2)); 156 | } 157 | 158 | function createMediaElements(sessionId,pipeline,ws,from,to,options, callback) { 159 | pipeline.create('WebRtcEndpoint', function(error, webRtcEndpoint) { 160 | if (error) { 161 | return callback(error); 162 | } 163 | 164 | pipeline.create('RtpEndpoint', function(error, rtpEndpoint){ 165 | if (error) { 166 | return callback(error); 167 | } 168 | createSipCall(sessionId,from+"@"+getIPAddress(),to,rtpEndpoint,options,function(error){ 169 | if (error) { 170 | return callback(error); 171 | } 172 | return callback(null, webRtcEndpoint, rtpEndpoint); 173 | }); 174 | }); 175 | }); 176 | } 177 | 178 | function connectMediaElements(webRtcEndpoint, rtpEndpoint,callback) { 179 | rtpEndpoint.connect(webRtcEndpoint, function(error) { 180 | if (error) { 181 | return callback(error); 182 | } 183 | webRtcEndpoint.connect(rtpEndpoint,function (error){ 184 | if (error) { 185 | return callback(error); 186 | } 187 | return callback(null); 188 | }); 189 | }); 190 | } 191 | 192 | 193 | function reConnectMediaElements(sessionId) { 194 | var webRtcEndpoint = MediaStack.sessions[sessionId].webRtcEndpoint; 195 | var rtpEndpoint = MediaStack.sessions[sessionId].rtpEndpoint; 196 | 197 | rtpEndpoint.connect(webRtcEndpoint, function(error) { 198 | if (!error) { 199 | webRtcEndpoint.connect(rtpEndpoint,function (error){ 200 | console.log("Reconnect Media "+sessionId); 201 | }); 202 | } 203 | /* 204 | if (MediaStack.sessions[sessionId].OldRtpEndpoint){ 205 | MediaStack.sessions[sessionId].OldRtpEndpoint.release(); 206 | MediaStack.sessions[sessionId].OldRtpEndpoint=null; 207 | }*/ 208 | }); 209 | } 210 | 211 | 212 | function createSipCall(sessionId,from,to,rtpEndpoint,options,callback){ 213 | rtpEndpoint.generateOffer(function(error, sdpOffer) { 214 | var modSdp = replace_ip(sdpOffer); 215 | modSdp = mungleSDP(modSdp); 216 | MediaStack.sip.invite (sessionId,from,to,modSdp,options,function (error,remoteSdp){ 217 | if (error){ 218 | return callback(error); 219 | } 220 | rtpEndpoint.processAnswer(remoteSdp,function(error){ 221 | if (error){ 222 | return callback(error); 223 | } 224 | // Insert EnCall timeout 225 | setTimeout(function(){ 226 | console.log("EndCall Timeout "+sessionId); 227 | MediaStack.sip.bye(sessionId); 228 | MediaStack.stopFromBye(sessionId); 229 | } 230 | ,config.maxCallSeconds*1000); 231 | return callback(null); 232 | }); 233 | }); 234 | }); 235 | } 236 | 237 | MediaStack.prototype.stop = function (sessionId) { 238 | MediaStack.sip.bye(sessionId); 239 | if (MediaStack.sessions[sessionId]) { 240 | var pipeline = MediaStack.sessions[sessionId].pipeline; 241 | if (pipeline != undefined){ 242 | console.info('Releasing pipeline'); 243 | pipeline.release(); 244 | } 245 | delete MediaStack.sessions[sessionId]; 246 | delete MediaStack.candidatesQueue[sessionId]; 247 | } 248 | } 249 | 250 | MediaStack.prototype.stopFromBye = function (sessionId) { 251 | if (MediaStack.sessions[sessionId]) { 252 | var ws = MediaStack.sessions[sessionId].ws; 253 | if (ws != undefined){ 254 | ws.send(JSON.stringify({ 255 | id : 'stopFromBye' 256 | })); 257 | } 258 | var pipeline = MediaStack.sessions[sessionId].pipeline; 259 | if (pipeline != undefined){ 260 | console.info('Releasing pipeline'); 261 | pipeline.release(); 262 | } 263 | delete MediaStack.sessions[sessionId]; 264 | delete MediaStack.candidatesQueue[sessionId]; 265 | } 266 | } 267 | 268 | MediaStack.prototype.onIceCandidate = function (sessionId, _candidate) { 269 | var candidate = kurento.getComplexType('IceCandidate')(_candidate); 270 | if (MediaStack.sessions[sessionId]!=undefined && MediaStack.sessions[sessionId].webRtcEndpoint!=undefined) { 271 | console.info('Sending candidate'); 272 | var webRtcEndpoint = MediaStack.sessions[sessionId].webRtcEndpoint; 273 | webRtcEndpoint.addIceCandidate(candidate); 274 | } 275 | else { 276 | console.info('Queueing candidate'); 277 | if (!MediaStack.candidatesQueue[sessionId]) { 278 | MediaStack.candidatesQueue[sessionId] = []; 279 | } 280 | MediaStack.candidatesQueue[sessionId].push(candidate); 281 | } 282 | } 283 | 284 | MediaStack.prototype.sendDtmf = function (sessionId, dtmf){ 285 | MediaStack.sip.infoDtmf(sessionId,dtmf); 286 | // reConnectMediaElements(sessionId); 287 | } 288 | 289 | MediaStack.prototype.reconnect = function (sessionId){ 290 | reConnectMediaElements(sessionId); 291 | } 292 | 293 | MediaStack.prototype.renegotiateWebRTC = function (sessionId,callback){ 294 | if (MediaStack.sessions[sessionId] && MediaStack.sessions[sessionId].pipeline){ 295 | var pipeline = MediaStack.sessions[sessionId].pipeline; 296 | MediaStack.sessions[sessionId].webRtcEndpoint.release(); 297 | MediaStack.sessions[sessionId].renegotiated=true; 298 | MediaStack.candidatesQueue[sessionId]=[]; 299 | 300 | pipeline.create('WebRtcEndpoint', function(error, webRtcEndpoint){ 301 | if (error) { 302 | return callback(error); 303 | } 304 | MediaStack.sessions[sessionId].webRtcEndpoint = webRtcEndpoint; 305 | webRtcEndpoint.generateOffer(function(error,sdpOffer) { 306 | if (error){ 307 | console.log("SdpOffer not accepted by kurento"); 308 | console.log(error); 309 | return callback(error); 310 | } 311 | var ws = MediaStack.sessions[sessionId].ws; 312 | if (ws != undefined){ 313 | ws.send(JSON.stringify({ 314 | id : 'renegotiateWebRTC', 315 | sdp : sdpOffer 316 | })); 317 | return callback(); 318 | } 319 | }); 320 | }); 321 | }; 322 | } 323 | 324 | MediaStack.prototype.renegotiateResponse = function (sessionId,sdp){ 325 | if (MediaStack.sessions[sessionId] && MediaStack.sessions[sessionId].pipeline && MediaStack.sessions[sessionId].webRtcEndpoint){ 326 | var webRtcEndpoint = MediaStack.sessions[sessionId].webRtcEndpoint; 327 | var pipeline = MediaStack.sessions[sessionId].pipeline; 328 | console.log("Collect Candidates"); 329 | if (MediaStack.candidatesQueue[sessionId]) { 330 | while(MediaStack.candidatesQueue[sessionId].length) { 331 | var candidate = MediaStack.candidatesQueue[sessionId].shift(); 332 | webRtcEndpoint.addIceCandidate(candidate); 333 | } 334 | } 335 | 336 | var ws = MediaStack.sessions[sessionId].ws; 337 | webRtcEndpoint.on('OnIceCandidate', function(event) { 338 | var candidate = kurento.getComplexType('IceCandidate')(event.candidate); 339 | ws.send(JSON.stringify({ 340 | id : 'iceCandidate', 341 | candidate : candidate 342 | })); 343 | }); 344 | 345 | webRtcEndpoint.processAnswer(sdp, function(error) { 346 | MediaStack.sessions[sessionId].renegotiated=false; 347 | if (error) { 348 | pipeline.release(); 349 | console.log("ProcessAnswer Error"+error); 350 | } 351 | MediaStack.sip.reponseToReInvite(sessionId); 352 | // reConnectMediaElements(sessionId); 353 | return; 354 | }); 355 | 356 | webRtcEndpoint.gatherCandidates(function(error) { 357 | if (error) { 358 | console.log("gatherCandidates Error"+error); 359 | } 360 | }); 361 | } 362 | } 363 | 364 | function disconnectElement (webRtcEndpoint,rtpEndpoint,callback){ 365 | rtpEndpoint.disconnect(webRtcEndpoint, function(error) { 366 | if (error) 367 | callback(error) 368 | webRtcEndpoint.disconnect(rtpEndpoint,function (error){ 369 | if (error) 370 | callback(error); 371 | 372 | rtpEndpoint.release((error)=>{ 373 | if (error) 374 | callback(error); 375 | callback(null); 376 | }); 377 | }); 378 | }); 379 | } 380 | 381 | MediaStack.prototype.renegotiateRTP = function (sessionId, remoteSdp,callback){ 382 | if (MediaStack.sessions[sessionId] && MediaStack.sessions[sessionId].pipeline){ 383 | var pipeline = MediaStack.sessions[sessionId].pipeline; 384 | disconnectElement(MediaStack.sessions[sessionId].webRtcEndpoint,MediaStack.sessions[sessionId].rtpEndpoint,function(){ 385 | 386 | //MediaStack.sessions[sessionId].rtpEndpoint.release(); 387 | pipeline.create('RtpEndpoint', function(error, rtpEndpoint){ 388 | if (error) { 389 | return callback(error); 390 | } 391 | rtpEndpoint.processOffer(remoteSdp,function(error,sdpOffer) { 392 | if (error){ 393 | console.log("SdpOffer not accepted by kurento"); 394 | console.log(error); 395 | return callback(error); 396 | } 397 | var modSdp = replace_ip(sdpOffer); 398 | modSdp = mungleSDP2(modSdp); 399 | 400 | MediaStack.sessions[sessionId].rtpEndpoint = rtpEndpoint; 401 | return callback(null,modSdp); 402 | }); 403 | }); 404 | }); 405 | } 406 | } 407 | 408 | 409 | module.exports = new MediaStack(); 410 | -------------------------------------------------------------------------------- /my-kurento-sip-gw.service: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/my-kurento-sip-gw.service -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kurento-SIP-GW", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "postinstall": "cd static && bower install" 7 | }, 8 | "dependencies": { 9 | "cookie-parser": "^1.3.5", 10 | "express": "~4.12.4", 11 | "express-session": "1.17.0", 12 | "minimist": "^1.1.1", 13 | "ws": "3.3.1", 14 | "kurento-client": "Kurento/kurento-client-js#master", 15 | "drachtio": "4.1.16", 16 | "sdp-transform": "*", 17 | "tree-kill": "*", 18 | "uuid": "*" 19 | }, 20 | "devDependencies": { 21 | "bower": "^1.4.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | vagrant up 2 | pause 3 | -------------------------------------------------------------------------------- /runDrachtio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./drachtio-server/build/drachtio -f drachtio.conf.xml -l info & 4 | 5 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * (C) Copyright 2014-2015 Kurento (http://kurento.org/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | var path = require('path'); 19 | var url = require('url'); 20 | var cookieParser = require('cookie-parser') 21 | var express = require('express'); 22 | var session = require('express-session') 23 | var minimist = require('minimist'); 24 | var ws = require('ws'); 25 | var fs = require('fs'); 26 | var https = require('https'); 27 | var sip = require('./sipstack.js'); 28 | var media = require('./mediastack.js'); 29 | var config = require('./config'); 30 | //var uuid = require('uuid/v4'); 31 | const { 32 | v1: uuidv1, 33 | v4: uuidv4, 34 | } = require('uuid'); 35 | 36 | var util = require('util'); 37 | 38 | var options = 39 | { 40 | key: fs.readFileSync('keys/server.key'), 41 | cert: fs.readFileSync('keys/server.crt') 42 | }; 43 | 44 | var app = express(); 45 | 46 | /* 47 | * Management of sessions 48 | */ 49 | app.use(cookieParser()); 50 | 51 | var sessionHandler = session({ 52 | secret : 'none', 53 | rolling : true, 54 | resave : true, 55 | saveUninitialized : true 56 | }); 57 | 58 | app.use(sessionHandler); 59 | 60 | /* 61 | * Server startup 62 | */ 63 | var asUrl = url.parse(config.kurento.as_uri); 64 | var port = asUrl.port; 65 | var server = https.createServer(options, app).listen(port, function() { 66 | console.log('Kurento SIP GW'); 67 | console.log('Open ' + url.format(asUrl) + ' with a WebRTC capable browser'); 68 | console.log('Call timeout ' + config.maxCallSeconds + ' seconds'); 69 | console.log('Maximum concurent calls ' + config.maxConcurentCalls); 70 | }); 71 | 72 | 73 | function kurentoPipelineRelease(sessionId){ 74 | console.log('Stop session ID '+sessionId); 75 | media.stopFromBye(sessionId); 76 | } 77 | 78 | sip.init(kurentoPipelineRelease); 79 | media.init(sip); 80 | sip.setMediaSatck(media); 81 | 82 | 83 | 84 | 85 | var wss = new ws.Server({ 86 | server : server, 87 | path : '/sip-gw' 88 | }); 89 | 90 | /* 91 | * Management of WebSocket messages 92 | */ 93 | wss.on('connection', function(ws,req) { 94 | var sessionId = null; 95 | var request = req; 96 | var response = { 97 | writeHead : {} 98 | }; 99 | 100 | sessionHandler(request, response, function(err) { 101 | sessionId = uuidv1(); 102 | console.log('Connection received with sessionId ' + sessionId); 103 | }); 104 | 105 | ws.on('error', function(error) { 106 | console.log('Connection ' + sessionId + ' error'); 107 | media.stop(sessionId); 108 | }); 109 | 110 | ws.on('close', function() { 111 | console.log('Connection ' + sessionId + ' closed'); 112 | media.stop(sessionId); 113 | }); 114 | 115 | ws.on('message', function(_message) { 116 | var message = JSON.parse(_message); 117 | console.log('Connection ' + sessionId + ' received message ', message); 118 | switch (message.id) { 119 | case 'start': 120 | media.start(sessionId, ws, message.from,message.to,message.sdpOffer, message.options, function(error, sdpAnswer) { 121 | if (error) { 122 | return ws.send(JSON.stringify({ 123 | id : 'error', 124 | message : error 125 | })); 126 | } 127 | ws.send(JSON.stringify({ 128 | id : 'startResponse', 129 | sdpAnswer : sdpAnswer 130 | })); 131 | }); 132 | break; 133 | case 'stop': 134 | media.stop(sessionId); 135 | break; 136 | case 'onIceCandidate': 137 | media.onIceCandidate(sessionId, message.candidate); 138 | break; 139 | case 'sendDtmf': 140 | media.sendDtmf(sessionId, message.dtmf); 141 | break; 142 | case 'renegotiateResponse': 143 | media.renegotiateResponse(sessionId,message.answerSdp); 144 | break; 145 | default: 146 | ws.send(JSON.stringify({ 147 | id : 'error', 148 | message : 'Invalid message ' + message 149 | })); 150 | break; 151 | } 152 | }); 153 | }); 154 | 155 | app.use(express.static(path.join(__dirname, 'static'))); 156 | -------------------------------------------------------------------------------- /sipstack.js: -------------------------------------------------------------------------------- 1 | var drachtio = require('drachtio'); 2 | var appSip = drachtio() ; 3 | var debug = require('debug')('basic') ; 4 | var transform = require('sdp-transform'); 5 | var config = require('./config'); 6 | 7 | function getConnectionIp (sdpJson){ 8 | return sdpJson.connection.ip; 9 | } 10 | 11 | function getPortByType (sdpJson,type){ 12 | var media = sdpJson.media; 13 | for (var i = 0 ; i < media.length ; i++){ 14 | if (media[i].type == type){ 15 | return media[i].port; 16 | } 17 | } 18 | return 0; 19 | } 20 | 21 | 22 | var SipStack = function () {}; 23 | SipStack.appSip = drachtio(); 24 | SipStack.dialogs = {}; 25 | SipStack.res = {}; 26 | SipStack.sessionIds = {}; 27 | SipStack.sessionIdByCalls = {}; 28 | SipStack.requests = {}; 29 | SipStack.localSDP = {}; 30 | SipStack.options = {}; 31 | SipStack.mediastack = null; 32 | SipStack.prototype.log = function () { 33 | console.log('doo!'); 34 | } 35 | 36 | SipStack.kurentoPipelineRelease = function (sessionId) { 37 | console.log('Bad callback end') ; 38 | 39 | }; 40 | 41 | SipStack.appSip.use('bye', function( req, res){ 42 | console.log('BYE recieved: %s', JSON.stringify(res) ) ; 43 | var callId = res.msg.headers['call-id']; 44 | console.log('BYE recieved: %s index %s' , JSON.stringify(res),callId ) ; 45 | // get sessionId 46 | var sessionId = SipStack.sessionIdByCalls[callId]; 47 | SipStack.kurentoPipelineRelease(sessionId); 48 | var dialog = SipStack.dialogs[sessionId]; 49 | 50 | delete SipStack.sessionIds[dialog]; 51 | delete SipStack.dialogs[sessionId]; 52 | delete SipStack.sessionIdByCalls[callId]; 53 | delete SipStack.localSDP[sessionId]; 54 | res.send(200) ; 55 | }) ; 56 | 57 | SipStack.appSip.use('invite', function( req, res){ 58 | console.log('INviTE recieved: %s', JSON.stringify(res) ) ; 59 | 60 | console.log(req.body); 61 | var callId = res.msg.headers['call-id']; 62 | // console.log('INVITE recieved: %s index %s' , JSON.stringify(req),callId ) ; 63 | // get sessionId 64 | 65 | 66 | var sessionId = SipStack.sessionIdByCalls[callId]; 67 | if (sessionId){ 68 | var remoteSdpStr = req.body; 69 | var dialog = SipStack.dialogs[sessionId]; 70 | var options = SipStack.options[sessionId]; 71 | if (options.renegotiate == "none"){ 72 | res.send( 200,{ 73 | body: SipStack.localSDP[sessionId] 74 | }); 75 | } 76 | else if (options.renegotiate == "rtp-webrtc"){ 77 | SipStack.mediastack.renegotiateRTP(sessionId,remoteSdpStr,function(err,newLocalSdp){ 78 | console.log("RTP renegotiated"); 79 | if (err) { 80 | console.log(err); 81 | return; 82 | } 83 | SipStack.res[sessionId] = res; 84 | SipStack.localSDP[sessionId] = newLocalSdp; 85 | SipStack.mediastack.renegotiateWebRTC(sessionId,function(err){ 86 | console.log("WebRTC renegotiated"); 87 | }); 88 | }); 89 | } 90 | else if (!options || !options.renegotiate || options.renegotiate == "rtp"){ // Default renegotiate only RTP 91 | SipStack.mediastack.renegotiateRTP(sessionId,remoteSdpStr,function(err,newLocalSdp){ 92 | console.log("RTP renegotiated"); 93 | if (err) { 94 | console.log(err); 95 | return; 96 | } 97 | SipStack.localSDP[sessionId] = newLocalSdp; 98 | res.send( 200,{ 99 | body: SipStack.localSDP[sessionId] 100 | },function(){ 101 | setTimeout( () => {SipStack.mediastack.reconnect(sessionId);},500); 102 | }); 103 | }); 104 | } 105 | } 106 | else 107 | res.send(404) ; 108 | }); 109 | 110 | 111 | SipStack.prototype.reponseToReInvite = function (sessionId){ 112 | if (SipStack.res[sessionId]){ 113 | console.log("Send 200 OK"+SipStack.localSDP[sessionId]); 114 | SipStack.res[sessionId].send( 200,{ 115 | body: SipStack.localSDP[sessionId]},function(){ 116 | //SipStack.mediastack.reconnect(sessionId); 117 | setTimeout( () => {SipStack.mediastack.reconnect(sessionId);},500); 118 | }); 119 | } 120 | } 121 | 122 | SipStack.prototype.init = function (callback){ 123 | SipStack.sipServerConnected = false; 124 | SipStack.kurentoPipelineRelease = callback; 125 | SipStack.sessions = {}; 126 | SipStack.appSip.connect(config.drachtio, 127 | function(err, hostport){ 128 | if( err ) throw err ; 129 | console.log("Connected to SIP server"); 130 | sipServerConnected = true; 131 | } 132 | ) ; 133 | } 134 | 135 | SipStack.prototype.invite = function (sessionId,from,to,localSDP,options,callback){ 136 | 137 | 138 | console.log('Mediastack in sip id=' + SipStack.mediastack.id); 139 | 140 | var sipDest = to; 141 | console.log("Send invite to "+ sipDest); 142 | console.log("Send local SDP to "+ localSDP); 143 | console.log("Invite options"+ options); 144 | 145 | 146 | if (sipDest.indexOf("sip:")!=0) 147 | sipDest="sip:"+sipDest; 148 | if (from.indexOf("sip:")!=0) 149 | from="sip:"+from; 150 | 151 | if (sipServerConnected){ 152 | SipStack.appSip.request({ 153 | uri: sipDest, 154 | method: 'INVITE', 155 | headers: { 156 | 'User-Agent': 'dracht.io', 157 | 'from': from 158 | }, 159 | body: localSDP 160 | }, 161 | function( err, req ){ 162 | if( err ) { 163 | console.log('Error '+ err ) ; 164 | return callback("reject",null); 165 | } 166 | console.log('sent request: %s', JSON.stringify(req) ) ; 167 | 168 | req.on('response', function(res,ack){ 169 | console.log('received response with status: %s', res.status) ; 170 | console.log('recieved response: %s', JSON.stringify(res) ) ; 171 | 172 | if( res.status == 200) { 173 | var remoteSdpStr = res.body; 174 | remoteSdpStr+="a=ptime:20"; 175 | var h264Payload = getH264Payload(remoteSdpStr); 176 | remoteSdpStr = remoteSdpStr.replace(new RegExp("a=rtcp-fb:\\*", "g"), "a=rtcp-fb:"+h264Payload); 177 | console.log('recieved response: %s', remoteSdpStr); 178 | console.log(req.msg.headers); 179 | console.log('dialogId recv: ', res.stackDialogId); 180 | SipStack.dialogs[sessionId] = res.stackDialogId ; 181 | SipStack.localSDP[sessionId] = localSDP; 182 | SipStack.options[sessionId] = options; 183 | SipStack.sessionIds[res.stackDialogId] = sessionId; 184 | var callId = req.msg.headers["call-id"]; 185 | SipStack.sessionIdByCalls[callId] = sessionId; 186 | delete SipStack.requests[sessionId]; 187 | ack() ; 188 | console.log("Ack sended"); 189 | return callback(null,remoteSdpStr); 190 | } 191 | 192 | else if (res.status >=300){ 193 | console.log('Invite rejected'); 194 | SipStack.kurentoPipelineRelease(sessionId); 195 | return callback("reject",null); 196 | } 197 | else if (res.status < 200){ 198 | console.log('Intermediate repsonse'); 199 | SipStack.requests[sessionId] = req ; 200 | } 201 | 202 | }) ; 203 | } 204 | ); 205 | return ""; 206 | } 207 | return ""; 208 | }; 209 | 210 | SipStack.prototype.bye = function (sessionId,sdpLocal) { 211 | 212 | if (SipStack.requests[sessionId]!=undefined){ 213 | SipStack.requests[sessionId].cancel(); 214 | } 215 | else { 216 | var dialog = SipStack.dialogs[sessionId]; 217 | if (dialog != undefined){ 218 | SipStack.appSip.request({ 219 | method: 'BYE', 220 | stackDialogId: dialog 221 | }, function(err, req){ 222 | if( err || req == undefined) { 223 | console.log(err); 224 | return; 225 | } 226 | var callId = req.msg.headers["call-id"]; 227 | delete SipStack.sessionIds[dialog]; 228 | delete SipStack.dialogs[sessionId]; 229 | delete SipStack.sessionIdByCalls[callId]; 230 | delete SipStack.localSDP[sessionId]; 231 | delete SipStack.options[sessionId]; 232 | req.on('response', function(response){ 233 | console.log('BYE '+response.status); 234 | }); 235 | }) ; 236 | } 237 | } 238 | }; 239 | 240 | SipStack.prototype.infoDtmf = function (sessionId,dtmf) { 241 | 242 | var dialog = SipStack.dialogs[sessionId]; 243 | if (dialog != undefined){ 244 | var messageBody="Signal="+dtmf+"\r\nDuration=160\r\n"; 245 | SipStack.appSip.request({ 246 | method: 'INFO', 247 | stackDialogId: dialog, 248 | headers: { 249 | 'User-Agent': 'dracht.io', 250 | 'Content-Type': 'application/dtmf-relay' 251 | }, 252 | body: messageBody 253 | }, function(err, req){ 254 | 255 | if(req!=undefined) 256 | console.log('sent request: %s', JSON.stringify(req) ) ; 257 | 258 | 259 | if( err || req == undefined) { 260 | console.log(err); 261 | console.log(req); 262 | return; 263 | } 264 | req.on('response', function(response){ 265 | console.log('INFO '+response.status); 266 | }); 267 | }) ; 268 | } 269 | }; 270 | 271 | 272 | SipStack.prototype.response = function (sessionId) { 273 | 274 | }; 275 | 276 | SipStack.prototype.registerWebRTCEndpoint = function (from) { 277 | 278 | }; 279 | 280 | SipStack.prototype.registerSIP = function (from,host,password) { 281 | 282 | }; 283 | 284 | function getH264Payload(sdp){ 285 | var sdpObject = transform.parse(sdp); 286 | // if (sdpObject.media.length < 2) 287 | // return 0; 288 | console.log("RTP lenght"+sdpObject.media[1].rtp.length); 289 | var newRTP = []; 290 | for (var i=0; i < sdpObject.media[1].rtp.length ; i++){ 291 | console.log("Media 1 Codec : "+sdpObject.media[1].rtp[i].codec); 292 | if (sdpObject.media[1].rtp[i].codec=="H264"){ 293 | newRTP.push(sdpObject.media[1].rtp[i]); 294 | break; 295 | } 296 | } 297 | var h264Payload=0; 298 | if (newRTP.length > 0) 299 | h264Payload = newRTP[0].payload; 300 | 301 | return h264Payload; 302 | } 303 | 304 | 305 | SipStack.prototype.setMediaSatck = function (media){ 306 | SipStack.mediastack = media; 307 | } 308 | 309 | 310 | /*Asuming a classic sdp with media[1] as the only video media*/ 311 | SipStack.prototype.getH264Payload = getH264Payload; 312 | 313 | module.exports = new SipStack(); 314 | -------------------------------------------------------------------------------- /ssh.bat: -------------------------------------------------------------------------------- 1 | vagrant ssh 2 | pause 3 | -------------------------------------------------------------------------------- /static/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kurento-hello-world", 3 | "description": "Kurento Browser JavaScript Tutorial", 4 | "authors": [ 5 | "Kurento " 6 | ], 7 | "main": "index.html", 8 | "moduleType": [ 9 | "globals" 10 | ], 11 | "license": "ALv2", 12 | "homepage": "http://www.kurento.org/", 13 | "private": true, 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "test", 19 | "tests" 20 | ], 21 | "dependencies": { 22 | "adapter.js": "v0.2.9", 23 | "bootstrap": "~3.3.0", 24 | "ekko-lightbox": "~3.3.0", 25 | "demo-console": "1.5.1", 26 | "kurento-utils": "master" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /static/css/kurento.css: -------------------------------------------------------------------------------- 1 | /* 2 | * (C) Copyright 2014-2015 Kurento (http://kurento.org/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | @CHARSET "UTF-8"; 18 | 19 | html { 20 | position: relative; 21 | min-height: 100%; 22 | } 23 | 24 | body { 25 | body 26 | } 27 | 28 | video,#console { 29 | display: block; 30 | font-size: 14px; 31 | line-height: 1.42857143; 32 | color: #555; 33 | background-color: #fff; 34 | background-image: none; 35 | border: 1px solid #525252; 36 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); 37 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); 38 | -webkit-transition: border-color ease-in-out .15s, box-shadow 39 | ease-in-out .15s; 40 | transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; 41 | } 42 | 43 | #console { 44 | min-height: 120px; 45 | max-height: 360px; 46 | } 47 | 48 | .col-md-2 { 49 | width: 80px; 50 | padding-top: 190px; 51 | } 52 | -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | *, *:before, *:after { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | html, body { 8 | font-size: 62.5%; 9 | height: 100%; 10 | overflow: hidden; 11 | } 12 | @media (max-width: 768px) { 13 | html, body { 14 | font-size: 50%; 15 | } 16 | } 17 | 18 | svg { 19 | display: inline-block; 20 | width: 2rem; 21 | height: 2rem; 22 | overflow: visible; 23 | } 24 | 25 | .svg-icon { 26 | cursor: pointer; 27 | } 28 | .svg-icon path { 29 | stroke: rgba(255, 255, 255, 0.9); 30 | fill: none; 31 | stroke-width: 1; 32 | } 33 | 34 | input, button { 35 | outline: none; 36 | border: none; 37 | } 38 | 39 | .cont { 40 | position: relative; 41 | height: 100%; 42 | background-image: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/142996/slider-2.jpg"); 43 | background-size: cover; 44 | overflow: auto; 45 | font-family: "Open Sans", Helvetica, Arial, sans-serif; 46 | } 47 | 48 | .demo { 49 | position: absolute; 50 | top: 50%; 51 | left: 50%; 52 | margin-left: -15rem; 53 | margin-top: -26.5rem; 54 | width: 30rem; 55 | height: 53rem; 56 | overflow: hidden; 57 | } 58 | 59 | .demo_app { 60 | position: absolute; 61 | top: 0%; 62 | left: 2%; 63 | width: 96%; 64 | height: 100%; 65 | overflow: hidden; 66 | } 67 | 68 | .login { 69 | position: relative; 70 | height: 100%; 71 | background: -webkit-linear-gradient(top, rgba(146, 135, 187, 0.8) 0%, rgba(0, 0, 0, 0.6) 100%); 72 | background: linear-gradient(to bottom, rgba(146, 135, 187, 0.8) 0%, rgba(0, 0, 0, 0.6) 100%); 73 | -webkit-transition: opacity 0.1s, -webkit-transform 0.3s cubic-bezier(0.17, -0.65, 0.665, 1.25); 74 | transition: opacity 0.1s, -webkit-transform 0.3s cubic-bezier(0.17, -0.65, 0.665, 1.25); 75 | transition: opacity 0.1s, transform 0.3s cubic-bezier(0.17, -0.65, 0.665, 1.25); 76 | transition: opacity 0.1s, transform 0.3s cubic-bezier(0.17, -0.65, 0.665, 1.25), -webkit-transform 0.3s cubic-bezier(0.17, -0.65, 0.665, 1.25); 77 | -webkit-transform: scale(1); 78 | transform: scale(1); 79 | } 80 | .login.inactive { 81 | opacity: 0; 82 | -webkit-transform: scale(1.1); 83 | transform: scale(1.1); 84 | } 85 | .login__check { 86 | position: absolute; 87 | top: 10%; 88 | left: 5%; 89 | width: 90%; 90 | } 91 | 92 | .title{ 93 | color: #ffffff; 94 | font-family: 'Raleway',sans-serif; 95 | font-size: 45px; 96 | font-weight: 800; 97 | line-height: 72px; 98 | position: absolute; 99 | top: 20%; 100 | text-align: center; 101 | text-transform: uppercase; 102 | } 103 | 104 | .login__form { 105 | position: absolute; 106 | top: 50%; 107 | left: 0; 108 | width: 100%; 109 | height: 50%; 110 | padding: 1.5rem 2.5rem; 111 | text-align: center; 112 | } 113 | .login__row { 114 | height: 5rem; 115 | padding-top: 1rem; 116 | border-bottom: 1px solid rgba(255, 255, 255, 0.2); 117 | } 118 | .login__icon { 119 | margin-bottom: -0.4rem; 120 | margin-right: 0.5rem; 121 | } 122 | .login__icon.name path { 123 | stroke-dasharray: 73.50196075439453; 124 | stroke-dashoffset: 73.50196075439453; 125 | -webkit-animation: animatePath 2s 0.5s forwards; 126 | animation: animatePath 2s 0.5s forwards; 127 | } 128 | .login__icon.pass path { 129 | stroke-dasharray: 92.10662841796875; 130 | stroke-dashoffset: 92.10662841796875; 131 | -webkit-animation: animatePath 2s 0.5s forwards; 132 | animation: animatePath 2s 0.5s forwards; 133 | } 134 | .login__input { 135 | display: inline-block; 136 | width: 22rem; 137 | height: 100%; 138 | padding-left: 1.5rem; 139 | font-size: 1.5rem; 140 | background: transparent; 141 | color: #FDFCFD; 142 | } 143 | .login__submit { 144 | position: relative; 145 | width: 100%; 146 | height: 4rem; 147 | margin: 5rem 0 2.2rem; 148 | color: rgba(255, 255, 255, 0.8); 149 | background: #FF3366; 150 | font-size: 1.5rem; 151 | border-radius: 3rem; 152 | cursor: pointer; 153 | overflow: hidden; 154 | -webkit-transition: width 0.3s 0.15s, font-size 0.1s 0.15s; 155 | transition: width 0.3s 0.15s, font-size 0.1s 0.15s; 156 | } 157 | .login__submit:after { 158 | content: ""; 159 | position: absolute; 160 | top: 50%; 161 | left: 50%; 162 | margin-left: -1.5rem; 163 | margin-top: -1.5rem; 164 | width: 3rem; 165 | height: 3rem; 166 | border: 2px dotted #fff; 167 | border-radius: 50%; 168 | border-left: none; 169 | border-bottom: none; 170 | -webkit-transition: opacity 0.1s 0.4s; 171 | transition: opacity 0.1s 0.4s; 172 | opacity: 0; 173 | } 174 | .login__submit.processing { 175 | width: 4rem; 176 | font-size: 0; 177 | } 178 | .login__submit.processing:after { 179 | opacity: 1; 180 | -webkit-animation: rotate 0.5s 0.4s infinite linear; 181 | animation: rotate 0.5s 0.4s infinite linear; 182 | } 183 | .login__submit.success { 184 | -webkit-transition: opacity 0.1s 0.3s, background-color 0.1s 0.3s, -webkit-transform 0.3s 0.1s ease-out; 185 | transition: opacity 0.1s 0.3s, background-color 0.1s 0.3s, -webkit-transform 0.3s 0.1s ease-out; 186 | transition: transform 0.3s 0.1s ease-out, opacity 0.1s 0.3s, background-color 0.1s 0.3s; 187 | transition: transform 0.3s 0.1s ease-out, opacity 0.1s 0.3s, background-color 0.1s 0.3s, -webkit-transform 0.3s 0.1s ease-out; 188 | -webkit-transform: scale(30); 189 | transform: scale(30); 190 | opacity: 0.9; 191 | } 192 | .login__submit.success:after { 193 | -webkit-transition: opacity 0.1s 0s; 194 | transition: opacity 0.1s 0s; 195 | opacity: 0; 196 | -webkit-animation: none; 197 | animation: none; 198 | } 199 | .login__signup { 200 | font-size: 1.2rem; 201 | color: #ABA8AE; 202 | } 203 | .login__signup a { 204 | color: #fff; 205 | cursor: pointer; 206 | } 207 | 208 | .app { 209 | position: absolute; 210 | top: 0; 211 | left: 0; 212 | width: 100%; 213 | height: 100%; 214 | opacity: 0; 215 | display: none; 216 | -webkit-transition: opacity 0.1s, -webkit-transform 0.3s cubic-bezier(0.68, -0.45, 0.465, 1.25); 217 | transition: opacity 0.1s, -webkit-transform 0.3s cubic-bezier(0.68, -0.45, 0.465, 1.25); 218 | transition: opacity 0.1s, transform 0.3s cubic-bezier(0.68, -0.45, 0.465, 1.25); 219 | transition: opacity 0.1s, transform 0.3s cubic-bezier(0.68, -0.45, 0.465, 1.25), -webkit-transform 0.3s cubic-bezier(0.68, -0.45, 0.465, 1.25); 220 | -webkit-transform: scale(1.2); 221 | transform: scale(1.2); 222 | } 223 | .app.active { 224 | opacity: 1; 225 | -webkit-transform: scale(1); 226 | transform: scale(1); 227 | } 228 | .app.active .app__user-photo { 229 | -webkit-transform: scale(1); 230 | transform: scale(1); 231 | } 232 | .app.active .app__meeting { 233 | -webkit-transform: translateY(0); 234 | transform: translateY(0); 235 | opacity: 1; 236 | } 237 | .app.active .app__logout { 238 | -webkit-transform: scale(1); 239 | transform: scale(1); 240 | } 241 | 242 | .app.active .mic { 243 | -webkit-transform: scale(1); 244 | transform: scale(1); 245 | } 246 | 247 | .app.active .video { 248 | -webkit-transform: scale(1); 249 | transform: scale(1); 250 | } 251 | 252 | .app__top { 253 | position: relative; 254 | height: 100%; 255 | background: rgba(0, 0, 0, 0.5); 256 | padding: 6rem 1.5rem 2rem; 257 | text-align: center; 258 | } 259 | 260 | .main_display { 261 | position: absolute; 262 | height: 100%; 263 | width: 85%; 264 | top : 0; 265 | } 266 | 267 | .app__video_remote { 268 | position: absolute; 269 | height: 99%; 270 | width: 100%; 271 | top : 0.5%; 272 | background: #0000; 273 | } 274 | 275 | .small_display{ 276 | position: absolute; 277 | width: 10%; 278 | bottom: 5%; 279 | right: 64px; 280 | z-index: 2; 281 | } 282 | 283 | .app_video_local_muted{ 284 | margin: 0 0 10px; 285 | font-size: 2vw; 286 | color: white; 287 | z-index: 3; 288 | top: 10%; 289 | width: 100%; 290 | position: absolute; 291 | display: none; 292 | } 293 | 294 | .app__video_local { 295 | background: #0000; 296 | width: 100%; 297 | } 298 | 299 | .btndtmf { 300 | 301 | } 302 | 303 | .app__dialpad { 304 | position: absolute; 305 | z-index: 2; 306 | background: rgba(25, 25, 25, .5);; 307 | } 308 | 309 | .dialpad { 310 | position: absolute; 311 | height: 20%; 312 | top: 10%; 313 | right: 120px; 314 | } 315 | 316 | .app__logout { 317 | position: absolute; 318 | z-index: 3; 319 | bottom: 3.3rem; 320 | right: 3.3rem; 321 | width: 4.6rem; 322 | height: 4.6rem; 323 | margin-right: -2.3rem; 324 | margin-bottom: -2.3rem; 325 | background: #FC3768; 326 | color: #fff; 327 | font-size: 2rem; 328 | border-radius: 50%; 329 | cursor: pointer; 330 | -webkit-transition: bottom 0.4s 0.1s, right 0.4s 0.1s, opacity 0.1s 0.7s, background-color 0.1s 0.7s, -webkit-transform 0.4s 0.4s; 331 | transition: bottom 0.4s 0.1s, right 0.4s 0.1s, opacity 0.1s 0.7s, background-color 0.1s 0.7s, -webkit-transform 0.4s 0.4s; 332 | transition: bottom 0.4s 0.1s, right 0.4s 0.1s, transform 0.4s 0.4s, opacity 0.1s 0.7s, background-color 0.1s 0.7s; 333 | transition: bottom 0.4s 0.1s, right 0.4s 0.1s, transform 0.4s 0.4s, opacity 0.1s 0.7s, background-color 0.1s 0.7s, -webkit-transform 0.4s 0.4s; 334 | -webkit-transform: scale(0); 335 | transform: scale(0); 336 | } 337 | .app__logout.clicked { 338 | bottom: 50%; 339 | right: 50%; 340 | -webkit-transform: scale(30) !important; 341 | transform: scale(30) !important; 342 | opacity: 0.9; 343 | } 344 | .app__logout.clicked svg { 345 | opacity: 0; 346 | } 347 | .app__logout-icon { 348 | position: absolute; 349 | width: 3rem; 350 | height: 3rem; 351 | top: 50%; 352 | left: 50%; 353 | margin-left: -1.5rem; 354 | margin-top: -1.5rem; 355 | -webkit-transition: opacity 0.1s; 356 | transition: opacity 0.1s; 357 | } 358 | .app__logout-icon path { 359 | stroke-width: 2px; 360 | stroke-dasharray: 64.36235046386719; 361 | stroke-dashoffset: 64.36235046386719; 362 | -webkit-animation: animatePath 0.5s 0.5s forwards; 363 | animation: animatePath 0.5s 0.5s forwards; 364 | } 365 | 366 | .mic { 367 | position: absolute; 368 | z-index: 3; 369 | bottom: 14.5rem; 370 | right: 3.3rem; 371 | width: 4.6rem; 372 | height: 4.6rem; 373 | margin-right: -2.3rem; 374 | margin-bottom: -2.3rem; 375 | background: #9C9C9C; 376 | color: #fff; 377 | font-size: 2rem; 378 | border-radius: 50%; 379 | cursor: pointer; 380 | -webkit-transition: bottom 0.4s 0.1s, right 0.4s 0.1s, opacity 0.1s 0.7s, -webkit-transform 0.4s 0.4s; 381 | transition: bottom 0.4s 0.1s, right 0.4s 0.1s, opacity 0.1s 0.7s, background-color 0.1s 0.2s, -webkit-transform 0.4s 0.4s; 382 | transition: bottom 0.4s 0.1s, right 0.4s 0.1s, transform 0.4s 0.4s, opacity 0.1s 0.7s; 383 | transition: bottom 0.4s 0.1s, right 0.4s 0.1s, transform 0.4s 0.4s, opacity 0.1s 0.7s, -webkit-transform 0.4s 0.4s; 384 | -webkit-transform: scale(0); 385 | transform: scale(0); 386 | } 387 | 388 | .mic:hover { 389 | background-color: #eaeaea; 390 | } 391 | 392 | .micButton-icon{ 393 | position: absolute; 394 | width: 3rem; 395 | height: 3rem; 396 | top: 50%; 397 | left: 50%; 398 | margin-left: -1.5rem; 399 | margin-top: -1.5rem; 400 | -webkit-transition: opacity 0.1s; 401 | transition: opacity 0.1s; 402 | } 403 | 404 | .mic .clicked { 405 | opacity: 0.5; 406 | } 407 | .micButton-icon path { 408 | stroke-width: 1px; 409 | stroke-dasharray: 64.36235046386719; 410 | stroke-dashoffset: 64.36235046386719; 411 | -webkit-animation: animatePath 0.5s 0.5s forwards; 412 | animation: animatePath 0.5s 0.5s forwards; 413 | } 414 | 415 | .video { 416 | position: absolute; 417 | z-index: 3; 418 | bottom: 9.5rem; 419 | right: 3.3rem; 420 | width: 4.6rem; 421 | height: 4.6rem; 422 | margin-right: -2.3rem; 423 | margin-bottom: -2.3rem; 424 | background: #9C9C9C; 425 | color: #fff; 426 | font-size: 2rem; 427 | border-radius: 50%; 428 | cursor: pointer; 429 | -webkit-transition: bottom 0.4s 0.1s, right 0.4s 0.1s, opacity 0.1s 0.7s, -webkit-transform 0.4s 0.4s; 430 | transition: bottom 0.4s 0.1s, right 0.4s 0.1s, opacity 0.1s 0.7s, -webkit-transform 0.4s 0.4s; 431 | transition: bottom 0.4s 0.1s, right 0.4s 0.1s, transform 0.4s 0.4s, opacity 0.1s 0.7s; 432 | transition: bottom 0.4s 0.1s, right 0.4s 0.1s, transform 0.4s 0.4s, opacity 0.1s 0.7s, -webkit-transform 0.4s 0.4s; 433 | -webkit-transform: scale(0); 434 | transform: scale(0); 435 | } 436 | 437 | .video:hover { 438 | background-color: #eaeaea; 439 | } 440 | 441 | .videoButton-icon{ 442 | position: absolute; 443 | width: 3rem; 444 | height: 3rem; 445 | top: 50%; 446 | left: 50%; 447 | margin-left: -1.5rem; 448 | margin-top: -1.5rem; 449 | -webkit-transition: opacity 0.1s; 450 | transition: opacity 0.1s; 451 | } 452 | .video .clicked { 453 | opacity: 0.5; 454 | } 455 | .videoButton-icon path { 456 | stroke-width: 1px; 457 | stroke-dasharray: 64.36235046386719; 458 | stroke-dashoffset: 64.36235046386719; 459 | -webkit-animation: animatePath 0.5s 0.5s forwards; 460 | animation: animatePath 0.5s 0.5s forwards; 461 | } 462 | 463 | .ripple { 464 | position: absolute; 465 | width: 15rem; 466 | height: 15rem; 467 | margin-left: -7.5rem; 468 | margin-top: -7.5rem; 469 | background: rgba(0, 0, 0, 0.4); 470 | -webkit-transform: scale(0); 471 | transform: scale(0); 472 | -webkit-animation: animRipple 0.4s; 473 | animation: animRipple 0.4s; 474 | border-radius: 50%; 475 | } 476 | 477 | .disabled-icon { 478 | position: absolute; 479 | display: none; 480 | width: 3rem; 481 | height: 3rem; 482 | top: 50%; 483 | left: 50%; 484 | margin-left: -1.5rem; 485 | margin-top: -1.5rem; 486 | -webkit-transition: opacity 0.1s; 487 | transition: opacity 0.1s; 488 | } 489 | 490 | .disabled-icon path { 491 | stroke-width: 3px; 492 | stroke-dasharray: 64.36235046386719; 493 | stroke-dashoffset: 64.36235046386719; 494 | -webkit-animation: animatePath 0.1s 0.3s forwards; 495 | animation: animatePath 0.1s 0.3s forwards; 496 | } 497 | 498 | .btn-default1 { 499 | color: #333; 500 | background-color: #a9a6a6; 501 | border-color: #0a0a0a; 502 | } 503 | 504 | @-webkit-keyframes animRipple { 505 | to { 506 | -webkit-transform: scale(3.5); 507 | transform: scale(3.5); 508 | opacity: 0; 509 | } 510 | } 511 | 512 | @keyframes animRipple { 513 | to { 514 | -webkit-transform: scale(3.5); 515 | transform: scale(3.5); 516 | opacity: 0; 517 | } 518 | } 519 | @-webkit-keyframes rotate { 520 | to { 521 | -webkit-transform: rotate(360deg); 522 | transform: rotate(360deg); 523 | } 524 | } 525 | @keyframes rotate { 526 | to { 527 | -webkit-transform: rotate(360deg); 528 | transform: rotate(360deg); 529 | } 530 | } 531 | @-webkit-keyframes animatePath { 532 | to { 533 | stroke-dashoffset: 0; 534 | } 535 | } 536 | @keyframes animatePath { 537 | to { 538 | stroke-dashoffset: 0; 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /static/img/giphy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/giphy.gif -------------------------------------------------------------------------------- /static/img/ic_perm_identity_black_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/img/ic_phone_black_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/img/images.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/images.jpg -------------------------------------------------------------------------------- /static/img/kurento.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/kurento.png -------------------------------------------------------------------------------- /static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/logo.png -------------------------------------------------------------------------------- /static/img/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/logo2.png -------------------------------------------------------------------------------- /static/img/naevatec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/naevatec.png -------------------------------------------------------------------------------- /static/img/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/pipeline.png -------------------------------------------------------------------------------- /static/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/spinner.gif -------------------------------------------------------------------------------- /static/img/transparent-1px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/transparent-1px.png -------------------------------------------------------------------------------- /static/img/urjc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/urjc.gif -------------------------------------------------------------------------------- /static/img/webrtc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daimoc/Kurento-SIP-GW/044496bcacc025c88c14ecd5b367aceb84147482/static/img/webrtc.png -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kurento SIP WebRTC Gateway 6 | 7 | 8 | 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 | 65 |
66 |
67 | 68 | 69 |
70 | 71 |

Video Muted

72 |
73 |
74 | 75 |
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 |
101 |
102 |
103 | 104 | 105 | 106 |
107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 |
115 |
116 | 117 | 118 | 119 | 120 | 121 | 122 |
123 | 124 |
125 |
126 |
127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /static/index_kurento.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Kurento Tutorial 1: Hello World 25 | 26 | 27 | 28 |
29 | 45 |
46 | 47 |
48 | 64 |
65 |
66 |

Local stream

67 | 68 |
69 |
70 | 71 | Start 72 |
73 |
74 | 75 | Stop 76 |
77 |
78 |

Remote stream

79 | 80 |
81 |
82 |
83 |
84 |

85 |
86 |
    87 |
    88 |
    89 |
    90 |
    91 | 92 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /static/js/DTMFSenderInband.js: -------------------------------------------------------------------------------- 1 | /* 2 | * DTMFSender.js 3 | * 4 | * This serves as a polyfill that adds a DTMF sender interface to the 5 | * RTCRTPSender objects on RTCRTPPeerConnecions for Firefox 44 and later. 6 | * Implementations simply include this file, and then use the DTMF sender 7 | * as described in the WebRTC specification. 8 | * 9 | * For versions of Firefox prior to 44, implementations need to manually 10 | * instantiate a version of the DTMFSender object, pass it a stream, and 11 | * then retreive "outputStream" from the sender object. Implmentations 12 | * may also choose to attach the sender to the corresponding RTCRTPSender, 13 | * if they wish. 14 | * 15 | * This Source Code Form is subject to the terms of the Mozilla Public License, 16 | * v. 2.0. If a copy of the MPL was not distributed with this file, You can 17 | * obtain one at https://mozilla.org/MPL/2.0/. 18 | */ 19 | 20 | 21 | // The MediaStream enhancements we need to make a polyfill work landed 22 | // at the same time as the "addTrack" method as added to MediaStream. 23 | // If this is possible, we monkeypatch ourselves into RTCPeerConnection.addTrack 24 | // so thatwe attach a new DTMF sender to each RTP Sender as they are created. 25 | 26 | var senderDTMFInband; 27 | 28 | 29 | 30 | function DTMFSenderInband(senderOrStream) { 31 | var ctx = this._audioCtx = new AudioContext(); 32 | this._outputStreamNode = ctx.createMediaStreamDestination(); 33 | var outputStream = this._outputStreamNode.stream; 34 | 35 | var inputStream; 36 | var rtpSender = null; 37 | 38 | if ("track" in senderOrStream) { 39 | rtpSender = senderOrStream; 40 | inputStream = new MediaStream([rtpSender.track]); 41 | } else { 42 | inputStream = senderOrStream; 43 | this.outputStream = outputStream; 44 | } 45 | 46 | this._source = ctx.createMediaStreamSource(inputStream); 47 | this._source.connect(this._outputStreamNode); 48 | 49 | this._f1Oscillator = ctx.createOscillator(); 50 | this._f1Oscillator.connect(this._outputStreamNode); 51 | this._f1Oscillator.frequency.value = 0; 52 | this._f1Oscillator.start(0); 53 | 54 | this._f2Oscillator = ctx.createOscillator(); 55 | this._f2Oscillator.connect(this._outputStreamNode); 56 | this._f2Oscillator.frequency.value = 0; 57 | this._f2Oscillator.start(0); 58 | 59 | if (rtpSender) { 60 | rtpSender.replaceTrack(outputStream.getAudioTracks()[0]) 61 | .then(function() { 62 | rtpSender.dtmf = this; 63 | }.bind(this)); 64 | } 65 | } 66 | 67 | /* Implements the same interface as RTCDTMFSender */ 68 | DTMFSenderInband.prototype = { 69 | 70 | ontonechange: undefined, 71 | 72 | get duration() { 73 | return this._duration; 74 | }, 75 | 76 | get interToneGap() { 77 | return this._interToneGap; 78 | }, 79 | 80 | get toneBuffer() { 81 | return this._toneBuffer; 82 | }, 83 | 84 | insertDTMF: function(tones, duration, interToneGap) { 85 | if (/[^0-9a-d#\*,]/i.test(tones)) { 86 | throw(new Error("InvalidCharacterError")); 87 | } 88 | 89 | this._duration = Math.min(6000, Math.max(40, duration || 100)); 90 | this._interToneGap = Math.max(40, interToneGap || 70); 91 | this._toneBuffer = tones; 92 | 93 | if (!this._playing) { 94 | setTimeout(this._playNextTone.bind(this), 0); 95 | this._playing = true; 96 | } 97 | }, 98 | 99 | /* Private */ 100 | _duration: 100, 101 | _interToneGap: 70, 102 | _toneBuffer: "", 103 | _f1Oscillator: null, 104 | _f2Oscillator: null, 105 | _playing: false, 106 | 107 | _freq: { 108 | "1": [ 1209, 697 ], 109 | "2": [ 1336, 697 ], 110 | "3": [ 1477, 697 ], 111 | "a": [ 1633, 697 ], 112 | "4": [ 1209, 770 ], 113 | "5": [ 1336, 770 ], 114 | "6": [ 1477, 770 ], 115 | "b": [ 1633, 770 ], 116 | "7": [ 1209, 852 ], 117 | "8": [ 1336, 852 ], 118 | "9": [ 1477, 852 ], 119 | "c": [ 1633, 852 ], 120 | "*": [ 1209, 941 ], 121 | "0": [ 1336, 941 ], 122 | "#": [ 1477, 941 ], 123 | "d": [ 1633, 941 ] 124 | }, 125 | 126 | _playNextTone: function() { 127 | if (this._toneBuffer.length == 0) { 128 | this._playing = false; 129 | this._f1Oscillator.frequency.value = 0; 130 | this._f2Oscillator.frequency.value = 0; 131 | if (this.ontonechange) { 132 | this.ontonechange({tone: ""}); 133 | } 134 | return; 135 | } 136 | 137 | var digit = this._toneBuffer.substr(0,1); 138 | this._toneBuffer = this._toneBuffer.substr(1); 139 | 140 | if (this.ontonechange) { 141 | this.ontonechange({tone: digit}); 142 | } 143 | 144 | if (digit == ',') { 145 | setTimeout(this._playNextTone.bind(this), 2000); 146 | return; 147 | } 148 | 149 | var f = this._freq[digit.toLowerCase()]; 150 | if (f) { 151 | this._f1Oscillator.frequency.value = f[0]; 152 | this._f2Oscillator.frequency.value = f[1]; 153 | setTimeout(this._stopTone.bind(this), this._duration); 154 | } else { 155 | // This shouldn't happen. If it does, just move on. 156 | setTimeout(this._playNextTone.bind(this), 0); 157 | } 158 | }, 159 | 160 | _stopTone: function() { 161 | this._f1Oscillator.frequency.value = 0; 162 | this._f2Oscillator.frequency.value = 0; 163 | setTimeout(this._playNextTone.bind(this), this._interToneGap); 164 | } 165 | }; 166 | -------------------------------------------------------------------------------- /static/js/config_client.js: -------------------------------------------------------------------------------- 1 | // Gateway Cnnfiguration 2 | var wsUrl = 'wss://' + location.host + '/sip-gw'; 3 | //var dtmfTransport ="inband"; 4 | var dtmfTransport ="sip"; 5 | 6 | // Renegotiate mode are how SIP calls deals with remote re-invite 7 | // none = only reply to sip but not vhnage on the media stack 8 | // rtp = Restart the rtp connection part on media stack (default value) 9 | // rtp-webrtc = Restart the rtp connection part on media stack and restart the peerconnectino on browser part 10 | var renegotiateMode = "rtp" 11 | -------------------------------------------------------------------------------- /static/js/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * (C) Copyright 2014-2015 Kurento (http://kurento.org/) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | var ws = new WebSocket(wsUrl); 19 | var videoInput; 20 | var videoOutput; 21 | var webRtcPeer; 22 | var pc; 23 | var state = null; 24 | var inbandsender ; 25 | 26 | var defaultMute = false; 27 | var audioTrack; 28 | var videoTrack; 29 | 30 | var audioSender; 31 | var videoSender; 32 | 33 | const I_CAN_START = 0; 34 | const I_CAN_STOP = 1; 35 | const I_AM_STARTING = 2; 36 | 37 | 38 | var streamBlack; 39 | var videoMuted; 40 | 41 | var callFrom; 42 | var callTo; 43 | 44 | var mediaConstraintsOption = { 45 | audio: true, 46 | video: true 47 | //{ 48 | 49 | // mediaSource: "window", // whole screen sharing 50 | /// width: {max: '800'}, 51 | // height: {max: '600'}, 52 | // frameRate: {max: '5'} 53 | 54 | //} 55 | } 56 | 57 | 58 | window.onload = function() { 59 | console.log('Page loaded ...'); 60 | videoInput = document.getElementById('videoInput'); 61 | videoOutput = document.getElementById('videoOutput'); 62 | setState(I_CAN_START); 63 | $('.btndtmf').attr('onclick', 'sendDtmf(event)'); 64 | $('#alertButton').click(function(){ 65 | $("#alert").hide(); 66 | }); 67 | $('#micButton').click(function(){ 68 | if(audioTrack){ 69 | toggleAudio(); 70 | } 71 | }); 72 | $('#videoButton').click(function(){ 73 | if (videoTrack){ 74 | toggleVideo(); 75 | } 76 | }); 77 | var canvas = document.getElementById('canvasBlack'); 78 | var ctx = canvas.getContext("2d"); 79 | ctx.fillStyle = "#000000"; 80 | ctx.fillRect(0, 0, 10, 10); 81 | 82 | streamBlack = canvas.captureStream().getVideoTracks()[0]; 83 | 84 | videoMuted = false; 85 | } 86 | 87 | function toggleAudio(){ 88 | $("#micMuted").toggle(); 89 | if (defaultMute){ 90 | audioTrack.enabled = !audioTrack.enabled; 91 | } 92 | else { 93 | var sender = getAudioSender(); 94 | if (sender == null) 95 | audioSender.replaceTrack(audioTrack); 96 | else { 97 | audioSender.replaceTrack(null) 98 | } 99 | } 100 | } 101 | function toggleVideo(){ 102 | $("#videoMuted").toggle(); 103 | $(".app_video_local_muted").toggle(); 104 | if (defaultMute){ 105 | videoTrack.enabled = !videoTrack.enabled; 106 | } 107 | else { 108 | var sender = getVideoSender(); 109 | //videoTrack.enabled = !videoTrack.enabled; 110 | videoMuted = !videoMuted; 111 | //if (sender == null) 112 | if (!videoMuted) 113 | videoSender.replaceTrack(videoTrack) 114 | else { 115 | videoSender.replaceTrack(streamBlack); 116 | } 117 | } 118 | } 119 | 120 | function getAudioSender(){ 121 | var sender = null; 122 | var senders = pc.getSenders(); 123 | for (var i = 0 ;i { 5 | let [key, val] = hash.split('=') 6 | params[key] = decodeURIComponent(val) 7 | }) 8 | 9 | return params 10 | } 11 | 12 | String.prototype.sansAccent = function(){ 13 | var accent = [ 14 | /[\300-\306]/g, /[\340-\346]/g, // A, a 15 | /[\310-\313]/g, /[\350-\353]/g, // E, e 16 | /[\314-\317]/g, /[\354-\357]/g, // I, i 17 | /[\322-\330]/g, /[\362-\370]/g, // O, o 18 | /[\331-\334]/g, /[\371-\374]/g, // U, u 19 | /[\321]/g, /[\361]/g, // N, n 20 | /[\307]/g, /[\347]/g, // C, c 21 | ]; 22 | var noaccent = ['A','a','E','e','I','i','O','o','U','u','N','n','C','c']; 23 | 24 | var str = this; 25 | for(var i = 0; i < accent.length; i++){ 26 | str = str.replace(accent[i], noaccent[i]); 27 | } 28 | 29 | return str; 30 | } 31 | 32 | $(document).ready(function() { 33 | 34 | var param = getUrlParams(window.location.search); 35 | var autojoin = param['autojoin']; 36 | 37 | $("#to").val(param['to']); 38 | $("#from").val(param['from']); 39 | 40 | 41 | var animating = false, 42 | submitPhase1 = 1100, 43 | submitPhase2 = 400, 44 | logoutPhase1 = 800, 45 | $login = $(".login"), 46 | $app = $(".app"); 47 | 48 | function ripple(elem, e) { 49 | $(".ripple").remove(); 50 | var elTop = elem.offset().top, 51 | elLeft = elem.offset().left, 52 | x = e.pageX - elLeft, 53 | y = e.pageY - elTop; 54 | var $ripple = $("
    "); 55 | $ripple.css({top: y, left: x}); 56 | elem.append($ripple); 57 | }; 58 | 59 | if (autojoin){ 60 | autojoin = false; 61 | setTimeout(function() { 62 | document.getElementById('start_call').click(); 63 | }, 2000); 64 | } 65 | 66 | $(document).on("click", ".login__submit", function(e) { 67 | 68 | 69 | var to = $("#to").val(); 70 | var from = $("#from").val(); 71 | if (!from) 72 | from="anonymous"; 73 | from = from.sansAccent(); 74 | start(from,to); 75 | 76 | if (animating) return; 77 | animating = true; 78 | var that = this; 79 | ripple($(that), e); 80 | 81 | $(that).addClass("processing"); 82 | setTimeout(function() { 83 | $(that).addClass("success"); 84 | setTimeout(function() { 85 | $("#main").addClass("demo_app"); 86 | $("#main").removeClass("demo"); 87 | $app.show(); 88 | $app.css("top"); 89 | $app.addClass("active"); 90 | }, submitPhase2 - 70); 91 | setTimeout(function() { 92 | $login.hide(); 93 | $login.addClass("inactive"); 94 | animating = false; 95 | $(that).removeClass("success processing"); 96 | }, submitPhase2); 97 | }, submitPhase1); 98 | }); 99 | 100 | $(document).on("click", ".app__logout", function(e) { 101 | stop(); 102 | if (animating) return; 103 | $(".ripple").remove(); 104 | animating = true; 105 | var that = this; 106 | $(that).addClass("clicked"); 107 | setTimeout(function() { 108 | $app.removeClass("active"); 109 | 110 | $("#main").addClass("demo"); 111 | $("#main").removeClass("demo_app"); 112 | 113 | $login.show(); 114 | $login.css("top"); 115 | $login.removeClass("inactive"); 116 | }, logoutPhase1 - 120); 117 | setTimeout(function() { 118 | $app.hide(); 119 | animating = false; 120 | $(that).removeClass("clicked"); 121 | }, logoutPhase1); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /stop.bat: -------------------------------------------------------------------------------- 1 | vagrant halt 2 | pause 3 | -------------------------------------------------------------------------------- /sync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Start kurento" 4 | sudo service kurento start 5 | 6 | echo "Start Dracthio" 7 | sudo service kurento start 8 | 9 | echo "Done" --------------------------------------------------------------------------------