talking_stick 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +40 -0
  3. data/.csslintrc +2 -0
  4. data/.eslintignore +1 -0
  5. data/.eslintrc +213 -0
  6. data/.rubocop.yml +1156 -0
  7. data/Changes.md +8 -0
  8. data/Gemfile +1 -0
  9. data/README.md +7 -6
  10. data/app/assets/javascripts/talking_stick/talking_stick/partner.js +8 -4
  11. data/app/assets/javascripts/talking_stick/talking_stick/rails_signaling.js +2 -2
  12. data/app/assets/javascripts/talking_stick/talking_stick.js.erb +19 -9
  13. data/app/controllers/talking_stick/application_controller.rb +1 -2
  14. data/app/controllers/talking_stick/participants_controller.rb +3 -4
  15. data/app/controllers/talking_stick/rooms_controller.rb +6 -5
  16. data/app/models/talking_stick/application_record.rb +5 -0
  17. data/app/models/talking_stick/participant.rb +2 -2
  18. data/app/models/talking_stick/room.rb +2 -2
  19. data/app/models/talking_stick/signal.rb +1 -1
  20. data/app/views/talking_stick/participants/_form.html.erb +1 -1
  21. data/app/views/talking_stick/participants/edit.html.erb +1 -1
  22. data/app/views/talking_stick/participants/index.html.erb +2 -2
  23. data/app/views/talking_stick/participants/new.html.erb +1 -1
  24. data/app/views/talking_stick/participants/show.html.erb +2 -2
  25. data/app/views/talking_stick/rooms/_form.html.erb +2 -2
  26. data/app/views/talking_stick/rooms/index.html.erb +4 -4
  27. data/app/views/talking_stick/rooms/show.html.erb +24 -22
  28. data/config/routes.rb +3 -3
  29. data/db/migrate/20150510181337_create_talking_stick_rooms.rb +1 -1
  30. data/db/migrate/20150510182258_create_talking_stick_participants.rb +1 -1
  31. data/db/migrate/20150511005922_create_talking_stick_signals.rb +1 -1
  32. data/db/migrate/20150722200822_add_slug_to_talking_stick_rooms.rb +1 -1
  33. data/lib/talking_stick/engine.rb +8 -2
  34. data/lib/talking_stick/version.rb +1 -1
  35. data/lib/talking_stick.rb +4 -0
  36. data/spec/dummy/config/application.rb +0 -4
  37. data/talking_stick.gemspec +3 -2
  38. data/vendor/assets/javascripts/talking_stick/adapter.js +2591 -181
  39. metadata +37 -12
@@ -1,243 +1,2653 @@
1
+ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2
+ /* eslint-env node */
3
+ 'use strict';
4
+
5
+ // SDP helpers.
6
+ var SDPUtils = {};
7
+
8
+ // Generate an alphanumeric identifier for cname or mids.
9
+ // TODO: use UUIDs instead? https://gist.github.com/jed/982883
10
+ SDPUtils.generateIdentifier = function() {
11
+ return Math.random().toString(36).substr(2, 10);
12
+ };
13
+
14
+ // The RTCP CNAME used by all peerconnections from the same JS.
15
+ SDPUtils.localCName = SDPUtils.generateIdentifier();
16
+
17
+ // Splits SDP into lines, dealing with both CRLF and LF.
18
+ SDPUtils.splitLines = function(blob) {
19
+ return blob.trim().split('\n').map(function(line) {
20
+ return line.trim();
21
+ });
22
+ };
23
+ // Splits SDP into sessionpart and mediasections. Ensures CRLF.
24
+ SDPUtils.splitSections = function(blob) {
25
+ var parts = blob.split('\nm=');
26
+ return parts.map(function(part, index) {
27
+ return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
28
+ });
29
+ };
30
+
31
+ // Returns lines that start with a certain prefix.
32
+ SDPUtils.matchPrefix = function(blob, prefix) {
33
+ return SDPUtils.splitLines(blob).filter(function(line) {
34
+ return line.indexOf(prefix) === 0;
35
+ });
36
+ };
37
+
38
+ // Parses an ICE candidate line. Sample input:
39
+ // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
40
+ // rport 55996"
41
+ SDPUtils.parseCandidate = function(line) {
42
+ var parts;
43
+ // Parse both variants.
44
+ if (line.indexOf('a=candidate:') === 0) {
45
+ parts = line.substring(12).split(' ');
46
+ } else {
47
+ parts = line.substring(10).split(' ');
48
+ }
49
+
50
+ var candidate = {
51
+ foundation: parts[0],
52
+ component: parts[1],
53
+ protocol: parts[2].toLowerCase(),
54
+ priority: parseInt(parts[3], 10),
55
+ ip: parts[4],
56
+ port: parseInt(parts[5], 10),
57
+ // skip parts[6] == 'typ'
58
+ type: parts[7]
59
+ };
60
+
61
+ for (var i = 8; i < parts.length; i += 2) {
62
+ switch (parts[i]) {
63
+ case 'raddr':
64
+ candidate.relatedAddress = parts[i + 1];
65
+ break;
66
+ case 'rport':
67
+ candidate.relatedPort = parseInt(parts[i + 1], 10);
68
+ break;
69
+ case 'tcptype':
70
+ candidate.tcpType = parts[i + 1];
71
+ break;
72
+ default: // Unknown extensions are silently ignored.
73
+ break;
74
+ }
75
+ }
76
+ return candidate;
77
+ };
78
+
79
+ // Translates a candidate object into SDP candidate attribute.
80
+ SDPUtils.writeCandidate = function(candidate) {
81
+ var sdp = [];
82
+ sdp.push(candidate.foundation);
83
+ sdp.push(candidate.component);
84
+ sdp.push(candidate.protocol.toUpperCase());
85
+ sdp.push(candidate.priority);
86
+ sdp.push(candidate.ip);
87
+ sdp.push(candidate.port);
88
+
89
+ var type = candidate.type;
90
+ sdp.push('typ');
91
+ sdp.push(type);
92
+ if (type !== 'host' && candidate.relatedAddress &&
93
+ candidate.relatedPort) {
94
+ sdp.push('raddr');
95
+ sdp.push(candidate.relatedAddress); // was: relAddr
96
+ sdp.push('rport');
97
+ sdp.push(candidate.relatedPort); // was: relPort
98
+ }
99
+ if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
100
+ sdp.push('tcptype');
101
+ sdp.push(candidate.tcpType);
102
+ }
103
+ return 'candidate:' + sdp.join(' ');
104
+ };
105
+
106
+ // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
107
+ // a=rtpmap:111 opus/48000/2
108
+ SDPUtils.parseRtpMap = function(line) {
109
+ var parts = line.substr(9).split(' ');
110
+ var parsed = {
111
+ payloadType: parseInt(parts.shift(), 10) // was: id
112
+ };
113
+
114
+ parts = parts[0].split('/');
115
+
116
+ parsed.name = parts[0];
117
+ parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
118
+ // was: channels
119
+ parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
120
+ return parsed;
121
+ };
122
+
123
+ // Generate an a=rtpmap line from RTCRtpCodecCapability or
124
+ // RTCRtpCodecParameters.
125
+ SDPUtils.writeRtpMap = function(codec) {
126
+ var pt = codec.payloadType;
127
+ if (codec.preferredPayloadType !== undefined) {
128
+ pt = codec.preferredPayloadType;
129
+ }
130
+ return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
131
+ (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
132
+ };
133
+
134
+ // Parses an a=extmap line (headerextension from RFC 5285). Sample input:
135
+ // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
136
+ SDPUtils.parseExtmap = function(line) {
137
+ var parts = line.substr(9).split(' ');
138
+ return {
139
+ id: parseInt(parts[0], 10),
140
+ uri: parts[1]
141
+ };
142
+ };
143
+
144
+ // Generates a=extmap line from RTCRtpHeaderExtensionParameters or
145
+ // RTCRtpHeaderExtension.
146
+ SDPUtils.writeExtmap = function(headerExtension) {
147
+ return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
148
+ ' ' + headerExtension.uri + '\r\n';
149
+ };
150
+
151
+ // Parses an ftmp line, returns dictionary. Sample input:
152
+ // a=fmtp:96 vbr=on;cng=on
153
+ // Also deals with vbr=on; cng=on
154
+ SDPUtils.parseFmtp = function(line) {
155
+ var parsed = {};
156
+ var kv;
157
+ var parts = line.substr(line.indexOf(' ') + 1).split(';');
158
+ for (var j = 0; j < parts.length; j++) {
159
+ kv = parts[j].trim().split('=');
160
+ parsed[kv[0].trim()] = kv[1];
161
+ }
162
+ return parsed;
163
+ };
164
+
165
+ // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
166
+ SDPUtils.writeFmtp = function(codec) {
167
+ var line = '';
168
+ var pt = codec.payloadType;
169
+ if (codec.preferredPayloadType !== undefined) {
170
+ pt = codec.preferredPayloadType;
171
+ }
172
+ if (codec.parameters && Object.keys(codec.parameters).length) {
173
+ var params = [];
174
+ Object.keys(codec.parameters).forEach(function(param) {
175
+ params.push(param + '=' + codec.parameters[param]);
176
+ });
177
+ line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
178
+ }
179
+ return line;
180
+ };
181
+
182
+ // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
183
+ // a=rtcp-fb:98 nack rpsi
184
+ SDPUtils.parseRtcpFb = function(line) {
185
+ var parts = line.substr(line.indexOf(' ') + 1).split(' ');
186
+ return {
187
+ type: parts.shift(),
188
+ parameter: parts.join(' ')
189
+ };
190
+ };
191
+ // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
192
+ SDPUtils.writeRtcpFb = function(codec) {
193
+ var lines = '';
194
+ var pt = codec.payloadType;
195
+ if (codec.preferredPayloadType !== undefined) {
196
+ pt = codec.preferredPayloadType;
197
+ }
198
+ if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
199
+ // FIXME: special handling for trr-int?
200
+ codec.rtcpFeedback.forEach(function(fb) {
201
+ lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + ' ' + fb.parameter +
202
+ '\r\n';
203
+ });
204
+ }
205
+ return lines;
206
+ };
207
+
208
+ // Parses an RFC 5576 ssrc media attribute. Sample input:
209
+ // a=ssrc:3735928559 cname:something
210
+ SDPUtils.parseSsrcMedia = function(line) {
211
+ var sp = line.indexOf(' ');
212
+ var parts = {
213
+ ssrc: parseInt(line.substr(7, sp - 7), 10)
214
+ };
215
+ var colon = line.indexOf(':', sp);
216
+ if (colon > -1) {
217
+ parts.attribute = line.substr(sp + 1, colon - sp - 1);
218
+ parts.value = line.substr(colon + 1);
219
+ } else {
220
+ parts.attribute = line.substr(sp + 1);
221
+ }
222
+ return parts;
223
+ };
224
+
225
+ // Extracts DTLS parameters from SDP media section or sessionpart.
226
+ // FIXME: for consistency with other functions this should only
227
+ // get the fingerprint line as input. See also getIceParameters.
228
+ SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
229
+ var lines = SDPUtils.splitLines(mediaSection);
230
+ // Search in session part, too.
231
+ lines = lines.concat(SDPUtils.splitLines(sessionpart));
232
+ var fpLine = lines.filter(function(line) {
233
+ return line.indexOf('a=fingerprint:') === 0;
234
+ })[0].substr(14);
235
+ // Note: a=setup line is ignored since we use the 'auto' role.
236
+ var dtlsParameters = {
237
+ role: 'auto',
238
+ fingerprints: [{
239
+ algorithm: fpLine.split(' ')[0],
240
+ value: fpLine.split(' ')[1]
241
+ }]
242
+ };
243
+ return dtlsParameters;
244
+ };
245
+
246
+ // Serializes DTLS parameters to SDP.
247
+ SDPUtils.writeDtlsParameters = function(params, setupType) {
248
+ var sdp = 'a=setup:' + setupType + '\r\n';
249
+ params.fingerprints.forEach(function(fp) {
250
+ sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
251
+ });
252
+ return sdp;
253
+ };
254
+ // Parses ICE information from SDP media section or sessionpart.
255
+ // FIXME: for consistency with other functions this should only
256
+ // get the ice-ufrag and ice-pwd lines as input.
257
+ SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
258
+ var lines = SDPUtils.splitLines(mediaSection);
259
+ // Search in session part, too.
260
+ lines = lines.concat(SDPUtils.splitLines(sessionpart));
261
+ var iceParameters = {
262
+ usernameFragment: lines.filter(function(line) {
263
+ return line.indexOf('a=ice-ufrag:') === 0;
264
+ })[0].substr(12),
265
+ password: lines.filter(function(line) {
266
+ return line.indexOf('a=ice-pwd:') === 0;
267
+ })[0].substr(10)
268
+ };
269
+ return iceParameters;
270
+ };
271
+
272
+ // Serializes ICE parameters to SDP.
273
+ SDPUtils.writeIceParameters = function(params) {
274
+ return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
275
+ 'a=ice-pwd:' + params.password + '\r\n';
276
+ };
277
+
278
+ // Parses the SDP media section and returns RTCRtpParameters.
279
+ SDPUtils.parseRtpParameters = function(mediaSection) {
280
+ var description = {
281
+ codecs: [],
282
+ headerExtensions: [],
283
+ fecMechanisms: [],
284
+ rtcp: []
285
+ };
286
+ var lines = SDPUtils.splitLines(mediaSection);
287
+ var mline = lines[0].split(' ');
288
+ for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
289
+ var pt = mline[i];
290
+ var rtpmapline = SDPUtils.matchPrefix(
291
+ mediaSection, 'a=rtpmap:' + pt + ' ')[0];
292
+ if (rtpmapline) {
293
+ var codec = SDPUtils.parseRtpMap(rtpmapline);
294
+ var fmtps = SDPUtils.matchPrefix(
295
+ mediaSection, 'a=fmtp:' + pt + ' ');
296
+ // Only the first a=fmtp:<pt> is considered.
297
+ codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
298
+ codec.rtcpFeedback = SDPUtils.matchPrefix(
299
+ mediaSection, 'a=rtcp-fb:' + pt + ' ')
300
+ .map(SDPUtils.parseRtcpFb);
301
+ description.codecs.push(codec);
302
+ // parse FEC mechanisms from rtpmap lines.
303
+ switch (codec.name.toUpperCase()) {
304
+ case 'RED':
305
+ case 'ULPFEC':
306
+ description.fecMechanisms.push(codec.name.toUpperCase());
307
+ break;
308
+ default: // only RED and ULPFEC are recognized as FEC mechanisms.
309
+ break;
310
+ }
311
+ }
312
+ }
313
+ SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
314
+ description.headerExtensions.push(SDPUtils.parseExtmap(line));
315
+ });
316
+ // FIXME: parse rtcp.
317
+ return description;
318
+ };
319
+
320
+ // Generates parts of the SDP media section describing the capabilities /
321
+ // parameters.
322
+ SDPUtils.writeRtpDescription = function(kind, caps) {
323
+ var sdp = '';
324
+
325
+ // Build the mline.
326
+ sdp += 'm=' + kind + ' ';
327
+ sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
328
+ sdp += ' UDP/TLS/RTP/SAVPF ';
329
+ sdp += caps.codecs.map(function(codec) {
330
+ if (codec.preferredPayloadType !== undefined) {
331
+ return codec.preferredPayloadType;
332
+ }
333
+ return codec.payloadType;
334
+ }).join(' ') + '\r\n';
335
+
336
+ sdp += 'c=IN IP4 0.0.0.0\r\n';
337
+ sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
338
+
339
+ // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
340
+ caps.codecs.forEach(function(codec) {
341
+ sdp += SDPUtils.writeRtpMap(codec);
342
+ sdp += SDPUtils.writeFmtp(codec);
343
+ sdp += SDPUtils.writeRtcpFb(codec);
344
+ });
345
+ // FIXME: add headerExtensions, fecMechanismş and rtcp.
346
+ sdp += 'a=rtcp-mux\r\n';
347
+ return sdp;
348
+ };
349
+
350
+ // Parses the SDP media section and returns an array of
351
+ // RTCRtpEncodingParameters.
352
+ SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
353
+ var encodingParameters = [];
354
+ var description = SDPUtils.parseRtpParameters(mediaSection);
355
+ var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
356
+ var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
357
+
358
+ // filter a=ssrc:... cname:, ignore PlanB-msid
359
+ var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
360
+ .map(function(line) {
361
+ return SDPUtils.parseSsrcMedia(line);
362
+ })
363
+ .filter(function(parts) {
364
+ return parts.attribute === 'cname';
365
+ });
366
+ var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
367
+ var secondarySsrc;
368
+
369
+ var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
370
+ .map(function(line) {
371
+ var parts = line.split(' ');
372
+ parts.shift();
373
+ return parts.map(function(part) {
374
+ return parseInt(part, 10);
375
+ });
376
+ });
377
+ if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
378
+ secondarySsrc = flows[0][1];
379
+ }
380
+
381
+ description.codecs.forEach(function(codec) {
382
+ if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
383
+ var encParam = {
384
+ ssrc: primarySsrc,
385
+ codecPayloadType: parseInt(codec.parameters.apt, 10),
386
+ rtx: {
387
+ payloadType: codec.payloadType,
388
+ ssrc: secondarySsrc
389
+ }
390
+ };
391
+ encodingParameters.push(encParam);
392
+ if (hasRed) {
393
+ encParam = JSON.parse(JSON.stringify(encParam));
394
+ encParam.fec = {
395
+ ssrc: secondarySsrc,
396
+ mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
397
+ };
398
+ encodingParameters.push(encParam);
399
+ }
400
+ }
401
+ });
402
+ if (encodingParameters.length === 0 && primarySsrc) {
403
+ encodingParameters.push({
404
+ ssrc: primarySsrc
405
+ });
406
+ }
407
+
408
+ // we support both b=AS and b=TIAS but interpret AS as TIAS.
409
+ var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
410
+ if (bandwidth.length) {
411
+ if (bandwidth[0].indexOf('b=TIAS:') === 0) {
412
+ bandwidth = parseInt(bandwidth[0].substr(7), 10);
413
+ } else if (bandwidth[0].indexOf('b=AS:') === 0) {
414
+ bandwidth = parseInt(bandwidth[0].substr(5), 10);
415
+ }
416
+ encodingParameters.forEach(function(params) {
417
+ params.maxBitrate = bandwidth;
418
+ });
419
+ }
420
+ return encodingParameters;
421
+ };
422
+
423
+ SDPUtils.writeSessionBoilerplate = function() {
424
+ // FIXME: sess-id should be an NTP timestamp.
425
+ return 'v=0\r\n' +
426
+ 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
427
+ 's=-\r\n' +
428
+ 't=0 0\r\n';
429
+ };
430
+
431
+ SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
432
+ var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
433
+
434
+ // Map ICE parameters (ufrag, pwd) to SDP.
435
+ sdp += SDPUtils.writeIceParameters(
436
+ transceiver.iceGatherer.getLocalParameters());
437
+
438
+ // Map DTLS parameters to SDP.
439
+ sdp += SDPUtils.writeDtlsParameters(
440
+ transceiver.dtlsTransport.getLocalParameters(),
441
+ type === 'offer' ? 'actpass' : 'active');
442
+
443
+ sdp += 'a=mid:' + transceiver.mid + '\r\n';
444
+
445
+ if (transceiver.rtpSender && transceiver.rtpReceiver) {
446
+ sdp += 'a=sendrecv\r\n';
447
+ } else if (transceiver.rtpSender) {
448
+ sdp += 'a=sendonly\r\n';
449
+ } else if (transceiver.rtpReceiver) {
450
+ sdp += 'a=recvonly\r\n';
451
+ } else {
452
+ sdp += 'a=inactive\r\n';
453
+ }
454
+
455
+ // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet.
456
+ if (transceiver.rtpSender) {
457
+ var msid = 'msid:' + stream.id + ' ' +
458
+ transceiver.rtpSender.track.id + '\r\n';
459
+ sdp += 'a=' + msid;
460
+ sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
461
+ ' ' + msid;
462
+ }
463
+ // FIXME: this should be written by writeRtpDescription.
464
+ sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
465
+ ' cname:' + SDPUtils.localCName + '\r\n';
466
+ return sdp;
467
+ };
468
+
469
+ // Gets the direction from the mediaSection or the sessionpart.
470
+ SDPUtils.getDirection = function(mediaSection, sessionpart) {
471
+ // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
472
+ var lines = SDPUtils.splitLines(mediaSection);
473
+ for (var i = 0; i < lines.length; i++) {
474
+ switch (lines[i]) {
475
+ case 'a=sendrecv':
476
+ case 'a=sendonly':
477
+ case 'a=recvonly':
478
+ case 'a=inactive':
479
+ return lines[i].substr(2);
480
+ default:
481
+ // FIXME: What should happen here?
482
+ }
483
+ }
484
+ if (sessionpart) {
485
+ return SDPUtils.getDirection(sessionpart);
486
+ }
487
+ return 'sendrecv';
488
+ };
489
+
490
+ // Expose public methods.
491
+ module.exports = SDPUtils;
492
+
493
+ },{}],2:[function(require,module,exports){
1
494
  /*
2
- * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
495
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3
496
  *
4
497
  * Use of this source code is governed by a BSD-style license
5
498
  * that can be found in the LICENSE file in the root of the source
6
499
  * tree.
7
500
  */
501
+ /* eslint-env node */
8
502
 
9
- /* More information about these options at jshint.com/docs/options */
10
- /* jshint browser: true, camelcase: true, curly: true, devel: true,
11
- eqeqeq: true, forin: false, globalstrict: true, node: true,
12
- quotmark: single, undef: true, unused: strict */
13
- /* global mozRTCIceCandidate, mozRTCPeerConnection, Promise,
14
- mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack */
15
- /* exported trace,requestUserMedia */
16
503
  'use strict';
17
504
 
18
- var RTCPeerConnection = null;
19
- var getUserMedia = null;
20
- var attachMediaStream = null;
21
- var reattachMediaStream = null;
22
- var webrtcDetectedBrowser = null;
23
- var webrtcDetectedVersion = null;
24
-
25
- function trace(text) {
26
- // This function is used for logging.
27
- if (text[text.length - 1] === '\n') {
28
- text = text.substring(0, text.length - 1);
29
- }
30
- if (window.performance) {
31
- var now = (window.performance.now() / 1000).toFixed(3);
32
- console.log(now + ': ' + text);
33
- } else {
34
- console.log(text);
505
+ // Shimming starts here.
506
+ (function() {
507
+ // Utils.
508
+ var logging = require('./utils').log;
509
+ var browserDetails = require('./utils').browserDetails;
510
+ // Export to the adapter global object visible in the browser.
511
+ module.exports.browserDetails = browserDetails;
512
+ module.exports.extractVersion = require('./utils').extractVersion;
513
+ module.exports.disableLog = require('./utils').disableLog;
514
+
515
+ // Uncomment the line below if you want logging to occur, including logging
516
+ // for the switch statement below. Can also be turned on in the browser via
517
+ // adapter.disableLog(false), but then logging from the switch statement below
518
+ // will not appear.
519
+ // require('./utils').disableLog(false);
520
+
521
+ // Browser shims.
522
+ var chromeShim = require('./chrome/chrome_shim') || null;
523
+ var edgeShim = require('./edge/edge_shim') || null;
524
+ var firefoxShim = require('./firefox/firefox_shim') || null;
525
+ var safariShim = require('./safari/safari_shim') || null;
526
+
527
+ // Shim browser if found.
528
+ switch (browserDetails.browser) {
529
+ case 'opera': // fallthrough as it uses chrome shims
530
+ case 'chrome':
531
+ if (!chromeShim || !chromeShim.shimPeerConnection) {
532
+ logging('Chrome shim is not included in this adapter release.');
533
+ return;
534
+ }
535
+ logging('adapter.js shimming chrome.');
536
+ // Export to the adapter global object visible in the browser.
537
+ module.exports.browserShim = chromeShim;
538
+
539
+ chromeShim.shimGetUserMedia();
540
+ chromeShim.shimMediaStream();
541
+ chromeShim.shimSourceObject();
542
+ chromeShim.shimPeerConnection();
543
+ chromeShim.shimOnTrack();
544
+ break;
545
+ case 'firefox':
546
+ if (!firefoxShim || !firefoxShim.shimPeerConnection) {
547
+ logging('Firefox shim is not included in this adapter release.');
548
+ return;
549
+ }
550
+ logging('adapter.js shimming firefox.');
551
+ // Export to the adapter global object visible in the browser.
552
+ module.exports.browserShim = firefoxShim;
553
+
554
+ firefoxShim.shimGetUserMedia();
555
+ firefoxShim.shimSourceObject();
556
+ firefoxShim.shimPeerConnection();
557
+ firefoxShim.shimOnTrack();
558
+ break;
559
+ case 'edge':
560
+ if (!edgeShim || !edgeShim.shimPeerConnection) {
561
+ logging('MS edge shim is not included in this adapter release.');
562
+ return;
563
+ }
564
+ logging('adapter.js shimming edge.');
565
+ // Export to the adapter global object visible in the browser.
566
+ module.exports.browserShim = edgeShim;
567
+
568
+ edgeShim.shimGetUserMedia();
569
+ edgeShim.shimPeerConnection();
570
+ break;
571
+ case 'safari':
572
+ if (!safariShim) {
573
+ logging('Safari shim is not included in this adapter release.');
574
+ return;
575
+ }
576
+ logging('adapter.js shimming safari.');
577
+ // Export to the adapter global object visible in the browser.
578
+ module.exports.browserShim = safariShim;
579
+
580
+ safariShim.shimGetUserMedia();
581
+ break;
582
+ default:
583
+ logging('Unsupported browser!');
35
584
  }
36
- }
585
+ })();
37
586
 
38
- if (navigator.mozGetUserMedia) {
39
- console.log('This appears to be Firefox');
587
+ },{"./chrome/chrome_shim":3,"./edge/edge_shim":5,"./firefox/firefox_shim":7,"./safari/safari_shim":9,"./utils":10}],3:[function(require,module,exports){
40
588
 
41
- webrtcDetectedBrowser = 'firefox';
589
+ /*
590
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
591
+ *
592
+ * Use of this source code is governed by a BSD-style license
593
+ * that can be found in the LICENSE file in the root of the source
594
+ * tree.
595
+ */
596
+ /* eslint-env node */
597
+ 'use strict';
598
+ var logging = require('../utils.js').log;
599
+ var browserDetails = require('../utils.js').browserDetails;
42
600
 
43
- webrtcDetectedVersion =
44
- parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
601
+ var chromeShim = {
602
+ shimMediaStream: function() {
603
+ window.MediaStream = window.MediaStream || window.webkitMediaStream;
604
+ },
45
605
 
46
- // The RTCPeerConnection object.
47
- RTCPeerConnection = function(pcConfig, pcConstraints) {
48
- // .urls is not supported in FF yet.
49
- if (pcConfig && pcConfig.iceServers) {
50
- for (var i = 0; i < pcConfig.iceServers.length; i++) {
51
- if (pcConfig.iceServers[i].hasOwnProperty('urls')) {
52
- pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls;
53
- delete pcConfig.iceServers[i].urls;
606
+ shimOnTrack: function() {
607
+ if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
608
+ window.RTCPeerConnection.prototype)) {
609
+ Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
610
+ get: function() {
611
+ return this._ontrack;
612
+ },
613
+ set: function(f) {
614
+ var self = this;
615
+ if (this._ontrack) {
616
+ this.removeEventListener('track', this._ontrack);
617
+ this.removeEventListener('addstream', this._ontrackpoly);
618
+ }
619
+ this.addEventListener('track', this._ontrack = f);
620
+ this.addEventListener('addstream', this._ontrackpoly = function(e) {
621
+ // onaddstream does not fire when a track is added to an existing
622
+ // stream. But stream.onaddtrack is implemented so we use that.
623
+ e.stream.addEventListener('addtrack', function(te) {
624
+ var event = new Event('track');
625
+ event.track = te.track;
626
+ event.receiver = {track: te.track};
627
+ event.streams = [e.stream];
628
+ self.dispatchEvent(event);
629
+ });
630
+ e.stream.getTracks().forEach(function(track) {
631
+ var event = new Event('track');
632
+ event.track = track;
633
+ event.receiver = {track: track};
634
+ event.streams = [e.stream];
635
+ this.dispatchEvent(event);
636
+ }.bind(this));
637
+ }.bind(this));
54
638
  }
639
+ });
640
+ }
641
+ },
642
+
643
+ shimSourceObject: function() {
644
+ if (typeof window === 'object') {
645
+ if (window.HTMLMediaElement &&
646
+ !('srcObject' in window.HTMLMediaElement.prototype)) {
647
+ // Shim the srcObject property, once, when HTMLMediaElement is found.
648
+ Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
649
+ get: function() {
650
+ return this._srcObject;
651
+ },
652
+ set: function(stream) {
653
+ var self = this;
654
+ // Use _srcObject as a private property for this shim
655
+ this._srcObject = stream;
656
+ if (this.src) {
657
+ URL.revokeObjectURL(this.src);
658
+ }
659
+
660
+ if (!stream) {
661
+ this.src = '';
662
+ return;
663
+ }
664
+ this.src = URL.createObjectURL(stream);
665
+ // We need to recreate the blob url when a track is added or
666
+ // removed. Doing it manually since we want to avoid a recursion.
667
+ stream.addEventListener('addtrack', function() {
668
+ if (self.src) {
669
+ URL.revokeObjectURL(self.src);
670
+ }
671
+ self.src = URL.createObjectURL(stream);
672
+ });
673
+ stream.addEventListener('removetrack', function() {
674
+ if (self.src) {
675
+ URL.revokeObjectURL(self.src);
676
+ }
677
+ self.src = URL.createObjectURL(stream);
678
+ });
679
+ }
680
+ });
55
681
  }
56
682
  }
57
- return new mozRTCPeerConnection(pcConfig, pcConstraints);
58
- };
683
+ },
59
684
 
60
- // The RTCSessionDescription object.
61
- window.RTCSessionDescription = mozRTCSessionDescription;
685
+ shimPeerConnection: function() {
686
+ // The RTCPeerConnection object.
687
+ window.RTCPeerConnection = function(pcConfig, pcConstraints) {
688
+ // Translate iceTransportPolicy to iceTransports,
689
+ // see https://code.google.com/p/webrtc/issues/detail?id=4869
690
+ logging('PeerConnection');
691
+ if (pcConfig && pcConfig.iceTransportPolicy) {
692
+ pcConfig.iceTransports = pcConfig.iceTransportPolicy;
693
+ }
62
694
 
63
- // The RTCIceCandidate object.
64
- window.RTCIceCandidate = mozRTCIceCandidate;
695
+ var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints);
696
+ var origGetStats = pc.getStats.bind(pc);
697
+ pc.getStats = function(selector, successCallback, errorCallback) {
698
+ var self = this;
699
+ var args = arguments;
65
700
 
66
- // getUserMedia shim (only difference is the prefix).
67
- // Code from Adam Barth.
68
- getUserMedia = navigator.mozGetUserMedia.bind(navigator);
69
- navigator.getUserMedia = getUserMedia;
701
+ // If selector is a function then we are in the old style stats so just
702
+ // pass back the original getStats format to avoid breaking old users.
703
+ if (arguments.length > 0 && typeof selector === 'function') {
704
+ return origGetStats(selector, successCallback);
705
+ }
70
706
 
71
- // Shim for MediaStreamTrack.getSources.
72
- MediaStreamTrack.getSources = function(successCb) {
73
- setTimeout(function() {
74
- var infos = [
75
- {kind: 'audio', id: 'default', label:'', facing:''},
76
- {kind: 'video', id: 'default', label:'', facing:''}
77
- ];
78
- successCb(infos);
79
- }, 0);
80
- };
707
+ var fixChromeStats_ = function(response) {
708
+ var standardReport = {};
709
+ var reports = response.result();
710
+ reports.forEach(function(report) {
711
+ var standardStats = {
712
+ id: report.id,
713
+ timestamp: report.timestamp,
714
+ type: report.type
715
+ };
716
+ report.names().forEach(function(name) {
717
+ standardStats[name] = report.stat(name);
718
+ });
719
+ standardReport[standardStats.id] = standardStats;
720
+ });
721
+
722
+ return standardReport;
723
+ };
81
724
 
82
- // Creates ICE server from the URL for FF.
83
- window.createIceServer = function(url, username, password) {
84
- var iceServer = null;
85
- var urlParts = url.split(':');
86
- if (urlParts[0].indexOf('stun') === 0) {
87
- // Create ICE server with STUN URL.
88
- iceServer = {
89
- 'url': url
725
+ // shim getStats with maplike support
726
+ var makeMapStats = function(stats, legacyStats) {
727
+ var map = new Map(Object.keys(stats).map(function(key) {
728
+ return[key, stats[key]];
729
+ }));
730
+ legacyStats = legacyStats || stats;
731
+ Object.keys(legacyStats).forEach(function(key) {
732
+ map[key] = legacyStats[key];
733
+ });
734
+ return map;
735
+ };
736
+
737
+ if (arguments.length >= 2) {
738
+ var successCallbackWrapper_ = function(response) {
739
+ args[1](makeMapStats(fixChromeStats_(response)));
740
+ };
741
+
742
+ return origGetStats.apply(this, [successCallbackWrapper_,
743
+ arguments[0]]);
744
+ }
745
+
746
+ // promise-support
747
+ return new Promise(function(resolve, reject) {
748
+ if (args.length === 1 && typeof selector === 'object') {
749
+ origGetStats.apply(self, [
750
+ function(response) {
751
+ resolve(makeMapStats(fixChromeStats_(response)));
752
+ }, reject]);
753
+ } else {
754
+ // Preserve legacy chrome stats only on legacy access of stats obj
755
+ origGetStats.apply(self, [
756
+ function(response) {
757
+ resolve(makeMapStats(fixChromeStats_(response),
758
+ response.result()));
759
+ }, reject]);
760
+ }
761
+ }).then(successCallback, errorCallback);
762
+ };
763
+
764
+ return pc;
765
+ };
766
+ window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype;
767
+
768
+ // wrap static methods. Currently just generateCertificate.
769
+ if (webkitRTCPeerConnection.generateCertificate) {
770
+ Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
771
+ get: function() {
772
+ return webkitRTCPeerConnection.generateCertificate;
773
+ }
774
+ });
775
+ }
776
+
777
+ ['createOffer', 'createAnswer'].forEach(function(method) {
778
+ var nativeMethod = webkitRTCPeerConnection.prototype[method];
779
+ webkitRTCPeerConnection.prototype[method] = function() {
780
+ var self = this;
781
+ if (arguments.length < 1 || (arguments.length === 1 &&
782
+ typeof arguments[0] === 'object')) {
783
+ var opts = arguments.length === 1 ? arguments[0] : undefined;
784
+ return new Promise(function(resolve, reject) {
785
+ nativeMethod.apply(self, [resolve, reject, opts]);
786
+ });
787
+ }
788
+ return nativeMethod.apply(this, arguments);
90
789
  };
91
- } else if (urlParts[0].indexOf('turn') === 0) {
92
- if (webrtcDetectedVersion < 27) {
93
- // Create iceServer with turn url.
94
- // Ignore the transport parameter from TURN url for FF version <=27.
95
- var turnUrlParts = url.split('?');
96
- // Return null for createIceServer if transport=tcp.
97
- if (turnUrlParts.length === 1 ||
98
- turnUrlParts[1].indexOf('transport=udp') === 0) {
99
- iceServer = {
100
- 'url': turnUrlParts[0],
101
- 'credential': password,
102
- 'username': username
790
+ });
791
+
792
+ // add promise support -- natively available in Chrome 51
793
+ if (browserDetails.version < 51) {
794
+ ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
795
+ .forEach(function(method) {
796
+ var nativeMethod = webkitRTCPeerConnection.prototype[method];
797
+ webkitRTCPeerConnection.prototype[method] = function() {
798
+ var args = arguments;
799
+ var self = this;
800
+ var promise = new Promise(function(resolve, reject) {
801
+ nativeMethod.apply(self, [args[0], resolve, reject]);
802
+ });
803
+ if (args.length < 2) {
804
+ return promise;
805
+ }
806
+ return promise.then(function() {
807
+ args[1].apply(null, []);
808
+ },
809
+ function(err) {
810
+ if (args.length >= 3) {
811
+ args[2].apply(null, [err]);
812
+ }
813
+ });
814
+ };
815
+ });
816
+ }
817
+
818
+ // support for addIceCandidate(null)
819
+ var nativeAddIceCandidate =
820
+ RTCPeerConnection.prototype.addIceCandidate;
821
+ RTCPeerConnection.prototype.addIceCandidate = function() {
822
+ return arguments[0] === null ? Promise.resolve()
823
+ : nativeAddIceCandidate.apply(this, arguments);
824
+ };
825
+
826
+ // shim implicit creation of RTCSessionDescription/RTCIceCandidate
827
+ ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
828
+ .forEach(function(method) {
829
+ var nativeMethod = webkitRTCPeerConnection.prototype[method];
830
+ webkitRTCPeerConnection.prototype[method] = function() {
831
+ arguments[0] = new ((method === 'addIceCandidate') ?
832
+ RTCIceCandidate : RTCSessionDescription)(arguments[0]);
833
+ return nativeMethod.apply(this, arguments);
103
834
  };
835
+ });
836
+ },
837
+
838
+ // Attach a media stream to an element.
839
+ attachMediaStream: function(element, stream) {
840
+ logging('DEPRECATED, attachMediaStream will soon be removed.');
841
+ if (browserDetails.version >= 43) {
842
+ element.srcObject = stream;
843
+ } else if (typeof element.src !== 'undefined') {
844
+ element.src = URL.createObjectURL(stream);
845
+ } else {
846
+ logging('Error attaching stream to element.');
847
+ }
848
+ },
849
+
850
+ reattachMediaStream: function(to, from) {
851
+ logging('DEPRECATED, reattachMediaStream will soon be removed.');
852
+ if (browserDetails.version >= 43) {
853
+ to.srcObject = from.srcObject;
854
+ } else {
855
+ to.src = from.src;
856
+ }
857
+ }
858
+ };
859
+
860
+
861
+ // Expose public methods.
862
+ module.exports = {
863
+ shimMediaStream: chromeShim.shimMediaStream,
864
+ shimOnTrack: chromeShim.shimOnTrack,
865
+ shimSourceObject: chromeShim.shimSourceObject,
866
+ shimPeerConnection: chromeShim.shimPeerConnection,
867
+ shimGetUserMedia: require('./getusermedia'),
868
+ attachMediaStream: chromeShim.attachMediaStream,
869
+ reattachMediaStream: chromeShim.reattachMediaStream
870
+ };
871
+
872
+ },{"../utils.js":10,"./getusermedia":4}],4:[function(require,module,exports){
873
+ /*
874
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
875
+ *
876
+ * Use of this source code is governed by a BSD-style license
877
+ * that can be found in the LICENSE file in the root of the source
878
+ * tree.
879
+ */
880
+ /* eslint-env node */
881
+ 'use strict';
882
+ var logging = require('../utils.js').log;
883
+
884
+ // Expose public methods.
885
+ module.exports = function() {
886
+ var constraintsToChrome_ = function(c) {
887
+ if (typeof c !== 'object' || c.mandatory || c.optional) {
888
+ return c;
889
+ }
890
+ var cc = {};
891
+ Object.keys(c).forEach(function(key) {
892
+ if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
893
+ return;
894
+ }
895
+ var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
896
+ if (r.exact !== undefined && typeof r.exact === 'number') {
897
+ r.min = r.max = r.exact;
898
+ }
899
+ var oldname_ = function(prefix, name) {
900
+ if (prefix) {
901
+ return prefix + name.charAt(0).toUpperCase() + name.slice(1);
902
+ }
903
+ return (name === 'deviceId') ? 'sourceId' : name;
904
+ };
905
+ if (r.ideal !== undefined) {
906
+ cc.optional = cc.optional || [];
907
+ var oc = {};
908
+ if (typeof r.ideal === 'number') {
909
+ oc[oldname_('min', key)] = r.ideal;
910
+ cc.optional.push(oc);
911
+ oc = {};
912
+ oc[oldname_('max', key)] = r.ideal;
913
+ cc.optional.push(oc);
914
+ } else {
915
+ oc[oldname_('', key)] = r.ideal;
916
+ cc.optional.push(oc);
104
917
  }
918
+ }
919
+ if (r.exact !== undefined && typeof r.exact !== 'number') {
920
+ cc.mandatory = cc.mandatory || {};
921
+ cc.mandatory[oldname_('', key)] = r.exact;
105
922
  } else {
106
- // FF 27 and above supports transport parameters in TURN url,
107
- // So passing in the full url to create iceServer.
108
- iceServer = {
109
- 'url': url,
110
- 'credential': password,
111
- 'username': username
112
- };
923
+ ['min', 'max'].forEach(function(mix) {
924
+ if (r[mix] !== undefined) {
925
+ cc.mandatory = cc.mandatory || {};
926
+ cc.mandatory[oldname_(mix, key)] = r[mix];
927
+ }
928
+ });
113
929
  }
930
+ });
931
+ if (c.advanced) {
932
+ cc.optional = (cc.optional || []).concat(c.advanced);
114
933
  }
115
- return iceServer;
934
+ return cc;
116
935
  };
117
936
 
118
- window.createIceServers = function(urls, username, password) {
119
- var iceServers = [];
120
- // Use .url for FireFox.
121
- for (var i = 0; i < urls.length; i++) {
122
- var iceServer =
123
- window.createIceServer(urls[i], username, password);
124
- if (iceServer !== null) {
125
- iceServers.push(iceServer);
937
+ var shimConstraints_ = function(constraints, func) {
938
+ constraints = JSON.parse(JSON.stringify(constraints));
939
+ if (constraints && constraints.audio) {
940
+ constraints.audio = constraintsToChrome_(constraints.audio);
941
+ }
942
+ if (constraints && typeof constraints.video === 'object') {
943
+ // Shim facingMode for mobile, where it defaults to "user".
944
+ var face = constraints.video.facingMode;
945
+ face = face && ((typeof face === 'object') ? face : {ideal: face});
946
+
947
+ if ((face && (face.exact === 'user' || face.exact === 'environment' ||
948
+ face.ideal === 'user' || face.ideal === 'environment')) &&
949
+ !(navigator.mediaDevices.getSupportedConstraints &&
950
+ navigator.mediaDevices.getSupportedConstraints().facingMode)) {
951
+ delete constraints.video.facingMode;
952
+ if (face.exact === 'environment' || face.ideal === 'environment') {
953
+ // Look for "back" in label, or use last cam (typically back cam).
954
+ return navigator.mediaDevices.enumerateDevices()
955
+ .then(function(devices) {
956
+ devices = devices.filter(function(d) {
957
+ return d.kind === 'videoinput';
958
+ });
959
+ var back = devices.find(function(d) {
960
+ return d.label.toLowerCase().indexOf('back') !== -1;
961
+ }) || (devices.length && devices[devices.length - 1]);
962
+ if (back) {
963
+ constraints.video.deviceId = face.exact ? {exact: back.deviceId} :
964
+ {ideal: back.deviceId};
965
+ }
966
+ constraints.video = constraintsToChrome_(constraints.video);
967
+ logging('chrome: ' + JSON.stringify(constraints));
968
+ return func(constraints);
969
+ });
970
+ }
126
971
  }
972
+ constraints.video = constraintsToChrome_(constraints.video);
127
973
  }
128
- return iceServers;
974
+ logging('chrome: ' + JSON.stringify(constraints));
975
+ return func(constraints);
129
976
  };
130
977
 
131
- // Attach a media stream to an element.
132
- attachMediaStream = function(element, stream) {
133
- console.log('Attaching media stream');
134
- element.mozSrcObject = stream;
978
+ var shimError_ = function(e) {
979
+ return {
980
+ name: {
981
+ PermissionDeniedError: 'NotAllowedError',
982
+ ConstraintNotSatisfiedError: 'OverconstrainedError'
983
+ }[e.name] || e.name,
984
+ message: e.message,
985
+ constraint: e.constraintName,
986
+ toString: function() {
987
+ return this.name + (this.message && ': ') + this.message;
988
+ }
989
+ };
990
+ };
991
+
992
+ var getUserMedia_ = function(constraints, onSuccess, onError) {
993
+ shimConstraints_(constraints, function(c) {
994
+ navigator.webkitGetUserMedia(c, onSuccess, function(e) {
995
+ onError(shimError_(e));
996
+ });
997
+ });
135
998
  };
136
999
 
137
- reattachMediaStream = function(to, from) {
138
- console.log('Reattaching media stream');
139
- to.mozSrcObject = from.mozSrcObject;
1000
+ navigator.getUserMedia = getUserMedia_;
1001
+
1002
+ // Returns the result of getUserMedia as a Promise.
1003
+ var getUserMediaPromise_ = function(constraints) {
1004
+ return new Promise(function(resolve, reject) {
1005
+ navigator.getUserMedia(constraints, resolve, reject);
1006
+ });
140
1007
  };
141
1008
 
142
- } else if (navigator.webkitGetUserMedia) {
143
- console.log('This appears to be Chrome');
1009
+ if (!navigator.mediaDevices) {
1010
+ navigator.mediaDevices = {
1011
+ getUserMedia: getUserMediaPromise_,
1012
+ enumerateDevices: function() {
1013
+ return new Promise(function(resolve) {
1014
+ var kinds = {audio: 'audioinput', video: 'videoinput'};
1015
+ return MediaStreamTrack.getSources(function(devices) {
1016
+ resolve(devices.map(function(device) {
1017
+ return {label: device.label,
1018
+ kind: kinds[device.kind],
1019
+ deviceId: device.id,
1020
+ groupId: ''};
1021
+ }));
1022
+ });
1023
+ });
1024
+ }
1025
+ };
1026
+ }
144
1027
 
145
- webrtcDetectedBrowser = 'chrome';
146
- // Temporary fix until crbug/374263 is fixed.
147
- // Setting Chrome version to 999, if version is unavailable.
148
- var result = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
149
- if (result !== null) {
150
- webrtcDetectedVersion = parseInt(result[2], 10);
1028
+ // A shim for getUserMedia method on the mediaDevices object.
1029
+ // TODO(KaptenJansson) remove once implemented in Chrome stable.
1030
+ if (!navigator.mediaDevices.getUserMedia) {
1031
+ navigator.mediaDevices.getUserMedia = function(constraints) {
1032
+ return getUserMediaPromise_(constraints);
1033
+ };
151
1034
  } else {
152
- webrtcDetectedVersion = 999;
1035
+ // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
1036
+ // function which returns a Promise, it does not accept spec-style
1037
+ // constraints.
1038
+ var origGetUserMedia = navigator.mediaDevices.getUserMedia.
1039
+ bind(navigator.mediaDevices);
1040
+ navigator.mediaDevices.getUserMedia = function(cs) {
1041
+ return shimConstraints_(cs, function(c) {
1042
+ return origGetUserMedia(c).catch(function(e) {
1043
+ return Promise.reject(shimError_(e));
1044
+ });
1045
+ });
1046
+ };
1047
+ }
1048
+
1049
+ // Dummy devicechange event methods.
1050
+ // TODO(KaptenJansson) remove once implemented in Chrome stable.
1051
+ if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
1052
+ navigator.mediaDevices.addEventListener = function() {
1053
+ logging('Dummy mediaDevices.addEventListener called.');
1054
+ };
1055
+ }
1056
+ if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
1057
+ navigator.mediaDevices.removeEventListener = function() {
1058
+ logging('Dummy mediaDevices.removeEventListener called.');
1059
+ };
153
1060
  }
1061
+ };
1062
+
1063
+ },{"../utils.js":10}],5:[function(require,module,exports){
1064
+ /*
1065
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
1066
+ *
1067
+ * Use of this source code is governed by a BSD-style license
1068
+ * that can be found in the LICENSE file in the root of the source
1069
+ * tree.
1070
+ */
1071
+ /* eslint-env node */
1072
+ 'use strict';
154
1073
 
155
- // Creates iceServer from the url for Chrome M33 and earlier.
156
- window.createIceServer = function(url, username, password) {
157
- var iceServer = null;
158
- var urlParts = url.split(':');
159
- if (urlParts[0].indexOf('stun') === 0) {
160
- // Create iceServer with stun url.
161
- iceServer = {
162
- 'url': url
1074
+ var SDPUtils = require('sdp');
1075
+ var logging = require('../utils').log;
1076
+
1077
+ var edgeShim = {
1078
+ shimPeerConnection: function() {
1079
+ if (window.RTCIceGatherer) {
1080
+ // ORTC defines an RTCIceCandidate object but no constructor.
1081
+ // Not implemented in Edge.
1082
+ if (!window.RTCIceCandidate) {
1083
+ window.RTCIceCandidate = function(args) {
1084
+ return args;
1085
+ };
1086
+ }
1087
+ // ORTC does not have a session description object but
1088
+ // other browsers (i.e. Chrome) that will support both PC and ORTC
1089
+ // in the future might have this defined already.
1090
+ if (!window.RTCSessionDescription) {
1091
+ window.RTCSessionDescription = function(args) {
1092
+ return args;
1093
+ };
1094
+ }
1095
+ }
1096
+
1097
+ window.RTCPeerConnection = function(config) {
1098
+ var self = this;
1099
+
1100
+ var _eventTarget = document.createDocumentFragment();
1101
+ ['addEventListener', 'removeEventListener', 'dispatchEvent']
1102
+ .forEach(function(method) {
1103
+ self[method] = _eventTarget[method].bind(_eventTarget);
1104
+ });
1105
+
1106
+ this.onicecandidate = null;
1107
+ this.onaddstream = null;
1108
+ this.ontrack = null;
1109
+ this.onremovestream = null;
1110
+ this.onsignalingstatechange = null;
1111
+ this.oniceconnectionstatechange = null;
1112
+ this.onnegotiationneeded = null;
1113
+ this.ondatachannel = null;
1114
+
1115
+ this.localStreams = [];
1116
+ this.remoteStreams = [];
1117
+ this.getLocalStreams = function() {
1118
+ return self.localStreams;
163
1119
  };
164
- } else if (urlParts[0].indexOf('turn') === 0) {
165
- // Chrome M28 & above uses below TURN format.
166
- iceServer = {
167
- 'url': url,
168
- 'credential': password,
169
- 'username': username
1120
+ this.getRemoteStreams = function() {
1121
+ return self.remoteStreams;
170
1122
  };
171
- }
172
- return iceServer;
173
- };
174
1123
 
175
- // Creates an ICEServer object from multiple URLs.
176
- window.createIceServers = function(urls, username, password) {
1124
+ this.localDescription = new RTCSessionDescription({
1125
+ type: '',
1126
+ sdp: ''
1127
+ });
1128
+ this.remoteDescription = new RTCSessionDescription({
1129
+ type: '',
1130
+ sdp: ''
1131
+ });
1132
+ this.signalingState = 'stable';
1133
+ this.iceConnectionState = 'new';
1134
+ this.iceGatheringState = 'new';
1135
+
1136
+ this.iceOptions = {
1137
+ gatherPolicy: 'all',
1138
+ iceServers: []
1139
+ };
1140
+ if (config && config.iceTransportPolicy) {
1141
+ switch (config.iceTransportPolicy) {
1142
+ case 'all':
1143
+ case 'relay':
1144
+ this.iceOptions.gatherPolicy = config.iceTransportPolicy;
1145
+ break;
1146
+ case 'none':
1147
+ // FIXME: remove once implementation and spec have added this.
1148
+ throw new TypeError('iceTransportPolicy "none" not supported');
1149
+ default:
1150
+ // don't set iceTransportPolicy.
1151
+ break;
1152
+ }
1153
+ }
1154
+ this.usingBundle = config && config.bundlePolicy === 'max-bundle';
1155
+
1156
+ if (config && config.iceServers) {
1157
+ // Edge does not like
1158
+ // 1) stun:
1159
+ // 2) turn: that does not have all of turn:host:port?transport=udp
1160
+ var iceServers = JSON.parse(JSON.stringify(config.iceServers));
1161
+ this.iceOptions.iceServers = iceServers.filter(function(server) {
1162
+ if (server && server.urls) {
1163
+ var urls = server.urls;
1164
+ if (typeof urls === 'string') {
1165
+ urls = [urls];
1166
+ }
1167
+ urls = urls.filter(function(url) {
1168
+ return url.indexOf('turn:') === 0 &&
1169
+ url.indexOf('transport=udp') !== -1;
1170
+ })[0];
1171
+ return !!urls;
1172
+ }
1173
+ return false;
1174
+ });
1175
+ }
1176
+
1177
+ // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
1178
+ // everything that is needed to describe a SDP m-line.
1179
+ this.transceivers = [];
1180
+
1181
+ // since the iceGatherer is currently created in createOffer but we
1182
+ // must not emit candidates until after setLocalDescription we buffer
1183
+ // them in this array.
1184
+ this._localIceCandidatesBuffer = [];
1185
+ };
1186
+
1187
+ window.RTCPeerConnection.prototype._emitBufferedCandidates = function() {
1188
+ var self = this;
1189
+ var sections = SDPUtils.splitSections(self.localDescription.sdp);
1190
+ // FIXME: need to apply ice candidates in a way which is async but
1191
+ // in-order
1192
+ this._localIceCandidatesBuffer.forEach(function(event) {
1193
+ var end = !event.candidate || Object.keys(event.candidate).length === 0;
1194
+ if (end) {
1195
+ for (var j = 1; j < sections.length; j++) {
1196
+ if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
1197
+ sections[j] += 'a=end-of-candidates\r\n';
1198
+ }
1199
+ }
1200
+ } else if (event.candidate.candidate.indexOf('typ endOfCandidates')
1201
+ === -1) {
1202
+ sections[event.candidate.sdpMLineIndex + 1] +=
1203
+ 'a=' + event.candidate.candidate + '\r\n';
1204
+ }
1205
+ self.localDescription.sdp = sections.join('');
1206
+ self.dispatchEvent(event);
1207
+ if (self.onicecandidate !== null) {
1208
+ self.onicecandidate(event);
1209
+ }
1210
+ if (!event.candidate && self.iceGatheringState !== 'complete') {
1211
+ var complete = self.transceivers.every(function(transceiver) {
1212
+ return transceiver.iceGatherer &&
1213
+ transceiver.iceGatherer.state === 'completed';
1214
+ });
1215
+ if (complete) {
1216
+ self.iceGatheringState = 'complete';
1217
+ }
1218
+ }
1219
+ });
1220
+ this._localIceCandidatesBuffer = [];
1221
+ };
1222
+
1223
+ window.RTCPeerConnection.prototype.addStream = function(stream) {
1224
+ // Clone is necessary for local demos mostly, attaching directly
1225
+ // to two different senders does not work (build 10547).
1226
+ this.localStreams.push(stream.clone());
1227
+ this._maybeFireNegotiationNeeded();
1228
+ };
1229
+
1230
+ window.RTCPeerConnection.prototype.removeStream = function(stream) {
1231
+ var idx = this.localStreams.indexOf(stream);
1232
+ if (idx > -1) {
1233
+ this.localStreams.splice(idx, 1);
1234
+ this._maybeFireNegotiationNeeded();
1235
+ }
1236
+ };
1237
+
1238
+ window.RTCPeerConnection.prototype.getSenders = function() {
1239
+ return this.transceivers.filter(function(transceiver) {
1240
+ return !!transceiver.rtpSender;
1241
+ })
1242
+ .map(function(transceiver) {
1243
+ return transceiver.rtpSender;
1244
+ });
1245
+ };
1246
+
1247
+ window.RTCPeerConnection.prototype.getReceivers = function() {
1248
+ return this.transceivers.filter(function(transceiver) {
1249
+ return !!transceiver.rtpReceiver;
1250
+ })
1251
+ .map(function(transceiver) {
1252
+ return transceiver.rtpReceiver;
1253
+ });
1254
+ };
1255
+
1256
+ // Determines the intersection of local and remote capabilities.
1257
+ window.RTCPeerConnection.prototype._getCommonCapabilities =
1258
+ function(localCapabilities, remoteCapabilities) {
1259
+ var commonCapabilities = {
1260
+ codecs: [],
1261
+ headerExtensions: [],
1262
+ fecMechanisms: []
1263
+ };
1264
+ localCapabilities.codecs.forEach(function(lCodec) {
1265
+ for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
1266
+ var rCodec = remoteCapabilities.codecs[i];
1267
+ if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
1268
+ lCodec.clockRate === rCodec.clockRate &&
1269
+ lCodec.numChannels === rCodec.numChannels) {
1270
+ // push rCodec so we reply with offerer payload type
1271
+ commonCapabilities.codecs.push(rCodec);
1272
+
1273
+ // FIXME: also need to determine intersection between
1274
+ // .rtcpFeedback and .parameters
1275
+ break;
1276
+ }
1277
+ }
1278
+ });
1279
+
1280
+ localCapabilities.headerExtensions
1281
+ .forEach(function(lHeaderExtension) {
1282
+ for (var i = 0; i < remoteCapabilities.headerExtensions.length;
1283
+ i++) {
1284
+ var rHeaderExtension = remoteCapabilities.headerExtensions[i];
1285
+ if (lHeaderExtension.uri === rHeaderExtension.uri) {
1286
+ commonCapabilities.headerExtensions.push(rHeaderExtension);
1287
+ break;
1288
+ }
1289
+ }
1290
+ });
1291
+
1292
+ // FIXME: fecMechanisms
1293
+ return commonCapabilities;
1294
+ };
1295
+
1296
+ // Create ICE gatherer, ICE transport and DTLS transport.
1297
+ window.RTCPeerConnection.prototype._createIceAndDtlsTransports =
1298
+ function(mid, sdpMLineIndex) {
1299
+ var self = this;
1300
+ var iceGatherer = new RTCIceGatherer(self.iceOptions);
1301
+ var iceTransport = new RTCIceTransport(iceGatherer);
1302
+ iceGatherer.onlocalcandidate = function(evt) {
1303
+ var event = new Event('icecandidate');
1304
+ event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
1305
+
1306
+ var cand = evt.candidate;
1307
+ var end = !cand || Object.keys(cand).length === 0;
1308
+ // Edge emits an empty object for RTCIceCandidateComplete‥
1309
+ if (end) {
1310
+ // polyfill since RTCIceGatherer.state is not implemented in
1311
+ // Edge 10547 yet.
1312
+ if (iceGatherer.state === undefined) {
1313
+ iceGatherer.state = 'completed';
1314
+ }
1315
+
1316
+ // Emit a candidate with type endOfCandidates to make the samples
1317
+ // work. Edge requires addIceCandidate with this empty candidate
1318
+ // to start checking. The real solution is to signal
1319
+ // end-of-candidates to the other side when getting the null
1320
+ // candidate but some apps (like the samples) don't do that.
1321
+ event.candidate.candidate =
1322
+ 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates';
1323
+ } else {
1324
+ // RTCIceCandidate doesn't have a component, needs to be added
1325
+ cand.component = iceTransport.component === 'RTCP' ? 2 : 1;
1326
+ event.candidate.candidate = SDPUtils.writeCandidate(cand);
1327
+ }
1328
+
1329
+ // update local description.
1330
+ var sections = SDPUtils.splitSections(self.localDescription.sdp);
1331
+ if (event.candidate.candidate.indexOf('typ endOfCandidates')
1332
+ === -1) {
1333
+ sections[event.candidate.sdpMLineIndex + 1] +=
1334
+ 'a=' + event.candidate.candidate + '\r\n';
1335
+ } else {
1336
+ sections[event.candidate.sdpMLineIndex + 1] +=
1337
+ 'a=end-of-candidates\r\n';
1338
+ }
1339
+ self.localDescription.sdp = sections.join('');
1340
+
1341
+ var complete = self.transceivers.every(function(transceiver) {
1342
+ return transceiver.iceGatherer &&
1343
+ transceiver.iceGatherer.state === 'completed';
1344
+ });
1345
+
1346
+ // Emit candidate if localDescription is set.
1347
+ // Also emits null candidate when all gatherers are complete.
1348
+ switch (self.iceGatheringState) {
1349
+ case 'new':
1350
+ self._localIceCandidatesBuffer.push(event);
1351
+ if (end && complete) {
1352
+ self._localIceCandidatesBuffer.push(
1353
+ new Event('icecandidate'));
1354
+ }
1355
+ break;
1356
+ case 'gathering':
1357
+ self._emitBufferedCandidates();
1358
+ self.dispatchEvent(event);
1359
+ if (self.onicecandidate !== null) {
1360
+ self.onicecandidate(event);
1361
+ }
1362
+ if (complete) {
1363
+ self.dispatchEvent(new Event('icecandidate'));
1364
+ if (self.onicecandidate !== null) {
1365
+ self.onicecandidate(new Event('icecandidate'));
1366
+ }
1367
+ self.iceGatheringState = 'complete';
1368
+ }
1369
+ break;
1370
+ case 'complete':
1371
+ // should not happen... currently!
1372
+ break;
1373
+ default: // no-op.
1374
+ break;
1375
+ }
1376
+ };
1377
+ iceTransport.onicestatechange = function() {
1378
+ self._updateConnectionState();
1379
+ };
1380
+
1381
+ var dtlsTransport = new RTCDtlsTransport(iceTransport);
1382
+ dtlsTransport.ondtlsstatechange = function() {
1383
+ self._updateConnectionState();
1384
+ };
1385
+ dtlsTransport.onerror = function() {
1386
+ // onerror does not set state to failed by itself.
1387
+ dtlsTransport.state = 'failed';
1388
+ self._updateConnectionState();
1389
+ };
1390
+
1391
+ return {
1392
+ iceGatherer: iceGatherer,
1393
+ iceTransport: iceTransport,
1394
+ dtlsTransport: dtlsTransport
1395
+ };
1396
+ };
1397
+
1398
+ // Start the RTP Sender and Receiver for a transceiver.
1399
+ window.RTCPeerConnection.prototype._transceive = function(transceiver,
1400
+ send, recv) {
1401
+ var params = this._getCommonCapabilities(transceiver.localCapabilities,
1402
+ transceiver.remoteCapabilities);
1403
+ if (send && transceiver.rtpSender) {
1404
+ params.encodings = transceiver.sendEncodingParameters;
1405
+ params.rtcp = {
1406
+ cname: SDPUtils.localCName
1407
+ };
1408
+ if (transceiver.recvEncodingParameters.length) {
1409
+ params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
1410
+ }
1411
+ transceiver.rtpSender.send(params);
1412
+ }
1413
+ if (recv && transceiver.rtpReceiver) {
1414
+ params.encodings = transceiver.recvEncodingParameters;
1415
+ params.rtcp = {
1416
+ cname: transceiver.cname
1417
+ };
1418
+ if (transceiver.sendEncodingParameters.length) {
1419
+ params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
1420
+ }
1421
+ transceiver.rtpReceiver.receive(params);
1422
+ }
1423
+ };
1424
+
1425
+ window.RTCPeerConnection.prototype.setLocalDescription =
1426
+ function(description) {
1427
+ var self = this;
1428
+ var sections;
1429
+ var sessionpart;
1430
+ if (description.type === 'offer') {
1431
+ // FIXME: What was the purpose of this empty if statement?
1432
+ // if (!this._pendingOffer) {
1433
+ // } else {
1434
+ if (this._pendingOffer) {
1435
+ // VERY limited support for SDP munging. Limited to:
1436
+ // * changing the order of codecs
1437
+ sections = SDPUtils.splitSections(description.sdp);
1438
+ sessionpart = sections.shift();
1439
+ sections.forEach(function(mediaSection, sdpMLineIndex) {
1440
+ var caps = SDPUtils.parseRtpParameters(mediaSection);
1441
+ self._pendingOffer[sdpMLineIndex].localCapabilities = caps;
1442
+ });
1443
+ this.transceivers = this._pendingOffer;
1444
+ delete this._pendingOffer;
1445
+ }
1446
+ } else if (description.type === 'answer') {
1447
+ sections = SDPUtils.splitSections(self.remoteDescription.sdp);
1448
+ sessionpart = sections.shift();
1449
+ var isIceLite = SDPUtils.matchPrefix(sessionpart,
1450
+ 'a=ice-lite').length > 0;
1451
+ sections.forEach(function(mediaSection, sdpMLineIndex) {
1452
+ var transceiver = self.transceivers[sdpMLineIndex];
1453
+ var iceGatherer = transceiver.iceGatherer;
1454
+ var iceTransport = transceiver.iceTransport;
1455
+ var dtlsTransport = transceiver.dtlsTransport;
1456
+ var localCapabilities = transceiver.localCapabilities;
1457
+ var remoteCapabilities = transceiver.remoteCapabilities;
1458
+ var rejected = mediaSection.split('\n', 1)[0]
1459
+ .split(' ', 2)[1] === '0';
1460
+
1461
+ if (!rejected) {
1462
+ var remoteIceParameters = SDPUtils.getIceParameters(
1463
+ mediaSection, sessionpart);
1464
+ if (isIceLite) {
1465
+ var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
1466
+ .map(function(cand) {
1467
+ return SDPUtils.parseCandidate(cand);
1468
+ })
1469
+ .filter(function(cand) {
1470
+ return cand.component === '1';
1471
+ });
1472
+ // ice-lite only includes host candidates in the SDP so we can
1473
+ // use setRemoteCandidates (which implies an
1474
+ // RTCIceCandidateComplete)
1475
+ if (cands.length) {
1476
+ iceTransport.setRemoteCandidates(cands);
1477
+ }
1478
+ }
1479
+ var remoteDtlsParameters = SDPUtils.getDtlsParameters(
1480
+ mediaSection, sessionpart);
1481
+ if (isIceLite) {
1482
+ remoteDtlsParameters.role = 'server';
1483
+ }
1484
+
1485
+ if (!self.usingBundle || sdpMLineIndex === 0) {
1486
+ iceTransport.start(iceGatherer, remoteIceParameters,
1487
+ isIceLite ? 'controlling' : 'controlled');
1488
+ dtlsTransport.start(remoteDtlsParameters);
1489
+ }
1490
+
1491
+ // Calculate intersection of capabilities.
1492
+ var params = self._getCommonCapabilities(localCapabilities,
1493
+ remoteCapabilities);
1494
+
1495
+ // Start the RTCRtpSender. The RTCRtpReceiver for this
1496
+ // transceiver has already been started in setRemoteDescription.
1497
+ self._transceive(transceiver,
1498
+ params.codecs.length > 0,
1499
+ false);
1500
+ }
1501
+ });
1502
+ }
1503
+
1504
+ this.localDescription = {
1505
+ type: description.type,
1506
+ sdp: description.sdp
1507
+ };
1508
+ switch (description.type) {
1509
+ case 'offer':
1510
+ this._updateSignalingState('have-local-offer');
1511
+ break;
1512
+ case 'answer':
1513
+ this._updateSignalingState('stable');
1514
+ break;
1515
+ default:
1516
+ throw new TypeError('unsupported type "' + description.type +
1517
+ '"');
1518
+ }
1519
+
1520
+ // If a success callback was provided, emit ICE candidates after it
1521
+ // has been executed. Otherwise, emit callback after the Promise is
1522
+ // resolved.
1523
+ var hasCallback = arguments.length > 1 &&
1524
+ typeof arguments[1] === 'function';
1525
+ if (hasCallback) {
1526
+ var cb = arguments[1];
1527
+ window.setTimeout(function() {
1528
+ cb();
1529
+ if (self.iceGatheringState === 'new') {
1530
+ self.iceGatheringState = 'gathering';
1531
+ }
1532
+ self._emitBufferedCandidates();
1533
+ }, 0);
1534
+ }
1535
+ var p = Promise.resolve();
1536
+ p.then(function() {
1537
+ if (!hasCallback) {
1538
+ if (self.iceGatheringState === 'new') {
1539
+ self.iceGatheringState = 'gathering';
1540
+ }
1541
+ // Usually candidates will be emitted earlier.
1542
+ window.setTimeout(self._emitBufferedCandidates.bind(self), 500);
1543
+ }
1544
+ });
1545
+ return p;
1546
+ };
1547
+
1548
+ window.RTCPeerConnection.prototype.setRemoteDescription =
1549
+ function(description) {
1550
+ var self = this;
1551
+ var stream = new MediaStream();
1552
+ var receiverList = [];
1553
+ var sections = SDPUtils.splitSections(description.sdp);
1554
+ var sessionpart = sections.shift();
1555
+ var isIceLite = SDPUtils.matchPrefix(sessionpart,
1556
+ 'a=ice-lite').length > 0;
1557
+ this.usingBundle = SDPUtils.matchPrefix(sessionpart,
1558
+ 'a=group:BUNDLE ').length > 0;
1559
+ sections.forEach(function(mediaSection, sdpMLineIndex) {
1560
+ var lines = SDPUtils.splitLines(mediaSection);
1561
+ var mline = lines[0].substr(2).split(' ');
1562
+ var kind = mline[0];
1563
+ var rejected = mline[1] === '0';
1564
+ var direction = SDPUtils.getDirection(mediaSection, sessionpart);
1565
+
1566
+ var transceiver;
1567
+ var iceGatherer;
1568
+ var iceTransport;
1569
+ var dtlsTransport;
1570
+ var rtpSender;
1571
+ var rtpReceiver;
1572
+ var sendEncodingParameters;
1573
+ var recvEncodingParameters;
1574
+ var localCapabilities;
1575
+
1576
+ var track;
1577
+ // FIXME: ensure the mediaSection has rtcp-mux set.
1578
+ var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
1579
+ var remoteIceParameters;
1580
+ var remoteDtlsParameters;
1581
+ if (!rejected) {
1582
+ remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
1583
+ sessionpart);
1584
+ remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
1585
+ sessionpart);
1586
+ remoteDtlsParameters.role = 'client';
1587
+ }
1588
+ recvEncodingParameters =
1589
+ SDPUtils.parseRtpEncodingParameters(mediaSection);
1590
+
1591
+ var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:');
1592
+ if (mid.length) {
1593
+ mid = mid[0].substr(6);
1594
+ } else {
1595
+ mid = SDPUtils.generateIdentifier();
1596
+ }
1597
+
1598
+ var cname;
1599
+ // Gets the first SSRC. Note that with RTX there might be multiple
1600
+ // SSRCs.
1601
+ var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
1602
+ .map(function(line) {
1603
+ return SDPUtils.parseSsrcMedia(line);
1604
+ })
1605
+ .filter(function(obj) {
1606
+ return obj.attribute === 'cname';
1607
+ })[0];
1608
+ if (remoteSsrc) {
1609
+ cname = remoteSsrc.value;
1610
+ }
1611
+
1612
+ var isComplete = SDPUtils.matchPrefix(mediaSection,
1613
+ 'a=end-of-candidates').length > 0;
1614
+ var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
1615
+ .map(function(cand) {
1616
+ return SDPUtils.parseCandidate(cand);
1617
+ })
1618
+ .filter(function(cand) {
1619
+ return cand.component === '1';
1620
+ });
1621
+ if (description.type === 'offer' && !rejected) {
1622
+ var transports = self.usingBundle && sdpMLineIndex > 0 ? {
1623
+ iceGatherer: self.transceivers[0].iceGatherer,
1624
+ iceTransport: self.transceivers[0].iceTransport,
1625
+ dtlsTransport: self.transceivers[0].dtlsTransport
1626
+ } : self._createIceAndDtlsTransports(mid, sdpMLineIndex);
1627
+
1628
+ if (isComplete) {
1629
+ transports.iceTransport.setRemoteCandidates(cands);
1630
+ }
1631
+
1632
+ localCapabilities = RTCRtpReceiver.getCapabilities(kind);
1633
+ sendEncodingParameters = [{
1634
+ ssrc: (2 * sdpMLineIndex + 2) * 1001
1635
+ }];
1636
+
1637
+ rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
1638
+
1639
+ track = rtpReceiver.track;
1640
+ receiverList.push([track, rtpReceiver]);
1641
+ // FIXME: not correct when there are multiple streams but that is
1642
+ // not currently supported in this shim.
1643
+ stream.addTrack(track);
1644
+
1645
+ // FIXME: look at direction.
1646
+ if (self.localStreams.length > 0 &&
1647
+ self.localStreams[0].getTracks().length >= sdpMLineIndex) {
1648
+ // FIXME: actually more complicated, needs to match types etc
1649
+ var localtrack = self.localStreams[0]
1650
+ .getTracks()[sdpMLineIndex];
1651
+ rtpSender = new RTCRtpSender(localtrack,
1652
+ transports.dtlsTransport);
1653
+ }
1654
+
1655
+ self.transceivers[sdpMLineIndex] = {
1656
+ iceGatherer: transports.iceGatherer,
1657
+ iceTransport: transports.iceTransport,
1658
+ dtlsTransport: transports.dtlsTransport,
1659
+ localCapabilities: localCapabilities,
1660
+ remoteCapabilities: remoteCapabilities,
1661
+ rtpSender: rtpSender,
1662
+ rtpReceiver: rtpReceiver,
1663
+ kind: kind,
1664
+ mid: mid,
1665
+ cname: cname,
1666
+ sendEncodingParameters: sendEncodingParameters,
1667
+ recvEncodingParameters: recvEncodingParameters
1668
+ };
1669
+ // Start the RTCRtpReceiver now. The RTPSender is started in
1670
+ // setLocalDescription.
1671
+ self._transceive(self.transceivers[sdpMLineIndex],
1672
+ false,
1673
+ direction === 'sendrecv' || direction === 'sendonly');
1674
+ } else if (description.type === 'answer' && !rejected) {
1675
+ transceiver = self.transceivers[sdpMLineIndex];
1676
+ iceGatherer = transceiver.iceGatherer;
1677
+ iceTransport = transceiver.iceTransport;
1678
+ dtlsTransport = transceiver.dtlsTransport;
1679
+ rtpSender = transceiver.rtpSender;
1680
+ rtpReceiver = transceiver.rtpReceiver;
1681
+ sendEncodingParameters = transceiver.sendEncodingParameters;
1682
+ localCapabilities = transceiver.localCapabilities;
1683
+
1684
+ self.transceivers[sdpMLineIndex].recvEncodingParameters =
1685
+ recvEncodingParameters;
1686
+ self.transceivers[sdpMLineIndex].remoteCapabilities =
1687
+ remoteCapabilities;
1688
+ self.transceivers[sdpMLineIndex].cname = cname;
1689
+
1690
+ if ((isIceLite || isComplete) && cands.length) {
1691
+ iceTransport.setRemoteCandidates(cands);
1692
+ }
1693
+ if (!self.usingBundle || sdpMLineIndex === 0) {
1694
+ iceTransport.start(iceGatherer, remoteIceParameters,
1695
+ 'controlling');
1696
+ dtlsTransport.start(remoteDtlsParameters);
1697
+ }
1698
+
1699
+ self._transceive(transceiver,
1700
+ direction === 'sendrecv' || direction === 'recvonly',
1701
+ direction === 'sendrecv' || direction === 'sendonly');
1702
+
1703
+ if (rtpReceiver &&
1704
+ (direction === 'sendrecv' || direction === 'sendonly')) {
1705
+ track = rtpReceiver.track;
1706
+ receiverList.push([track, rtpReceiver]);
1707
+ stream.addTrack(track);
1708
+ } else {
1709
+ // FIXME: actually the receiver should be created later.
1710
+ delete transceiver.rtpReceiver;
1711
+ }
1712
+ }
1713
+ });
1714
+
1715
+ this.remoteDescription = {
1716
+ type: description.type,
1717
+ sdp: description.sdp
1718
+ };
1719
+ switch (description.type) {
1720
+ case 'offer':
1721
+ this._updateSignalingState('have-remote-offer');
1722
+ break;
1723
+ case 'answer':
1724
+ this._updateSignalingState('stable');
1725
+ break;
1726
+ default:
1727
+ throw new TypeError('unsupported type "' + description.type +
1728
+ '"');
1729
+ }
1730
+ if (stream.getTracks().length) {
1731
+ self.remoteStreams.push(stream);
1732
+ window.setTimeout(function() {
1733
+ var event = new Event('addstream');
1734
+ event.stream = stream;
1735
+ self.dispatchEvent(event);
1736
+ if (self.onaddstream !== null) {
1737
+ window.setTimeout(function() {
1738
+ self.onaddstream(event);
1739
+ }, 0);
1740
+ }
1741
+
1742
+ receiverList.forEach(function(item) {
1743
+ var track = item[0];
1744
+ var receiver = item[1];
1745
+ var trackEvent = new Event('track');
1746
+ trackEvent.track = track;
1747
+ trackEvent.receiver = receiver;
1748
+ trackEvent.streams = [stream];
1749
+ self.dispatchEvent(event);
1750
+ if (self.ontrack !== null) {
1751
+ window.setTimeout(function() {
1752
+ self.ontrack(trackEvent);
1753
+ }, 0);
1754
+ }
1755
+ });
1756
+ }, 0);
1757
+ }
1758
+ if (arguments.length > 1 && typeof arguments[1] === 'function') {
1759
+ window.setTimeout(arguments[1], 0);
1760
+ }
1761
+ return Promise.resolve();
1762
+ };
1763
+
1764
+ window.RTCPeerConnection.prototype.close = function() {
1765
+ this.transceivers.forEach(function(transceiver) {
1766
+ /* not yet
1767
+ if (transceiver.iceGatherer) {
1768
+ transceiver.iceGatherer.close();
1769
+ }
1770
+ */
1771
+ if (transceiver.iceTransport) {
1772
+ transceiver.iceTransport.stop();
1773
+ }
1774
+ if (transceiver.dtlsTransport) {
1775
+ transceiver.dtlsTransport.stop();
1776
+ }
1777
+ if (transceiver.rtpSender) {
1778
+ transceiver.rtpSender.stop();
1779
+ }
1780
+ if (transceiver.rtpReceiver) {
1781
+ transceiver.rtpReceiver.stop();
1782
+ }
1783
+ });
1784
+ // FIXME: clean up tracks, local streams, remote streams, etc
1785
+ this._updateSignalingState('closed');
1786
+ };
1787
+
1788
+ // Update the signaling state.
1789
+ window.RTCPeerConnection.prototype._updateSignalingState =
1790
+ function(newState) {
1791
+ this.signalingState = newState;
1792
+ var event = new Event('signalingstatechange');
1793
+ this.dispatchEvent(event);
1794
+ if (this.onsignalingstatechange !== null) {
1795
+ this.onsignalingstatechange(event);
1796
+ }
1797
+ };
1798
+
1799
+ // Determine whether to fire the negotiationneeded event.
1800
+ window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded =
1801
+ function() {
1802
+ // Fire away (for now).
1803
+ var event = new Event('negotiationneeded');
1804
+ this.dispatchEvent(event);
1805
+ if (this.onnegotiationneeded !== null) {
1806
+ this.onnegotiationneeded(event);
1807
+ }
1808
+ };
1809
+
1810
+ // Update the connection state.
1811
+ window.RTCPeerConnection.prototype._updateConnectionState = function() {
1812
+ var self = this;
1813
+ var newState;
1814
+ var states = {
1815
+ 'new': 0,
1816
+ closed: 0,
1817
+ connecting: 0,
1818
+ checking: 0,
1819
+ connected: 0,
1820
+ completed: 0,
1821
+ failed: 0
1822
+ };
1823
+ this.transceivers.forEach(function(transceiver) {
1824
+ states[transceiver.iceTransport.state]++;
1825
+ states[transceiver.dtlsTransport.state]++;
1826
+ });
1827
+ // ICETransport.completed and connected are the same for this purpose.
1828
+ states.connected += states.completed;
1829
+
1830
+ newState = 'new';
1831
+ if (states.failed > 0) {
1832
+ newState = 'failed';
1833
+ } else if (states.connecting > 0 || states.checking > 0) {
1834
+ newState = 'connecting';
1835
+ } else if (states.disconnected > 0) {
1836
+ newState = 'disconnected';
1837
+ } else if (states.new > 0) {
1838
+ newState = 'new';
1839
+ } else if (states.connected > 0 || states.completed > 0) {
1840
+ newState = 'connected';
1841
+ }
1842
+
1843
+ if (newState !== self.iceConnectionState) {
1844
+ self.iceConnectionState = newState;
1845
+ var event = new Event('iceconnectionstatechange');
1846
+ this.dispatchEvent(event);
1847
+ if (this.oniceconnectionstatechange !== null) {
1848
+ this.oniceconnectionstatechange(event);
1849
+ }
1850
+ }
1851
+ };
1852
+
1853
+ window.RTCPeerConnection.prototype.createOffer = function() {
1854
+ var self = this;
1855
+ if (this._pendingOffer) {
1856
+ throw new Error('createOffer called while there is a pending offer.');
1857
+ }
1858
+ var offerOptions;
1859
+ if (arguments.length === 1 && typeof arguments[0] !== 'function') {
1860
+ offerOptions = arguments[0];
1861
+ } else if (arguments.length === 3) {
1862
+ offerOptions = arguments[2];
1863
+ }
1864
+
1865
+ var tracks = [];
1866
+ var numAudioTracks = 0;
1867
+ var numVideoTracks = 0;
1868
+ // Default to sendrecv.
1869
+ if (this.localStreams.length) {
1870
+ numAudioTracks = this.localStreams[0].getAudioTracks().length;
1871
+ numVideoTracks = this.localStreams[0].getVideoTracks().length;
1872
+ }
1873
+ // Determine number of audio and video tracks we need to send/recv.
1874
+ if (offerOptions) {
1875
+ // Reject Chrome legacy constraints.
1876
+ if (offerOptions.mandatory || offerOptions.optional) {
1877
+ throw new TypeError(
1878
+ 'Legacy mandatory/optional constraints not supported.');
1879
+ }
1880
+ if (offerOptions.offerToReceiveAudio !== undefined) {
1881
+ numAudioTracks = offerOptions.offerToReceiveAudio;
1882
+ }
1883
+ if (offerOptions.offerToReceiveVideo !== undefined) {
1884
+ numVideoTracks = offerOptions.offerToReceiveVideo;
1885
+ }
1886
+ }
1887
+ if (this.localStreams.length) {
1888
+ // Push local streams.
1889
+ this.localStreams[0].getTracks().forEach(function(track) {
1890
+ tracks.push({
1891
+ kind: track.kind,
1892
+ track: track,
1893
+ wantReceive: track.kind === 'audio' ?
1894
+ numAudioTracks > 0 : numVideoTracks > 0
1895
+ });
1896
+ if (track.kind === 'audio') {
1897
+ numAudioTracks--;
1898
+ } else if (track.kind === 'video') {
1899
+ numVideoTracks--;
1900
+ }
1901
+ });
1902
+ }
1903
+ // Create M-lines for recvonly streams.
1904
+ while (numAudioTracks > 0 || numVideoTracks > 0) {
1905
+ if (numAudioTracks > 0) {
1906
+ tracks.push({
1907
+ kind: 'audio',
1908
+ wantReceive: true
1909
+ });
1910
+ numAudioTracks--;
1911
+ }
1912
+ if (numVideoTracks > 0) {
1913
+ tracks.push({
1914
+ kind: 'video',
1915
+ wantReceive: true
1916
+ });
1917
+ numVideoTracks--;
1918
+ }
1919
+ }
1920
+
1921
+ var sdp = SDPUtils.writeSessionBoilerplate();
1922
+ var transceivers = [];
1923
+ tracks.forEach(function(mline, sdpMLineIndex) {
1924
+ // For each track, create an ice gatherer, ice transport,
1925
+ // dtls transport, potentially rtpsender and rtpreceiver.
1926
+ var track = mline.track;
1927
+ var kind = mline.kind;
1928
+ var mid = SDPUtils.generateIdentifier();
1929
+
1930
+ var transports = self.usingBundle && sdpMLineIndex > 0 ? {
1931
+ iceGatherer: transceivers[0].iceGatherer,
1932
+ iceTransport: transceivers[0].iceTransport,
1933
+ dtlsTransport: transceivers[0].dtlsTransport
1934
+ } : self._createIceAndDtlsTransports(mid, sdpMLineIndex);
1935
+
1936
+ var localCapabilities = RTCRtpSender.getCapabilities(kind);
1937
+ var rtpSender;
1938
+ var rtpReceiver;
1939
+
1940
+ // generate an ssrc now, to be used later in rtpSender.send
1941
+ var sendEncodingParameters = [{
1942
+ ssrc: (2 * sdpMLineIndex + 1) * 1001
1943
+ }];
1944
+ if (track) {
1945
+ rtpSender = new RTCRtpSender(track, transports.dtlsTransport);
1946
+ }
1947
+
1948
+ if (mline.wantReceive) {
1949
+ rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
1950
+ }
1951
+
1952
+ transceivers[sdpMLineIndex] = {
1953
+ iceGatherer: transports.iceGatherer,
1954
+ iceTransport: transports.iceTransport,
1955
+ dtlsTransport: transports.dtlsTransport,
1956
+ localCapabilities: localCapabilities,
1957
+ remoteCapabilities: null,
1958
+ rtpSender: rtpSender,
1959
+ rtpReceiver: rtpReceiver,
1960
+ kind: kind,
1961
+ mid: mid,
1962
+ sendEncodingParameters: sendEncodingParameters,
1963
+ recvEncodingParameters: null
1964
+ };
1965
+ });
1966
+ if (this.usingBundle) {
1967
+ sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) {
1968
+ return t.mid;
1969
+ }).join(' ') + '\r\n';
1970
+ }
1971
+ tracks.forEach(function(mline, sdpMLineIndex) {
1972
+ var transceiver = transceivers[sdpMLineIndex];
1973
+ sdp += SDPUtils.writeMediaSection(transceiver,
1974
+ transceiver.localCapabilities, 'offer', self.localStreams[0]);
1975
+ });
1976
+
1977
+ this._pendingOffer = transceivers;
1978
+ var desc = new RTCSessionDescription({
1979
+ type: 'offer',
1980
+ sdp: sdp
1981
+ });
1982
+ if (arguments.length && typeof arguments[0] === 'function') {
1983
+ window.setTimeout(arguments[0], 0, desc);
1984
+ }
1985
+ return Promise.resolve(desc);
1986
+ };
1987
+
1988
+ window.RTCPeerConnection.prototype.createAnswer = function() {
1989
+ var self = this;
1990
+
1991
+ var sdp = SDPUtils.writeSessionBoilerplate();
1992
+ if (this.usingBundle) {
1993
+ sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
1994
+ return t.mid;
1995
+ }).join(' ') + '\r\n';
1996
+ }
1997
+ this.transceivers.forEach(function(transceiver) {
1998
+ // Calculate intersection of capabilities.
1999
+ var commonCapabilities = self._getCommonCapabilities(
2000
+ transceiver.localCapabilities,
2001
+ transceiver.remoteCapabilities);
2002
+
2003
+ sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
2004
+ 'answer', self.localStreams[0]);
2005
+ });
2006
+
2007
+ var desc = new RTCSessionDescription({
2008
+ type: 'answer',
2009
+ sdp: sdp
2010
+ });
2011
+ if (arguments.length && typeof arguments[0] === 'function') {
2012
+ window.setTimeout(arguments[0], 0, desc);
2013
+ }
2014
+ return Promise.resolve(desc);
2015
+ };
2016
+
2017
+ window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
2018
+ if (candidate === null) {
2019
+ this.transceivers.forEach(function(transceiver) {
2020
+ transceiver.iceTransport.addRemoteCandidate({});
2021
+ });
2022
+ } else {
2023
+ var mLineIndex = candidate.sdpMLineIndex;
2024
+ if (candidate.sdpMid) {
2025
+ for (var i = 0; i < this.transceivers.length; i++) {
2026
+ if (this.transceivers[i].mid === candidate.sdpMid) {
2027
+ mLineIndex = i;
2028
+ break;
2029
+ }
2030
+ }
2031
+ }
2032
+ var transceiver = this.transceivers[mLineIndex];
2033
+ if (transceiver) {
2034
+ var cand = Object.keys(candidate.candidate).length > 0 ?
2035
+ SDPUtils.parseCandidate(candidate.candidate) : {};
2036
+ // Ignore Chrome's invalid candidates since Edge does not like them.
2037
+ if (cand.protocol === 'tcp' && cand.port === 0) {
2038
+ return;
2039
+ }
2040
+ // Ignore RTCP candidates, we assume RTCP-MUX.
2041
+ if (cand.component !== '1') {
2042
+ return;
2043
+ }
2044
+ // A dirty hack to make samples work.
2045
+ if (cand.type === 'endOfCandidates') {
2046
+ cand = {};
2047
+ }
2048
+ transceiver.iceTransport.addRemoteCandidate(cand);
2049
+
2050
+ // update the remoteDescription.
2051
+ var sections = SDPUtils.splitSections(this.remoteDescription.sdp);
2052
+ sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim()
2053
+ : 'a=end-of-candidates') + '\r\n';
2054
+ this.remoteDescription.sdp = sections.join('');
2055
+ }
2056
+ }
2057
+ if (arguments.length > 1 && typeof arguments[1] === 'function') {
2058
+ window.setTimeout(arguments[1], 0);
2059
+ }
2060
+ return Promise.resolve();
2061
+ };
2062
+
2063
+ window.RTCPeerConnection.prototype.getStats = function() {
2064
+ var promises = [];
2065
+ this.transceivers.forEach(function(transceiver) {
2066
+ ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
2067
+ 'dtlsTransport'].forEach(function(method) {
2068
+ if (transceiver[method]) {
2069
+ promises.push(transceiver[method].getStats());
2070
+ }
2071
+ });
2072
+ });
2073
+ var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
2074
+ arguments[1];
2075
+ return new Promise(function(resolve) {
2076
+ // shim getStats with maplike support
2077
+ var results = new Map();
2078
+ Promise.all(promises).then(function(res) {
2079
+ res.forEach(function(result) {
2080
+ Object.keys(result).forEach(function(id) {
2081
+ results.set(id, result[id]);
2082
+ results[id] = result[id];
2083
+ });
2084
+ });
2085
+ if (cb) {
2086
+ window.setTimeout(cb, 0, results);
2087
+ }
2088
+ resolve(results);
2089
+ });
2090
+ });
2091
+ };
2092
+ },
2093
+
2094
+ // Attach a media stream to an element.
2095
+ attachMediaStream: function(element, stream) {
2096
+ logging('DEPRECATED, attachMediaStream will soon be removed.');
2097
+ element.srcObject = stream;
2098
+ },
2099
+
2100
+ reattachMediaStream: function(to, from) {
2101
+ logging('DEPRECATED, reattachMediaStream will soon be removed.');
2102
+ to.srcObject = from.srcObject;
2103
+ }
2104
+ };
2105
+
2106
+ // Expose public methods.
2107
+ module.exports = {
2108
+ shimPeerConnection: edgeShim.shimPeerConnection,
2109
+ shimGetUserMedia: require('./getusermedia'),
2110
+ attachMediaStream: edgeShim.attachMediaStream,
2111
+ reattachMediaStream: edgeShim.reattachMediaStream
2112
+ };
2113
+
2114
+ },{"../utils":10,"./getusermedia":6,"sdp":1}],6:[function(require,module,exports){
2115
+ /*
2116
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
2117
+ *
2118
+ * Use of this source code is governed by a BSD-style license
2119
+ * that can be found in the LICENSE file in the root of the source
2120
+ * tree.
2121
+ */
2122
+ /* eslint-env node */
2123
+ 'use strict';
2124
+
2125
+ // Expose public methods.
2126
+ module.exports = function() {
2127
+ var shimError_ = function(e) {
177
2128
  return {
178
- 'urls': urls,
179
- 'credential': password,
180
- 'username': username
2129
+ name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name,
2130
+ message: e.message,
2131
+ constraint: e.constraint,
2132
+ toString: function() {
2133
+ return this.name;
2134
+ }
181
2135
  };
182
2136
  };
183
2137
 
184
- // The RTCPeerConnection object.
185
- RTCPeerConnection = function(pcConfig, pcConstraints) {
186
- return new webkitRTCPeerConnection(pcConfig, pcConstraints);
2138
+ // getUserMedia error shim.
2139
+ var origGetUserMedia = navigator.mediaDevices.getUserMedia.
2140
+ bind(navigator.mediaDevices);
2141
+ navigator.mediaDevices.getUserMedia = function(c) {
2142
+ return origGetUserMedia(c).catch(function(e) {
2143
+ return Promise.reject(shimError_(e));
2144
+ });
187
2145
  };
2146
+ };
2147
+
2148
+ },{}],7:[function(require,module,exports){
2149
+ /*
2150
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
2151
+ *
2152
+ * Use of this source code is governed by a BSD-style license
2153
+ * that can be found in the LICENSE file in the root of the source
2154
+ * tree.
2155
+ */
2156
+ /* eslint-env node */
2157
+ 'use strict';
2158
+
2159
+ var logging = require('../utils').log;
2160
+ var browserDetails = require('../utils').browserDetails;
188
2161
 
189
- // Get UserMedia (only difference is the prefix).
190
- // Code from Adam Barth.
191
- getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
192
- navigator.getUserMedia = getUserMedia;
2162
+ var firefoxShim = {
2163
+ shimOnTrack: function() {
2164
+ if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
2165
+ window.RTCPeerConnection.prototype)) {
2166
+ Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
2167
+ get: function() {
2168
+ return this._ontrack;
2169
+ },
2170
+ set: function(f) {
2171
+ if (this._ontrack) {
2172
+ this.removeEventListener('track', this._ontrack);
2173
+ this.removeEventListener('addstream', this._ontrackpoly);
2174
+ }
2175
+ this.addEventListener('track', this._ontrack = f);
2176
+ this.addEventListener('addstream', this._ontrackpoly = function(e) {
2177
+ e.stream.getTracks().forEach(function(track) {
2178
+ var event = new Event('track');
2179
+ event.track = track;
2180
+ event.receiver = {track: track};
2181
+ event.streams = [e.stream];
2182
+ this.dispatchEvent(event);
2183
+ }.bind(this));
2184
+ }.bind(this));
2185
+ }
2186
+ });
2187
+ }
2188
+ },
2189
+
2190
+ shimSourceObject: function() {
2191
+ // Firefox has supported mozSrcObject since FF22, unprefixed in 42.
2192
+ if (typeof window === 'object') {
2193
+ if (window.HTMLMediaElement &&
2194
+ !('srcObject' in window.HTMLMediaElement.prototype)) {
2195
+ // Shim the srcObject property, once, when HTMLMediaElement is found.
2196
+ Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
2197
+ get: function() {
2198
+ return this.mozSrcObject;
2199
+ },
2200
+ set: function(stream) {
2201
+ this.mozSrcObject = stream;
2202
+ }
2203
+ });
2204
+ }
2205
+ }
2206
+ },
2207
+
2208
+ shimPeerConnection: function() {
2209
+ if (typeof window !== 'object' || !(window.RTCPeerConnection ||
2210
+ window.mozRTCPeerConnection)) {
2211
+ return; // probably media.peerconnection.enabled=false in about:config
2212
+ }
2213
+ // The RTCPeerConnection object.
2214
+ if (!window.RTCPeerConnection) {
2215
+ window.RTCPeerConnection = function(pcConfig, pcConstraints) {
2216
+ if (browserDetails.version < 38) {
2217
+ // .urls is not supported in FF < 38.
2218
+ // create RTCIceServers with a single url.
2219
+ if (pcConfig && pcConfig.iceServers) {
2220
+ var newIceServers = [];
2221
+ for (var i = 0; i < pcConfig.iceServers.length; i++) {
2222
+ var server = pcConfig.iceServers[i];
2223
+ if (server.hasOwnProperty('urls')) {
2224
+ for (var j = 0; j < server.urls.length; j++) {
2225
+ var newServer = {
2226
+ url: server.urls[j]
2227
+ };
2228
+ if (server.urls[j].indexOf('turn') === 0) {
2229
+ newServer.username = server.username;
2230
+ newServer.credential = server.credential;
2231
+ }
2232
+ newIceServers.push(newServer);
2233
+ }
2234
+ } else {
2235
+ newIceServers.push(pcConfig.iceServers[i]);
2236
+ }
2237
+ }
2238
+ pcConfig.iceServers = newIceServers;
2239
+ }
2240
+ }
2241
+ return new mozRTCPeerConnection(pcConfig, pcConstraints);
2242
+ };
2243
+ window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype;
2244
+
2245
+ // wrap static methods. Currently just generateCertificate.
2246
+ if (mozRTCPeerConnection.generateCertificate) {
2247
+ Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
2248
+ get: function() {
2249
+ return mozRTCPeerConnection.generateCertificate;
2250
+ }
2251
+ });
2252
+ }
2253
+
2254
+ window.RTCSessionDescription = mozRTCSessionDescription;
2255
+ window.RTCIceCandidate = mozRTCIceCandidate;
2256
+ }
2257
+
2258
+ // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
2259
+ ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
2260
+ .forEach(function(method) {
2261
+ var nativeMethod = RTCPeerConnection.prototype[method];
2262
+ RTCPeerConnection.prototype[method] = function() {
2263
+ arguments[0] = new ((method === 'addIceCandidate') ?
2264
+ RTCIceCandidate : RTCSessionDescription)(arguments[0]);
2265
+ return nativeMethod.apply(this, arguments);
2266
+ };
2267
+ });
2268
+
2269
+ // support for addIceCandidate(null)
2270
+ var nativeAddIceCandidate =
2271
+ RTCPeerConnection.prototype.addIceCandidate;
2272
+ RTCPeerConnection.prototype.addIceCandidate = function() {
2273
+ return arguments[0] === null ? Promise.resolve()
2274
+ : nativeAddIceCandidate.apply(this, arguments);
2275
+ };
2276
+
2277
+ // shim getStats with maplike support
2278
+ var makeMapStats = function(stats) {
2279
+ var map = new Map();
2280
+ Object.keys(stats).forEach(function(key) {
2281
+ map.set(key, stats[key]);
2282
+ map[key] = stats[key];
2283
+ });
2284
+ return map;
2285
+ };
2286
+
2287
+ var nativeGetStats = RTCPeerConnection.prototype.getStats;
2288
+ RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) {
2289
+ return nativeGetStats.apply(this, [selector || null])
2290
+ .then(function(stats) {
2291
+ return makeMapStats(stats);
2292
+ })
2293
+ .then(onSucc, onErr);
2294
+ };
2295
+ },
193
2296
 
194
2297
  // Attach a media stream to an element.
195
- attachMediaStream = function(element, stream) {
196
- if (typeof element.srcObject !== 'undefined') {
197
- element.srcObject = stream;
198
- } else if (typeof element.mozSrcObject !== 'undefined') {
199
- element.mozSrcObject = stream;
200
- } else if (typeof element.src !== 'undefined') {
201
- element.src = URL.createObjectURL(stream);
202
- } else {
203
- console.log('Error attaching stream to element.');
2298
+ attachMediaStream: function(element, stream) {
2299
+ logging('DEPRECATED, attachMediaStream will soon be removed.');
2300
+ element.srcObject = stream;
2301
+ },
2302
+
2303
+ reattachMediaStream: function(to, from) {
2304
+ logging('DEPRECATED, reattachMediaStream will soon be removed.');
2305
+ to.srcObject = from.srcObject;
2306
+ }
2307
+ };
2308
+
2309
+ // Expose public methods.
2310
+ module.exports = {
2311
+ shimOnTrack: firefoxShim.shimOnTrack,
2312
+ shimSourceObject: firefoxShim.shimSourceObject,
2313
+ shimPeerConnection: firefoxShim.shimPeerConnection,
2314
+ shimGetUserMedia: require('./getusermedia'),
2315
+ attachMediaStream: firefoxShim.attachMediaStream,
2316
+ reattachMediaStream: firefoxShim.reattachMediaStream
2317
+ };
2318
+
2319
+ },{"../utils":10,"./getusermedia":8}],8:[function(require,module,exports){
2320
+ /*
2321
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
2322
+ *
2323
+ * Use of this source code is governed by a BSD-style license
2324
+ * that can be found in the LICENSE file in the root of the source
2325
+ * tree.
2326
+ */
2327
+ /* eslint-env node */
2328
+ 'use strict';
2329
+
2330
+ var logging = require('../utils').log;
2331
+ var browserDetails = require('../utils').browserDetails;
2332
+
2333
+ // Expose public methods.
2334
+ module.exports = function() {
2335
+ var shimError_ = function(e) {
2336
+ return {
2337
+ name: {
2338
+ SecurityError: 'NotAllowedError',
2339
+ PermissionDeniedError: 'NotAllowedError'
2340
+ }[e.name] || e.name,
2341
+ message: {
2342
+ 'The operation is insecure.': 'The request is not allowed by the ' +
2343
+ 'user agent or the platform in the current context.'
2344
+ }[e.message] || e.message,
2345
+ constraint: e.constraint,
2346
+ toString: function() {
2347
+ return this.name + (this.message && ': ') + this.message;
2348
+ }
2349
+ };
2350
+ };
2351
+
2352
+ // getUserMedia constraints shim.
2353
+ var getUserMedia_ = function(constraints, onSuccess, onError) {
2354
+ var constraintsToFF37_ = function(c) {
2355
+ if (typeof c !== 'object' || c.require) {
2356
+ return c;
2357
+ }
2358
+ var require = [];
2359
+ Object.keys(c).forEach(function(key) {
2360
+ if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
2361
+ return;
2362
+ }
2363
+ var r = c[key] = (typeof c[key] === 'object') ?
2364
+ c[key] : {ideal: c[key]};
2365
+ if (r.min !== undefined ||
2366
+ r.max !== undefined || r.exact !== undefined) {
2367
+ require.push(key);
2368
+ }
2369
+ if (r.exact !== undefined) {
2370
+ if (typeof r.exact === 'number') {
2371
+ r. min = r.max = r.exact;
2372
+ } else {
2373
+ c[key] = r.exact;
2374
+ }
2375
+ delete r.exact;
2376
+ }
2377
+ if (r.ideal !== undefined) {
2378
+ c.advanced = c.advanced || [];
2379
+ var oc = {};
2380
+ if (typeof r.ideal === 'number') {
2381
+ oc[key] = {min: r.ideal, max: r.ideal};
2382
+ } else {
2383
+ oc[key] = r.ideal;
2384
+ }
2385
+ c.advanced.push(oc);
2386
+ delete r.ideal;
2387
+ if (!Object.keys(r).length) {
2388
+ delete c[key];
2389
+ }
2390
+ }
2391
+ });
2392
+ if (require.length) {
2393
+ c.require = require;
2394
+ }
2395
+ return c;
2396
+ };
2397
+ constraints = JSON.parse(JSON.stringify(constraints));
2398
+ if (browserDetails.version < 38) {
2399
+ logging('spec: ' + JSON.stringify(constraints));
2400
+ if (constraints.audio) {
2401
+ constraints.audio = constraintsToFF37_(constraints.audio);
2402
+ }
2403
+ if (constraints.video) {
2404
+ constraints.video = constraintsToFF37_(constraints.video);
2405
+ }
2406
+ logging('ff37: ' + JSON.stringify(constraints));
204
2407
  }
2408
+ return navigator.mozGetUserMedia(constraints, onSuccess, function(e) {
2409
+ onError(shimError_(e));
2410
+ });
205
2411
  };
206
2412
 
207
- reattachMediaStream = function(to, from) {
208
- to.src = from.src;
2413
+ // Returns the result of getUserMedia as a Promise.
2414
+ var getUserMediaPromise_ = function(constraints) {
2415
+ return new Promise(function(resolve, reject) {
2416
+ getUserMedia_(constraints, resolve, reject);
2417
+ });
209
2418
  };
210
- } else {
211
- console.log('Browser does not appear to be WebRTC-capable');
212
- }
213
2419
 
214
- // Returns the result of getUserMedia as a Promise.
215
- function requestUserMedia(constraints) {
216
- return new Promise(function(resolve, reject) {
217
- var onSuccess = function(stream) {
218
- resolve(stream);
2420
+ // Shim for mediaDevices on older versions.
2421
+ if (!navigator.mediaDevices) {
2422
+ navigator.mediaDevices = {getUserMedia: getUserMediaPromise_,
2423
+ addEventListener: function() { },
2424
+ removeEventListener: function() { }
219
2425
  };
220
- var onError = function(error) {
221
- reject(error);
2426
+ }
2427
+ navigator.mediaDevices.enumerateDevices =
2428
+ navigator.mediaDevices.enumerateDevices || function() {
2429
+ return new Promise(function(resolve) {
2430
+ var infos = [
2431
+ {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
2432
+ {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
2433
+ ];
2434
+ resolve(infos);
2435
+ });
2436
+ };
2437
+
2438
+ if (browserDetails.version < 41) {
2439
+ // Work around http://bugzil.la/1169665
2440
+ var orgEnumerateDevices =
2441
+ navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
2442
+ navigator.mediaDevices.enumerateDevices = function() {
2443
+ return orgEnumerateDevices().then(undefined, function(e) {
2444
+ if (e.name === 'NotFoundError') {
2445
+ return [];
2446
+ }
2447
+ throw e;
2448
+ });
222
2449
  };
2450
+ }
2451
+ if (browserDetails.version < 49) {
2452
+ var origGetUserMedia = navigator.mediaDevices.getUserMedia.
2453
+ bind(navigator.mediaDevices);
2454
+ navigator.mediaDevices.getUserMedia = function(c) {
2455
+ return origGetUserMedia(c).catch(function(e) {
2456
+ return Promise.reject(shimError_(e));
2457
+ });
2458
+ };
2459
+ }
2460
+ navigator.getUserMedia = function(constraints, onSuccess, onError) {
2461
+ if (browserDetails.version < 44) {
2462
+ return getUserMedia_(constraints, onSuccess, onError);
2463
+ }
2464
+ // Replace Firefox 44+'s deprecation warning with unprefixed version.
2465
+ console.warn('navigator.getUserMedia has been replaced by ' +
2466
+ 'navigator.mediaDevices.getUserMedia');
2467
+ navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
2468
+ };
2469
+ };
2470
+
2471
+ },{"../utils":10}],9:[function(require,module,exports){
2472
+ /*
2473
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
2474
+ *
2475
+ * Use of this source code is governed by a BSD-style license
2476
+ * that can be found in the LICENSE file in the root of the source
2477
+ * tree.
2478
+ */
2479
+ 'use strict';
2480
+ var safariShim = {
2481
+ // TODO: DrAlex, should be here, double check against LayoutTests
2482
+ // shimOnTrack: function() { },
2483
+
2484
+ // TODO: DrAlex
2485
+ // attachMediaStream: function(element, stream) { },
2486
+ // reattachMediaStream: function(to, from) { },
2487
+
2488
+ // TODO: once the back-end for the mac port is done, add.
2489
+ // TODO: check for webkitGTK+
2490
+ // shimPeerConnection: function() { },
2491
+
2492
+ shimGetUserMedia: function() {
2493
+ navigator.getUserMedia = navigator.webkitGetUserMedia;
2494
+ }
2495
+ };
2496
+
2497
+ // Expose public methods.
2498
+ module.exports = {
2499
+ shimGetUserMedia: safariShim.shimGetUserMedia
2500
+ // TODO
2501
+ // shimOnTrack: safariShim.shimOnTrack,
2502
+ // shimPeerConnection: safariShim.shimPeerConnection,
2503
+ // attachMediaStream: safariShim.attachMediaStream,
2504
+ // reattachMediaStream: safariShim.reattachMediaStream
2505
+ };
2506
+
2507
+ },{}],10:[function(require,module,exports){
2508
+ /*
2509
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
2510
+ *
2511
+ * Use of this source code is governed by a BSD-style license
2512
+ * that can be found in the LICENSE file in the root of the source
2513
+ * tree.
2514
+ */
2515
+ /* eslint-env node */
2516
+ 'use strict';
2517
+
2518
+ var logDisabled_ = true;
223
2519
 
224
- try {
225
- getUserMedia(constraints, onSuccess, onError);
226
- } catch (e) {
227
- reject(e);
2520
+ // Utility methods.
2521
+ var utils = {
2522
+ disableLog: function(bool) {
2523
+ if (typeof bool !== 'boolean') {
2524
+ return new Error('Argument type: ' + typeof bool +
2525
+ '. Please use a boolean.');
228
2526
  }
229
- });
230
- }
231
-
232
- if (typeof module !== 'undefined') {
233
- module.exports = {
234
- RTCPeerConnection: RTCPeerConnection,
235
- getUserMedia: getUserMedia,
236
- attachMediaStream: attachMediaStream,
237
- reattachMediaStream: reattachMediaStream,
238
- webrtcDetectedBrowser: webrtcDetectedBrowser,
239
- webrtcDetectedVersion: webrtcDetectedVersion
240
- //requestUserMedia: not exposed on purpose.
241
- //trace: not exposed on purpose.
242
- };
243
- }
2527
+ logDisabled_ = bool;
2528
+ return (bool) ? 'adapter.js logging disabled' :
2529
+ 'adapter.js logging enabled';
2530
+ },
2531
+
2532
+ log: function() {
2533
+ if (typeof window === 'object') {
2534
+ if (logDisabled_) {
2535
+ return;
2536
+ }
2537
+ if (typeof console !== 'undefined' && typeof console.log === 'function') {
2538
+ console.log.apply(console, arguments);
2539
+ }
2540
+ }
2541
+ },
2542
+
2543
+ /**
2544
+ * Extract browser version out of the provided user agent string.
2545
+ *
2546
+ * @param {!string} uastring userAgent string.
2547
+ * @param {!string} expr Regular expression used as match criteria.
2548
+ * @param {!number} pos position in the version string to be returned.
2549
+ * @return {!number} browser version.
2550
+ */
2551
+ extractVersion: function(uastring, expr, pos) {
2552
+ var match = uastring.match(expr);
2553
+ return match && match.length >= pos && parseInt(match[pos], 10);
2554
+ },
2555
+
2556
+ /**
2557
+ * Browser detector.
2558
+ *
2559
+ * @return {object} result containing browser, version and minVersion
2560
+ * properties.
2561
+ */
2562
+ detectBrowser: function() {
2563
+ // Returned result object.
2564
+ var result = {};
2565
+ result.browser = null;
2566
+ result.version = null;
2567
+ result.minVersion = null;
2568
+
2569
+ // Fail early if it's not a browser
2570
+ if (typeof window === 'undefined' || !window.navigator) {
2571
+ result.browser = 'Not a browser.';
2572
+ return result;
2573
+ }
2574
+
2575
+ // Firefox.
2576
+ if (navigator.mozGetUserMedia) {
2577
+ result.browser = 'firefox';
2578
+ result.version = this.extractVersion(navigator.userAgent,
2579
+ /Firefox\/([0-9]+)\./, 1);
2580
+ result.minVersion = 31;
2581
+
2582
+ // all webkit-based browsers
2583
+ } else if (navigator.webkitGetUserMedia) {
2584
+ // Chrome, Chromium, Webview, Opera, all use the chrome shim for now
2585
+ if (window.webkitRTCPeerConnection) {
2586
+ result.browser = 'chrome';
2587
+ result.version = this.extractVersion(navigator.userAgent,
2588
+ /Chrom(e|ium)\/([0-9]+)\./, 2);
2589
+ result.minVersion = 38;
2590
+
2591
+ // Safari or unknown webkit-based
2592
+ // for the time being Safari has support for MediaStreams but not webRTC
2593
+ } else {
2594
+ // Safari UA substrings of interest for reference:
2595
+ // - webkit version: AppleWebKit/602.1.25 (also used in Op,Cr)
2596
+ // - safari UI version: Version/9.0.3 (unique to Safari)
2597
+ // - safari UI webkit version: Safari/601.4.4 (also used in Op,Cr)
2598
+ //
2599
+ // if the webkit version and safari UI webkit versions are equals,
2600
+ // ... this is a stable version.
2601
+ //
2602
+ // only the internal webkit version is important today to know if
2603
+ // media streams are supported
2604
+ //
2605
+ if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) {
2606
+ result.browser = 'safari';
2607
+ result.version = this.extractVersion(navigator.userAgent,
2608
+ /AppleWebKit\/([0-9]+)\./, 1);
2609
+ result.minVersion = 602;
2610
+
2611
+ // unknown webkit-based browser
2612
+ } else {
2613
+ result.browser = 'Unsupported webkit-based browser ' +
2614
+ 'with GUM support but no WebRTC support.';
2615
+ return result;
2616
+ }
2617
+ }
2618
+
2619
+ // Edge.
2620
+ } else if (navigator.mediaDevices &&
2621
+ navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) {
2622
+ result.browser = 'edge';
2623
+ result.version = this.extractVersion(navigator.userAgent,
2624
+ /Edge\/(\d+).(\d+)$/, 2);
2625
+ result.minVersion = 10547;
2626
+
2627
+ // Default fallthrough: not supported.
2628
+ } else {
2629
+ result.browser = 'Not a supported browser.';
2630
+ return result;
2631
+ }
2632
+
2633
+ // Warn if version is less than minVersion.
2634
+ if (result.version < result.minVersion) {
2635
+ utils.log('Browser: ' + result.browser + ' Version: ' + result.version +
2636
+ ' < minimum supported version: ' + result.minVersion +
2637
+ '\n some things might not work!');
2638
+ }
2639
+
2640
+ return result;
2641
+ }
2642
+ };
2643
+
2644
+ // Export.
2645
+ module.exports = {
2646
+ log: utils.log,
2647
+ disableLog: utils.disableLog,
2648
+ browserDetails: utils.detectBrowser(),
2649
+ extractVersion: utils.extractVersion
2650
+ };
2651
+
2652
+ },{}]},{},[2])(2)
2653
+ });