├── .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 |
21 |
22 |
CoLang
23 | The easiest way to practice a foreign language.
24 |
25 | {{loginButtons}}
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
73 |
74 |
75 | {{greeting}}
76 |
77 |
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 | }
--------------------------------------------------------------------------------