├── static ├── img │ ├── urjc.gif │ ├── kurento.png │ ├── spinner.gif │ ├── webrtc.png │ ├── naevatec.png │ ├── pipeline.png │ ├── mario-wings.png │ └── transparent-1px.png ├── bower.json ├── css │ └── kurento.css ├── index.html └── js │ └── index.js ├── package.json ├── README.md ├── keys ├── README.md ├── server.csr ├── server.crt └── server.key ├── confs ├── kurento │ ├── WebRtcEndpoint.conf.ini │ └── kurento.conf.json └── asterisk │ ├── extensions.conf │ └── sip.conf └── server.js /static/img/urjc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilityfeat/kurento-asterisk/HEAD/static/img/urjc.gif -------------------------------------------------------------------------------- /static/img/kurento.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilityfeat/kurento-asterisk/HEAD/static/img/kurento.png -------------------------------------------------------------------------------- /static/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilityfeat/kurento-asterisk/HEAD/static/img/spinner.gif -------------------------------------------------------------------------------- /static/img/webrtc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilityfeat/kurento-asterisk/HEAD/static/img/webrtc.png -------------------------------------------------------------------------------- /static/img/naevatec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilityfeat/kurento-asterisk/HEAD/static/img/naevatec.png -------------------------------------------------------------------------------- /static/img/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilityfeat/kurento-asterisk/HEAD/static/img/pipeline.png -------------------------------------------------------------------------------- /static/img/mario-wings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilityfeat/kurento-asterisk/HEAD/static/img/mario-wings.png -------------------------------------------------------------------------------- /static/img/transparent-1px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilityfeat/kurento-asterisk/HEAD/static/img/transparent-1px.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kurento-one2one-call", 3 | "version": "6.6.1-dev", 4 | "private": true, 5 | "scripts": { 6 | "postinstall": "cd static && bower install" 7 | }, 8 | "dependencies": { 9 | "express": "~4.12.4", 10 | "kurento-client": "Kurento/kurento-client-js", 11 | "minimist": "^1.1.1", 12 | "sip.js": "^0.7.5", 13 | "ws": "~1.0.1" 14 | }, 15 | "devDependencies": { 16 | "bower": "^1.4.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![logo](https://webrtc.ventures/wp-content/uploads/2017/01/webrtc-logo.png)](https://webrtc.ventures) 2 | 3 | # Integration of Kurento with Asterisk 4 | 5 | ##### To install this sample follow these steps: 6 | 1) npm install 7 | 2) npm start 8 | 3) open this url in a WebRTC compatible browser: https://localhost:8443/ 9 | 10 | ###### To learn more about this work visit our post blog: [Kurento and Asterisk: a powerful couple](https://webrtc.ventures/2017/02/kurento-asterisk-powerful-couple/) 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /static/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kurento-one2one-call", 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 | "draggabilly": "~1.2.4", 25 | "demo-console": "1.5.1", 26 | "ekko-lightbox": "~3.3.0", 27 | "kurento-utils": "master" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /confs/kurento/WebRtcEndpoint.conf.ini: -------------------------------------------------------------------------------- 1 | ; /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini 2 | ; 3 | ; Only IP address are supported, not domain names for addresses 4 | ; You have to find a valid stun server. You can check if it works 5 | ; usin this tool: 6 | ; http://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/ 7 | stunServerAddress=xx.xxx.xx.xx 8 | stunServerPort=xxxx 9 | 10 | ; turnURL gives the necessary info to configure TURN for WebRTC. 11 | ; 'address' must be an IP (not a domain). 12 | ; 'transport' is optional (UDP by default). 13 | turnURL=user:pass@xx.xx.xx.xx 14 | 15 | ;pemCertificate is deprecated. Please use pemCertificateRSA instead 16 | ;pemCertificate= 17 | ;pemCertificateRSA= 18 | ;pemCertificateECDSA= 19 | -------------------------------------------------------------------------------- /confs/kurento/kurento.conf.json: -------------------------------------------------------------------------------- 1 | //(/etc/kurento/kurento.conf.json) 2 | { 3 | "mediaServer" : { 4 | "resources": { 5 | // //Resources usage limit for raising an exception when an object creation is attempted 6 | // "exceptionLimit": "0.8", 7 | // // Resources usage limit for restarting the server when no objects are alive 8 | // "killLimit": "0.7", 9 | // Garbage collector period in seconds 10 | "garbageCollectorPeriod": 240 11 | }, 12 | "net" : { 13 | "websocket": { 14 | "port": 8111, 15 | "secure": { 16 | "port": 8433, 17 | "certificate": "defaultCertificate.pem", 18 | "password": "" 19 | }, 20 | //"registrar": { 21 | // "address": "ws://localhost:9090", 22 | // "localAddress": "localhost" 23 | //}, 24 | "path": "kurento", 25 | "threads": 10 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /confs/asterisk/extensions.conf: -------------------------------------------------------------------------------- 1 | ;/etc/asterisk/extensions.conf 2 | [default] 3 | [from-internal] 4 | exten => 1000,1,Answer() 5 | same => n,Playback(demo-congrats) 6 | same => n,Hangup() 7 | 8 | ;exten => 2000,1,Dial(SIP/2001,10) 9 | ;exten => 2000,n,Voicemail(2000@vm-demo,u) 10 | ;exten => 2000,n,Hangup() 11 | 12 | ;;;;;;;;;;;;;;;;;;;;;; 13 | ;;KURENTO EXTENSIONS;; 14 | ;;;;;;;;;;;;;;;;;;;;;; 15 | 16 | exten => _200[0-4],1,Dial(SIP/${EXTEN},10) 17 | exten => _200[0-4],n,VoiceMail(${EXTEN}@vm-demo,u) 18 | 19 | ;exten => 2001,1,Answer(10) 20 | ;exten => 2001,n,Voicemail(2001@vm-demo,u) 21 | ;exten => 2001,n,Hangup() 22 | 23 | ;;;;;;;;;;;;;;;;;;;;;;;; 24 | ;;SOFTPHONE EXTENSIONS;; 25 | ;;;;;;;;;;;;;;;;;;;;;;;; 26 | 27 | exten => _300[0-4],1,Dial(SIP/${EXTEN},10) 28 | exten => _300[0-4],n,VoiceMail(${EXTEN}@vm-demo,u) 29 | ; exten => 3000,n,Answer() 30 | ; exten => 3000,n,Hangup() 31 | 32 | exten => 6001,1,Dial(SIP/6001,10) 33 | 34 | exten => 8000,1,Answer 35 | exten => 8000,2,Wait(1) 36 | exten => 8000,3,VoicemailMain(${CALLERIDNUM}|s) 37 | exten => 8000,4,Hangup 38 | -------------------------------------------------------------------------------- /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.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 | -------------------------------------------------------------------------------- /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 | padding-top: 40px; 26 | body 27 | } 28 | 29 | video,#console { 30 | display: block; 31 | font-size: 14px; 32 | line-height: 1.42857143; 33 | color: #555; 34 | background-color: #fff; 35 | background-image: none; 36 | border: 1px solid #ccc; 37 | border-radius: 4px; 38 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); 39 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); 40 | -webkit-transition: border-color ease-in-out .15s, box-shadow 41 | ease-in-out .15s; 42 | transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; 43 | } 44 | 45 | #console { 46 | min-height: 120px; 47 | max-height: 360px; 48 | } 49 | 50 | #videoContainer { 51 | position: absolute; 52 | float: left; 53 | } 54 | 55 | #videoBig { 56 | width: 640px; 57 | height: 480px; 58 | top: 0; 59 | left: 0; 60 | z-index: 1; 61 | } 62 | 63 | div#videoSmall { 64 | width: 240px; 65 | height: 180px; 66 | padding: 0px; 67 | position: absolute; 68 | top: 15px; 69 | left: 400px; 70 | cursor: pointer; 71 | z-index: 10; 72 | padding: 0px; 73 | } 74 | 75 | div.dragged { 76 | cursor: all-scroll !important; 77 | border-color: blue !important; 78 | z-index: 10 !important; 79 | } 80 | 81 | .sip-container { 82 | background-color: #e0e0e0; 83 | padding: 10px; 84 | } 85 | -------------------------------------------------------------------------------- /static/index.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 | 25 | 26 | SIP / Kurento Call 27 | 28 | 29 | 30 |
31 | 38 |
39 | 40 |
41 | 44 |
45 |
46 |
47 |
48 |
49 | 50 | 51 | 52 | 53 |
54 |
55 | 60 |
61 |
62 |
63 |
64 |
65 |
66 | 67 | 68 |
69 |
70 | 78 |
79 |
80 |

81 |
82 |
    83 |
    84 |
    85 |
    86 |
    87 | 88 |
    89 |
    90 | 91 |
    92 |
    93 |
    94 |
    95 | 96 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /confs/asterisk/sip.conf: -------------------------------------------------------------------------------- 1 | ;/etc/asterisk/sip.conf 2 | [general] 3 | udpbindaddr=0.0.0.0:5060 4 | realm=proto.webrtc.ventures ;replace with your Asterisk server public IP address or host 5 | transport=udp,tls,ws,wss 6 | localnet=172.0.0.0/255.255.0.0 7 | externip=proto.webrtc.ventures 8 | nat=force_rport,comedia 9 | videosupport=yes 10 | 11 | [6001] ;sipml5 12 | host=dynamic 13 | secret=asteriskpass 14 | context=from-internal 15 | type=friend 16 | encryption=no 17 | avpf=yes 18 | force_avp=yes 19 | icesupport=yes 20 | directmedia=no 21 | disallow=all 22 | allow=ulaw,alaw,gsm,opus 23 | allow=vp8 24 | dtlsenable=yes 25 | dtlsverify=no 26 | dtlscertfile=/home/ubuntu/nssl/asterisk.pem 27 | dtlscafile=/home/ubuntu/nssl/asterisk.pem 28 | dtlssetup=actpass 29 | videosupport=yes 30 | 31 | ;;;;;;;;;;;;;;;;;;;;;; 32 | ;;KURENTO EXTENSIONS;; 33 | ;;;;;;;;;;;;;;;;;;;;;; 34 | 35 | [2000] ;kurento-appserver 36 | host=dynamic 37 | secret=asteriskpass 38 | context=from-internal 39 | transport=ws,wss,udp 40 | type=friend 41 | encryption=no 42 | avpf=yes 43 | ; force_avp=yes 44 | icesupport=yes 45 | directmedia=no 46 | disallow=all 47 | allow=ulaw,opus 48 | ;ulaw,alaw,gsm 49 | allow=vp8 50 | ; dtlsenable=no 51 | ; dtlsverify=no 52 | ; dtlscertfile=/home/ubuntu/nssl/asterisk.pem 53 | ; dtlscafile=/home/ubuntu/nssl/asterisk.pem 54 | ; dtlssetup=actpass 55 | videosupport=yes 56 | 57 | [2001] ;kurento-appserver 58 | host=dynamic 59 | secret=asteriskpass 60 | context=from-internal 61 | transport=ws,wss,udp 62 | type=friend 63 | encryption=no 64 | avpf=yes 65 | ; force_avp=yes 66 | icesupport=yes 67 | directmedia=no 68 | disallow=all 69 | allow=ulaw,opus 70 | ;ulaw,alaw,gsm 71 | allow=vp8 72 | ; dtlsenable=no 73 | ; dtlsverify=no 74 | ; dtlscertfile=/home/ubuntu/nssl/asterisk.pem 75 | ; dtlscafile=/home/ubuntu/nssl/asterisk.pem 76 | ; dtlssetup=actpass 77 | videosupport=yes 78 | 79 | [2003] ;kurento-appserver 80 | host=dynamic 81 | secret=asteriskpass 82 | context=from-internal 83 | transport=ws,wss,udp 84 | type=friend 85 | encryption=no 86 | avpf=yes 87 | ; force_avp=yes 88 | icesupport=yes 89 | directmedia=no 90 | disallow=all 91 | allow=ulaw,opus 92 | ;ulaw,alaw,gsm 93 | allow=vp8 94 | ; dtlsenable=no 95 | ; dtlsverify=no 96 | ; dtlscertfile=/home/ubuntu/nssl/asterisk.pem 97 | ; dtlscafile=/home/ubuntu/nssl/asterisk.pem 98 | ; dtlssetup=actpass 99 | videosupport=yes 100 | 101 | 102 | [2004] ;kurento-appserver 103 | host=dynamic 104 | secret=asteriskpass 105 | context=from-internal 106 | transport=ws,wss,udp 107 | type=friend 108 | encryption=no 109 | avpf=yes 110 | ; force_avp=yes 111 | icesupport=yes 112 | directmedia=no 113 | disallow=all 114 | allow=ulaw,opus 115 | ;ulaw,alaw,gsm 116 | allow=vp8 117 | ; dtlsenable=no 118 | ; dtlsverify=no 119 | ; dtlscertfile=/home/ubuntu/nssl/asterisk.pem 120 | ; dtlscafile=/home/ubuntu/nssl/asterisk.pem 121 | ; dtlssetup=actpass 122 | videosupport=yes 123 | 124 | ;;;;;;;;;;;;;;;;;;;;;;;; 125 | ;;SOFTPHONE EXTENSIONS;; 126 | ;;;;;;;;;;;;;;;;;;;;;;;; 127 | 128 | [3000] 129 | host=dynamic 130 | secret=asteriskpass 131 | context=from-internal 132 | type=friend 133 | disallow=all 134 | allow=ulaw,opus 135 | ;ulaw,alaw,gsm 136 | allow=vp8 137 | ;h263,h263p,h264,mpeg4 138 | icesupport=yes 139 | directmedia=no 140 | videosupport=yes 141 | 142 | [3001] 143 | host=dynamic 144 | secret=asteriskpass 145 | context=from-internal 146 | type=friend 147 | disallow=all 148 | allow=ulaw,opus 149 | ;ulaw,alaw,gsm 150 | allow=vp8 151 | ;h263,h263p,h264,mpeg4 152 | icesupport=yes 153 | directmedia=no 154 | videosupport=yes 155 | 156 | [3002] 157 | host=dynamic 158 | secret=asteriskpass 159 | context=from-internal 160 | type=friend 161 | disallow=all 162 | allow=ulaw,opus 163 | ;ulaw,alaw,gsm 164 | allow=vp8 165 | ;h263,h263p,h264,mpeg4 166 | icesupport=yes 167 | directmedia=no 168 | videosupport=yes 169 | 170 | [3003] 171 | host=dynamic 172 | secret=asteriskpass 173 | context=from-internal 174 | type=friend 175 | disallow=all 176 | allow=ulaw,opus 177 | ;ulaw,alaw,gsm 178 | allow=vp8 179 | ;h263,h263p,h264,mpeg4 180 | icesupport=yes 181 | directmedia=no 182 | videosupport=yes 183 | 184 | [3004] 185 | host=dynamic 186 | secret=asteriskpass 187 | context=from-internal 188 | type=friend 189 | disallow=all 190 | allow=ulaw,opus 191 | ;ulaw,alaw,gsm 192 | allow=vp8 193 | ;h263,h263p,h264,mpeg4 194 | icesupport=yes 195 | directmedia=no 196 | videosupport=yes 197 | -------------------------------------------------------------------------------- /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('wss://' + location.host + '/one2one'); 19 | var videoInput; 20 | var videoOutput; 21 | var webRtcPeer; 22 | 23 | var registerName = null; 24 | const NOT_REGISTERED = 0; 25 | const REGISTERING = 1; 26 | const REGISTERED = 2; 27 | var registerState = null 28 | 29 | function setRegisterState(nextState) { 30 | switch (nextState) { 31 | case NOT_REGISTERED: 32 | $('#register').attr('disabled', false); 33 | $('#call').attr('disabled', true); 34 | $('#terminate').attr('disabled', true); 35 | break; 36 | 37 | case REGISTERING: 38 | $('#register').attr('disabled', true); 39 | break; 40 | 41 | case REGISTERED: 42 | $('#register').attr('disabled', true); 43 | setCallState(NO_CALL); 44 | break; 45 | 46 | default: 47 | return; 48 | } 49 | registerState = nextState; 50 | } 51 | 52 | const NO_CALL = 0; 53 | const PROCESSING_CALL = 1; 54 | const IN_CALL = 2; 55 | var callState = null 56 | 57 | function setCallState(nextState) { 58 | switch (nextState) { 59 | case NO_CALL: 60 | $('#call').attr('disabled', false); 61 | $('#terminate').attr('disabled', true); 62 | break; 63 | 64 | case PROCESSING_CALL: 65 | $('#call').attr('disabled', true); 66 | $('#terminate').attr('disabled', true); 67 | break; 68 | case IN_CALL: 69 | $('#call').attr('disabled', true); 70 | $('#terminate').attr('disabled', false); 71 | break; 72 | default: 73 | return; 74 | } 75 | callState = nextState; 76 | } 77 | 78 | window.onload = function() { 79 | console = new Console(); 80 | setRegisterState(NOT_REGISTERED); 81 | var drag = new Draggabilly(document.getElementById('videoSmall')); 82 | videoInput = document.getElementById('videoInput'); 83 | videoOutput = document.getElementById('videoOutput'); 84 | document.getElementById('ext').focus(); 85 | 86 | document.getElementById('register').addEventListener('click', function() { 87 | register(); 88 | }); 89 | document.getElementById('call').addEventListener('click', function() { 90 | call(); 91 | }); 92 | document.getElementById('terminate').addEventListener('click', function() { 93 | stop(); 94 | }); 95 | } 96 | 97 | window.onbeforeunload = function() { 98 | ws.close(); 99 | } 100 | 101 | ws.onmessage = function(message) { 102 | var parsedMessage = JSON.parse(message.data); 103 | // console.info('Received message: ' + message.data); 104 | 105 | switch (parsedMessage.id) { 106 | case 'registerResponse': 107 | registerResponse(parsedMessage); 108 | break; 109 | case 'callResponse': 110 | callResponse(parsedMessage); 111 | break; 112 | case 'incomingCall': 113 | incomingCall(parsedMessage); 114 | break; 115 | case 'startCommunication': 116 | startCommunication(parsedMessage); 117 | break; 118 | case 'stopCommunication': 119 | console.info("Communication ended by remote peer"); 120 | stop(true); 121 | break; 122 | case 'iceCandidate': 123 | webRtcPeer.addIceCandidate(parsedMessage.candidate) 124 | break; 125 | default: 126 | console.error('Unrecognized message', parsedMessage); 127 | } 128 | } 129 | 130 | function registerResponse(message) { 131 | if (message.response == 'accepted') { 132 | setRegisterState(REGISTERED); 133 | } else { 134 | setRegisterState(NOT_REGISTERED); 135 | var errorMessage = message.message ? message.message 136 | : 'Unknown reason for register rejection.'; 137 | console.log(errorMessage); 138 | alert('Error registering user. See console for further information.'); 139 | } 140 | } 141 | 142 | function callResponse(message) { 143 | if (message.response != 'accepted') { 144 | console.info('Call not accepted by peer. Closing call'); 145 | var errorMessage = message.message ? message.message 146 | : 'Unknown reason for call rejection.'; 147 | console.log(errorMessage); 148 | stop(true); 149 | } else { 150 | setCallState(IN_CALL); 151 | webRtcPeer.processAnswer(message.sdpAnswer); 152 | } 153 | } 154 | 155 | function startCommunication(message) { 156 | setCallState(IN_CALL); 157 | webRtcPeer.processAnswer(message.sdpAnswer); 158 | } 159 | 160 | function incomingCall(message) { 161 | // If bussy just reject without disturbing user 162 | if (callState != NO_CALL) { 163 | var response = { 164 | id : 'incomingCallResponse', 165 | from : message.from, 166 | callResponse : 'reject', 167 | message : 'bussy' 168 | 169 | }; 170 | return sendMessage(response); 171 | } 172 | 173 | setCallState(PROCESSING_CALL); 174 | if (confirm('User ' + message.from 175 | + ' is calling you. Do you accept the call?')) { 176 | showSpinner(videoInput, videoOutput); 177 | 178 | var options = { 179 | localVideo : videoInput, 180 | remoteVideo : videoOutput, 181 | onicecandidate : onIceCandidate, 182 | configuration : { iceServers : [{"urls" : "stun:107.22.152.22:3478"}, 183 | {"urls" : "turn:107.22.152.22", "username":"ubuntu", "credential":"kurentoserverpass"}]} 184 | } 185 | 186 | webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, 187 | function(error) { 188 | if (error) { 189 | console.error(error); 190 | setCallState(NO_CALL); 191 | } 192 | 193 | this.generateOffer(function(error, offerSdp) { 194 | if (error) { 195 | console.error(error); 196 | setCallState(NO_CALL); 197 | } 198 | var response = { 199 | id : 'incomingCallResponse', 200 | from : message.from, 201 | callResponse : 'accept', 202 | sdpOffer : offerSdp 203 | }; 204 | sendMessage(response); 205 | }); 206 | }); 207 | 208 | } else { 209 | var response = { 210 | id : 'incomingCallResponse', 211 | from : message.from, 212 | callResponse : 'reject', 213 | message : 'user declined' 214 | }; 215 | sendMessage(response); 216 | stop(true); 217 | } 218 | } 219 | 220 | function register() { 221 | var ext = document.getElementById('ext').value; 222 | var password = document.getElementById('password').value; 223 | 224 | if (ext == '') { 225 | window.alert("You must insert your user ext"); 226 | return; 227 | } 228 | 229 | setRegisterState(REGISTERING); 230 | 231 | var message = { 232 | id : 'register', 233 | ext : ext, 234 | password: password 235 | }; 236 | sendMessage(message); 237 | document.getElementById('peer').focus(); 238 | } 239 | 240 | function call() { 241 | if (document.getElementById('peer').value == '') { 242 | window.alert("You must specify the peer ext"); 243 | return; 244 | } 245 | 246 | setCallState(PROCESSING_CALL); 247 | 248 | showSpinner(videoInput, videoOutput); 249 | 250 | var options = { 251 | localVideo : videoInput, 252 | remoteVideo : videoOutput, 253 | onicecandidate : onIceCandidate, 254 | configuration : { iceServers : [{"urls" : "stun:107.22.152.22:3478"}, 255 | {"urls" : "turn:107.22.152.22", "username":"ubuntu", "credential":"kurentoserverpass"}]} 256 | } 257 | console.log('create webRtcPeer ...'); 258 | 259 | webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, function(error) { 260 | if (error) { 261 | console.error(error); 262 | setCallState(NO_CALL); 263 | } 264 | console.log('created webRtcPeer') 265 | 266 | this.generateOffer(function(error, offerSdp) { 267 | if (error) { 268 | console.error(error); 269 | setCallState(NO_CALL); 270 | } 271 | console.log('Generate offer') 272 | 273 | var message = { 274 | id : 'call', 275 | to : document.getElementById('peer').value, 276 | sdpOffer : offerSdp 277 | }; 278 | sendMessage(message); 279 | }); 280 | }); 281 | 282 | } 283 | 284 | function stop(message) { 285 | setCallState(NO_CALL); 286 | if (webRtcPeer) { 287 | webRtcPeer.dispose(); 288 | webRtcPeer = null; 289 | 290 | if (!message) { 291 | var message = { 292 | id : 'stop' 293 | } 294 | sendMessage(message); 295 | } 296 | } 297 | hideSpinner(videoInput, videoOutput); 298 | } 299 | 300 | function sendMessage(message) { 301 | var jsonMessage = JSON.stringify(message); 302 | console.log('Senging message: ' + jsonMessage); 303 | ws.send(jsonMessage); 304 | } 305 | 306 | function onIceCandidate(candidate) { 307 | console.log('Local candidate' + JSON.stringify(candidate)); 308 | 309 | var message = { 310 | id : 'onIceCandidate', 311 | candidate : candidate 312 | } 313 | sendMessage(message); 314 | } 315 | 316 | function showSpinner() { 317 | for (var i = 0; i < arguments.length; i++) { 318 | arguments[i].poster = './img/transparent-1px.png'; 319 | arguments[i].style.background = 'center transparent url("./img/spinner.gif") no-repeat'; 320 | } 321 | } 322 | 323 | function hideSpinner() { 324 | for (var i = 0; i < arguments.length; i++) { 325 | arguments[i].src = ''; 326 | arguments[i].poster = './img/webrtc.png'; 327 | arguments[i].style.background = ''; 328 | } 329 | } 330 | 331 | /** 332 | * Lightbox utility (to display media pipeline image in a modal dialog) 333 | */ 334 | $(document).delegate('*[data-toggle="lightbox"]', 'click', function(event) { 335 | event.preventDefault(); 336 | $(this).ekkoLightbox(); 337 | }); 338 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * (C) Copyright 2014 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 express = require('express'); 20 | var ws = require('ws'); 21 | var minimist = require('minimist'); 22 | var url = require('url'); 23 | var kurento = require('kurento-client'); 24 | var fs = require('fs'); 25 | var https = require('https'); 26 | var SIP = require('sip.js'); 27 | 28 | var argv = minimist(process.argv.slice(2), { 29 | default: { 30 | as_uri: "https://localhost:8443/", 31 | ws_uri: "ws://107.22.152.22:8111/kurento" 32 | } 33 | }); 34 | 35 | const OVERLAY_URL = 'https://github.com/Kurento/kurento-tutorial-node/raw/master/kurento-magic-mirror/static/img/mario-wings.png'; 36 | 37 | var options = 38 | { 39 | key: fs.readFileSync('keys/server.key'), 40 | cert: fs.readFileSync('keys/server.crt') 41 | }; 42 | 43 | var app = express(); 44 | 45 | /* 46 | * Definition of global variables. 47 | */ 48 | 49 | var kurentoClient = null; 50 | var userRegistry = new UserRegistry(); 51 | var pipelines = {}; 52 | var candidatesQueue = {}; 53 | var idCounter = 0; 54 | var sipServer = '107.22.152.22'; 55 | 56 | function nextUniqueId() { 57 | idCounter++; 58 | return idCounter.toString(); 59 | } 60 | 61 | const IDLE = 0; 62 | const INCOMING_CALL = 1; 63 | const OUTGOING_CALL = 2; 64 | 65 | /* 66 | * Definition of helper classes 67 | */ 68 | 69 | // Represents caller and callee sessions 70 | function UserSession(id, ext, ua, ws) { 71 | this.id = id; 72 | this.ext = ext; 73 | this.ws = ws; 74 | this.ua = ua; 75 | this.sdpOffer = null; 76 | this.asteriskSdp = null; 77 | // this.generateAsteriskOffer = null; 78 | this.endPoint = null; 79 | this.callStatus = IDLE 80 | } 81 | 82 | UserSession.prototype.sendMessage = function(message) { 83 | this.ws.send(JSON.stringify(message)); 84 | } 85 | 86 | UserSession.prototype.setEndPoint = function(endPoint) { 87 | this.endPoint = endPoint; 88 | } 89 | 90 | // Represents registrar of users 91 | function UserRegistry() { 92 | this.usersById = {}; 93 | this.usersByExt = {}; 94 | } 95 | 96 | UserRegistry.prototype.getById = function(id) { 97 | return this.usersById[id]; 98 | } 99 | 100 | UserRegistry.prototype.getByExt = function(ext) { 101 | return this.usersByExt[ext]; 102 | } 103 | 104 | UserRegistry.prototype.register = function(user) { 105 | this.usersById[user.id] = user; 106 | this.usersByExt[user.ext] = user; 107 | } 108 | 109 | UserRegistry.prototype.unregister = function(id) { 110 | var user = this.getById(id); 111 | if (user) delete this.usersById[id]; 112 | if (user) delete this.usersByExt[user.ext]; 113 | } 114 | 115 | UserRegistry.prototype.removeById = function(id) { 116 | var userSession = this.usersById[id]; 117 | if (!userSession) return; 118 | delete this.usersById[id]; 119 | delete this.usersByExt[userSession.ext]; 120 | } 121 | 122 | /* ************************************************** 123 | We implemented a custom media handler for SIP.js 124 | that would take care of handling session descriptions 125 | for the Kurento - Asterisk communication. 126 | ************************************************** */ 127 | function KurentoMediaHandler(ext, session, options) { 128 | this.session = session; 129 | this.ext = ext; 130 | } 131 | 132 | KurentoMediaHandler.prototype = { 133 | 134 | isReady: function () { return true; }, 135 | 136 | close: function () {}, 137 | 138 | peerConnection: { 139 | close: new Function() 140 | }, 141 | 142 | render: new Function(), 143 | mute: new Function(), 144 | unmute: new Function(), 145 | 146 | getDescription: function (onSuccess, onFailure, mediaHint) { 147 | let user = userRegistry.getByExt(this.ext); 148 | if(user.asteriskSdp.type === 'answer') { 149 | onSuccess(user.asteriskSdp.value); 150 | } else if (user.pipeline) { 151 | user.pipeline.rtpEndPoint.processOffer(user.asteriskSdp.value).then(onSuccess).catch(onFailure); 152 | } 153 | }, 154 | 155 | setDescription: function (desc, onSuccess, onFailure) { 156 | let user = userRegistry.getByExt(this.ext); 157 | user.asteriskSdp = { 158 | type: 'offer', 159 | value: desc 160 | }; 161 | onSuccess(); 162 | } 163 | }; 164 | 165 | // Represents a B2B active call 166 | function CallMediaPipeline() { 167 | this.pipeline = null; 168 | this.webRtcEndpoint = null; 169 | this.rtpEndPoint = null; 170 | } 171 | 172 | 173 | /* ***************************** 174 | We implemented a simple Kurento media pipeline with a WebRtcEndpoint to 175 | connect to the web client, a RtpEndpoint to connect to Asterisk and a 176 | FaceOverlayFilter to apply the overlay image on top of the video stream 177 | *************************** */ 178 | CallMediaPipeline.prototype.createPipeline = function(userId, ws, callback) { 179 | var self = this; 180 | getKurentoClient(function(error, kurentoClient) { 181 | if (error) { 182 | return callback(error); 183 | } 184 | 185 | kurentoClient.create('MediaPipeline', function(error, pipeline) { 186 | if (error) { 187 | return callback(error); 188 | } 189 | 190 | let endPoints = [ 191 | {type: "RtpEndpoint", params: {}}, 192 | {type: "WebRtcEndpoint", params: {}}, 193 | {type: "FaceOverlayFilter", params: {}}, 194 | ]; 195 | 196 | pipeline.create(endPoints, function(error, [rtpEndPoint, webEndPoint, faceFilter]) { 197 | 198 | let user = userRegistry.getById(userId); 199 | 200 | rtpEndPoint.on('ConnectionStateChanged', (evt) => { 201 | console.log(`RtpEndpoint(${user.ext})`, `${evt.oldState} => ${evt.newState}`) 202 | }); 203 | 204 | webEndPoint.on('ConnectionStateChanged', (evt) => { 205 | console.log(`WebRTCEndpoint(${user.ext})`, `${evt.oldState} => ${evt.newState}`) 206 | }); 207 | 208 | if (error) { 209 | pipeline.release(); 210 | return callback(error); 211 | } 212 | 213 | if (candidatesQueue[userId]) { 214 | while(candidatesQueue[userId].length) { 215 | var candidate = candidatesQueue[userId].shift(); 216 | webEndPoint.addIceCandidate(candidate); 217 | } 218 | } 219 | 220 | webEndPoint.on('OnIceCandidate', function(event) { 221 | var candidate = kurento.getComplexType('IceCandidate')(event.candidate); 222 | userRegistry.getById(userId).ws.send(JSON.stringify({ 223 | id : 'iceCandidate', 224 | candidate : candidate 225 | })); 226 | }); 227 | 228 | let promises = [ 229 | faceFilter.setOverlayedImage(OVERLAY_URL, -0.35, -1.2, 1.6, 1.6), 230 | 231 | webEndPoint.connect(faceFilter), 232 | rtpEndPoint.connect(webEndPoint), 233 | faceFilter.connect(rtpEndPoint) 234 | ]; 235 | 236 | Promise.all(promises).then(() => { 237 | self.rtpEndPoint = rtpEndPoint; 238 | self.webRtcEndpoint = webEndPoint; 239 | self.pipeline = pipeline; 240 | callback(null); 241 | }).catch(callback); 242 | 243 | }); 244 | }); 245 | }) 246 | } 247 | 248 | CallMediaPipeline.prototype.generateSdpAnswer = function(sdpOffer, callback) { 249 | this.webRtcEndpoint.processOffer(sdpOffer, callback); 250 | this.webRtcEndpoint.gatherCandidates(function(error) { 251 | if (error) { 252 | return callback(error); 253 | } 254 | }); 255 | } 256 | 257 | CallMediaPipeline.prototype.release = function() { 258 | if (this.pipeline) this.pipeline.release(); 259 | this.pipeline = null; 260 | } 261 | 262 | /* 263 | * Server startup 264 | */ 265 | 266 | var asUrl = url.parse(argv.as_uri); 267 | var port = asUrl.port; 268 | var server = https.createServer(options, app).listen(port, function() { 269 | console.log('Kurento Tutorial started'); 270 | console.log('Open ' + url.format(asUrl) + ' with a WebRTC capable browser'); 271 | }); 272 | 273 | var wss = new ws.Server({ 274 | server : server, 275 | path : '/one2one' 276 | }); 277 | 278 | wss.on('connection', function(ws) { 279 | var sessionId = nextUniqueId(); 280 | console.log('Connection received with sessionId ' + sessionId); 281 | 282 | ws.on('error', function(error) { 283 | console.log('Connection ' + sessionId + ' error'); 284 | stop(sessionId); 285 | }); 286 | 287 | ws.on('close', function() { 288 | console.log('Connection ' + sessionId + ' closed'); 289 | stop(sessionId); 290 | userRegistry.unregister(sessionId); 291 | }); 292 | 293 | ws.on('message', function(_message) { 294 | var message = JSON.parse(_message); 295 | console.log('Connection ' + sessionId + ' received message ', message); 296 | 297 | switch (message.id) { 298 | case 'register': 299 | register(sessionId, message.ext, message.password, ws); 300 | break; 301 | 302 | case 'call': 303 | call(sessionId, message.to, message.sdpOffer); 304 | break; 305 | 306 | case 'incomingCallResponse': 307 | incomingCallResponse(sessionId, message.callResponse, message.sdpOffer, ws); 308 | break; 309 | 310 | case 'stop': 311 | stop(sessionId); 312 | break; 313 | 314 | case 'onIceCandidate': 315 | onIceCandidate(sessionId, message.candidate); 316 | break; 317 | 318 | default: 319 | ws.send(JSON.stringify({ 320 | id : 'error', 321 | message : 'Invalid message ' + message 322 | })); 323 | break; 324 | } 325 | 326 | }); 327 | }); 328 | 329 | // Recover kurentoClient for the first time. 330 | function getKurentoClient(callback) { 331 | if (kurentoClient !== null) { 332 | return callback(null, kurentoClient); 333 | } 334 | 335 | kurento(argv.ws_uri, function(error, _kurentoClient) { 336 | if (error) { 337 | var message = 'Coult not find media server at address ' + argv.ws_uri; 338 | return callback(message + ". Exiting with error " + error); 339 | } 340 | 341 | kurentoClient = _kurentoClient; 342 | callback(null, kurentoClient); 343 | }); 344 | } 345 | 346 | function stop(sessionId) { 347 | 348 | var user = userRegistry.getById(sessionId); 349 | 350 | if (user) { 351 | user.pipeline && user.pipeline.release(); 352 | user.pipeline = null; 353 | 354 | var message = { 355 | id: 'stopCommunication', 356 | message: 'remote user hanged out' 357 | } 358 | user.sendMessage(message) 359 | } 360 | 361 | clearCandidatesQueue(sessionId); 362 | } 363 | 364 | function incomingCallResponse(calleeId, callResponse, calleeSdp, ws) { 365 | 366 | clearCandidatesQueue(calleeId); 367 | 368 | var callee = userRegistry.getById(calleeId); 369 | callee.sdpOffer = calleeSdp; 370 | 371 | if (callResponse === 'accept') { 372 | createCallPipeline(callee, (error, sdpAnswer) => { 373 | 374 | /* *************************************************** 375 | If the callee accepted the call, a Kurento media 376 | pipeline is created and the RTPEndPoint processes 377 | the SDP offer we got from Asterisk 378 | *************************************************** */ 379 | callee.pipeline.rtpEndPoint.processOffer(callee.asteriskSdp.value).then((answer) => { 380 | callee.asteriskSdp = { 381 | type: 'answer', 382 | value: answer 383 | }; 384 | callee.session.accept(); 385 | }); 386 | 387 | // We let the client know everything's ready to begin streaming 388 | var message = { 389 | id: 'startCommunication', 390 | sdpAnswer 391 | }; 392 | callee.sendMessage(message); 393 | }); 394 | } else { 395 | callee.session.reject(); 396 | } 397 | } 398 | 399 | function createCallPipeline(user, callback) { 400 | let pipeline = new CallMediaPipeline(); 401 | pipeline.createPipeline(user.id, ws, function(error) { 402 | if (error) { 403 | pipeline.release(); 404 | return callback(error); 405 | } 406 | 407 | pipeline.generateSdpAnswer(user.sdpOffer, function(error, sdpAnswer) { 408 | if (error) { 409 | pipeline.release(); 410 | return callback(error); 411 | } 412 | 413 | user.pipeline = pipeline; 414 | console.log(`${user.ext} <- pipeline`); 415 | callback(null, sdpAnswer); 416 | }); 417 | }); 418 | } 419 | 420 | function setupCallSession(user) { 421 | 422 | function rejectCall(rejectCause) { 423 | var message = { 424 | id: 'callResponse', 425 | response: 'rejected', 426 | message: rejectCause 427 | }; 428 | user.session = null; 429 | user.sendMessage(message); 430 | } 431 | 432 | function terminateCall(evt) { 433 | console.log('TERMINATED', evt.reason_phrase); 434 | user.pipeline && user.pipeline.release(); 435 | user.pipeline = null; 436 | if(user.session) { 437 | user.session = null; 438 | stop(user.id); 439 | } 440 | } 441 | 442 | // This can be omitted 443 | user.session.on('progress', () => console.log('SESSION: progress')); 444 | user.session.on('cancel', () => console.log('SESSION: cancel')); 445 | user.session.on('refer', () => console.log('SESSION: refer')); 446 | user.session.on('replaced', () => console.log('SESSION: replaced')); 447 | user.session.on('dtmf', () => console.log('SESSION: dtmf')); 448 | user.session.on('muted', () => console.log('SESSION: muted')); 449 | user.session.on('unmuted', () => console.log('SESSION: unmuted')); 450 | user.session.on('bye', () => console.log('SESSION: bye')); 451 | 452 | user.session.on('terminated', terminateCall); 453 | user.session.on('failed', terminateCall); 454 | user.session.on('rejected', terminateCall); 455 | 456 | // This would tell a caller that its INVITE was accepted 457 | user.session.on('accepted', () => { 458 | let message = { 459 | id: 'callResponse', 460 | response : 'accepted', 461 | sdpAnswer: user.sdpAnswer 462 | }; 463 | user.sendMessage(message); 464 | }); 465 | 466 | // Afterwards a Kurento media pipeline is created to initiate communication 467 | createCallPipeline(user, (error, sdpAnswer) => { 468 | if(error) { 469 | if(user.pipeline) user.pipeline.release(); 470 | console.log('Error', error); 471 | rejectCall(JSON.stringify(error)); 472 | } else { 473 | user.sdpAnswer = sdpAnswer; 474 | } 475 | }); 476 | } 477 | 478 | function call(callerId, to, sdpOffer) { 479 | clearCandidatesQueue(callerId); 480 | 481 | var caller = userRegistry.getById(callerId); 482 | caller.callStatus = OUTGOING_CALL; 483 | caller.sdpOffer = sdpOffer; 484 | 485 | function onError(callerReason) { 486 | if (pipeline) pipeline.release(); 487 | if (caller) { 488 | var callerMessage = { 489 | id: 'callResponse', 490 | response: 'rejected' 491 | } 492 | if (callerReason) callerMessage.message = callerReason; 493 | caller.sendMessage(callerMessage); 494 | } 495 | } 496 | 497 | /* *************************************************** 498 | When a call is originated on the web client, an INVITE 499 | is sent to Asterisk, where it will routed to its 500 | destination or handled according to the dial plan 501 | *************************************************** */ 502 | if(!caller.session) { 503 | console.log(`${caller.ext} calling ${to} ...`); 504 | var session = caller.ua.invite(`sip:${to}@${sipServer}`, { 505 | inviteWithoutSdp: true 506 | }); 507 | caller.session = session; 508 | setupCallSession(caller); 509 | } else { 510 | console.log('Session xists', caller.session); 511 | } 512 | } 513 | 514 | function makeHandlerFactory(ext) { 515 | return (session, opts) => { 516 | return new KurentoMediaHandler(ext, session, opts); 517 | } 518 | } 519 | /* *************************************************** 520 | When a client asks to be registered, a SIP user agent is created 521 | *************************************************** */ 522 | function register(id, ext, password, ws, callback) { 523 | function onError(error) { 524 | ws.send(JSON.stringify({id:'registerResponse', response : 'rejected ', message: error})); 525 | } 526 | 527 | if (!ext) { 528 | return onError("empty user ext"); 529 | } 530 | 531 | if (userRegistry.getByExt(ext)) { 532 | return onError("User " + ext + " is already registered"); 533 | } 534 | 535 | var userAgent = new SIP.UA({ 536 | uri: `sip:${ext}@${sipServer}`, 537 | wsServers: [`ws://${sipServer}:8088/ws`], 538 | authorizationUser: ext, 539 | stunServers: ['stun:107.22.152.22:3478'], 540 | password, 541 | mediaHandlerFactory: makeHandlerFactory(ext), 542 | hackIpInContact: true, 543 | traceSip: false 544 | }); 545 | userRegistry.register(new UserSession(id, ext, userAgent, ws)); 546 | 547 | userAgent.on('registered', () => { 548 | console.log(`SIP client registered for ${ext}`); 549 | try { 550 | ws.send(JSON.stringify({id: 'registerResponse', response: 'accepted'})); 551 | } catch(exception) { 552 | onError(exception); 553 | } 554 | }); 555 | 556 | /* ********************************** 557 | When an INVITE message is received, 558 | the web client is notified that there's an 559 | incoming call. The user decides if she accepts 560 | it or rejects it/ 561 | ********************************** */ 562 | userAgent.on('invite', (session) => { 563 | 564 | var fromExt = 'Anonymous'; 565 | if(session.request.headers.From && session.request.headers.From[0]) { 566 | let [ext, _] = session.request.headers.From[0].raw.split(';'); 567 | fromExt = ext; 568 | } 569 | 570 | let callee = userRegistry.getById(id); 571 | if(callee.session) { 572 | session.reject(); 573 | } else { 574 | callee.session = session; 575 | let message = { 576 | id: 'incomingCall', 577 | from: fromExt 578 | }; 579 | callee.sendMessage(message); 580 | } 581 | }); 582 | 583 | userAgent.on('disconnect', function() { 584 | console.log(`${id}:${ext} user agent disconnected`); 585 | }); 586 | } 587 | 588 | function clearCandidatesQueue(sessionId) { 589 | if (candidatesQueue[sessionId]) { 590 | delete candidatesQueue[sessionId]; 591 | } 592 | } 593 | 594 | function onIceCandidate(sessionId, _candidate) { 595 | var candidate = kurento.getComplexType('IceCandidate')(_candidate); 596 | var user = userRegistry.getById(sessionId); 597 | 598 | if (pipelines[user.id] && pipelines[user.id].webRtcEndpoint && pipelines[user.id].webRtcEndpoint[user.id]) { 599 | var webRtcEndpoint = pipelines[user.id].webRtcEndpoint[user.id]; 600 | webRtcEndpoint.addIceCandidate(candidate); 601 | } 602 | else { 603 | if (!candidatesQueue[user.id]) { 604 | candidatesQueue[user.id] = []; 605 | } 606 | candidatesQueue[sessionId].push(candidate); 607 | } 608 | } 609 | 610 | app.use(express.static(path.join(__dirname, 'static'))); 611 | --------------------------------------------------------------------------------