Subversion Repositories qbpwcf-lib(archive)

Rev

Rev 915 | Details | Compare with Previous | Last modification | View Log | RSS feed

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