├── .meteor ├── .gitignore ├── release └── packages ├── packages ├── webrtcio │ ├── .gitignore │ ├── .npm │ │ └── package │ │ │ ├── .gitignore │ │ │ ├── README │ │ │ └── npm-shrinkwrap.json │ ├── meteor-webrtcio.js │ ├── package.js │ └── webrtc.io.js └── .gitignore ├── .gitignore ├── public ├── vid │ └── output.mp4 ├── fonts │ ├── Neou-Bold.otf │ ├── Neou-Bold.ttf │ ├── Neou-Thin.otf │ ├── Neou-Thin.ttf │ └── NEOU typeface - License.txt └── css │ └── nyu-hacks.css ├── smart.json ├── smart.lock ├── nyu-hacks.js └── nyu-hacks.html /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | 0.6.6.3 2 | -------------------------------------------------------------------------------- /packages/webrtcio/.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | -------------------------------------------------------------------------------- /packages/.gitignore: -------------------------------------------------------------------------------- 1 | /bootstrap-3 2 | /opentok 3 | -------------------------------------------------------------------------------- /packages/webrtcio/.npm/package/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Meteor ### 2 | .meteor/local 3 | .meteor/meteorite 4 | -------------------------------------------------------------------------------- /packages/webrtcio/meteor-webrtcio.js: -------------------------------------------------------------------------------- 1 | webRTC = Npm.require('webrtc.io').listen(4040); -------------------------------------------------------------------------------- /public/vid/output.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/nyu-hacks/master/public/vid/output.mp4 -------------------------------------------------------------------------------- /public/fonts/Neou-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/nyu-hacks/master/public/fonts/Neou-Bold.otf -------------------------------------------------------------------------------- /public/fonts/Neou-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/nyu-hacks/master/public/fonts/Neou-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/Neou-Thin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/nyu-hacks/master/public/fonts/Neou-Thin.otf -------------------------------------------------------------------------------- /public/fonts/Neou-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/nyu-hacks/master/public/fonts/Neou-Thin.ttf -------------------------------------------------------------------------------- /smart.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | "bootstrap-3": {}, 4 | "opentok": {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/fonts/NEOU typeface - License.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/nyu-hacks/master/public/fonts/NEOU typeface - License.txt -------------------------------------------------------------------------------- /packages/webrtcio/package.js: -------------------------------------------------------------------------------- 1 | Npm.depends({ 2 | 'webrtc.io': '0.0.4' 3 | }); 4 | 5 | Package.on_use(function(api) { 6 | api.add_files('meteor-webrtcio.js', 'server'); 7 | api.add_files('webrtc.io.js', 'client'); 8 | }); -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # 3 | # 'meteor add' and 'meteor remove' will edit this file for you, 4 | # but you can also edit it by hand. 5 | 6 | standard-app-packages 7 | autopublish 8 | insecure 9 | preserve-inputs 10 | bootstrap-3 11 | accounts-facebook 12 | accounts-ui 13 | opentok 14 | webrtcio 15 | -------------------------------------------------------------------------------- /packages/webrtcio/.npm/package/README: -------------------------------------------------------------------------------- 1 | This directory and the files immediately inside it are automatically generated 2 | when you change this package's NPM dependencies. Commit the files in this 3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control 4 | so that others run the same versions of sub-dependencies. 5 | 6 | You should NOT check in the node_modules directory that Meteor automatically 7 | creates; if you are using git, the .gitignore file tells git to ignore it. 8 | -------------------------------------------------------------------------------- /smart.lock: -------------------------------------------------------------------------------- 1 | { 2 | "meteor": {}, 3 | "dependencies": { 4 | "basePackages": { 5 | "bootstrap-3": {}, 6 | "opentok": {} 7 | }, 8 | "packages": { 9 | "bootstrap-3": { 10 | "git": "https://github.com/mangasocial/meteor-bootstrap-3.git", 11 | "tag": "v0.3.6", 12 | "commit": "711dbcabe333c8714298b2fd1de97fdcdae6efa7" 13 | }, 14 | "opentok": { 15 | "git": "https://github.com/cultofmetatron/OpenTok-meteor.git", 16 | "tag": "v1.0.0", 17 | "commit": "f21c8bfa4c48793632a0ccb09b3d765499aa1e7b" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/webrtcio/.npm/package/npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "webrtc.io": { 4 | "version": "0.0.4", 5 | "dependencies": { 6 | "ws": { 7 | "version": "0.4.31", 8 | "dependencies": { 9 | "commander": { 10 | "version": "0.6.1" 11 | }, 12 | "nan": { 13 | "version": "0.3.2" 14 | }, 15 | "tinycolor": { 16 | "version": "0.0.1" 17 | }, 18 | "options": { 19 | "version": "0.0.5" 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nyu-hacks.js: -------------------------------------------------------------------------------- 1 | if (Meteor.isClient) { 2 | 3 | Template.main.getname = function() { 4 | var k = Meteor.user().profile.name; 5 | console.log(k); 6 | return k; 7 | }; 8 | 9 | Template.main.events({ 10 | 'click #logout' : function() { 11 | Meteor.logout() 12 | } 13 | }); 14 | 15 | Accounts.ui.config({ 16 | requestPermissions: { 17 | facebook: ['user_likes'], 18 | }, 19 | passwordSignupFields: 'USERNAME_ONLY' 20 | }); 21 | } 22 | 23 | if (Meteor.isServer) { 24 | Meteor.startup(function () { 25 | // code to run on server at startup 26 | rtc.on('chat_msg', function(data, socket) { 27 | var roomList = rtc.rooms[data.room] || []; 28 | console.log(socket); 29 | for (var i = 0; i < roomList.length; i++) { 30 | var socketId = roomList[i]; 31 | 32 | if (socketId !== socket.id) { 33 | var soc = rtc.getSocket(socketId); 34 | 35 | if (soc) { 36 | soc.send(JSON.stringify({ 37 | "eventName": "receive_chat_msg", 38 | "data": { 39 | "messages": data.messages, 40 | "color": data.color 41 | } 42 | }), function(error) { 43 | if (error) { 44 | console.log(error); 45 | } 46 | }); 47 | } 48 | } 49 | } 50 | }); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /nyu-hacks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Colang - The easiest way to practice a foreign language. 4 | 5 | 6 | 7 | 8 | {{#if currentUser}} 9 | 10 | {{>main}} 11 | 12 | {{else}} 13 | 14 | {{>splash}} 15 | 16 | {{/if}} 17 | 18 | 19 | 20 | 35 | 36 | 37 | 78 | -------------------------------------------------------------------------------- /public/css/nyu-hacks.css: -------------------------------------------------------------------------------- 1 | /* 2 | * HTML5 Boilerplate 3 | * 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | */ 8 | 9 | /* ========================================================================== 10 | Base styles: opinionated defaults 11 | ========================================================================== */ 12 | 13 | html, 14 | button, 15 | input, 16 | select, 17 | textarea { 18 | color: #222; 19 | } 20 | 21 | body { 22 | font-size: 1em; 23 | line-height: 1.4; 24 | } 25 | 26 | .navbar-fixed-top { 27 | background: rgb(0,85,0); 28 | } 29 | 30 | /* 31 | * Remove text-shadow in selection highlight: h5bp.com/i 32 | * These selection rule sets have to be separate. 33 | * Customize the background color to match your design. 34 | */ 35 | 36 | ::-moz-selection { 37 | background: #b3d4fc; 38 | text-shadow: none; 39 | } 40 | 41 | ::selection { 42 | background: #b3d4fc; 43 | text-shadow: none; 44 | } 45 | 46 | /* 47 | * A better looking default horizontal rule 48 | */ 49 | 50 | hr { 51 | display: block; 52 | height: 1px; 53 | border: 0; 54 | border-top: 1px solid #ccc; 55 | margin: 1em 0; 56 | padding: 0; 57 | } 58 | 59 | /* 60 | * Remove the gap between images and the bottom of their containers: h5bp.com/i/440 61 | */ 62 | 63 | img { 64 | vertical-align: middle; 65 | } 66 | 67 | /* 68 | * Remove default fieldset styles. 69 | */ 70 | 71 | fieldset { 72 | border: 0; 73 | margin: 0; 74 | padding: 0; 75 | } 76 | 77 | /* 78 | * Allow only vertical resizing of textareas. 79 | */ 80 | 81 | textarea { 82 | resize: vertical; 83 | } 84 | 85 | /* ========================================================================== 86 | Chrome Frame prompt 87 | ========================================================================== */ 88 | 89 | .chromeframe { 90 | margin: 0.2em 0; 91 | background: #ccc; 92 | color: #000; 93 | padding: 0.2em 0; 94 | } 95 | 96 | /* ========================================================================== 97 | Author's custom styles 98 | ========================================================================== */ 99 | body { 100 | color: #FFF; 101 | background: none; 102 | font-family: Helvetica, Arial, sans-serif; 103 | font-size: 1.0em; 104 | } 105 | h1, h2, h3, h4 { 106 | font-family: dafont, sans-serif; 107 | font-weight: normal; 108 | text-align: center; 109 | padding: 10px; 110 | margin-top: 5px; 111 | background: none; 112 | } 113 | h4 { 114 | font-size: 1.1em; 115 | line-height: 1.0em; 116 | color: #9900FF; 117 | } 118 | .center { 119 | text-align: center; 120 | } 121 | div#container { 122 | background: none; 123 | padding: 0px; 124 | padding-bottom: 150px; 125 | } 126 | #main { 127 | padding-top:20%; 128 | color: black; 129 | } 130 | #heading { 131 | /* Fallback for web browsers that doesn't support RGBa */ 132 | background: rgb(0, 0, 0); 133 | /* RGBa with 0.6 opacity */ 134 | background: rgba(0, 0, 0, 0.6); 135 | /* For IE 5.5 - 7*/ 136 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000); 137 | /* For IE 8*/ 138 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)"; 139 | 140 | text-align: center; 141 | min-height: 200px; 142 | margin-top: 150px; 143 | } 144 | #video_background { 145 | position: absolute; 146 | top: 0px; 147 | right: 0px; 148 | min-width: 100%; 149 | height: 600px; 150 | width: auto; 151 | height: auto; 152 | z-index: -1000; 153 | overflow: hidden; 154 | } 155 | #logistics { 156 | margin: 90px 0px; 157 | } 158 | #banner { 159 | background: #000; 160 | } 161 | div.section { 162 | padding: 15px; 163 | text-align: left; 164 | font-size: 0.8em; 165 | } 166 | #about { 167 | background: #000; 168 | } 169 | .highlight { 170 | color: #9900FF; 171 | } 172 | .black { 173 | color: #000; 174 | } 175 | .red { 176 | color: red; 177 | } 178 | .center { text-align: center; } 179 | .transbackground { 180 | /* Fallback for web browsers that doesn't support RGBa */ 181 | background: rgb(0, 0, 0); 182 | /* RGBa with 0.6 opacity */ 183 | background: rgba(0, 0, 0, 0.6); 184 | /* For IE 5.5 - 7*/ 185 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000); 186 | /* For IE 8*/ 187 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)"; 188 | } 189 | #theme { 190 | padding: 50px 0px; 191 | background: url('/static/img/crowd.png') repeat scroll center; 192 | background-size: 100%; 193 | color: #333; 194 | } 195 | #sponsors { 196 | padding: 25px 0px; 197 | } 198 | #sponsors .row-fluid { 199 | padding: 15px 0px; 200 | } 201 | #sponsors h3 { 202 | color: #9900FF; 203 | } 204 | div.logo_container { 205 | text-align: center; 206 | } 207 | img.logo { 208 | width: 200px; 209 | } 210 | div#tisch { 211 | padding: 50px 0px; 212 | background: url('/static/img/tisch.jpg') no-repeat fixed center; 213 | background-size: 100%; 214 | } 215 | 216 | 217 | 218 | @font-face 219 | { 220 | font-family: dafont; 221 | src: url(../fonts/Neou-Thin.otf); 222 | src: url(../fonts/Neou-Thin.ttf); 223 | } 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | /* ========================================================================== 236 | Helper classes 237 | ========================================================================== */ 238 | 239 | /* 240 | * Image replacement 241 | */ 242 | 243 | .ir { 244 | background-color: transparent; 245 | border: 0; 246 | overflow: hidden; 247 | /* IE 6/7 fallback */ 248 | *text-indent: -9999px; 249 | } 250 | 251 | .ir:before { 252 | content: ""; 253 | display: block; 254 | width: 0; 255 | height: 150%; 256 | } 257 | 258 | /* 259 | * Hide from both screenreaders and browsers: h5bp.com/u 260 | */ 261 | 262 | .hidden { 263 | display: none !important; 264 | visibility: hidden; 265 | } 266 | 267 | /* 268 | * Hide only visually, but have it available for screenreaders: h5bp.com/v 269 | */ 270 | 271 | .visuallyhidden { 272 | border: 0; 273 | clip: rect(0 0 0 0); 274 | height: 1px; 275 | margin: -1px; 276 | overflow: hidden; 277 | padding: 0; 278 | position: absolute; 279 | width: 1px; 280 | } 281 | 282 | /* 283 | * Extends the .visuallyhidden class to allow the element to be focusable 284 | * when navigated to via the keyboard: h5bp.com/p 285 | */ 286 | 287 | .visuallyhidden.focusable:active, 288 | .visuallyhidden.focusable:focus { 289 | clip: auto; 290 | height: auto; 291 | margin: 0; 292 | overflow: visible; 293 | position: static; 294 | width: auto; 295 | } 296 | 297 | /* 298 | * Hide visually and from screenreaders, but maintain layout 299 | */ 300 | 301 | .invisible { 302 | visibility: hidden; 303 | } 304 | 305 | /* 306 | * Clearfix: contain floats 307 | * 308 | * For modern browsers 309 | * 1. The space content is one way to avoid an Opera bug when the 310 | * `contenteditable` attribute is included anywhere else in the document. 311 | * Otherwise it causes space to appear at the top and bottom of elements 312 | * that receive the `clearfix` class. 313 | * 2. The use of `table` rather than `block` is only necessary if using 314 | * `:before` to contain the top-margins of child elements. 315 | */ 316 | 317 | .clearfix:before, 318 | .clearfix:after { 319 | content: " "; /* 1 */ 320 | display: table; /* 2 */ 321 | } 322 | 323 | .clearfix:after { 324 | clear: both; 325 | } 326 | 327 | /* 328 | * For IE 6/7 only 329 | * Include this rule to trigger hasLayout and contain floats. 330 | */ 331 | 332 | .clearfix { 333 | *zoom: 1; 334 | } 335 | 336 | /* ========================================================================== 337 | EXAMPLE Media Queries for Responsive Design. 338 | These examples override the primary ('mobile first') styles. 339 | Modify as content requires. 340 | ========================================================================== */ 341 | 342 | @media only screen and (min-width: 35em) { 343 | /* Style adjustments for viewports that meet the condition */ 344 | } 345 | 346 | @media print, 347 | (-o-min-device-pixel-ratio: 5/4), 348 | (-webkit-min-device-pixel-ratio: 1.25), 349 | (min-resolution: 120dpi) { 350 | /* Style adjustments for high resolution devices */ 351 | } 352 | 353 | /* ========================================================================== 354 | Print styles. 355 | Inlined to avoid required HTTP connection: h5bp.com/r 356 | ========================================================================== */ 357 | 358 | @media print { 359 | * { 360 | background: transparent !important; 361 | color: #000 !important; /* Black prints faster: h5bp.com/s */ 362 | box-shadow: none !important; 363 | text-shadow: none !important; 364 | } 365 | 366 | a, 367 | a:visited { 368 | text-decoration: underline; 369 | } 370 | 371 | a[href]:after { 372 | content: " (" attr(href) ")"; 373 | } 374 | 375 | abbr[title]:after { 376 | content: " (" attr(title) ")"; 377 | } 378 | 379 | /* 380 | * Don't show links for images, or javascript/internal links 381 | */ 382 | 383 | .ir a:after, 384 | a[href^="javascript:"]:after, 385 | a[href^="#"]:after { 386 | content: ""; 387 | } 388 | 389 | pre, 390 | blockquote { 391 | border: 1px solid #999; 392 | page-break-inside: avoid; 393 | } 394 | 395 | thead { 396 | display: table-header-group; /* h5bp.com/t */ 397 | } 398 | 399 | tr, 400 | img { 401 | page-break-inside: avoid; 402 | } 403 | 404 | img { 405 | max-width: 100% !important; 406 | } 407 | 408 | @page { 409 | margin: 0.5cm; 410 | } 411 | 412 | p, 413 | h2, 414 | h3 { 415 | orphans: 3; 416 | widows: 3; 417 | } 418 | 419 | h2, 420 | h3 { 421 | page-break-after: avoid; 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /packages/webrtcio/webrtc.io.js: -------------------------------------------------------------------------------- 1 | console.log("webrtc.io.js loaded"); 2 | 3 | //CLIENT 4 | 5 | // Fallbacks for vendor-specific variables until the spec is finalized. 6 | 7 | var PeerConnection = (window.PeerConnection || window.webkitPeerConnection00 || window.webkitRTCPeerConnection || window.mozRTCPeerConnection); 8 | var URL = (window.URL || window.webkitURL || window.msURL || window.oURL); 9 | var getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); 10 | var nativeRTCIceCandidate = (window.mozRTCIceCandidate || window.RTCIceCandidate); 11 | var nativeRTCSessionDescription = (window.mozRTCSessionDescription || window.RTCSessionDescription); // order is very important: "RTCSessionDescription" defined in Nighly but useless 12 | 13 | var sdpConstraints = { 14 | 'mandatory': { 15 | 'OfferToReceiveAudio': true, 16 | 'OfferToReceiveVideo': true 17 | } 18 | }; 19 | 20 | if (navigator.webkitGetUserMedia) { 21 | if (!webkitMediaStream.prototype.getVideoTracks) { 22 | webkitMediaStream.prototype.getVideoTracks = function() { 23 | return this.videoTracks; 24 | }; 25 | webkitMediaStream.prototype.getAudioTracks = function() { 26 | return this.audioTracks; 27 | }; 28 | } 29 | 30 | // New syntax of getXXXStreams method in M26. 31 | if (!webkitRTCPeerConnection.prototype.getLocalStreams) { 32 | webkitRTCPeerConnection.prototype.getLocalStreams = function() { 33 | return this.localStreams; 34 | }; 35 | webkitRTCPeerConnection.prototype.getRemoteStreams = function() { 36 | return this.remoteStreams; 37 | }; 38 | } 39 | } 40 | 41 | (function() { 42 | 43 | var rtc; 44 | if ('undefined' === typeof module) { 45 | rtc = this.rtc = {}; 46 | } else { 47 | rtc = module.exports = {}; 48 | } 49 | 50 | 51 | // Holds a connection to the server. 52 | rtc._socket = null; 53 | 54 | // Holds identity for the client 55 | rtc._me = null; 56 | 57 | // Holds callbacks for certain events. 58 | rtc._events = {}; 59 | 60 | rtc.on = function(eventName, callback) { 61 | rtc._events[eventName] = rtc._events[eventName] || []; 62 | rtc._events[eventName].push(callback); 63 | }; 64 | 65 | rtc.fire = function(eventName, _) { 66 | var events = rtc._events[eventName]; 67 | var args = Array.prototype.slice.call(arguments, 1); 68 | 69 | if (!events) { 70 | return; 71 | } 72 | 73 | for (var i = 0, len = events.length; i < len; i++) { 74 | events[i].apply(null, args); 75 | } 76 | }; 77 | 78 | // Holds the STUN/ICE server to use for PeerConnections. 79 | rtc.SERVER = function() { 80 | if (navigator.mozGetUserMedia) { 81 | return { 82 | "iceServers": [{ 83 | "url": "stun:23.21.150.121" 84 | }] 85 | }; 86 | } 87 | return { 88 | "iceServers": [{ 89 | "url": "stun:stun.l.google.com:19302" 90 | }] 91 | }; 92 | }; 93 | 94 | 95 | // Reference to the lone PeerConnection instance. 96 | rtc.peerConnections = {}; 97 | 98 | // Array of known peer socket ids 99 | rtc.connections = []; 100 | // Stream-related variables. 101 | rtc.streams = []; 102 | rtc.numStreams = 0; 103 | rtc.initializedStreams = 0; 104 | 105 | 106 | // Reference to the data channels 107 | rtc.dataChannels = {}; 108 | 109 | // PeerConnection datachannel configuration 110 | rtc.dataChannelConfig = { 111 | "optional": [{ 112 | "RtpDataChannels": true 113 | }, { 114 | "DtlsSrtpKeyAgreement": true 115 | }] 116 | }; 117 | 118 | rtc.pc_constraints = { 119 | "optional": [{ 120 | "DtlsSrtpKeyAgreement": true 121 | }] 122 | }; 123 | 124 | 125 | // check whether data channel is supported. 126 | rtc.checkDataChannelSupport = function() { 127 | try { 128 | // raises exception if createDataChannel is not supported 129 | var pc = new PeerConnection(rtc.SERVER(), rtc.dataChannelConfig); 130 | var channel = pc.createDataChannel('supportCheck', { 131 | reliable: false 132 | }); 133 | channel.close(); 134 | return true; 135 | } catch (e) { 136 | return false; 137 | } 138 | }; 139 | 140 | rtc.dataChannelSupport = rtc.checkDataChannelSupport(); 141 | 142 | 143 | /** 144 | * Connects to the websocket server. 145 | */ 146 | rtc.connect = function(server, room) { 147 | console.log(server, room); 148 | room = room || ""; // by default, join a room called the blank string 149 | rtc._socket = new WebSocket("ws://localhost:4040"); 150 | console.log(rtc._socket); 151 | rtc._socket.onopen = function() { 152 | 153 | rtc._socket.send(JSON.stringify({ 154 | "eventName": "join_room", 155 | "data": { 156 | "room": room 157 | } 158 | })); 159 | 160 | rtc._socket.onmessage = function(msg) { 161 | var json = JSON.parse(msg.data); 162 | rtc.fire(json.eventName, json.data); 163 | }; 164 | 165 | rtc._socket.onerror = function(err) { 166 | console.error('onerror'); 167 | console.error(err); 168 | }; 169 | 170 | rtc._socket.onclose = function(data) { 171 | rtc.fire('disconnect stream', rtc._socket.id); 172 | delete rtc.peerConnections[rtc._socket.id]; 173 | }; 174 | 175 | rtc.on('get_peers', function(data) { 176 | rtc.connections = data.connections; 177 | rtc._me = data.you; 178 | // fire connections event and pass peers 179 | rtc.fire('connections', rtc.connections); 180 | }); 181 | 182 | rtc.on('receive_ice_candidate', function(data) { 183 | var candidate = new nativeRTCIceCandidate(data); 184 | rtc.peerConnections[data.socketId].addIceCandidate(candidate); 185 | rtc.fire('receive ice candidate', candidate); 186 | }); 187 | 188 | rtc.on('new_peer_connected', function(data) { 189 | rtc.connections.push(data.socketId); 190 | 191 | var pc = rtc.createPeerConnection(data.socketId); 192 | for (var i = 0; i < rtc.streams.length; i++) { 193 | var stream = rtc.streams[i]; 194 | pc.addStream(stream); 195 | } 196 | }); 197 | 198 | rtc.on('remove_peer_connected', function(data) { 199 | rtc.fire('disconnect stream', data.socketId); 200 | delete rtc.peerConnections[data.socketId]; 201 | }); 202 | 203 | rtc.on('receive_offer', function(data) { 204 | rtc.receiveOffer(data.socketId, data.sdp); 205 | rtc.fire('receive offer', data); 206 | }); 207 | 208 | rtc.on('receive_answer', function(data) { 209 | rtc.receiveAnswer(data.socketId, data.sdp); 210 | rtc.fire('receive answer', data); 211 | }); 212 | 213 | rtc.fire('connect'); 214 | }; 215 | }; 216 | 217 | 218 | rtc.sendOffers = function() { 219 | for (var i = 0, len = rtc.connections.length; i < len; i++) { 220 | var socketId = rtc.connections[i]; 221 | rtc.sendOffer(socketId); 222 | } 223 | }; 224 | 225 | rtc.onClose = function(data) { 226 | rtc.on('close_stream', function() { 227 | rtc.fire('close_stream', data); 228 | }); 229 | }; 230 | 231 | rtc.createPeerConnections = function() { 232 | for (var i = 0; i < rtc.connections.length; i++) { 233 | rtc.createPeerConnection(rtc.connections[i]); 234 | } 235 | }; 236 | 237 | rtc.createPeerConnection = function(id) { 238 | 239 | var config = rtc.pc_constraints; 240 | if (rtc.dataChannelSupport) config = rtc.dataChannelConfig; 241 | 242 | var pc = rtc.peerConnections[id] = new PeerConnection(rtc.SERVER(), config); 243 | pc.onicecandidate = function(event) { 244 | if (event.candidate) { 245 | rtc._socket.send(JSON.stringify({ 246 | "eventName": "send_ice_candidate", 247 | "data": { 248 | "label": event.candidate.sdpMLineIndex, 249 | "candidate": event.candidate.candidate, 250 | "socketId": id 251 | } 252 | })); 253 | } 254 | rtc.fire('ice candidate', event.candidate); 255 | }; 256 | 257 | pc.onopen = function() { 258 | // TODO: Finalize this API 259 | rtc.fire('peer connection opened'); 260 | }; 261 | 262 | pc.onaddstream = function(event) { 263 | // TODO: Finalize this API 264 | rtc.fire('add remote stream', event.stream, id); 265 | }; 266 | 267 | if (rtc.dataChannelSupport) { 268 | pc.ondatachannel = function(evt) { 269 | console.log('data channel connecting ' + id); 270 | rtc.addDataChannel(id, evt.channel); 271 | }; 272 | } 273 | 274 | return pc; 275 | }; 276 | 277 | rtc.sendOffer = function(socketId) { 278 | var pc = rtc.peerConnections[socketId]; 279 | 280 | var constraints = { 281 | "optional": [], 282 | "mandatory": { 283 | "MozDontOfferDataChannel": true 284 | } 285 | }; 286 | // temporary measure to remove Moz* constraints in Chrome 287 | if (navigator.webkitGetUserMedia) { 288 | for (var prop in constraints.mandatory) { 289 | if (prop.indexOf("Moz") != -1) { 290 | delete constraints.mandatory[prop]; 291 | } 292 | } 293 | } 294 | constraints = mergeConstraints(constraints, sdpConstraints); 295 | 296 | pc.createOffer(function(session_description) { 297 | session_description.sdp = preferOpus(session_description.sdp); 298 | pc.setLocalDescription(session_description); 299 | rtc._socket.send(JSON.stringify({ 300 | "eventName": "send_offer", 301 | "data": { 302 | "socketId": socketId, 303 | "sdp": session_description 304 | } 305 | })); 306 | }, null, sdpConstraints); 307 | }; 308 | 309 | rtc.receiveOffer = function(socketId, sdp) { 310 | var pc = rtc.peerConnections[socketId]; 311 | rtc.sendAnswer(socketId, sdp); 312 | }; 313 | 314 | rtc.sendAnswer = function(socketId, sdp) { 315 | var pc = rtc.peerConnections[socketId]; 316 | pc.setRemoteDescription(new nativeRTCSessionDescription(sdp)); 317 | pc.createAnswer(function(session_description) { 318 | pc.setLocalDescription(session_description); 319 | rtc._socket.send(JSON.stringify({ 320 | "eventName": "send_answer", 321 | "data": { 322 | "socketId": socketId, 323 | "sdp": session_description 324 | } 325 | })); 326 | //TODO Unused variable!? 327 | var offer = pc.remoteDescription; 328 | }, null, sdpConstraints); 329 | }; 330 | 331 | 332 | rtc.receiveAnswer = function(socketId, sdp) { 333 | var pc = rtc.peerConnections[socketId]; 334 | pc.setRemoteDescription(new nativeRTCSessionDescription(sdp)); 335 | }; 336 | 337 | 338 | rtc.createStream = function(opt, onSuccess, onFail) { 339 | var options; 340 | onSuccess = onSuccess || function() {}; 341 | onFail = onFail || function() {}; 342 | 343 | options = { 344 | video: !! opt.video, 345 | audio: !! opt.audio 346 | }; 347 | 348 | if (getUserMedia) { 349 | rtc.numStreams++; 350 | getUserMedia.call(navigator, options, function(stream) { 351 | 352 | rtc.streams.push(stream); 353 | rtc.initializedStreams++; 354 | onSuccess(stream); 355 | if (rtc.initializedStreams === rtc.numStreams) { 356 | rtc.fire('ready'); 357 | } 358 | }, function() { 359 | alert("Could not connect stream."); 360 | onFail(); 361 | }); 362 | } else { 363 | alert('webRTC is not yet supported in this browser.'); 364 | } 365 | }; 366 | 367 | rtc.addStreams = function() { 368 | for (var i = 0; i < rtc.streams.length; i++) { 369 | var stream = rtc.streams[i]; 370 | for (var connection in rtc.peerConnections) { 371 | rtc.peerConnections[connection].addStream(stream); 372 | } 373 | } 374 | }; 375 | 376 | rtc.attachStream = function(stream, domId) { 377 | var element = document.getElementById(domId); 378 | if (navigator.mozGetUserMedia) { 379 | console.log("Attaching media stream"); 380 | element.mozSrcObject = stream; 381 | element.play(); 382 | } else { 383 | element.src = webkitURL.createObjectURL(stream); 384 | } 385 | }; 386 | 387 | 388 | rtc.createDataChannel = function(pcOrId, label) { 389 | if (!rtc.dataChannelSupport) { 390 | //TODO this should be an exception 391 | alert('webRTC data channel is not yet supported in this browser,' + 392 | ' or you must turn on experimental flags'); 393 | return; 394 | } 395 | 396 | var id, pc; 397 | if (typeof(pcOrId) === 'string') { 398 | id = pcOrId; 399 | pc = rtc.peerConnections[pcOrId]; 400 | } else { 401 | pc = pcOrId; 402 | id = undefined; 403 | for (var key in rtc.peerConnections) { 404 | if (rtc.peerConnections[key] === pc) id = key; 405 | } 406 | } 407 | 408 | if (!id) throw new Error('attempt to createDataChannel with unknown id'); 409 | 410 | if (!pc || !(pc instanceof PeerConnection)) throw new Error('attempt to createDataChannel without peerConnection'); 411 | 412 | // need a label 413 | label = label || 'fileTransfer' || String(id); 414 | 415 | // chrome only supports reliable false atm. 416 | var options = { 417 | reliable: false 418 | }; 419 | 420 | var channel; 421 | try { 422 | console.log('createDataChannel ' + id); 423 | channel = pc.createDataChannel(label, options); 424 | } catch (error) { 425 | console.log('seems that DataChannel is NOT actually supported!'); 426 | throw error; 427 | } 428 | 429 | return rtc.addDataChannel(id, channel); 430 | }; 431 | 432 | rtc.addDataChannel = function(id, channel) { 433 | 434 | channel.onopen = function() { 435 | console.log('data stream open ' + id); 436 | rtc.fire('data stream open', channel); 437 | }; 438 | 439 | channel.onclose = function(event) { 440 | delete rtc.dataChannels[id]; 441 | console.log('data stream close ' + id); 442 | rtc.fire('data stream close', channel); 443 | }; 444 | 445 | channel.onmessage = function(message) { 446 | console.log('data stream message ' + id); 447 | console.log(message); 448 | rtc.fire('data stream data', channel, message.data); 449 | }; 450 | 451 | channel.onerror = function(err) { 452 | console.log('data stream error ' + id + ': ' + err); 453 | rtc.fire('data stream error', channel, err); 454 | }; 455 | 456 | // track dataChannel 457 | rtc.dataChannels[id] = channel; 458 | return channel; 459 | }; 460 | 461 | rtc.addDataChannels = function() { 462 | if (!rtc.dataChannelSupport) return; 463 | 464 | for (var connection in rtc.peerConnections) 465 | rtc.createDataChannel(connection); 466 | }; 467 | 468 | 469 | rtc.on('ready', function() { 470 | rtc.createPeerConnections(); 471 | rtc.addStreams(); 472 | rtc.addDataChannels(); 473 | rtc.sendOffers(); 474 | }); 475 | 476 | }).call(this); 477 | 478 | function preferOpus(sdp) { 479 | var sdpLines = sdp.split('\r\n'); 480 | var mLineIndex = null; 481 | // Search for m line. 482 | for (var i = 0; i < sdpLines.length; i++) { 483 | if (sdpLines[i].search('m=audio') !== -1) { 484 | mLineIndex = i; 485 | break; 486 | } 487 | } 488 | if (mLineIndex === null) return sdp; 489 | 490 | // If Opus is available, set it as the default in m line. 491 | for (var j = 0; j < sdpLines.length; j++) { 492 | if (sdpLines[j].search('opus/48000') !== -1) { 493 | var opusPayload = extractSdp(sdpLines[j], /:(\d+) opus\/48000/i); 494 | if (opusPayload) sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload); 495 | break; 496 | } 497 | } 498 | 499 | // Remove CN in m line and sdp. 500 | sdpLines = removeCN(sdpLines, mLineIndex); 501 | 502 | sdp = sdpLines.join('\r\n'); 503 | return sdp; 504 | } 505 | 506 | function extractSdp(sdpLine, pattern) { 507 | var result = sdpLine.match(pattern); 508 | return (result && result.length == 2) ? result[1] : null; 509 | } 510 | 511 | function setDefaultCodec(mLine, payload) { 512 | var elements = mLine.split(' '); 513 | var newLine = []; 514 | var index = 0; 515 | for (var i = 0; i < elements.length; i++) { 516 | if (index === 3) // Format of media starts from the fourth. 517 | newLine[index++] = payload; // Put target payload to the first. 518 | if (elements[i] !== payload) newLine[index++] = elements[i]; 519 | } 520 | return newLine.join(' '); 521 | } 522 | 523 | function removeCN(sdpLines, mLineIndex) { 524 | var mLineElements = sdpLines[mLineIndex].split(' '); 525 | // Scan from end for the convenience of removing an item. 526 | for (var i = sdpLines.length - 1; i >= 0; i--) { 527 | var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i); 528 | if (payload) { 529 | var cnPos = mLineElements.indexOf(payload); 530 | if (cnPos !== -1) { 531 | // Remove CN payload from m line. 532 | mLineElements.splice(cnPos, 1); 533 | } 534 | // Remove CN line in sdp 535 | sdpLines.splice(i, 1); 536 | } 537 | } 538 | 539 | sdpLines[mLineIndex] = mLineElements.join(' '); 540 | return sdpLines; 541 | } 542 | 543 | function mergeConstraints(cons1, cons2) { 544 | var merged = cons1; 545 | for (var name in cons2.mandatory) { 546 | merged.mandatory[name] = cons2.mandatory[name]; 547 | } 548 | merged.optional.concat(cons2.optional); 549 | return merged; 550 | } --------------------------------------------------------------------------------