├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE ├── README.md ├── docs.json ├── examples └── streaming.js ├── index.js ├── package.json ├── session.js └── test ├── all.js ├── audiobridge.js ├── connect.js ├── helpers └── url.js ├── streaming.js └── videocall.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | 5 | notifications: 6 | email: 7 | - damon.oehlman@nicta.com.au 8 | irc: irc.freenode.org#rtc.io 9 | 10 | before_install: 11 | - sudo apt-get update -qq 12 | - sudo apt-get install -qq build-essential libmicrohttpd-dev libjansson-dev libnice-dev libssl-dev libsrtp-dev libsofia-sip-ua-dev libglib2.0-dev libogg-dev libini-config-dev libcollection-dev pkg-config gengetopt 13 | - wget http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz 14 | - tar xf opus-1.1.tar.gz 15 | - cd opus-1.1 16 | - ./configure --prefix=/usr 17 | - make 18 | - sudo make install 19 | - cd ../ 20 | - git clone https://github.com/meetecho/janus-gateway.git 21 | - cd janus-gateway 22 | - make 23 | - ./janus & echo $! > janus.pid 24 | - cd ../ 25 | 26 | after_script: 27 | - kill $(cat janus.pid) -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Damon Oehlman (https://github.com/DamonOehlman) 2 | Silvia Pfeiffer (https://github.com/silviapfeiffer) 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2013 National ICT Australia Limited (NICTA) 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rtc-janus 2 | 3 | An in progress node and browserify compatible integration layer for the 4 | [janus WebRTC Gateway](https://github.com/meetecho/janus-gateway). 5 | 6 | 7 | [![NPM](https://nodei.co/npm/rtc-janus.png)](https://nodei.co/npm/rtc-janus/) 8 | 9 | [![Build Status](https://travis-ci.org/rtc-io/rtc-janus.png?branch=master)](https://travis-ci.org/rtc-io/rtc-janus) 10 | [![experimental](http://hughsk.github.io/stability-badges/dist/experimental.svg)](http://github.com/hughsk/stability-badges) 11 | 12 | ## Example Usage 13 | 14 | ```js 15 | var async = require('async'); 16 | var janus = require('rtc-janus'); 17 | 18 | janus('http://localhost:8088/janus', function(err, session) { 19 | if (err) { 20 | return console.error(err); 21 | } 22 | 23 | // activate the required plugins 24 | async.map( 25 | ['streaming', 'videocall'], 26 | session.activate.bind(session), 27 | function(err) { 28 | console.log(arguments); 29 | } 30 | ); 31 | }); 32 | ``` 33 | 34 | ## Reference 35 | 36 | ### JanusSession 37 | 38 | Create a new JanusSession instance 39 | 40 | #### activate(namespace, callback) 41 | 42 | Activate the specified plugin. A plugin can be specified by it's full 43 | namespace (e.g. `janus.plugin.streaming`) or if it is a standard janus 44 | plugin through just it's id (e.g. `streaming`). 45 | 46 | #### connect(uri, callback) 47 | 48 | Create a new connection to the janus gateway 49 | 50 | #### disconnect(callback) 51 | 52 | Disconnect from the gateway 53 | 54 | ## License(s) 55 | 56 | ### Apache 2.0 57 | 58 | Copyright 2014 National ICT Australia Limited (NICTA) 59 | 60 | Licensed under the Apache License, Version 2.0 (the "License"); 61 | you may not use this file except in compliance with the License. 62 | You may obtain a copy of the License at 63 | 64 | http://www.apache.org/licenses/LICENSE-2.0 65 | 66 | Unless required by applicable law or agreed to in writing, software 67 | distributed under the License is distributed on an "AS IS" BASIS, 68 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 69 | See the License for the specific language governing permissions and 70 | limitations under the License. 71 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "badges": { 3 | "nodeico": true, 4 | "travis": true, 5 | "stability": "experimental" 6 | }, 7 | 8 | "license": { 9 | "holder": "National ICT Australia Limited (NICTA)" 10 | } 11 | } -------------------------------------------------------------------------------- /examples/streaming.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var janus = require('..'); 3 | 4 | janus('http://localhost:8088/janus', function(err, session) { 5 | if (err) { 6 | return console.error(err); 7 | } 8 | 9 | // activate the required plugins 10 | async.map( 11 | ['streaming', 'videocall'], 12 | session.activate.bind(session), 13 | function(err) { 14 | console.log(arguments); 15 | } 16 | ); 17 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | /** 5 | # rtc-janus 6 | 7 | An in progress node and browserify compatible integration layer for the 8 | [janus WebRTC Gateway](https://github.com/meetecho/janus-gateway). 9 | 10 | ## Example Usage 11 | 12 | <<< examples/streaming.js 13 | 14 | ## Reference 15 | 16 | **/ 17 | 18 | var JanusSession = require('./session'); 19 | 20 | module.exports = function(uri, opts, callback) { 21 | var session; 22 | 23 | if (typeof opts == 'function') { 24 | callback = opts; 25 | opts = {}; 26 | } 27 | 28 | // create the new janus session 29 | session = new JanusSession(opts); 30 | 31 | // connect 32 | session.connect(uri, function(err) { 33 | if (err) { 34 | return callback(err); 35 | } 36 | 37 | callback(null, session); 38 | }); 39 | 40 | return session; 41 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtc-janus", 3 | "version": "0.1.0", 4 | "description": "Node and browserify compatible integration layer for the Janus WebRTC gateway", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test/all.js", 8 | "gendocs": "gendocs > README.md" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/rtc-io/rtc-janus.git" 13 | }, 14 | "keywords": [ 15 | "webrtc", 16 | "rtc.io", 17 | "janus", 18 | "gateway", 19 | "video", 20 | "streaming" 21 | ], 22 | "author": "Damon Oehlman ", 23 | "license": "Apache 2.0", 24 | "bugs": { 25 | "url": "https://github.com/rtc-io/rtc-janus/issues" 26 | }, 27 | "homepage": "https://github.com/rtc-io/rtc-janus", 28 | "dependencies": { 29 | "hyperquest": "DamonOehlman/hyperquest#69a92a10453e91d36d2677300c068d7cc6e84b83", 30 | "uuid": "~1.4.1", 31 | "cog": "~0.5.1" 32 | }, 33 | "devDependencies": { 34 | "tape": "~2.4.2", 35 | "async": "~0.2.10", 36 | "random-name": "~0.1.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /session.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | var EventEmitter = require('events').EventEmitter; 5 | var util = require('util'); 6 | var request = require('hyperquest'); 7 | var uuid = require('uuid'); 8 | var extend = require('cog/extend'); 9 | var jsonparse = require('cog/jsonparse'); 10 | var reTrailingSlash = /\/$/; 11 | 12 | /** 13 | ### JanusSession 14 | 15 | Create a new JanusSession instance 16 | **/ 17 | function JanusSession(opts) { 18 | if (! (this instanceof JanusSession)) { 19 | return new JanusSession(opts); 20 | } 21 | 22 | EventEmitter.call(this); 23 | 24 | // initilaise the poll interval 25 | this.pollInterval = (opts || {}).pollInterval || 500; 26 | 27 | // initialise the id to null as this is generated by the server 28 | this.id = null; 29 | 30 | // set the uri to null 31 | this.uri = null; 32 | 33 | // initialise the plugins hash which will store plugin handle ids 34 | this.plugins = {}; 35 | 36 | // initialise a poll request object 37 | this.pollRequest = null; 38 | } 39 | 40 | util.inherits(JanusSession, EventEmitter); 41 | module.exports = JanusSession; 42 | 43 | var proto = JanusSession.prototype; 44 | 45 | /** 46 | #### activate(namespace, callback) 47 | 48 | Activate the specified plugin. A plugin can be specified by it's full 49 | namespace (e.g. `janus.plugin.streaming`) or if it is a standard janus 50 | plugin through just it's id (e.g. `streaming`). 51 | 52 | **/ 53 | proto.activate = function(namespace, callback) { 54 | var parts = namespace.split('.'); 55 | var session = this; 56 | var pluginName; 57 | 58 | // if we have not been provided, dot delimited plugin name then 59 | // prepend janus.plugin to the pluginName 60 | if (parts.length === 1) { 61 | namespace = 'janus.plugin.' + namespace; 62 | parts = namespace.split('.'); 63 | } 64 | 65 | // get the plugin name (last part of the namespace) 66 | pluginName = parts[parts.length - 1]; 67 | 68 | this._command('attach', { plugin: namespace }, function(err, data) { 69 | var id = data && data.id; 70 | 71 | if (err) { 72 | return callback(err); 73 | } 74 | 75 | // update the plugin handles to include this handle 76 | session.plugins[pluginName] = id; 77 | 78 | // patch in the plugin method 79 | session[pluginName] = proto._message.bind(session, id); 80 | 81 | // fire the callback 82 | callback(null, id); 83 | }); 84 | }; 85 | 86 | /** 87 | #### connect(uri, callback) 88 | 89 | Create a new connection to the janus gateway 90 | **/ 91 | proto.connect = function(uri, callback) { 92 | var session = this; 93 | var transaction = uuid.v4(); 94 | 95 | // update the url 96 | this.uri = uri.replace(reTrailingSlash, ''); 97 | 98 | this._command('create', function(err, data) { 99 | if (err) { 100 | return callback(err); 101 | } 102 | 103 | session.id = data && data.id; 104 | 105 | // start polling for response messages 106 | session._poll(); 107 | 108 | // trigger the callback 109 | callback(); 110 | }); 111 | }; 112 | 113 | /** 114 | #### disconnect(callback) 115 | 116 | Disconnect from the gateway 117 | **/ 118 | proto.disconnect = function(callback) { 119 | var session = this; 120 | 121 | // send the destroy command 122 | return this._command('destroy', function(err) { 123 | if (err) { 124 | return callback(err); 125 | } 126 | 127 | if (session.pollRequest) { 128 | session.pollRequest.end(); 129 | } 130 | 131 | // clear the session id and trigger the callback 132 | session.id = null; 133 | callback(); 134 | }); 135 | }; 136 | 137 | proto._command = function(command, payload, callback) { 138 | if (typeof payload == 'function') { 139 | callback = payload; 140 | payload = {}; 141 | } 142 | 143 | return this._post(extend({}, payload, { 144 | janus: command 145 | }), callback); 146 | }; 147 | 148 | proto._message = function(id, body, callback) { 149 | var payload; 150 | var session = this; 151 | var transactionId; 152 | var jsep; 153 | var dupBody = {}; 154 | 155 | if (typeof body == 'function') { 156 | callback = body; 157 | body = {}; 158 | } 159 | 160 | Object.keys(body).forEach(function(key) { 161 | if (key === 'jsep') { 162 | jsep = body[key]; 163 | } 164 | else { 165 | dupBody[key] = body[key]; 166 | } 167 | }); 168 | 169 | // initialise the payload 170 | payload = { 171 | body: dupBody, 172 | janus: 'message', 173 | jsep: jsep 174 | }; 175 | 176 | transactionId = this._post(payload, { path: id, ok: 'ack' }, function(err) { 177 | if (err) { 178 | return callback(err); 179 | } 180 | 181 | session.once('event:' + transactionId, function(pluginData, body) { 182 | callback(null, pluginData, body); 183 | }); 184 | }); 185 | 186 | console.log('sent transaction: ' + transactionId); 187 | }; 188 | 189 | proto._poll =function() { 190 | var req; 191 | var session = this; 192 | var chunks = []; 193 | var requestOpts = { 194 | uri: this.uri + '/' + this.id + '?rid=' + Date.now(), 195 | withCredentials: false 196 | }; 197 | 198 | // if we have no session id then abort 199 | if (! this.id) { 200 | return; 201 | } 202 | 203 | // create the request 204 | req = this.pollRequest = request.get(requestOpts); 205 | req.on('response', function(res) { 206 | var ok = res && res.statusCode === 200; 207 | 208 | if (! ok) { 209 | // TODO: more error details 210 | return session.emit('poll:error', new Error('request failed: ' + res.statusCode)); 211 | } 212 | 213 | res.on('data', function(data) { 214 | chunks.push(data.toString()); 215 | }); 216 | 217 | res.on('end', function() { 218 | var body = body = jsonparse(chunks.join('')); 219 | var eventName = body && body.janus; 220 | var data; 221 | 222 | if (body && body.transaction) { 223 | switch (body.janus) { 224 | case 'event': { 225 | data = body.plugindata; 226 | 227 | // extract the embedded data object if it exists 228 | if (data.data) { 229 | data = data.data; 230 | } 231 | 232 | // if we've got embedded result data, then extract that also 233 | if (data.result) { 234 | data = data.result; 235 | } 236 | 237 | eventName = body.janus + ':' + body.transaction; 238 | break; 239 | } 240 | } 241 | 242 | if (data) { 243 | // if we have jsep as part of the body request 244 | // patch that into the data 245 | data.jsep = body.jsep; 246 | } 247 | 248 | session.emit(eventName, data, body); 249 | } 250 | 251 | // // check for success 252 | // if (body.janus !== okResponse) { 253 | // return callback(new Error('request failed: ' + body.janus)); 254 | // } 255 | 256 | // // check the transaction is a match 257 | // if (body.transaction !== payload.transaction) { 258 | // return callback(new Error('request mismatch from janus')); 259 | // } 260 | 261 | // callback(null, body.data);; 262 | 263 | // poll again 264 | session._poll(); 265 | }); 266 | }); 267 | 268 | // intercept errors 269 | // TODO: determine correct handling approach 270 | req.on('error', function(err) { 271 | }); 272 | }; 273 | 274 | proto._post = function(payload, opts, callback) { 275 | var req; 276 | var chunks = []; 277 | var okResponse = 'success'; 278 | var transactionId = uuid.v4(); 279 | var requestOpts = { 280 | uri: this.uri, 281 | withCredentials: false 282 | }; 283 | 284 | if (typeof opts == 'function') { 285 | callback = opts; 286 | opts = {}; 287 | } 288 | 289 | // if we have been provided a custom ok message, then use that instead 290 | if (opts.ok) { 291 | okResponse = opts.ok; 292 | } 293 | 294 | // if we have a valid session id then route the request to that session 295 | if (this.id) { 296 | requestOpts.uri += '/' + this.id + (opts && opts.path ? '/' + opts.path : ''); 297 | } 298 | 299 | // create the request 300 | req = request.post(requestOpts); 301 | 302 | // attach a transaction to the payload 303 | payload = extend({ transaction: transactionId }, payload); 304 | 305 | req.setHeader('Content-Type', 'application/json'); 306 | req.write(JSON.stringify(payload)); 307 | 308 | req.on('response', function(res) { 309 | var ok = res && res.statusCode === 200; 310 | 311 | res.on('data', function(data) { 312 | chunks.push(data.toString()); 313 | }); 314 | 315 | res.on('end', function() { 316 | var body; 317 | 318 | if (! ok) { 319 | // TODO: more error details 320 | return callback(new Error('request failed: ' + res.statusCode)); 321 | } 322 | 323 | // parse the response body 324 | body = jsonparse(chunks.join('')); 325 | 326 | // check for success 327 | if (body.janus !== okResponse) { 328 | return callback(new Error('request failed: ' + body.janus)); 329 | } 330 | 331 | // check the transaction is a match 332 | if (body.transaction !== payload.transaction) { 333 | return callback(new Error('request mismatch from janus')); 334 | } 335 | 336 | callback(null, body.data);; 337 | }); 338 | }); 339 | 340 | req.on('error', callback); 341 | req.end(); 342 | 343 | // return the transaction id 344 | return transactionId; 345 | }; -------------------------------------------------------------------------------- /test/all.js: -------------------------------------------------------------------------------- 1 | require('./connect'); 2 | require('./streaming'); 3 | require('./audiobridge'); -------------------------------------------------------------------------------- /test/audiobridge.js: -------------------------------------------------------------------------------- 1 | var janus = require('../'); 2 | var test = require('tape'); 3 | var randomName = require('random-name'); 4 | var session; 5 | var baseUrl = require('./helpers/url'); 6 | var roomId; 7 | 8 | test('create a new session', function(t) { 9 | t.plan(2); 10 | 11 | session = janus(baseUrl, function(err) { 12 | t.ifError(err); 13 | t.ok(session.id, 'got valid session id'); 14 | }); 15 | }); 16 | 17 | test('request the audiobridge plugin', function(t) { 18 | t.plan(2); 19 | 20 | session.activate('audiobridge', function(err) { 21 | t.ifError(err); 22 | t.ok(session.plugins.audiobridge, 'audiobridge plugin activated'); 23 | }); 24 | }); 25 | 26 | test('request a new room', function(t) { 27 | var data = { 28 | request: 'create', 29 | description: 'My Awesome Test Room', 30 | sampling: 16000, 31 | record: false 32 | }; 33 | 34 | t.plan(3); 35 | session.audiobridge(data, function(err, response) { 36 | t.ifError(err); 37 | t.ok(response && response.audiobridge === 'created', 'created ok'); 38 | 39 | // save the room id 40 | t.ok(roomId = response.room, 'got room id'); 41 | }); 42 | }); 43 | 44 | test('participant #1 join the new room', function(t) { 45 | var data = { 46 | request: 'join', 47 | room: roomId, 48 | display: randomName() 49 | }; 50 | 51 | t.plan(4); 52 | session.audiobridge(data, function(err, response) { 53 | t.ifError(err); 54 | t.ok(response && response.audiobridge === 'joined', 'joined ok'); 55 | t.equal(response.room, roomId, 'joined correct room'); 56 | t.equal(response.participants.length, 0, 'we are the first participant'); 57 | }); 58 | }); 59 | 60 | test('participant #2 join the new room', function(t) { 61 | var data = { 62 | request: 'join', 63 | room: roomId, 64 | display: randomName() 65 | }; 66 | 67 | t.plan(4); 68 | session.audiobridge(data, function(err, response) { 69 | t.ifError(err); 70 | t.ok(response && response.audiobridge === 'joined', 'joined ok'); 71 | t.equal(response.room, roomId, 'joined correct room'); 72 | t.equal(response.participants.length, 1, 'we are the second participant'); 73 | }); 74 | }); 75 | 76 | test('disconnect session', function(t) { 77 | t.plan(1); 78 | session.disconnect(function(err) { 79 | t.ifError(err, 'ok'); 80 | }); 81 | }); -------------------------------------------------------------------------------- /test/connect.js: -------------------------------------------------------------------------------- 1 | var JanusSession = require('../session'); 2 | var test = require('tape'); 3 | var session; 4 | var baseUrl = require('./helpers/url'); 5 | var janus = require('../'); 6 | 7 | test('create a new session', function(t) { 8 | t.plan(1); 9 | session = new JanusSession(); 10 | t.ok(session instanceof JanusSession, 'session instance created'); 11 | }); 12 | 13 | test('attempt connection with server', function(t) { 14 | t.plan(2); 15 | 16 | // attempt connection 17 | session.connect(baseUrl, function(err) { 18 | t.ifError(err); 19 | t.ok(session.id, 'assigned session id'); 20 | }); 21 | }); 22 | 23 | test('disconnect', function(t) { 24 | t.plan(1); 25 | session.disconnect(function(err) { 26 | t.ifError(err); 27 | }); 28 | }); 29 | 30 | test('create a session through the main janus module entry point', function(t) { 31 | t.plan(2); 32 | 33 | janus(baseUrl, function(err, s) { 34 | t.ifError(err); 35 | t.ok(s && s.id, 'assigned session id'); 36 | 37 | // save the session 38 | session = s; 39 | }); 40 | }); 41 | 42 | test('disconnect session #2', function(t) { 43 | t.plan(1); 44 | session.disconnect(function(err) { 45 | t.ifError(err); 46 | }); 47 | }); -------------------------------------------------------------------------------- /test/helpers/url.js: -------------------------------------------------------------------------------- 1 | module.exports = process.env.JANUS_URL || 'http://localhost:8088/janus'; -------------------------------------------------------------------------------- /test/streaming.js: -------------------------------------------------------------------------------- 1 | var janus = require('../'); 2 | var test = require('tape'); 3 | var session; 4 | var baseUrl = require('./helpers/url'); 5 | var streamList; 6 | 7 | test('create a new session', function(t) { 8 | t.plan(2); 9 | 10 | session = janus(baseUrl, function(err) { 11 | t.ifError(err); 12 | t.ok(session.id, 'got valid session id'); 13 | }); 14 | }); 15 | 16 | test('request the streaming plugin', function(t) { 17 | t.plan(2); 18 | 19 | session.activate('streaming', function(err) { 20 | t.ifError(err); 21 | 22 | // ensure the session plugins videocall is active 23 | t.ok(session.plugins.streaming, 'streaming plugin activated'); 24 | }); 25 | }); 26 | 27 | test('request the steaming session list', function(t) { 28 | t.plan(4); 29 | 30 | session.streaming({ request: 'list' }, function(err, data) { 31 | t.ifError(err); 32 | t.ok(data && data.list, 'got expected response'); 33 | t.ok(Array.isArray(data.list), 'list is a valid array'); 34 | t.ok(data.list.length > 0, 'have valid stream information'); 35 | 36 | streamList = data.list; 37 | }); 38 | }); 39 | 40 | test('ensure we have stream 2 to test against', function(t) { 41 | t.plan(1); 42 | targetStream = streamList.filter(function(stream) { 43 | return stream.id === 2; 44 | })[0]; 45 | t.ok(targetStream, 'have test stream (id == 2)'); 46 | }); 47 | 48 | test('request stream play', function(t) { 49 | t.plan(3); 50 | session.streaming({ request: 'watch', id: 2 }, function(err, data) { 51 | t.ifError(err); 52 | t.ok(data && data.status === 'preparing', 'preparing stream'); 53 | t.ok(data.jsep, 'got jsep data in response'); 54 | }); 55 | }); 56 | 57 | test('disconnect session', function(t) { 58 | t.plan(1); 59 | session.disconnect(function(err) { 60 | t.ifError(err, 'ok'); 61 | }); 62 | }); -------------------------------------------------------------------------------- /test/videocall.js: -------------------------------------------------------------------------------- 1 | var janus = require('../'); 2 | var test = require('tape'); 3 | var session; 4 | var baseUrl = require('./helpers/url'); 5 | 6 | test('create a new session', function(t) { 7 | t.plan(2); 8 | 9 | session = janus(baseUrl, function(err) { 10 | t.ifError(err); 11 | t.ok(session.id, 'got valid session id'); 12 | }); 13 | }); 14 | 15 | test('request the videocall plugin', function(t) { 16 | t.plan(2); 17 | 18 | session.activate('videocall', function(err) { 19 | t.ifError(err); 20 | 21 | // ensure the session plugins videocall is active 22 | t.ok(session.plugins.videocall, 'video call plugin activated'); 23 | }); 24 | }); --------------------------------------------------------------------------------