Passed
Push — master ( 65d735...8430b6 )
by Jacob
01:20
created

amd/src/adapter.js   F

Complexity

Total Complexity 952
Complexity/F 2.69

Size

Lines of Code 3892
Function Count 354

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
c 1
b 0
f 0
nc 0
dl 0
loc 3892
rs 2.4
wmc 952
mnd 7
bc 762
fnc 354
bpm 2.1525
cpm 2.6892
noi 42

How to fix   Complexity   

Complexity

Complex classes like amd/src/adapter.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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: parseInt(parts[1], 10),
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: // extension handling, in particular ufrag
73
        candidate[parts[i]] = parts[i + 1];
74
        break;
75
    }
76
  }
77
  return candidate;
78
};
79
80
// Translates a candidate object into SDP candidate attribute.
81
SDPUtils.writeCandidate = function(candidate) {
82
  var sdp = [];
83
  sdp.push(candidate.foundation);
84
  sdp.push(candidate.component);
85
  sdp.push(candidate.protocol.toUpperCase());
86
  sdp.push(candidate.priority);
87
  sdp.push(candidate.ip);
88
  sdp.push(candidate.port);
89
90
  var type = candidate.type;
91
  sdp.push('typ');
92
  sdp.push(type);
93
  if (type !== 'host' && candidate.relatedAddress &&
94
      candidate.relatedPort) {
95
    sdp.push('raddr');
96
    sdp.push(candidate.relatedAddress); // was: relAddr
97
    sdp.push('rport');
98
    sdp.push(candidate.relatedPort); // was: relPort
99
  }
100
  if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
101
    sdp.push('tcptype');
102
    sdp.push(candidate.tcpType);
103
  }
104
  if (candidate.ufrag) {
105
    sdp.push('ufrag');
106
    sdp.push(candidate.ufrag);
107
  }
108
  return 'candidate:' + sdp.join(' ');
109
};
110
111
// Parses an ice-options line, returns an array of option tags.
112
// a=ice-options:foo bar
113
SDPUtils.parseIceOptions = function(line) {
114
  return line.substr(14).split(' ');
115
}
116
117
// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
118
// a=rtpmap:111 opus/48000/2
119
SDPUtils.parseRtpMap = function(line) {
120
  var parts = line.substr(9).split(' ');
121
  var parsed = {
122
    payloadType: parseInt(parts.shift(), 10) // was: id
123
  };
124
125
  parts = parts[0].split('/');
126
127
  parsed.name = parts[0];
128
  parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
129
  // was: channels
130
  parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
131
  return parsed;
132
};
133
134
// Generate an a=rtpmap line from RTCRtpCodecCapability or
135
// RTCRtpCodecParameters.
136
SDPUtils.writeRtpMap = function(codec) {
137
  var pt = codec.payloadType;
138
  if (codec.preferredPayloadType !== undefined) {
139
    pt = codec.preferredPayloadType;
140
  }
141
  return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
142
      (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
143
};
144
145
// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
146
// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
147
// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
148
SDPUtils.parseExtmap = function(line) {
149
  var parts = line.substr(9).split(' ');
150
  return {
151
    id: parseInt(parts[0], 10),
152
    direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
153
    uri: parts[1]
154
  };
155
};
156
157
// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
158
// RTCRtpHeaderExtension.
159
SDPUtils.writeExtmap = function(headerExtension) {
160
  return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
161
      (headerExtension.direction && headerExtension.direction !== 'sendrecv'
162
          ? '/' + headerExtension.direction
163
          : '') +
164
      ' ' + headerExtension.uri + '\r\n';
165
};
166
167
// Parses an ftmp line, returns dictionary. Sample input:
168
// a=fmtp:96 vbr=on;cng=on
169
// Also deals with vbr=on; cng=on
170
SDPUtils.parseFmtp = function(line) {
171
  var parsed = {};
172
  var kv;
173
  var parts = line.substr(line.indexOf(' ') + 1).split(';');
174
  for (var j = 0; j < parts.length; j++) {
175
    kv = parts[j].trim().split('=');
176
    parsed[kv[0].trim()] = kv[1];
177
  }
178
  return parsed;
179
};
180
181
// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
182
SDPUtils.writeFmtp = function(codec) {
183
  var line = '';
184
  var pt = codec.payloadType;
185
  if (codec.preferredPayloadType !== undefined) {
186
    pt = codec.preferredPayloadType;
187
  }
188
  if (codec.parameters && Object.keys(codec.parameters).length) {
189
    var params = [];
190
    Object.keys(codec.parameters).forEach(function(param) {
191
      params.push(param + '=' + codec.parameters[param]);
192
    });
193
    line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
194
  }
195
  return line;
196
};
197
198
// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
199
// a=rtcp-fb:98 nack rpsi
200
SDPUtils.parseRtcpFb = function(line) {
201
  var parts = line.substr(line.indexOf(' ') + 1).split(' ');
202
  return {
203
    type: parts.shift(),
204
    parameter: parts.join(' ')
205
  };
206
};
207
// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
208
SDPUtils.writeRtcpFb = function(codec) {
209
  var lines = '';
210
  var pt = codec.payloadType;
211
  if (codec.preferredPayloadType !== undefined) {
212
    pt = codec.preferredPayloadType;
213
  }
214
  if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
215
    // FIXME: special handling for trr-int?
216
    codec.rtcpFeedback.forEach(function(fb) {
217
      lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +
218
      (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +
219
          '\r\n';
220
    });
221
  }
222
  return lines;
223
};
224
225
// Parses an RFC 5576 ssrc media attribute. Sample input:
226
// a=ssrc:3735928559 cname:something
227
SDPUtils.parseSsrcMedia = function(line) {
228
  var sp = line.indexOf(' ');
229
  var parts = {
230
    ssrc: parseInt(line.substr(7, sp - 7), 10)
231
  };
232
  var colon = line.indexOf(':', sp);
233
  if (colon > -1) {
234
    parts.attribute = line.substr(sp + 1, colon - sp - 1);
235
    parts.value = line.substr(colon + 1);
236
  } else {
237
    parts.attribute = line.substr(sp + 1);
238
  }
239
  return parts;
240
};
241
242
// Extracts the MID (RFC 5888) from a media section.
243
// returns the MID or undefined if no mid line was found.
244
SDPUtils.getMid = function(mediaSection) {
245
  var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
246
  if (mid) {
247
    return mid.substr(6);
248
  }
249
}
250
251
SDPUtils.parseFingerprint = function(line) {
252
  var parts = line.substr(14).split(' ');
253
  return {
254
    algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
255
    value: parts[1]
256
  };
257
};
258
259
// Extracts DTLS parameters from SDP media section or sessionpart.
260
// FIXME: for consistency with other functions this should only
261
//   get the fingerprint line as input. See also getIceParameters.
262
SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
263
  var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
264
      'a=fingerprint:');
265
  // Note: a=setup line is ignored since we use the 'auto' role.
266
  // Note2: 'algorithm' is not case sensitive except in Edge.
267
  return {
268
    role: 'auto',
269
    fingerprints: lines.map(SDPUtils.parseFingerprint)
270
  };
271
};
272
273
// Serializes DTLS parameters to SDP.
274
SDPUtils.writeDtlsParameters = function(params, setupType) {
275
  var sdp = 'a=setup:' + setupType + '\r\n';
276
  params.fingerprints.forEach(function(fp) {
277
    sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
278
  });
279
  return sdp;
280
};
281
// Parses ICE information from SDP media section or sessionpart.
282
// FIXME: for consistency with other functions this should only
283
//   get the ice-ufrag and ice-pwd lines as input.
284
SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
285
  var lines = SDPUtils.splitLines(mediaSection);
286
  // Search in session part, too.
287
  lines = lines.concat(SDPUtils.splitLines(sessionpart));
288
  var iceParameters = {
289
    usernameFragment: lines.filter(function(line) {
290
      return line.indexOf('a=ice-ufrag:') === 0;
291
    })[0].substr(12),
292
    password: lines.filter(function(line) {
293
      return line.indexOf('a=ice-pwd:') === 0;
294
    })[0].substr(10)
295
  };
296
  return iceParameters;
297
};
298
299
// Serializes ICE parameters to SDP.
300
SDPUtils.writeIceParameters = function(params) {
301
  return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
302
      'a=ice-pwd:' + params.password + '\r\n';
303
};
304
305
// Parses the SDP media section and returns RTCRtpParameters.
306
SDPUtils.parseRtpParameters = function(mediaSection) {
307
  var description = {
308
    codecs: [],
309
    headerExtensions: [],
310
    fecMechanisms: [],
311
    rtcp: []
312
  };
313
  var lines = SDPUtils.splitLines(mediaSection);
314
  var mline = lines[0].split(' ');
315
  for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
316
    var pt = mline[i];
317
    var rtpmapline = SDPUtils.matchPrefix(
318
        mediaSection, 'a=rtpmap:' + pt + ' ')[0];
319
    if (rtpmapline) {
320
      var codec = SDPUtils.parseRtpMap(rtpmapline);
321
      var fmtps = SDPUtils.matchPrefix(
322
          mediaSection, 'a=fmtp:' + pt + ' ');
323
      // Only the first a=fmtp:<pt> is considered.
324
      codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
325
      codec.rtcpFeedback = SDPUtils.matchPrefix(
326
          mediaSection, 'a=rtcp-fb:' + pt + ' ')
327
        .map(SDPUtils.parseRtcpFb);
328
      description.codecs.push(codec);
329
      // parse FEC mechanisms from rtpmap lines.
330
      switch (codec.name.toUpperCase()) {
331
        case 'RED':
332
        case 'ULPFEC':
333
          description.fecMechanisms.push(codec.name.toUpperCase());
334
          break;
335
        default: // only RED and ULPFEC are recognized as FEC mechanisms.
336
          break;
337
      }
338
    }
339
  }
340
  SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
341
    description.headerExtensions.push(SDPUtils.parseExtmap(line));
342
  });
343
  // FIXME: parse rtcp.
344
  return description;
345
};
346
347
// Generates parts of the SDP media section describing the capabilities /
348
// parameters.
349
SDPUtils.writeRtpDescription = function(kind, caps) {
350
  var sdp = '';
351
352
  // Build the mline.
353
  sdp += 'm=' + kind + ' ';
354
  sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
355
  sdp += ' UDP/TLS/RTP/SAVPF ';
356
  sdp += caps.codecs.map(function(codec) {
357
    if (codec.preferredPayloadType !== undefined) {
358
      return codec.preferredPayloadType;
359
    }
360
    return codec.payloadType;
361
  }).join(' ') + '\r\n';
362
363
  sdp += 'c=IN IP4 0.0.0.0\r\n';
364
  sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
365
366
  // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
367
  caps.codecs.forEach(function(codec) {
368
    sdp += SDPUtils.writeRtpMap(codec);
369
    sdp += SDPUtils.writeFmtp(codec);
370
    sdp += SDPUtils.writeRtcpFb(codec);
371
  });
372
  var maxptime = 0;
373
  caps.codecs.forEach(function(codec) {
374
    if (codec.maxptime > maxptime) {
375
      maxptime = codec.maxptime;
376
    }
377
  });
378
  if (maxptime > 0) {
379
    sdp += 'a=maxptime:' + maxptime + '\r\n';
380
  }
381
  sdp += 'a=rtcp-mux\r\n';
382
383
  caps.headerExtensions.forEach(function(extension) {
384
    sdp += SDPUtils.writeExtmap(extension);
385
  });
386
  // FIXME: write fecMechanisms.
387
  return sdp;
388
};
389
390
// Parses the SDP media section and returns an array of
391
// RTCRtpEncodingParameters.
392
SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
393
  var encodingParameters = [];
394
  var description = SDPUtils.parseRtpParameters(mediaSection);
395
  var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
396
  var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
397
398
  // filter a=ssrc:... cname:, ignore PlanB-msid
399
  var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
400
  .map(function(line) {
401
    return SDPUtils.parseSsrcMedia(line);
402
  })
403
  .filter(function(parts) {
404
    return parts.attribute === 'cname';
405
  });
406
  var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
407
  var secondarySsrc;
408
409
  var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
410
  .map(function(line) {
411
    var parts = line.split(' ');
412
    parts.shift();
413
    return parts.map(function(part) {
414
      return parseInt(part, 10);
415
    });
416
  });
417
  if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
418
    secondarySsrc = flows[0][1];
419
  }
420
421
  description.codecs.forEach(function(codec) {
422
    if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
423
      var encParam = {
424
        ssrc: primarySsrc,
425
        codecPayloadType: parseInt(codec.parameters.apt, 10),
426
        rtx: {
427
          ssrc: secondarySsrc
428
        }
429
      };
430
      encodingParameters.push(encParam);
431
      if (hasRed) {
432
        encParam = JSON.parse(JSON.stringify(encParam));
433
        encParam.fec = {
434
          ssrc: secondarySsrc,
435
          mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
436
        };
437
        encodingParameters.push(encParam);
438
      }
439
    }
440
  });
441
  if (encodingParameters.length === 0 && primarySsrc) {
442
    encodingParameters.push({
443
      ssrc: primarySsrc
444
    });
445
  }
446
447
  // we support both b=AS and b=TIAS but interpret AS as TIAS.
448
  var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
449
  if (bandwidth.length) {
450
    if (bandwidth[0].indexOf('b=TIAS:') === 0) {
451
      bandwidth = parseInt(bandwidth[0].substr(7), 10);
452
    } else if (bandwidth[0].indexOf('b=AS:') === 0) {
453
      // use formula from JSEP to convert b=AS to TIAS value.
454
      bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95
455
          - (50 * 40 * 8);
456
    } else {
457
      bandwidth = undefined;
458
    }
459
    encodingParameters.forEach(function(params) {
460
      params.maxBitrate = bandwidth;
461
    });
462
  }
463
  return encodingParameters;
464
};
465
466
// parses http://draft.ortc.org/#rtcrtcpparameters*
467
SDPUtils.parseRtcpParameters = function(mediaSection) {
468
  var rtcpParameters = {};
469
470
  var cname;
471
  // Gets the first SSRC. Note that with RTX there might be multiple
472
  // SSRCs.
473
  var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
474
      .map(function(line) {
475
        return SDPUtils.parseSsrcMedia(line);
476
      })
477
      .filter(function(obj) {
478
        return obj.attribute === 'cname';
479
      })[0];
480
  if (remoteSsrc) {
481
    rtcpParameters.cname = remoteSsrc.value;
482
    rtcpParameters.ssrc = remoteSsrc.ssrc;
483
  }
484
485
  // Edge uses the compound attribute instead of reducedSize
486
  // compound is !reducedSize
487
  var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
488
  rtcpParameters.reducedSize = rsize.length > 0;
489
  rtcpParameters.compound = rsize.length === 0;
490
491
  // parses the rtcp-mux attrіbute.
492
  // Note that Edge does not support unmuxed RTCP.
493
  var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
494
  rtcpParameters.mux = mux.length > 0;
495
496
  return rtcpParameters;
497
};
498
499
// parses either a=msid: or a=ssrc:... msid lines and returns
500
// the id of the MediaStream and MediaStreamTrack.
501
SDPUtils.parseMsid = function(mediaSection) {
502
  var parts;
503
  var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
504
  if (spec.length === 1) {
505
    parts = spec[0].substr(7).split(' ');
506
    return {stream: parts[0], track: parts[1]};
507
  }
508
  var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
509
  .map(function(line) {
510
    return SDPUtils.parseSsrcMedia(line);
511
  })
512
  .filter(function(parts) {
513
    return parts.attribute === 'msid';
514
  });
515
  if (planB.length > 0) {
516
    parts = planB[0].value.split(' ');
517
    return {stream: parts[0], track: parts[1]};
518
  }
519
};
520
521
// Generate a session ID for SDP.
522
// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
523
// recommends using a cryptographically random +ve 64-bit value
524
// but right now this should be acceptable and within the right range
525
SDPUtils.generateSessionId = function() {
526
  return Math.random().toString().substr(2, 21);
527
};
528
529
// Write boilder plate for start of SDP
530
// sessId argument is optional - if not supplied it will
531
// be generated randomly
532
SDPUtils.writeSessionBoilerplate = function(sessId) {
533
  var sessionId;
534
  if (sessId) {
535
    sessionId = sessId;
536
  } else {
537
    sessionId = SDPUtils.generateSessionId();
538
  }
539
  // FIXME: sess-id should be an NTP timestamp.
540
  return 'v=0\r\n' +
541
      'o=thisisadapterortc ' + sessionId + ' 2 IN IP4 127.0.0.1\r\n' +
542
      's=-\r\n' +
543
      't=0 0\r\n';
544
};
545
546
SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
547
  var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
548
549
  // Map ICE parameters (ufrag, pwd) to SDP.
550
  sdp += SDPUtils.writeIceParameters(
551
      transceiver.iceGatherer.getLocalParameters());
552
553
  // Map DTLS parameters to SDP.
554
  sdp += SDPUtils.writeDtlsParameters(
555
      transceiver.dtlsTransport.getLocalParameters(),
556
      type === 'offer' ? 'actpass' : 'active');
557
558
  sdp += 'a=mid:' + transceiver.mid + '\r\n';
559
560
  if (transceiver.direction) {
561
    sdp += 'a=' + transceiver.direction + '\r\n';
562
  } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
563
    sdp += 'a=sendrecv\r\n';
564
  } else if (transceiver.rtpSender) {
565
    sdp += 'a=sendonly\r\n';
566
  } else if (transceiver.rtpReceiver) {
567
    sdp += 'a=recvonly\r\n';
568
  } else {
569
    sdp += 'a=inactive\r\n';
570
  }
571
572
  if (transceiver.rtpSender) {
573
    // spec.
574
    var msid = 'msid:' + stream.id + ' ' +
575
        transceiver.rtpSender.track.id + '\r\n';
576
    sdp += 'a=' + msid;
577
578
    // for Chrome.
579
    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
580
        ' ' + msid;
581
    if (transceiver.sendEncodingParameters[0].rtx) {
582
      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
583
          ' ' + msid;
584
      sdp += 'a=ssrc-group:FID ' +
585
          transceiver.sendEncodingParameters[0].ssrc + ' ' +
586
          transceiver.sendEncodingParameters[0].rtx.ssrc +
587
          '\r\n';
588
    }
589
  }
590
  // FIXME: this should be written by writeRtpDescription.
591
  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
592
      ' cname:' + SDPUtils.localCName + '\r\n';
593
  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
594
    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
595
        ' cname:' + SDPUtils.localCName + '\r\n';
596
  }
597
  return sdp;
598
};
599
600
// Gets the direction from the mediaSection or the sessionpart.
601
SDPUtils.getDirection = function(mediaSection, sessionpart) {
602
  // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
603
  var lines = SDPUtils.splitLines(mediaSection);
604
  for (var i = 0; i < lines.length; i++) {
605
    switch (lines[i]) {
606
      case 'a=sendrecv':
607
      case 'a=sendonly':
608
      case 'a=recvonly':
609
      case 'a=inactive':
610
        return lines[i].substr(2);
611
      default:
612
        // FIXME: What should happen here?
613
    }
614
  }
615
  if (sessionpart) {
616
    return SDPUtils.getDirection(sessionpart);
617
  }
618
  return 'sendrecv';
619
};
620
621
SDPUtils.getKind = function(mediaSection) {
622
  var lines = SDPUtils.splitLines(mediaSection);
623
  var mline = lines[0].split(' ');
624
  return mline[0].substr(2);
625
};
626
627
SDPUtils.isRejected = function(mediaSection) {
628
  return mediaSection.split(' ', 2)[1] === '0';
629
};
630
631
// Expose public methods.
632
module.exports = SDPUtils;
633
634
},{}],2:[function(require,module,exports){
635
(function (global){
636
/*
637
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
638
 *
639
 *  Use of this source code is governed by a BSD-style license
640
 *  that can be found in the LICENSE file in the root of the source
641
 *  tree.
642
 */
643
 /* eslint-env node */
644
645
'use strict';
646
647
var adapterFactory = require('./adapter_factory.js');
648
module.exports = adapterFactory({window: global.window});
649
650
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
651
},{"./adapter_factory.js":3}],3:[function(require,module,exports){
652
/*
653
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
654
 *
655
 *  Use of this source code is governed by a BSD-style license
656
 *  that can be found in the LICENSE file in the root of the source
657
 *  tree.
658
 */
659
 /* eslint-env node */
660
661
'use strict';
662
663
// Shimming starts here.
664
module.exports = function(dependencies, opts) {
665
  var window = dependencies && dependencies.window;
666
667
  var options = Object.assign({
668
    shimChrome: true,
669
    shimFirefox: true,
670
    shimEdge: true,
671
    shimSafari: true,
672
  }, opts);
673
674
  // Utils.
675
  var utils = require('./utils');
676
  var logging = utils.log;
677
  var browserDetails = utils.detectBrowser(window);
678
679
  // Export to the adapter global object visible in the browser.
680
  var adapter = {
681
    browserDetails: browserDetails,
682
    extractVersion: utils.extractVersion,
683
    disableLog: utils.disableLog,
684
    disableWarnings: utils.disableWarnings
685
  };
686
687
  // Uncomment the line below if you want logging to occur, including logging
688
  // for the switch statement below. Can also be turned on in the browser via
689
  // adapter.disableLog(false), but then logging from the switch statement below
690
  // will not appear.
691
  // require('./utils').disableLog(false);
692
693
  // Browser shims.
694
  var chromeShim = require('./chrome/chrome_shim') || null;
695
  var edgeShim = require('./edge/edge_shim') || null;
696
  var firefoxShim = require('./firefox/firefox_shim') || null;
697
  var safariShim = require('./safari/safari_shim') || null;
698
699
  // Shim browser if found.
700
  switch (browserDetails.browser) {
701
    case 'chrome':
702
      if (!chromeShim || !chromeShim.shimPeerConnection ||
703
          !options.shimChrome) {
704
        logging('Chrome shim is not included in this adapter release.');
705
        return adapter;
706
      }
707
      logging('adapter.js shimming chrome.');
708
      // Export to the adapter global object visible in the browser.
709
      adapter.browserShim = chromeShim;
710
711
      chromeShim.shimGetUserMedia(window);
712
      chromeShim.shimMediaStream(window);
713
      utils.shimCreateObjectURL(window);
714
      chromeShim.shimSourceObject(window);
715
      chromeShim.shimPeerConnection(window);
716
      chromeShim.shimOnTrack(window);
717
      chromeShim.shimAddTrack(window);
718
      chromeShim.shimGetSendersWithDtmf(window);
719
      break;
720
    case 'firefox':
721
      if (!firefoxShim || !firefoxShim.shimPeerConnection ||
722
          !options.shimFirefox) {
723
        logging('Firefox shim is not included in this adapter release.');
724
        return adapter;
725
      }
726
      logging('adapter.js shimming firefox.');
727
      // Export to the adapter global object visible in the browser.
728
      adapter.browserShim = firefoxShim;
729
730
      firefoxShim.shimGetUserMedia(window);
731
      utils.shimCreateObjectURL(window);
732
      firefoxShim.shimSourceObject(window);
733
      firefoxShim.shimPeerConnection(window);
734
      firefoxShim.shimOnTrack(window);
735
      break;
736
    case 'edge':
737
      if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) {
738
        logging('MS edge shim is not included in this adapter release.');
739
        return adapter;
740
      }
741
      logging('adapter.js shimming edge.');
742
      // Export to the adapter global object visible in the browser.
743
      adapter.browserShim = edgeShim;
744
745
      edgeShim.shimGetUserMedia(window);
746
      utils.shimCreateObjectURL(window);
747
      edgeShim.shimPeerConnection(window);
748
      edgeShim.shimReplaceTrack(window);
749
      break;
750
    case 'safari':
751
      if (!safariShim || !options.shimSafari) {
752
        logging('Safari shim is not included in this adapter release.');
753
        return adapter;
754
      }
755
      logging('adapter.js shimming safari.');
756
      // Export to the adapter global object visible in the browser.
757
      adapter.browserShim = safariShim;
758
      // shim window.URL.createObjectURL Safari (technical preview)
759
      utils.shimCreateObjectURL(window);
760
      safariShim.shimRTCIceServerUrls(window);
761
      safariShim.shimCallbacksAPI(window);
762
      safariShim.shimLocalStreamsAPI(window);
763
      safariShim.shimRemoteStreamsAPI(window);
764
      safariShim.shimGetUserMedia(window);
765
      break;
766
    default:
767
      logging('Unsupported browser!');
768
      break;
769
  }
770
771
  return adapter;
772
};
773
774
},{"./chrome/chrome_shim":4,"./edge/edge_shim":6,"./firefox/firefox_shim":9,"./safari/safari_shim":11,"./utils":12}],4:[function(require,module,exports){
775
776
/*
777
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
778
 *
779
 *  Use of this source code is governed by a BSD-style license
780
 *  that can be found in the LICENSE file in the root of the source
781
 *  tree.
782
 */
783
 /* eslint-env node */
784
'use strict';
785
var utils = require('../utils.js');
786
var logging = utils.log;
787
788
var chromeShim = {
789
  shimMediaStream: function(window) {
790
    window.MediaStream = window.MediaStream || window.webkitMediaStream;
791
  },
792
793
  shimOnTrack: function(window) {
794
    if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
795
        window.RTCPeerConnection.prototype)) {
796
      Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
797
        get: function() {
798
          return this._ontrack;
799
        },
800
        set: function(f) {
801
          var self = this;
802
          if (this._ontrack) {
803
            this.removeEventListener('track', this._ontrack);
804
            this.removeEventListener('addstream', this._ontrackpoly);
805
          }
806
          this.addEventListener('track', this._ontrack = f);
807
          this.addEventListener('addstream', this._ontrackpoly = function(e) {
808
            // onaddstream does not fire when a track is added to an existing
809
            // stream. But stream.onaddtrack is implemented so we use that.
810
            e.stream.addEventListener('addtrack', function(te) {
811
              var receiver;
812
              if (window.RTCPeerConnection.prototype.getReceivers) {
813
                receiver = self.getReceivers().find(function(r) {
814
                  return r.track.id === te.track.id;
815
                });
816
              } else {
817
                receiver = {track: te.track};
818
              }
819
820
              var event = new Event('track');
821
              event.track = te.track;
822
              event.receiver = receiver;
823
              event.streams = [e.stream];
824
              self.dispatchEvent(event);
825
            });
826
            e.stream.getTracks().forEach(function(track) {
827
              var receiver;
828
              if (window.RTCPeerConnection.prototype.getReceivers) {
829
                receiver = self.getReceivers().find(function(r) {
830
                  return r.track.id === track.id;
831
                });
832
              } else {
833
                receiver = {track: track};
834
              }
835
              var event = new Event('track');
836
              event.track = track;
837
              event.receiver = receiver;
838
              event.streams = [e.stream];
839
              this.dispatchEvent(event);
840
            }.bind(this));
841
          }.bind(this));
842
        }
843
      });
844
    }
845
  },
846
847
  shimGetSendersWithDtmf: function(window) {
848
    if (typeof window === 'object' && window.RTCPeerConnection &&
849
        !('getSenders' in window.RTCPeerConnection.prototype) &&
850
        'createDTMFSender' in window.RTCPeerConnection.prototype) {
851
      var shimSenderWithDtmf = function(pc, track) {
852
        return {
853
          track: track,
854
          get dtmf() {
855
            if (this._dtmf === undefined) {
856
              if (track.kind === 'audio') {
857
                this._dtmf = pc.createDTMFSender(track);
858
              } else {
859
                this._dtmf = null;
860
              }
861
            }
862
            return this._dtmf;
863
          }
864
        };
865
      };
866
867
      // shim addTrack when getSenders is not available.
868
      if (!window.RTCPeerConnection.prototype.getSenders) {
869
        window.RTCPeerConnection.prototype.getSenders = function() {
870
          return this._senders || [];
871
        };
872
        var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
873
        window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
874
          var pc = this;
875
          var sender = origAddTrack.apply(pc, arguments);
876
          if (!sender) {
877
            sender = shimSenderWithDtmf(pc, track);
878
            pc._senders.push(sender);
879
          }
880
          return sender;
881
        };
882
      }
883
      var origAddStream = window.RTCPeerConnection.prototype.addStream;
884
      window.RTCPeerConnection.prototype.addStream = function(stream) {
885
        var pc = this;
886
        pc._senders = pc._senders || [];
887
        origAddStream.apply(pc, [stream]);
888
        stream.getTracks().forEach(function(track) {
889
          pc._senders.push(shimSenderWithDtmf(pc, track));
890
        });
891
      };
892
893
      var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
894
      window.RTCPeerConnection.prototype.removeStream = function(stream) {
895
        var pc = this;
896
        pc._senders = pc._senders || [];
897
        origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]);
898
899
        stream.getTracks().forEach(function(track) {
900
          var sender = pc._senders.find(function(s) {
901
            return s.track === track;
902
          });
903
          if (sender) {
904
            pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender
905
          }
906
        });
907
      };
908
    } else if (typeof window === 'object' && window.RTCPeerConnection &&
909
               'getSenders' in window.RTCPeerConnection.prototype &&
910
               'createDTMFSender' in window.RTCPeerConnection.prototype &&
911
               window.RTCRtpSender &&
912
               !('dtmf' in window.RTCRtpSender.prototype)) {
913
      var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
914
      window.RTCPeerConnection.prototype.getSenders = function() {
915
        var pc = this;
916
        var senders = origGetSenders.apply(pc, []);
917
        senders.forEach(function(sender) {
918
          sender._pc = pc;
919
        });
920
        return senders;
921
      };
922
923
      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
924
        get: function() {
925
          if (this._dtmf === undefined) {
926
            if (this.track.kind === 'audio') {
927
              this._dtmf = this._pc.createDTMFSender(this.track);
928
            } else {
929
              this._dtmf = null;
930
            }
931
          }
932
          return this._dtmf;
933
        },
934
      });
935
    }
936
  },
937
938
  shimSourceObject: function(window) {
939
    var URL = window && window.URL;
940
941
    if (typeof window === 'object') {
942
      if (window.HTMLMediaElement &&
943
        !('srcObject' in window.HTMLMediaElement.prototype)) {
944
        // Shim the srcObject property, once, when HTMLMediaElement is found.
945
        Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
946
          get: function() {
947
            return this._srcObject;
948
          },
949
          set: function(stream) {
950
            var self = this;
951
            // Use _srcObject as a private property for this shim
952
            this._srcObject = stream;
953
            if (this.src) {
954
              URL.revokeObjectURL(this.src);
955
            }
956
957
            if (!stream) {
958
              this.src = '';
959
              return undefined;
960
            }
961
            this.src = URL.createObjectURL(stream);
962
            // We need to recreate the blob url when a track is added or
963
            // removed. Doing it manually since we want to avoid a recursion.
964
            stream.addEventListener('addtrack', function() {
965
              if (self.src) {
966
                URL.revokeObjectURL(self.src);
967
              }
968
              self.src = URL.createObjectURL(stream);
969
            });
970
            stream.addEventListener('removetrack', function() {
971
              if (self.src) {
972
                URL.revokeObjectURL(self.src);
973
              }
974
              self.src = URL.createObjectURL(stream);
975
            });
976
          }
977
        });
978
      }
979
    }
980
  },
981
982
  shimAddTrack: function(window) {
983
    // shim addTrack (when getSenders is available)
984
    if (window.RTCPeerConnection.prototype.addTrack) {
985
      return;
986
    }
987
988
    // also shim pc.getLocalStreams when addTrack is shimmed
989
    // to return the original streams.
990
    var origGetLocalStreams = window.RTCPeerConnection.prototype
991
        .getLocalStreams;
992
    window.RTCPeerConnection.prototype.getLocalStreams = function() {
993
      var self = this;
994
      var nativeStreams = origGetLocalStreams.apply(this);
995
      self._reverseStreams = self._reverseStreams || {};
996
      return nativeStreams.map(function(stream) {
997
        return self._reverseStreams[stream.id];
998
      });
999
    };
1000
1001
    var origAddStream = window.RTCPeerConnection.prototype.addStream;
1002
    window.RTCPeerConnection.prototype.addStream = function(stream) {
1003
      var pc = this;
1004
      pc._streams = pc._streams || {};
1005
      pc._reverseStreams = pc._reverseStreams || {};
1006
1007
      // Add identity mapping for consistency with addTrack.
1008
      // Unless this is being used with a stream from addTrack.
1009
      if (!pc._reverseStreams[stream.id]) {
1010
        pc._streams[stream.id] = stream;
1011
        pc._reverseStreams[stream.id] = stream;
1012
      }
1013
      origAddStream.apply(pc, [stream]);
1014
    };
1015
1016
    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
1017
    window.RTCPeerConnection.prototype.removeStream = function(stream) {
1018
      var pc = this;
1019
      pc._streams = pc._streams || {};
1020
      pc._reverseStreams = pc._reverseStreams || {};
1021
1022
      origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]);
1023
      delete pc._reverseStreams[(pc._streams[stream.id] ?
1024
          pc._streams[stream.id].id : stream.id)];
1025
      delete pc._streams[stream.id];
1026
    };
1027
1028
    window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
1029
      var pc = this;
1030
      if (pc.signalingState === 'closed') {
1031
        throw new DOMException(
1032
          'The RTCPeerConnection\'s signalingState is \'closed\'.',
1033
          'InvalidStateError');
1034
      }
1035
      var streams = [].slice.call(arguments, 1);
1036
      if (streams.length !== 1 ||
1037
          !streams[0].getTracks().find(function(t) {
1038
            return t === track;
1039
          })) {
1040
        // this is not fully correct but all we can manage without
1041
        // [[associated MediaStreams]] internal slot.
1042
        throw new DOMException(
1043
          'The adapter.js addTrack polyfill only supports a single ' +
1044
          ' stream which is associated with the specified track.',
1045
          'NotSupportedError');
1046
      }
1047
1048
      var alreadyExists = pc.getSenders().find(function(s) {
1049
        return s.track === track;
1050
      });
1051
      if (alreadyExists) {
1052
        throw new DOMException('Track already exists.',
1053
            'InvalidAccessError');
1054
      }
1055
1056
      pc._streams = pc._streams || {};
1057
      pc._reverseStreams = pc._reverseStreams || {};
1058
      var oldStream = pc._streams[stream.id];
1059
      if (oldStream) {
1060
        // this is using odd Chrome behaviour, use with caution:
1061
        // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
1062
        oldStream.addTrack(track);
1063
        pc.dispatchEvent(new Event('negotiationneeded'));
1064
      } else {
1065
        var newStream = new window.MediaStream([track]);
1066
        pc._streams[stream.id] = newStream;
1067
        pc._reverseStreams[newStream.id] = stream;
1068
        pc.addStream(newStream);
1069
      }
1070
      return pc.getSenders().find(function(s) {
1071
        return s.track === track;
1072
      });
1073
    };
1074
  },
1075
1076
  shimPeerConnection: function(window) {
1077
    var browserDetails = utils.detectBrowser(window);
1078
1079
    // The RTCPeerConnection object.
1080
    if (!window.RTCPeerConnection) {
1081
      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
1082
        // Translate iceTransportPolicy to iceTransports,
1083
        // see https://code.google.com/p/webrtc/issues/detail?id=4869
1084
        // this was fixed in M56 along with unprefixing RTCPeerConnection.
1085
        logging('PeerConnection');
1086
        if (pcConfig && pcConfig.iceTransportPolicy) {
1087
          pcConfig.iceTransports = pcConfig.iceTransportPolicy;
1088
        }
1089
1090
        return new window.webkitRTCPeerConnection(pcConfig, pcConstraints);
1091
      };
1092
      window.RTCPeerConnection.prototype =
1093
          window.webkitRTCPeerConnection.prototype;
1094
      // wrap static methods. Currently just generateCertificate.
1095
      if (window.webkitRTCPeerConnection.generateCertificate) {
1096
        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
1097
          get: function() {
1098
            return window.webkitRTCPeerConnection.generateCertificate;
1099
          }
1100
        });
1101
      }
1102
    } else {
1103
      // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
1104
      var OrigPeerConnection = window.RTCPeerConnection;
1105
      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
1106
        if (pcConfig && pcConfig.iceServers) {
1107
          var newIceServers = [];
1108
          for (var i = 0; i < pcConfig.iceServers.length; i++) {
1109
            var server = pcConfig.iceServers[i];
1110
            if (!server.hasOwnProperty('urls') &&
1111
                server.hasOwnProperty('url')) {
1112
              console.warn('RTCIceServer.url is deprecated! Use urls instead.');
1113
              server = JSON.parse(JSON.stringify(server));
1114
              server.urls = server.url;
1115
              newIceServers.push(server);
1116
            } else {
1117
              newIceServers.push(pcConfig.iceServers[i]);
1118
            }
1119
          }
1120
          pcConfig.iceServers = newIceServers;
1121
        }
1122
        return new OrigPeerConnection(pcConfig, pcConstraints);
1123
      };
1124
      window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
1125
      // wrap static methods. Currently just generateCertificate.
1126
      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
1127
        get: function() {
1128
          return OrigPeerConnection.generateCertificate;
1129
        }
1130
      });
1131
    }
1132
1133
    var origGetStats = window.RTCPeerConnection.prototype.getStats;
1134
    window.RTCPeerConnection.prototype.getStats = function(selector,
1135
        successCallback, errorCallback) {
1136
      var self = this;
1137
      var args = arguments;
1138
1139
      // If selector is a function then we are in the old style stats so just
1140
      // pass back the original getStats format to avoid breaking old users.
1141
      if (arguments.length > 0 && typeof selector === 'function') {
1142
        return origGetStats.apply(this, arguments);
1143
      }
1144
1145
      // When spec-style getStats is supported, return those when called with
1146
      // either no arguments or the selector argument is null.
1147
      if (origGetStats.length === 0 && (arguments.length === 0 ||
1148
          typeof arguments[0] !== 'function')) {
1149
        return origGetStats.apply(this, []);
1150
      }
1151
1152
      var fixChromeStats_ = function(response) {
1153
        var standardReport = {};
1154
        var reports = response.result();
1155
        reports.forEach(function(report) {
1156
          var standardStats = {
1157
            id: report.id,
1158
            timestamp: report.timestamp,
1159
            type: {
1160
              localcandidate: 'local-candidate',
1161
              remotecandidate: 'remote-candidate'
1162
            }[report.type] || report.type
1163
          };
1164
          report.names().forEach(function(name) {
1165
            standardStats[name] = report.stat(name);
1166
          });
1167
          standardReport[standardStats.id] = standardStats;
1168
        });
1169
1170
        return standardReport;
1171
      };
1172
1173
      // shim getStats with maplike support
1174
      var makeMapStats = function(stats) {
1175
        return new Map(Object.keys(stats).map(function(key) {
1176
          return [key, stats[key]];
1177
        }));
1178
      };
1179
1180
      if (arguments.length >= 2) {
1181
        var successCallbackWrapper_ = function(response) {
1182
          args[1](makeMapStats(fixChromeStats_(response)));
1183
        };
1184
1185
        return origGetStats.apply(this, [successCallbackWrapper_,
1186
          arguments[0]]);
1187
      }
1188
1189
      // promise-support
1190
      return new Promise(function(resolve, reject) {
1191
        origGetStats.apply(self, [
1192
          function(response) {
1193
            resolve(makeMapStats(fixChromeStats_(response)));
1194
          }, reject]);
1195
      }).then(successCallback, errorCallback);
1196
    };
1197
1198
    // add promise support -- natively available in Chrome 51
1199
    if (browserDetails.version < 51) {
1200
      ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
1201
          .forEach(function(method) {
1202
            var nativeMethod = window.RTCPeerConnection.prototype[method];
1203
            window.RTCPeerConnection.prototype[method] = function() {
1204
              var args = arguments;
1205
              var self = this;
1206
              var promise = new Promise(function(resolve, reject) {
1207
                nativeMethod.apply(self, [args[0], resolve, reject]);
1208
              });
1209
              if (args.length < 2) {
1210
                return promise;
1211
              }
1212
              return promise.then(function() {
1213
                args[1].apply(null, []);
1214
              },
1215
              function(err) {
1216
                if (args.length >= 3) {
1217
                  args[2].apply(null, [err]);
1218
                }
1219
              });
1220
            };
1221
          });
1222
    }
1223
1224
    // promise support for createOffer and createAnswer. Available (without
1225
    // bugs) since M52: crbug/619289
1226
    if (browserDetails.version < 52) {
1227
      ['createOffer', 'createAnswer'].forEach(function(method) {
1228
        var nativeMethod = window.RTCPeerConnection.prototype[method];
1229
        window.RTCPeerConnection.prototype[method] = function() {
1230
          var self = this;
1231
          if (arguments.length < 1 || (arguments.length === 1 &&
1232
              typeof arguments[0] === 'object')) {
1233
            var opts = arguments.length === 1 ? arguments[0] : undefined;
1234
            return new Promise(function(resolve, reject) {
1235
              nativeMethod.apply(self, [resolve, reject, opts]);
1236
            });
1237
          }
1238
          return nativeMethod.apply(this, arguments);
1239
        };
1240
      });
1241
    }
1242
1243
    // shim implicit creation of RTCSessionDescription/RTCIceCandidate
1244
    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
1245
        .forEach(function(method) {
1246
          var nativeMethod = window.RTCPeerConnection.prototype[method];
1247
          window.RTCPeerConnection.prototype[method] = function() {
1248
            arguments[0] = new ((method === 'addIceCandidate') ?
1249
                window.RTCIceCandidate :
1250
                window.RTCSessionDescription)(arguments[0]);
1251
            return nativeMethod.apply(this, arguments);
1252
          };
1253
        });
1254
1255
    // support for addIceCandidate(null or undefined)
1256
    var nativeAddIceCandidate =
1257
        window.RTCPeerConnection.prototype.addIceCandidate;
1258
    window.RTCPeerConnection.prototype.addIceCandidate = function() {
1259
      if (!arguments[0]) {
1260
        if (arguments[1]) {
1261
          arguments[1].apply(null);
1262
        }
1263
        return Promise.resolve();
1264
      }
1265
      return nativeAddIceCandidate.apply(this, arguments);
1266
    };
1267
  }
1268
};
1269
1270
1271
// Expose public methods.
1272
module.exports = {
1273
  shimMediaStream: chromeShim.shimMediaStream,
1274
  shimOnTrack: chromeShim.shimOnTrack,
1275
  shimAddTrack: chromeShim.shimAddTrack,
1276
  shimGetSendersWithDtmf: chromeShim.shimGetSendersWithDtmf,
1277
  shimSourceObject: chromeShim.shimSourceObject,
1278
  shimPeerConnection: chromeShim.shimPeerConnection,
1279
  shimGetUserMedia: require('./getusermedia')
1280
};
1281
1282
},{"../utils.js":12,"./getusermedia":5}],5:[function(require,module,exports){
1283
/*
1284
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
1285
 *
1286
 *  Use of this source code is governed by a BSD-style license
1287
 *  that can be found in the LICENSE file in the root of the source
1288
 *  tree.
1289
 */
1290
 /* eslint-env node */
1291
'use strict';
1292
var utils = require('../utils.js');
1293
var logging = utils.log;
1294
1295
// Expose public methods.
1296
module.exports = function(window) {
1297
  var browserDetails = utils.detectBrowser(window);
1298
  var navigator = window && window.navigator;
1299
1300
  var constraintsToChrome_ = function(c) {
1301
    if (typeof c !== 'object' || c.mandatory || c.optional) {
1302
      return c;
1303
    }
1304
    var cc = {};
1305
    Object.keys(c).forEach(function(key) {
1306
      if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
1307
        return;
1308
      }
1309
      var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
1310
      if (r.exact !== undefined && typeof r.exact === 'number') {
1311
        r.min = r.max = r.exact;
1312
      }
1313
      var oldname_ = function(prefix, name) {
1314
        if (prefix) {
1315
          return prefix + name.charAt(0).toUpperCase() + name.slice(1);
1316
        }
1317
        return (name === 'deviceId') ? 'sourceId' : name;
1318
      };
1319
      if (r.ideal !== undefined) {
1320
        cc.optional = cc.optional || [];
1321
        var oc = {};
1322
        if (typeof r.ideal === 'number') {
1323
          oc[oldname_('min', key)] = r.ideal;
1324
          cc.optional.push(oc);
1325
          oc = {};
1326
          oc[oldname_('max', key)] = r.ideal;
1327
          cc.optional.push(oc);
1328
        } else {
1329
          oc[oldname_('', key)] = r.ideal;
1330
          cc.optional.push(oc);
1331
        }
1332
      }
1333
      if (r.exact !== undefined && typeof r.exact !== 'number') {
1334
        cc.mandatory = cc.mandatory || {};
1335
        cc.mandatory[oldname_('', key)] = r.exact;
1336
      } else {
1337
        ['min', 'max'].forEach(function(mix) {
1338
          if (r[mix] !== undefined) {
1339
            cc.mandatory = cc.mandatory || {};
1340
            cc.mandatory[oldname_(mix, key)] = r[mix];
1341
          }
1342
        });
1343
      }
1344
    });
1345
    if (c.advanced) {
1346
      cc.optional = (cc.optional || []).concat(c.advanced);
1347
    }
1348
    return cc;
1349
  };
1350
1351
  var shimConstraints_ = function(constraints, func) {
1352
    constraints = JSON.parse(JSON.stringify(constraints));
1353
    if (constraints && typeof constraints.audio === 'object') {
1354
      var remap = function(obj, a, b) {
1355
        if (a in obj && !(b in obj)) {
1356
          obj[b] = obj[a];
1357
          delete obj[a];
1358
        }
1359
      };
1360
      constraints = JSON.parse(JSON.stringify(constraints));
1361
      remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');
1362
      remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');
1363
      constraints.audio = constraintsToChrome_(constraints.audio);
1364
    }
1365
    if (constraints && typeof constraints.video === 'object') {
1366
      // Shim facingMode for mobile & surface pro.
1367
      var face = constraints.video.facingMode;
1368
      face = face && ((typeof face === 'object') ? face : {ideal: face});
1369
      var getSupportedFacingModeLies = browserDetails.version < 61;
1370
1371
      if ((face && (face.exact === 'user' || face.exact === 'environment' ||
1372
                    face.ideal === 'user' || face.ideal === 'environment')) &&
1373
          !(navigator.mediaDevices.getSupportedConstraints &&
1374
            navigator.mediaDevices.getSupportedConstraints().facingMode &&
1375
            !getSupportedFacingModeLies)) {
1376
        delete constraints.video.facingMode;
1377
        var matches;
1378
        if (face.exact === 'environment' || face.ideal === 'environment') {
1379
          matches = ['back', 'rear'];
1380
        } else if (face.exact === 'user' || face.ideal === 'user') {
1381
          matches = ['front'];
1382
        }
1383
        if (matches) {
1384
          // Look for matches in label, or use last cam for back (typical).
1385
          return navigator.mediaDevices.enumerateDevices()
1386
          .then(function(devices) {
1387
            devices = devices.filter(function(d) {
1388
              return d.kind === 'videoinput';
1389
            });
1390
            var dev = devices.find(function(d) {
1391
              return matches.some(function(match) {
1392
                return d.label.toLowerCase().indexOf(match) !== -1;
1393
              });
1394
            });
1395
            if (!dev && devices.length && matches.indexOf('back') !== -1) {
1396
              dev = devices[devices.length - 1]; // more likely the back cam
1397
            }
1398
            if (dev) {
1399
              constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :
1400
                                                        {ideal: dev.deviceId};
1401
            }
1402
            constraints.video = constraintsToChrome_(constraints.video);
1403
            logging('chrome: ' + JSON.stringify(constraints));
1404
            return func(constraints);
1405
          });
1406
        }
1407
      }
1408
      constraints.video = constraintsToChrome_(constraints.video);
1409
    }
1410
    logging('chrome: ' + JSON.stringify(constraints));
1411
    return func(constraints);
1412
  };
1413
1414
  var shimError_ = function(e) {
1415
    return {
1416
      name: {
1417
        PermissionDeniedError: 'NotAllowedError',
1418
        InvalidStateError: 'NotReadableError',
1419
        DevicesNotFoundError: 'NotFoundError',
1420
        ConstraintNotSatisfiedError: 'OverconstrainedError',
1421
        TrackStartError: 'NotReadableError',
1422
        MediaDeviceFailedDueToShutdown: 'NotReadableError',
1423
        MediaDeviceKillSwitchOn: 'NotReadableError'
1424
      }[e.name] || e.name,
1425
      message: e.message,
1426
      constraint: e.constraintName,
1427
      toString: function() {
1428
        return this.name + (this.message && ': ') + this.message;
1429
      }
1430
    };
1431
  };
1432
1433
  var getUserMedia_ = function(constraints, onSuccess, onError) {
1434
    shimConstraints_(constraints, function(c) {
1435
      navigator.webkitGetUserMedia(c, onSuccess, function(e) {
1436
        onError(shimError_(e));
1437
      });
1438
    });
1439
  };
1440
1441
  navigator.getUserMedia = getUserMedia_;
1442
1443
  // Returns the result of getUserMedia as a Promise.
1444
  var getUserMediaPromise_ = function(constraints) {
1445
    return new Promise(function(resolve, reject) {
1446
      navigator.getUserMedia(constraints, resolve, reject);
1447
    });
1448
  };
1449
1450
  if (!navigator.mediaDevices) {
1451
    navigator.mediaDevices = {
1452
      getUserMedia: getUserMediaPromise_,
1453
      enumerateDevices: function() {
1454
        return new Promise(function(resolve) {
1455
          var kinds = {audio: 'audioinput', video: 'videoinput'};
1456
          return window.MediaStreamTrack.getSources(function(devices) {
1457
            resolve(devices.map(function(device) {
1458
              return {label: device.label,
1459
                kind: kinds[device.kind],
1460
                deviceId: device.id,
1461
                groupId: ''};
1462
            }));
1463
          });
1464
        });
1465
      },
1466
      getSupportedConstraints: function() {
1467
        return {
1468
          deviceId: true, echoCancellation: true, facingMode: true,
1469
          frameRate: true, height: true, width: true
1470
        };
1471
      }
1472
    };
1473
  }
1474
1475
  // A shim for getUserMedia method on the mediaDevices object.
1476
  // TODO(KaptenJansson) remove once implemented in Chrome stable.
1477
  if (!navigator.mediaDevices.getUserMedia) {
1478
    navigator.mediaDevices.getUserMedia = function(constraints) {
1479
      return getUserMediaPromise_(constraints);
1480
    };
1481
  } else {
1482
    // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
1483
    // function which returns a Promise, it does not accept spec-style
1484
    // constraints.
1485
    var origGetUserMedia = navigator.mediaDevices.getUserMedia.
1486
        bind(navigator.mediaDevices);
1487
    navigator.mediaDevices.getUserMedia = function(cs) {
1488
      return shimConstraints_(cs, function(c) {
1489
        return origGetUserMedia(c).then(function(stream) {
1490
          if (c.audio && !stream.getAudioTracks().length ||
1491
              c.video && !stream.getVideoTracks().length) {
1492
            stream.getTracks().forEach(function(track) {
1493
              track.stop();
1494
            });
1495
            throw new DOMException('', 'NotFoundError');
1496
          }
1497
          return stream;
1498
        }, function(e) {
1499
          return Promise.reject(shimError_(e));
1500
        });
1501
      });
1502
    };
1503
  }
1504
1505
  // Dummy devicechange event methods.
1506
  // TODO(KaptenJansson) remove once implemented in Chrome stable.
1507
  if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
1508
    navigator.mediaDevices.addEventListener = function() {
1509
      logging('Dummy mediaDevices.addEventListener called.');
1510
    };
1511
  }
1512
  if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
1513
    navigator.mediaDevices.removeEventListener = function() {
1514
      logging('Dummy mediaDevices.removeEventListener called.');
1515
    };
1516
  }
1517
};
1518
1519
},{"../utils.js":12}],6:[function(require,module,exports){
1520
/*
1521
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
1522
 *
1523
 *  Use of this source code is governed by a BSD-style license
1524
 *  that can be found in the LICENSE file in the root of the source
1525
 *  tree.
1526
 */
1527
 /* eslint-env node */
1528
'use strict';
1529
1530
var utils = require('../utils');
1531
var shimRTCPeerConnection = require('./rtcpeerconnection_shim');
1532
1533
module.exports = {
1534
  shimGetUserMedia: require('./getusermedia'),
1535
  shimPeerConnection: function(window) {
1536
    var browserDetails = utils.detectBrowser(window);
1537
1538
    if (window.RTCIceGatherer) {
1539
      // ORTC defines an RTCIceCandidate object but no constructor.
1540
      // Not implemented in Edge.
1541
      if (!window.RTCIceCandidate) {
1542
        window.RTCIceCandidate = function(args) {
1543
          return args;
1544
        };
1545
      }
1546
      // ORTC does not have a session description object but
1547
      // other browsers (i.e. Chrome) that will support both PC and ORTC
1548
      // in the future might have this defined already.
1549
      if (!window.RTCSessionDescription) {
1550
        window.RTCSessionDescription = function(args) {
1551
          return args;
1552
        };
1553
      }
1554
      // this adds an additional event listener to MediaStrackTrack that signals
1555
      // when a tracks enabled property was changed. Workaround for a bug in
1556
      // addStream, see below. No longer required in 15025+
1557
      if (browserDetails.version < 15025) {
1558
        var origMSTEnabled = Object.getOwnPropertyDescriptor(
1559
            window.MediaStreamTrack.prototype, 'enabled');
1560
        Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', {
1561
          set: function(value) {
1562
            origMSTEnabled.set.call(this, value);
1563
            var ev = new Event('enabled');
1564
            ev.enabled = value;
1565
            this.dispatchEvent(ev);
1566
          }
1567
        });
1568
      }
1569
    }
1570
1571
    // ORTC defines the DTMF sender a bit different.
1572
    // https://github.com/w3c/ortc/issues/714
1573
    if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) {
1574
      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
1575
        get: function() {
1576
          if (this._dtmf === undefined) {
1577
            if (this.track.kind === 'audio') {
1578
              this._dtmf = new window.RTCDtmfSender(this);
1579
            } else if (this.track.kind === 'video') {
1580
              this._dtmf = null;
1581
            }
1582
          }
1583
          return this._dtmf;
1584
        }
1585
      });
1586
    }
1587
1588
    window.RTCPeerConnection =
1589
        shimRTCPeerConnection(window, browserDetails.version);
1590
  },
1591
  shimReplaceTrack: function(window) {
1592
    // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
1593
    if (window.RTCRtpSender &&
1594
        !('replaceTrack' in window.RTCRtpSender.prototype)) {
1595
      window.RTCRtpSender.prototype.replaceTrack =
1596
          window.RTCRtpSender.prototype.setTrack;
1597
    }
1598
  }
1599
};
1600
1601
},{"../utils":12,"./getusermedia":7,"./rtcpeerconnection_shim":8}],7:[function(require,module,exports){
1602
/*
1603
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
1604
 *
1605
 *  Use of this source code is governed by a BSD-style license
1606
 *  that can be found in the LICENSE file in the root of the source
1607
 *  tree.
1608
 */
1609
 /* eslint-env node */
1610
'use strict';
1611
1612
// Expose public methods.
1613
module.exports = function(window) {
1614
  var navigator = window && window.navigator;
1615
1616
  var shimError_ = function(e) {
1617
    return {
1618
      name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name,
1619
      message: e.message,
1620
      constraint: e.constraint,
1621
      toString: function() {
1622
        return this.name;
1623
      }
1624
    };
1625
  };
1626
1627
  // getUserMedia error shim.
1628
  var origGetUserMedia = navigator.mediaDevices.getUserMedia.
1629
      bind(navigator.mediaDevices);
1630
  navigator.mediaDevices.getUserMedia = function(c) {
1631
    return origGetUserMedia(c).catch(function(e) {
1632
      return Promise.reject(shimError_(e));
1633
    });
1634
  };
1635
};
1636
1637
},{}],8:[function(require,module,exports){
1638
/*
1639
 *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
1640
 *
1641
 *  Use of this source code is governed by a BSD-style license
1642
 *  that can be found in the LICENSE file in the root of the source
1643
 *  tree.
1644
 */
1645
 /* eslint-env node */
1646
'use strict';
1647
1648
var SDPUtils = require('sdp');
1649
1650
// sort tracks such that they follow an a-v-a-v...
1651
// pattern.
1652
function sortTracks(tracks) {
1653
  var audioTracks = tracks.filter(function(track) {
1654
    return track.kind === 'audio';
1655
  });
1656
  var videoTracks = tracks.filter(function(track) {
1657
    return track.kind === 'video';
1658
  });
1659
  tracks = [];
1660
  while (audioTracks.length || videoTracks.length) {
1661
    if (audioTracks.length) {
1662
      tracks.push(audioTracks.shift());
1663
    }
1664
    if (videoTracks.length) {
1665
      tracks.push(videoTracks.shift());
1666
    }
1667
  }
1668
  return tracks;
1669
}
1670
1671
// Edge does not like
1672
// 1) stun:
1673
// 2) turn: that does not have all of turn:host:port?transport=udp
1674
// 3) turn: with ipv6 addresses
1675
// 4) turn: occurring muliple times
1676
function filterIceServers(iceServers, edgeVersion) {
1677
  var hasTurn = false;
1678
  iceServers = JSON.parse(JSON.stringify(iceServers));
1679
  return iceServers.filter(function(server) {
1680
    if (server && (server.urls || server.url)) {
1681
      var urls = server.urls || server.url;
1682
      if (server.url && !server.urls) {
1683
        console.warn('RTCIceServer.url is deprecated! Use urls instead.');
1684
      }
1685
      var isString = typeof urls === 'string';
1686
      if (isString) {
1687
        urls = [urls];
1688
      }
1689
      urls = urls.filter(function(url) {
1690
        var validTurn = url.indexOf('turn:') === 0 &&
1691
            url.indexOf('transport=udp') !== -1 &&
1692
            url.indexOf('turn:[') === -1 &&
1693
            !hasTurn;
1694
1695
        if (validTurn) {
1696
          hasTurn = true;
1697
          return true;
1698
        }
1699
        return url.indexOf('stun:') === 0 && edgeVersion >= 14393;
1700
      });
1701
1702
      delete server.url;
1703
      server.urls = isString ? urls[0] : urls;
1704
      return !!urls.length;
1705
    }
1706
    return false;
1707
  });
1708
}
1709
1710
// Determines the intersection of local and remote capabilities.
1711
function getCommonCapabilities(localCapabilities, remoteCapabilities) {
1712
  var commonCapabilities = {
1713
    codecs: [],
1714
    headerExtensions: [],
1715
    fecMechanisms: []
1716
  };
1717
1718
  var findCodecByPayloadType = function(pt, codecs) {
1719
    pt = parseInt(pt, 10);
1720
    for (var i = 0; i < codecs.length; i++) {
1721
      if (codecs[i].payloadType === pt ||
1722
          codecs[i].preferredPayloadType === pt) {
1723
        return codecs[i];
1724
      }
1725
    }
1726
  };
1727
1728
  var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
1729
    var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
1730
    var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
1731
    return lCodec && rCodec &&
1732
        lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
1733
  };
1734
1735
  localCapabilities.codecs.forEach(function(lCodec) {
1736
    for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
1737
      var rCodec = remoteCapabilities.codecs[i];
1738
      if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
1739
          lCodec.clockRate === rCodec.clockRate) {
1740
        if (lCodec.name.toLowerCase() === 'rtx' &&
1741
            lCodec.parameters && rCodec.parameters.apt) {
1742
          // for RTX we need to find the local rtx that has a apt
1743
          // which points to the same local codec as the remote one.
1744
          if (!rtxCapabilityMatches(lCodec, rCodec,
1745
              localCapabilities.codecs, remoteCapabilities.codecs)) {
1746
            continue;
1747
          }
1748
        }
1749
        rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
1750
        // number of channels is the highest common number of channels
1751
        rCodec.numChannels = Math.min(lCodec.numChannels,
1752
            rCodec.numChannels);
1753
        // push rCodec so we reply with offerer payload type
1754
        commonCapabilities.codecs.push(rCodec);
1755
1756
        // determine common feedback mechanisms
1757
        rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {
1758
          for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
1759
            if (lCodec.rtcpFeedback[j].type === fb.type &&
1760
                lCodec.rtcpFeedback[j].parameter === fb.parameter) {
1761
              return true;
1762
            }
1763
          }
1764
          return false;
1765
        });
1766
        // FIXME: also need to determine .parameters
1767
        //  see https://github.com/openpeer/ortc/issues/569
1768
        break;
1769
      }
1770
    }
1771
  });
1772
1773
  localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {
1774
    for (var i = 0; i < remoteCapabilities.headerExtensions.length;
1775
         i++) {
1776
      var rHeaderExtension = remoteCapabilities.headerExtensions[i];
1777
      if (lHeaderExtension.uri === rHeaderExtension.uri) {
1778
        commonCapabilities.headerExtensions.push(rHeaderExtension);
1779
        break;
1780
      }
1781
    }
1782
  });
1783
1784
  // FIXME: fecMechanisms
1785
  return commonCapabilities;
1786
}
1787
1788
// is action=setLocalDescription with type allowed in signalingState
1789
function isActionAllowedInSignalingState(action, type, signalingState) {
1790
  return {
1791
    offer: {
1792
      setLocalDescription: ['stable', 'have-local-offer'],
1793
      setRemoteDescription: ['stable', 'have-remote-offer']
1794
    },
1795
    answer: {
1796
      setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],
1797
      setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']
1798
    }
1799
  }[type][action].indexOf(signalingState) !== -1;
1800
}
1801
1802
module.exports = function(window, edgeVersion) {
1803
  var RTCPeerConnection = function(config) {
1804
    var self = this;
1805
1806
    var _eventTarget = document.createDocumentFragment();
1807
    ['addEventListener', 'removeEventListener', 'dispatchEvent']
1808
        .forEach(function(method) {
1809
          self[method] = _eventTarget[method].bind(_eventTarget);
1810
        });
1811
1812
    this.needNegotiation = false;
1813
1814
    this.onicecandidate = null;
1815
    this.onaddstream = null;
1816
    this.ontrack = null;
1817
    this.onremovestream = null;
1818
    this.onsignalingstatechange = null;
1819
    this.oniceconnectionstatechange = null;
1820
    this.onicegatheringstatechange = null;
1821
    this.onnegotiationneeded = null;
1822
    this.ondatachannel = null;
1823
    this.canTrickleIceCandidates = null;
1824
1825
    this.localStreams = [];
1826
    this.remoteStreams = [];
1827
    this.getLocalStreams = function() {
1828
      return self.localStreams;
1829
    };
1830
    this.getRemoteStreams = function() {
1831
      return self.remoteStreams;
1832
    };
1833
1834
    this.localDescription = new window.RTCSessionDescription({
1835
      type: '',
1836
      sdp: ''
1837
    });
1838
    this.remoteDescription = new window.RTCSessionDescription({
1839
      type: '',
1840
      sdp: ''
1841
    });
1842
    this.signalingState = 'stable';
1843
    this.iceConnectionState = 'new';
1844
    this.iceGatheringState = 'new';
1845
1846
    this.iceOptions = {
1847
      gatherPolicy: 'all',
1848
      iceServers: []
1849
    };
1850
    if (config && config.iceTransportPolicy) {
1851
      switch (config.iceTransportPolicy) {
1852
        case 'all':
1853
        case 'relay':
1854
          this.iceOptions.gatherPolicy = config.iceTransportPolicy;
1855
          break;
1856
        default:
1857
          // don't set iceTransportPolicy.
1858
          break;
1859
      }
1860
    }
1861
    this.usingBundle = config && config.bundlePolicy === 'max-bundle';
1862
1863
    if (config && config.iceServers) {
1864
      this.iceOptions.iceServers = filterIceServers(config.iceServers,
1865
          edgeVersion);
1866
    }
1867
    this._config = config || {};
1868
1869
    // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
1870
    // everything that is needed to describe a SDP m-line.
1871
    this.transceivers = [];
1872
1873
    // since the iceGatherer is currently created in createOffer but we
1874
    // must not emit candidates until after setLocalDescription we buffer
1875
    // them in this array.
1876
    this._localIceCandidatesBuffer = [];
1877
1878
    this._sdpSessionId = SDPUtils.generateSessionId();
1879
  };
1880
1881
  RTCPeerConnection.prototype._emitGatheringStateChange = function() {
1882
    var event = new Event('icegatheringstatechange');
1883
    this.dispatchEvent(event);
1884
    if (this.onicegatheringstatechange !== null) {
1885
      this.onicegatheringstatechange(event);
1886
    }
1887
  };
1888
1889
  RTCPeerConnection.prototype._emitBufferedCandidates = function() {
1890
    var self = this;
1891
    var sections = SDPUtils.splitSections(self.localDescription.sdp);
1892
    // FIXME: need to apply ice candidates in a way which is async but
1893
    // in-order
1894
    this._localIceCandidatesBuffer.forEach(function(event) {
1895
      var end = !event.candidate || Object.keys(event.candidate).length === 0;
1896
      if (end) {
1897
        for (var j = 1; j < sections.length; j++) {
1898
          if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
1899
            sections[j] += 'a=end-of-candidates\r\n';
1900
          }
1901
        }
1902
      } else {
1903
        sections[event.candidate.sdpMLineIndex + 1] +=
1904
            'a=' + event.candidate.candidate + '\r\n';
1905
      }
1906
      self.localDescription.sdp = sections.join('');
1907
      self.dispatchEvent(event);
1908
      if (self.onicecandidate !== null) {
1909
        self.onicecandidate(event);
1910
      }
1911
      if (!event.candidate && self.iceGatheringState !== 'complete') {
1912
        var complete = self.transceivers.every(function(transceiver) {
1913
          return transceiver.iceGatherer &&
1914
              transceiver.iceGatherer.state === 'completed';
1915
        });
1916
        if (complete && self.iceGatheringStateChange !== 'complete') {
1917
          self.iceGatheringState = 'complete';
1918
          self._emitGatheringStateChange();
1919
        }
1920
      }
1921
    });
1922
    this._localIceCandidatesBuffer = [];
1923
  };
1924
1925
  RTCPeerConnection.prototype.getConfiguration = function() {
1926
    return this._config;
1927
  };
1928
1929
  // internal helper to create a transceiver object.
1930
  // (whih is not yet the same as the WebRTC 1.0 transceiver)
1931
  RTCPeerConnection.prototype._createTransceiver = function(kind) {
1932
    var hasBundleTransport = this.transceivers.length > 0;
1933
    var transceiver = {
1934
      track: null,
1935
      iceGatherer: null,
1936
      iceTransport: null,
1937
      dtlsTransport: null,
1938
      localCapabilities: null,
1939
      remoteCapabilities: null,
1940
      rtpSender: null,
1941
      rtpReceiver: null,
1942
      kind: kind,
1943
      mid: null,
1944
      sendEncodingParameters: null,
1945
      recvEncodingParameters: null,
1946
      stream: null,
1947
      wantReceive: true
1948
    };
1949
    if (this.usingBundle && hasBundleTransport) {
1950
      transceiver.iceTransport = this.transceivers[0].iceTransport;
1951
      transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
1952
    } else {
1953
      var transports = this._createIceAndDtlsTransports();
1954
      transceiver.iceTransport = transports.iceTransport;
1955
      transceiver.dtlsTransport = transports.dtlsTransport;
1956
    }
1957
    this.transceivers.push(transceiver);
1958
    return transceiver;
1959
  };
1960
1961
  RTCPeerConnection.prototype.addTrack = function(track, stream) {
1962
    var transceiver;
1963
    for (var i = 0; i < this.transceivers.length; i++) {
1964
      if (!this.transceivers[i].track &&
1965
          this.transceivers[i].kind === track.kind) {
1966
        transceiver = this.transceivers[i];
1967
      }
1968
    }
1969
    if (!transceiver) {
1970
      transceiver = this._createTransceiver(track.kind);
1971
    }
1972
1973
    transceiver.track = track;
1974
    transceiver.stream = stream;
1975
    transceiver.rtpSender = new window.RTCRtpSender(track,
1976
        transceiver.dtlsTransport);
1977
1978
    this._maybeFireNegotiationNeeded();
1979
    return transceiver.rtpSender;
1980
  };
1981
1982
  RTCPeerConnection.prototype.addStream = function(stream) {
1983
    var self = this;
1984
    if (edgeVersion >= 15025) {
1985
      this.localStreams.push(stream);
1986
      stream.getTracks().forEach(function(track) {
1987
        self.addTrack(track, stream);
1988
      });
1989
    } else {
1990
      // Clone is necessary for local demos mostly, attaching directly
1991
      // to two different senders does not work (build 10547).
1992
      // Fixed in 15025 (or earlier)
1993
      var clonedStream = stream.clone();
1994
      stream.getTracks().forEach(function(track, idx) {
1995
        var clonedTrack = clonedStream.getTracks()[idx];
1996
        track.addEventListener('enabled', function(event) {
1997
          clonedTrack.enabled = event.enabled;
1998
        });
1999
      });
2000
      clonedStream.getTracks().forEach(function(track) {
2001
        self.addTrack(track, clonedStream);
2002
      });
2003
      this.localStreams.push(clonedStream);
2004
    }
2005
    this._maybeFireNegotiationNeeded();
2006
  };
2007
2008
  RTCPeerConnection.prototype.removeStream = function(stream) {
2009
    var idx = this.localStreams.indexOf(stream);
2010
    if (idx > -1) {
2011
      this.localStreams.splice(idx, 1);
2012
      this._maybeFireNegotiationNeeded();
2013
    }
2014
  };
2015
2016
  RTCPeerConnection.prototype.getSenders = function() {
2017
    return this.transceivers.filter(function(transceiver) {
2018
      return !!transceiver.rtpSender;
2019
    })
2020
    .map(function(transceiver) {
2021
      return transceiver.rtpSender;
2022
    });
2023
  };
2024
2025
  RTCPeerConnection.prototype.getReceivers = function() {
2026
    return this.transceivers.filter(function(transceiver) {
2027
      return !!transceiver.rtpReceiver;
2028
    })
2029
    .map(function(transceiver) {
2030
      return transceiver.rtpReceiver;
2031
    });
2032
  };
2033
2034
  // Create ICE gatherer and hook it up.
2035
  RTCPeerConnection.prototype._createIceGatherer = function(mid,
2036
      sdpMLineIndex) {
2037
    var self = this;
2038
    var iceGatherer = new window.RTCIceGatherer(self.iceOptions);
2039
    iceGatherer.onlocalcandidate = function(evt) {
2040
      var event = new Event('icecandidate');
2041
      event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
2042
2043
      var cand = evt.candidate;
2044
      var end = !cand || Object.keys(cand).length === 0;
2045
      // Edge emits an empty object for RTCIceCandidateComplete‥
2046
      if (end) {
2047
        // polyfill since RTCIceGatherer.state is not implemented in
2048
        // Edge 10547 yet.
2049
        if (iceGatherer.state === undefined) {
2050
          iceGatherer.state = 'completed';
2051
        }
2052
      } else {
2053
        // RTCIceCandidate doesn't have a component, needs to be added
2054
        cand.component = 1;
2055
        event.candidate.candidate = SDPUtils.writeCandidate(cand);
2056
      }
2057
2058
      // update local description.
2059
      var sections = SDPUtils.splitSections(self.localDescription.sdp);
2060
      if (!end) {
2061
        sections[event.candidate.sdpMLineIndex + 1] +=
2062
            'a=' + event.candidate.candidate + '\r\n';
2063
      } else {
2064
        sections[event.candidate.sdpMLineIndex + 1] +=
2065
            'a=end-of-candidates\r\n';
2066
      }
2067
      self.localDescription.sdp = sections.join('');
2068
      var transceivers = self._pendingOffer ? self._pendingOffer :
2069
          self.transceivers;
2070
      var complete = transceivers.every(function(transceiver) {
2071
        return transceiver.iceGatherer &&
2072
            transceiver.iceGatherer.state === 'completed';
2073
      });
2074
2075
      // Emit candidate if localDescription is set.
2076
      // Also emits null candidate when all gatherers are complete.
2077
      switch (self.iceGatheringState) {
2078
        case 'new':
2079
          if (!end) {
2080
            self._localIceCandidatesBuffer.push(event);
2081
          }
2082
          if (end && complete) {
2083
            self._localIceCandidatesBuffer.push(
2084
                new Event('icecandidate'));
2085
          }
2086
          break;
2087
        case 'gathering':
2088
          self._emitBufferedCandidates();
2089
          if (!end) {
2090
            self.dispatchEvent(event);
2091
            if (self.onicecandidate !== null) {
2092
              self.onicecandidate(event);
2093
            }
2094
          }
2095
          if (complete) {
2096
            self.dispatchEvent(new Event('icecandidate'));
2097
            if (self.onicecandidate !== null) {
2098
              self.onicecandidate(new Event('icecandidate'));
2099
            }
2100
            self.iceGatheringState = 'complete';
2101
            self._emitGatheringStateChange();
2102
          }
2103
          break;
2104
        case 'complete':
2105
          // should not happen... currently!
2106
          break;
2107
        default: // no-op.
2108
          break;
2109
      }
2110
    };
2111
    return iceGatherer;
2112
  };
2113
2114
  // Create ICE transport and DTLS transport.
2115
  RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
2116
    var self = this;
2117
    var iceTransport = new window.RTCIceTransport(null);
2118
    iceTransport.onicestatechange = function() {
2119
      self._updateConnectionState();
2120
    };
2121
2122
    var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
2123
    dtlsTransport.ondtlsstatechange = function() {
2124
      self._updateConnectionState();
2125
    };
2126
    dtlsTransport.onerror = function() {
2127
      // onerror does not set state to failed by itself.
2128
      Object.defineProperty(dtlsTransport, 'state',
2129
          {value: 'failed', writable: true});
2130
      self._updateConnectionState();
2131
    };
2132
2133
    return {
2134
      iceTransport: iceTransport,
2135
      dtlsTransport: dtlsTransport
2136
    };
2137
  };
2138
2139
  // Destroy ICE gatherer, ICE transport and DTLS transport.
2140
  // Without triggering the callbacks.
2141
  RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
2142
      sdpMLineIndex) {
2143
    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
2144
    if (iceGatherer) {
2145
      delete iceGatherer.onlocalcandidate;
2146
      delete this.transceivers[sdpMLineIndex].iceGatherer;
2147
    }
2148
    var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
2149
    if (iceTransport) {
2150
      delete iceTransport.onicestatechange;
2151
      delete this.transceivers[sdpMLineIndex].iceTransport;
2152
    }
2153
    var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
2154
    if (dtlsTransport) {
2155
      delete dtlsTransport.ondtlsstatechange;
2156
      delete dtlsTransport.onerror;
2157
      delete this.transceivers[sdpMLineIndex].dtlsTransport;
2158
    }
2159
  };
2160
2161
  // Start the RTP Sender and Receiver for a transceiver.
2162
  RTCPeerConnection.prototype._transceive = function(transceiver,
2163
      send, recv) {
2164
    var params = getCommonCapabilities(transceiver.localCapabilities,
2165
        transceiver.remoteCapabilities);
2166
    if (send && transceiver.rtpSender) {
2167
      params.encodings = transceiver.sendEncodingParameters;
2168
      params.rtcp = {
2169
        cname: SDPUtils.localCName,
2170
        compound: transceiver.rtcpParameters.compound
2171
      };
2172
      if (transceiver.recvEncodingParameters.length) {
2173
        params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
2174
      }
2175
      transceiver.rtpSender.send(params);
2176
    }
2177
    if (recv && transceiver.rtpReceiver) {
2178
      // remove RTX field in Edge 14942
2179
      if (transceiver.kind === 'video'
2180
          && transceiver.recvEncodingParameters
2181
          && edgeVersion < 15019) {
2182
        transceiver.recvEncodingParameters.forEach(function(p) {
2183
          delete p.rtx;
2184
        });
2185
      }
2186
      params.encodings = transceiver.recvEncodingParameters;
2187
      params.rtcp = {
2188
        cname: transceiver.rtcpParameters.cname,
2189
        compound: transceiver.rtcpParameters.compound
2190
      };
2191
      if (transceiver.sendEncodingParameters.length) {
2192
        params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
2193
      }
2194
      transceiver.rtpReceiver.receive(params);
2195
    }
2196
  };
2197
2198
  RTCPeerConnection.prototype.setLocalDescription = function(description) {
2199
    var self = this;
2200
2201
    if (!isActionAllowedInSignalingState('setLocalDescription',
2202
        description.type, this.signalingState)) {
2203
      var e = new Error('Can not set local ' + description.type +
2204
          ' in state ' + this.signalingState);
2205
      e.name = 'InvalidStateError';
2206
      if (arguments.length > 2 && typeof arguments[2] === 'function') {
2207
        window.setTimeout(arguments[2], 0, e);
2208
      }
2209
      return Promise.reject(e);
2210
    }
2211
2212
    var sections;
2213
    var sessionpart;
2214
    if (description.type === 'offer') {
2215
      // FIXME: What was the purpose of this empty if statement?
2216
      // if (!this._pendingOffer) {
2217
      // } else {
2218
      if (this._pendingOffer) {
2219
        // VERY limited support for SDP munging. Limited to:
2220
        // * changing the order of codecs
2221
        sections = SDPUtils.splitSections(description.sdp);
2222
        sessionpart = sections.shift();
2223
        sections.forEach(function(mediaSection, sdpMLineIndex) {
2224
          var caps = SDPUtils.parseRtpParameters(mediaSection);
2225
          self._pendingOffer[sdpMLineIndex].localCapabilities = caps;
2226
        });
2227
        this.transceivers = this._pendingOffer;
2228
        delete this._pendingOffer;
2229
      }
2230
    } else if (description.type === 'answer') {
2231
      sections = SDPUtils.splitSections(self.remoteDescription.sdp);
2232
      sessionpart = sections.shift();
2233
      var isIceLite = SDPUtils.matchPrefix(sessionpart,
2234
          'a=ice-lite').length > 0;
2235
      sections.forEach(function(mediaSection, sdpMLineIndex) {
2236
        var transceiver = self.transceivers[sdpMLineIndex];
2237
        var iceGatherer = transceiver.iceGatherer;
2238
        var iceTransport = transceiver.iceTransport;
2239
        var dtlsTransport = transceiver.dtlsTransport;
2240
        var localCapabilities = transceiver.localCapabilities;
2241
        var remoteCapabilities = transceiver.remoteCapabilities;
2242
2243
        var rejected = SDPUtils.isRejected(mediaSection);
2244
2245
        if (!rejected && !transceiver.isDatachannel) {
2246
          var remoteIceParameters = SDPUtils.getIceParameters(
2247
              mediaSection, sessionpart);
2248
          var remoteDtlsParameters = SDPUtils.getDtlsParameters(
2249
              mediaSection, sessionpart);
2250
          if (isIceLite) {
2251
            remoteDtlsParameters.role = 'server';
2252
          }
2253
2254
          if (!self.usingBundle || sdpMLineIndex === 0) {
2255
            iceTransport.start(iceGatherer, remoteIceParameters,
2256
                isIceLite ? 'controlling' : 'controlled');
2257
            dtlsTransport.start(remoteDtlsParameters);
2258
          }
2259
2260
          // Calculate intersection of capabilities.
2261
          var params = getCommonCapabilities(localCapabilities,
2262
              remoteCapabilities);
2263
2264
          // Start the RTCRtpSender. The RTCRtpReceiver for this
2265
          // transceiver has already been started in setRemoteDescription.
2266
          self._transceive(transceiver,
2267
              params.codecs.length > 0,
2268
              false);
2269
        }
2270
      });
2271
    }
2272
2273
    this.localDescription = {
2274
      type: description.type,
2275
      sdp: description.sdp
2276
    };
2277
    switch (description.type) {
2278
      case 'offer':
2279
        this._updateSignalingState('have-local-offer');
2280
        break;
2281
      case 'answer':
2282
        this._updateSignalingState('stable');
2283
        break;
2284
      default:
2285
        throw new TypeError('unsupported type "' + description.type +
2286
            '"');
2287
    }
2288
2289
    // If a success callback was provided, emit ICE candidates after it
2290
    // has been executed. Otherwise, emit callback after the Promise is
2291
    // resolved.
2292
    var hasCallback = arguments.length > 1 &&
2293
      typeof arguments[1] === 'function';
2294
    if (hasCallback) {
2295
      var cb = arguments[1];
2296
      window.setTimeout(function() {
2297
        cb();
2298
        if (self.iceGatheringState === 'new') {
2299
          self.iceGatheringState = 'gathering';
2300
          self._emitGatheringStateChange();
2301
        }
2302
        self._emitBufferedCandidates();
2303
      }, 0);
2304
    }
2305
    var p = Promise.resolve();
2306
    p.then(function() {
2307
      if (!hasCallback) {
2308
        if (self.iceGatheringState === 'new') {
2309
          self.iceGatheringState = 'gathering';
2310
          self._emitGatheringStateChange();
2311
        }
2312
        // Usually candidates will be emitted earlier.
2313
        window.setTimeout(self._emitBufferedCandidates.bind(self), 500);
2314
      }
2315
    });
2316
    return p;
2317
  };
2318
2319
  RTCPeerConnection.prototype.setRemoteDescription = function(description) {
2320
    var self = this;
2321
2322
    if (!isActionAllowedInSignalingState('setRemoteDescription',
2323
        description.type, this.signalingState)) {
2324
      var e = new Error('Can not set remote ' + description.type +
2325
          ' in state ' + this.signalingState);
2326
      e.name = 'InvalidStateError';
2327
      if (arguments.length > 2 && typeof arguments[2] === 'function') {
2328
        window.setTimeout(arguments[2], 0, e);
2329
      }
2330
      return Promise.reject(e);
2331
    }
2332
2333
    var streams = {};
2334
    var receiverList = [];
2335
    var sections = SDPUtils.splitSections(description.sdp);
2336
    var sessionpart = sections.shift();
2337
    var isIceLite = SDPUtils.matchPrefix(sessionpart,
2338
        'a=ice-lite').length > 0;
2339
    var usingBundle = SDPUtils.matchPrefix(sessionpart,
2340
        'a=group:BUNDLE ').length > 0;
2341
    this.usingBundle = usingBundle;
2342
    var iceOptions = SDPUtils.matchPrefix(sessionpart,
2343
        'a=ice-options:')[0];
2344
    if (iceOptions) {
2345
      this.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
2346
          .indexOf('trickle') >= 0;
2347
    } else {
2348
      this.canTrickleIceCandidates = false;
2349
    }
2350
2351
    sections.forEach(function(mediaSection, sdpMLineIndex) {
2352
      var lines = SDPUtils.splitLines(mediaSection);
2353
      var kind = SDPUtils.getKind(mediaSection);
2354
      var rejected = SDPUtils.isRejected(mediaSection);
2355
      var protocol = lines[0].substr(2).split(' ')[2];
2356
2357
      var direction = SDPUtils.getDirection(mediaSection, sessionpart);
2358
      var remoteMsid = SDPUtils.parseMsid(mediaSection);
2359
2360
      var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier();
2361
2362
      // Reject datachannels which are not implemented yet.
2363
      if (kind === 'application' && protocol === 'DTLS/SCTP') {
2364
        self.transceivers[sdpMLineIndex] = {
2365
          mid: mid,
2366
          isDatachannel: true
2367
        };
2368
        return;
2369
      }
2370
2371
      var transceiver;
2372
      var iceGatherer;
2373
      var iceTransport;
2374
      var dtlsTransport;
2375
      var rtpReceiver;
2376
      var sendEncodingParameters;
2377
      var recvEncodingParameters;
2378
      var localCapabilities;
2379
2380
      var track;
2381
      // FIXME: ensure the mediaSection has rtcp-mux set.
2382
      var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
2383
      var remoteIceParameters;
2384
      var remoteDtlsParameters;
2385
      if (!rejected) {
2386
        remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
2387
            sessionpart);
2388
        remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
2389
            sessionpart);
2390
        remoteDtlsParameters.role = 'client';
2391
      }
2392
      recvEncodingParameters =
2393
          SDPUtils.parseRtpEncodingParameters(mediaSection);
2394
2395
      var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
2396
2397
      var isComplete = SDPUtils.matchPrefix(mediaSection,
2398
          'a=end-of-candidates', sessionpart).length > 0;
2399
      var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
2400
          .map(function(cand) {
2401
            return SDPUtils.parseCandidate(cand);
2402
          })
2403
          .filter(function(cand) {
2404
            return cand.component === '1' || cand.component === 1;
2405
          });
2406
2407
      // Check if we can use BUNDLE and dispose transports.
2408
      if ((description.type === 'offer' || description.type === 'answer') &&
2409
          !rejected && usingBundle && sdpMLineIndex > 0 &&
2410
          self.transceivers[sdpMLineIndex]) {
2411
        self._disposeIceAndDtlsTransports(sdpMLineIndex);
2412
        self.transceivers[sdpMLineIndex].iceGatherer =
2413
            self.transceivers[0].iceGatherer;
2414
        self.transceivers[sdpMLineIndex].iceTransport =
2415
            self.transceivers[0].iceTransport;
2416
        self.transceivers[sdpMLineIndex].dtlsTransport =
2417
            self.transceivers[0].dtlsTransport;
2418
        if (self.transceivers[sdpMLineIndex].rtpSender) {
2419
          self.transceivers[sdpMLineIndex].rtpSender.setTransport(
2420
              self.transceivers[0].dtlsTransport);
2421
        }
2422
        if (self.transceivers[sdpMLineIndex].rtpReceiver) {
2423
          self.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
2424
              self.transceivers[0].dtlsTransport);
2425
        }
2426
      }
2427
      if (description.type === 'offer' && !rejected) {
2428
        transceiver = self.transceivers[sdpMLineIndex] ||
2429
            self._createTransceiver(kind);
2430
        transceiver.mid = mid;
2431
2432
        if (!transceiver.iceGatherer) {
2433
          transceiver.iceGatherer = usingBundle && sdpMLineIndex > 0 ?
2434
              self.transceivers[0].iceGatherer :
2435
              self._createIceGatherer(mid, sdpMLineIndex);
2436
        }
2437
2438
        if (isComplete && cands.length &&
2439
            (!usingBundle || sdpMLineIndex === 0)) {
2440
          transceiver.iceTransport.setRemoteCandidates(cands);
2441
        }
2442
2443
        localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);
2444
2445
        // filter RTX until additional stuff needed for RTX is implemented
2446
        // in adapter.js
2447
        if (edgeVersion < 15019) {
2448
          localCapabilities.codecs = localCapabilities.codecs.filter(
2449
              function(codec) {
2450
                return codec.name !== 'rtx';
2451
              });
2452
        }
2453
2454
        sendEncodingParameters = [{
2455
          ssrc: (2 * sdpMLineIndex + 2) * 1001
2456
        }];
2457
2458
        if (direction === 'sendrecv' || direction === 'sendonly') {
2459
          rtpReceiver = new window.RTCRtpReceiver(transceiver.dtlsTransport,
2460
              kind);
2461
2462
          track = rtpReceiver.track;
2463
          // FIXME: does not work with Plan B.
2464
          if (remoteMsid) {
2465
            if (!streams[remoteMsid.stream]) {
2466
              streams[remoteMsid.stream] = new window.MediaStream();
2467
              Object.defineProperty(streams[remoteMsid.stream], 'id', {
2468
                get: function() {
2469
                  return remoteMsid.stream;
2470
                }
2471
              });
2472
            }
2473
            Object.defineProperty(track, 'id', {
2474
              get: function() {
2475
                return remoteMsid.track;
2476
              }
2477
            });
2478
            streams[remoteMsid.stream].addTrack(track);
2479
            receiverList.push([track, rtpReceiver,
2480
              streams[remoteMsid.stream]]);
2481
          } else {
2482
            if (!streams.default) {
2483
              streams.default = new window.MediaStream();
2484
            }
2485
            streams.default.addTrack(track);
2486
            receiverList.push([track, rtpReceiver, streams.default]);
2487
          }
2488
        }
2489
2490
        transceiver.localCapabilities = localCapabilities;
2491
        transceiver.remoteCapabilities = remoteCapabilities;
2492
        transceiver.rtpReceiver = rtpReceiver;
2493
        transceiver.rtcpParameters = rtcpParameters;
2494
        transceiver.sendEncodingParameters = sendEncodingParameters;
2495
        transceiver.recvEncodingParameters = recvEncodingParameters;
2496
2497
        // Start the RTCRtpReceiver now. The RTPSender is started in
2498
        // setLocalDescription.
2499
        self._transceive(self.transceivers[sdpMLineIndex],
2500
            false,
2501
            direction === 'sendrecv' || direction === 'sendonly');
2502
      } else if (description.type === 'answer' && !rejected) {
2503
        transceiver = self.transceivers[sdpMLineIndex];
2504
        iceGatherer = transceiver.iceGatherer;
2505
        iceTransport = transceiver.iceTransport;
2506
        dtlsTransport = transceiver.dtlsTransport;
2507
        rtpReceiver = transceiver.rtpReceiver;
2508
        sendEncodingParameters = transceiver.sendEncodingParameters;
2509
        localCapabilities = transceiver.localCapabilities;
2510
2511
        self.transceivers[sdpMLineIndex].recvEncodingParameters =
2512
            recvEncodingParameters;
2513
        self.transceivers[sdpMLineIndex].remoteCapabilities =
2514
            remoteCapabilities;
2515
        self.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
2516
2517
        if (!usingBundle || sdpMLineIndex === 0) {
2518
          if ((isIceLite || isComplete) && cands.length) {
2519
            iceTransport.setRemoteCandidates(cands);
2520
          }
2521
          iceTransport.start(iceGatherer, remoteIceParameters,
2522
              'controlling');
2523
          dtlsTransport.start(remoteDtlsParameters);
2524
        }
2525
2526
        self._transceive(transceiver,
2527
            direction === 'sendrecv' || direction === 'recvonly',
2528
            direction === 'sendrecv' || direction === 'sendonly');
2529
2530
        if (rtpReceiver &&
2531
            (direction === 'sendrecv' || direction === 'sendonly')) {
2532
          track = rtpReceiver.track;
2533
          if (remoteMsid) {
2534
            if (!streams[remoteMsid.stream]) {
2535
              streams[remoteMsid.stream] = new window.MediaStream();
2536
            }
2537
            streams[remoteMsid.stream].addTrack(track);
2538
            receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);
2539
          } else {
2540
            if (!streams.default) {
2541
              streams.default = new window.MediaStream();
2542
            }
2543
            streams.default.addTrack(track);
2544
            receiverList.push([track, rtpReceiver, streams.default]);
2545
          }
2546
        } else {
2547
          // FIXME: actually the receiver should be created later.
2548
          delete transceiver.rtpReceiver;
2549
        }
2550
      }
2551
    });
2552
2553
    this.remoteDescription = {
2554
      type: description.type,
2555
      sdp: description.sdp
2556
    };
2557
    switch (description.type) {
2558
      case 'offer':
2559
        this._updateSignalingState('have-remote-offer');
2560
        break;
2561
      case 'answer':
2562
        this._updateSignalingState('stable');
2563
        break;
2564
      default:
2565
        throw new TypeError('unsupported type "' + description.type +
2566
            '"');
2567
    }
2568
    Object.keys(streams).forEach(function(sid) {
2569
      var stream = streams[sid];
2570
      if (stream.getTracks().length) {
2571
        self.remoteStreams.push(stream);
2572
        var event = new Event('addstream');
2573
        event.stream = stream;
2574
        self.dispatchEvent(event);
2575
        if (self.onaddstream !== null) {
2576
          window.setTimeout(function() {
2577
            self.onaddstream(event);
2578
          }, 0);
2579
        }
2580
2581
        receiverList.forEach(function(item) {
2582
          var track = item[0];
2583
          var receiver = item[1];
2584
          if (stream.id !== item[2].id) {
2585
            return;
2586
          }
2587
          var trackEvent = new Event('track');
2588
          trackEvent.track = track;
2589
          trackEvent.receiver = receiver;
2590
          trackEvent.streams = [stream];
2591
          self.dispatchEvent(trackEvent);
2592
          if (self.ontrack !== null) {
2593
            window.setTimeout(function() {
2594
              self.ontrack(trackEvent);
2595
            }, 0);
2596
          }
2597
        });
2598
      }
2599
    });
2600
2601
    // check whether addIceCandidate({}) was called within four seconds after
2602
    // setRemoteDescription.
2603
    window.setTimeout(function() {
2604
      if (!(self && self.transceivers)) {
2605
        return;
2606
      }
2607
      self.transceivers.forEach(function(transceiver) {
2608
        if (transceiver.iceTransport &&
2609
            transceiver.iceTransport.state === 'new' &&
2610
            transceiver.iceTransport.getRemoteCandidates().length > 0) {
2611
          console.warn('Timeout for addRemoteCandidate. Consider sending ' +
2612
              'an end-of-candidates notification');
2613
          transceiver.iceTransport.addRemoteCandidate({});
2614
        }
2615
      });
2616
    }, 4000);
2617
2618
    if (arguments.length > 1 && typeof arguments[1] === 'function') {
2619
      window.setTimeout(arguments[1], 0);
2620
    }
2621
    return Promise.resolve();
2622
  };
2623
2624
  RTCPeerConnection.prototype.close = function() {
2625
    this.transceivers.forEach(function(transceiver) {
2626
      /* not yet
2627
      if (transceiver.iceGatherer) {
2628
        transceiver.iceGatherer.close();
2629
      }
2630
      */
2631
      if (transceiver.iceTransport) {
2632
        transceiver.iceTransport.stop();
2633
      }
2634
      if (transceiver.dtlsTransport) {
2635
        transceiver.dtlsTransport.stop();
2636
      }
2637
      if (transceiver.rtpSender) {
2638
        transceiver.rtpSender.stop();
2639
      }
2640
      if (transceiver.rtpReceiver) {
2641
        transceiver.rtpReceiver.stop();
2642
      }
2643
    });
2644
    // FIXME: clean up tracks, local streams, remote streams, etc
2645
    this._updateSignalingState('closed');
2646
  };
2647
2648
  // Update the signaling state.
2649
  RTCPeerConnection.prototype._updateSignalingState = function(newState) {
2650
    this.signalingState = newState;
2651
    var event = new Event('signalingstatechange');
2652
    this.dispatchEvent(event);
2653
    if (this.onsignalingstatechange !== null) {
2654
      this.onsignalingstatechange(event);
2655
    }
2656
  };
2657
2658
  // Determine whether to fire the negotiationneeded event.
2659
  RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
2660
    var self = this;
2661
    if (this.signalingState !== 'stable' || this.needNegotiation === true) {
2662
      return;
2663
    }
2664
    this.needNegotiation = true;
2665
    window.setTimeout(function() {
2666
      if (self.needNegotiation === false) {
2667
        return;
2668
      }
2669
      self.needNegotiation = false;
2670
      var event = new Event('negotiationneeded');
2671
      self.dispatchEvent(event);
2672
      if (self.onnegotiationneeded !== null) {
2673
        self.onnegotiationneeded(event);
2674
      }
2675
    }, 0);
2676
  };
2677
2678
  // Update the connection state.
2679
  RTCPeerConnection.prototype._updateConnectionState = function() {
2680
    var self = this;
2681
    var newState;
2682
    var states = {
2683
      'new': 0,
2684
      closed: 0,
2685
      connecting: 0,
2686
      checking: 0,
2687
      connected: 0,
2688
      completed: 0,
2689
      disconnected: 0,
2690
      failed: 0
2691
    };
2692
    this.transceivers.forEach(function(transceiver) {
2693
      states[transceiver.iceTransport.state]++;
2694
      states[transceiver.dtlsTransport.state]++;
2695
    });
2696
    // ICETransport.completed and connected are the same for this purpose.
2697
    states.connected += states.completed;
2698
2699
    newState = 'new';
2700
    if (states.failed > 0) {
2701
      newState = 'failed';
2702
    } else if (states.connecting > 0 || states.checking > 0) {
2703
      newState = 'connecting';
2704
    } else if (states.disconnected > 0) {
2705
      newState = 'disconnected';
2706
    } else if (states.new > 0) {
2707
      newState = 'new';
2708
    } else if (states.connected > 0 || states.completed > 0) {
2709
      newState = 'connected';
2710
    }
2711
2712
    if (newState !== self.iceConnectionState) {
2713
      self.iceConnectionState = newState;
2714
      var event = new Event('iceconnectionstatechange');
2715
      this.dispatchEvent(event);
2716
      if (this.oniceconnectionstatechange !== null) {
2717
        this.oniceconnectionstatechange(event);
2718
      }
2719
    }
2720
  };
2721
2722
  RTCPeerConnection.prototype.createOffer = function() {
2723
    var self = this;
2724
    if (this._pendingOffer) {
2725
      throw new Error('createOffer called while there is a pending offer.');
2726
    }
2727
    var offerOptions;
2728
    if (arguments.length === 1 && typeof arguments[0] !== 'function') {
2729
      offerOptions = arguments[0];
2730
    } else if (arguments.length === 3) {
2731
      offerOptions = arguments[2];
2732
    }
2733
2734
    var numAudioTracks = this.transceivers.filter(function(t) {
2735
      return t.kind === 'audio';
2736
    }).length;
2737
    var numVideoTracks = this.transceivers.filter(function(t) {
2738
      return t.kind === 'video';
2739
    }).length;
2740
2741
    // Determine number of audio and video tracks we need to send/recv.
2742
    if (offerOptions) {
2743
      // Reject Chrome legacy constraints.
2744
      if (offerOptions.mandatory || offerOptions.optional) {
2745
        throw new TypeError(
2746
            'Legacy mandatory/optional constraints not supported.');
2747
      }
2748
      if (offerOptions.offerToReceiveAudio !== undefined) {
2749
        if (offerOptions.offerToReceiveAudio === true) {
2750
          numAudioTracks = 1;
2751
        } else if (offerOptions.offerToReceiveAudio === false) {
2752
          numAudioTracks = 0;
2753
        } else {
2754
          numAudioTracks = offerOptions.offerToReceiveAudio;
2755
        }
2756
      }
2757
      if (offerOptions.offerToReceiveVideo !== undefined) {
2758
        if (offerOptions.offerToReceiveVideo === true) {
2759
          numVideoTracks = 1;
2760
        } else if (offerOptions.offerToReceiveVideo === false) {
2761
          numVideoTracks = 0;
2762
        } else {
2763
          numVideoTracks = offerOptions.offerToReceiveVideo;
2764
        }
2765
      }
2766
    }
2767
2768
    this.transceivers.forEach(function(transceiver) {
2769
      if (transceiver.kind === 'audio') {
2770
        numAudioTracks--;
2771
        if (numAudioTracks < 0) {
2772
          transceiver.wantReceive = false;
2773
        }
2774
      } else if (transceiver.kind === 'video') {
2775
        numVideoTracks--;
2776
        if (numVideoTracks < 0) {
2777
          transceiver.wantReceive = false;
2778
        }
2779
      }
2780
    });
2781
2782
    // Create M-lines for recvonly streams.
2783
    while (numAudioTracks > 0 || numVideoTracks > 0) {
2784
      if (numAudioTracks > 0) {
2785
        this._createTransceiver('audio');
2786
        numAudioTracks--;
2787
      }
2788
      if (numVideoTracks > 0) {
2789
        this._createTransceiver('video');
2790
        numVideoTracks--;
2791
      }
2792
    }
2793
    // reorder tracks
2794
    var transceivers = sortTracks(this.transceivers);
2795
2796
    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId);
2797
    transceivers.forEach(function(transceiver, sdpMLineIndex) {
2798
      // For each track, create an ice gatherer, ice transport,
2799
      // dtls transport, potentially rtpsender and rtpreceiver.
2800
      var track = transceiver.track;
2801
      var kind = transceiver.kind;
2802
      var mid = SDPUtils.generateIdentifier();
2803
      transceiver.mid = mid;
2804
2805
      if (!transceiver.iceGatherer) {
2806
        transceiver.iceGatherer = self.usingBundle && sdpMLineIndex > 0 ?
2807
            transceivers[0].iceGatherer :
2808
            self._createIceGatherer(mid, sdpMLineIndex);
2809
      }
2810
2811
      var localCapabilities = window.RTCRtpSender.getCapabilities(kind);
2812
      // filter RTX until additional stuff needed for RTX is implemented
2813
      // in adapter.js
2814
      if (edgeVersion < 15019) {
2815
        localCapabilities.codecs = localCapabilities.codecs.filter(
2816
            function(codec) {
2817
              return codec.name !== 'rtx';
2818
            });
2819
      }
2820
      localCapabilities.codecs.forEach(function(codec) {
2821
        // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
2822
        // by adding level-asymmetry-allowed=1
2823
        if (codec.name === 'H264' &&
2824
            codec.parameters['level-asymmetry-allowed'] === undefined) {
2825
          codec.parameters['level-asymmetry-allowed'] = '1';
2826
        }
2827
      });
2828
2829
      // generate an ssrc now, to be used later in rtpSender.send
2830
      var sendEncodingParameters = [{
2831
        ssrc: (2 * sdpMLineIndex + 1) * 1001
2832
      }];
2833
      if (track) {
2834
        // add RTX
2835
        if (edgeVersion >= 15019 && kind === 'video') {
2836
          sendEncodingParameters[0].rtx = {
2837
            ssrc: (2 * sdpMLineIndex + 1) * 1001 + 1
2838
          };
2839
        }
2840
      }
2841
2842
      if (transceiver.wantReceive) {
2843
        transceiver.rtpReceiver = new window.RTCRtpReceiver(
2844
          transceiver.dtlsTransport,
2845
          kind
2846
        );
2847
      }
2848
2849
      transceiver.localCapabilities = localCapabilities;
2850
      transceiver.sendEncodingParameters = sendEncodingParameters;
2851
    });
2852
2853
    // always offer BUNDLE and dispose on return if not supported.
2854
    if (this._config.bundlePolicy !== 'max-compat') {
2855
      sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) {
2856
        return t.mid;
2857
      }).join(' ') + '\r\n';
2858
    }
2859
    sdp += 'a=ice-options:trickle\r\n';
2860
2861
    transceivers.forEach(function(transceiver, sdpMLineIndex) {
2862
      sdp += SDPUtils.writeMediaSection(transceiver,
2863
          transceiver.localCapabilities, 'offer', transceiver.stream);
2864
      sdp += 'a=rtcp-rsize\r\n';
2865
    });
2866
2867
    this._pendingOffer = transceivers;
2868
    var desc = new window.RTCSessionDescription({
2869
      type: 'offer',
2870
      sdp: sdp
2871
    });
2872
    if (arguments.length && typeof arguments[0] === 'function') {
2873
      window.setTimeout(arguments[0], 0, desc);
2874
    }
2875
    return Promise.resolve(desc);
2876
  };
2877
2878
  RTCPeerConnection.prototype.createAnswer = function() {
2879
    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId);
2880
    if (this.usingBundle) {
2881
      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
2882
        return t.mid;
2883
      }).join(' ') + '\r\n';
2884
    }
2885
    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
2886
      if (transceiver.isDatachannel) {
2887
        sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
2888
            'c=IN IP4 0.0.0.0\r\n' +
2889
            'a=mid:' + transceiver.mid + '\r\n';
2890
        return;
2891
      }
2892
2893
      // FIXME: look at direction.
2894
      if (transceiver.stream) {
2895
        var localTrack;
2896
        if (transceiver.kind === 'audio') {
2897
          localTrack = transceiver.stream.getAudioTracks()[0];
2898
        } else if (transceiver.kind === 'video') {
2899
          localTrack = transceiver.stream.getVideoTracks()[0];
2900
        }
2901
        if (localTrack) {
2902
          // add RTX
2903
          if (edgeVersion >= 15019 && transceiver.kind === 'video') {
2904
            transceiver.sendEncodingParameters[0].rtx = {
2905
              ssrc: (2 * sdpMLineIndex + 2) * 1001 + 1
2906
            };
2907
          }
2908
        }
2909
      }
2910
2911
      // Calculate intersection of capabilities.
2912
      var commonCapabilities = getCommonCapabilities(
2913
          transceiver.localCapabilities,
2914
          transceiver.remoteCapabilities);
2915
2916
      var hasRtx = commonCapabilities.codecs.filter(function(c) {
2917
        return c.name.toLowerCase() === 'rtx';
2918
      }).length;
2919
      if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
2920
        delete transceiver.sendEncodingParameters[0].rtx;
2921
      }
2922
2923
      sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
2924
          'answer', transceiver.stream);
2925
      if (transceiver.rtcpParameters &&
2926
          transceiver.rtcpParameters.reducedSize) {
2927
        sdp += 'a=rtcp-rsize\r\n';
2928
      }
2929
    });
2930
2931
    var desc = new window.RTCSessionDescription({
2932
      type: 'answer',
2933
      sdp: sdp
2934
    });
2935
    if (arguments.length && typeof arguments[0] === 'function') {
2936
      window.setTimeout(arguments[0], 0, desc);
2937
    }
2938
    return Promise.resolve(desc);
2939
  };
2940
2941
  RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
2942
    if (!candidate) {
2943
      for (var j = 0; j < this.transceivers.length; j++) {
2944
        this.transceivers[j].iceTransport.addRemoteCandidate({});
2945
        if (this.usingBundle) {
2946
          return Promise.resolve();
2947
        }
2948
      }
2949
    } else {
2950
      var mLineIndex = candidate.sdpMLineIndex;
2951
      if (candidate.sdpMid) {
2952
        for (var i = 0; i < this.transceivers.length; i++) {
2953
          if (this.transceivers[i].mid === candidate.sdpMid) {
2954
            mLineIndex = i;
2955
            break;
2956
          }
2957
        }
2958
      }
2959
      var transceiver = this.transceivers[mLineIndex];
2960
      if (transceiver) {
2961
        var cand = Object.keys(candidate.candidate).length > 0 ?
2962
            SDPUtils.parseCandidate(candidate.candidate) : {};
2963
        // Ignore Chrome's invalid candidates since Edge does not like them.
2964
        if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
2965
          return Promise.resolve();
2966
        }
2967
        // Ignore RTCP candidates, we assume RTCP-MUX.
2968
        if (cand.component &&
2969
            !(cand.component === '1' || cand.component === 1)) {
2970
          return Promise.resolve();
2971
        }
2972
        transceiver.iceTransport.addRemoteCandidate(cand);
2973
2974
        // update the remoteDescription.
2975
        var sections = SDPUtils.splitSections(this.remoteDescription.sdp);
2976
        sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim()
2977
            : 'a=end-of-candidates') + '\r\n';
2978
        this.remoteDescription.sdp = sections.join('');
2979
      }
2980
    }
2981
    if (arguments.length > 1 && typeof arguments[1] === 'function') {
2982
      window.setTimeout(arguments[1], 0);
2983
    }
2984
    return Promise.resolve();
2985
  };
2986
2987
  RTCPeerConnection.prototype.getStats = function() {
2988
    var promises = [];
2989
    this.transceivers.forEach(function(transceiver) {
2990
      ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
2991
        'dtlsTransport'].forEach(function(method) {
2992
          if (transceiver[method]) {
2993
            promises.push(transceiver[method].getStats());
2994
          }
2995
        });
2996
    });
2997
    var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
2998
        arguments[1];
2999
    var fixStatsType = function(stat) {
3000
      return {
3001
        inboundrtp: 'inbound-rtp',
3002
        outboundrtp: 'outbound-rtp',
3003
        candidatepair: 'candidate-pair',
3004
        localcandidate: 'local-candidate',
3005
        remotecandidate: 'remote-candidate'
3006
      }[stat.type] || stat.type;
3007
    };
3008
    return new Promise(function(resolve) {
3009
      // shim getStats with maplike support
3010
      var results = new Map();
3011
      Promise.all(promises).then(function(res) {
3012
        res.forEach(function(result) {
3013
          Object.keys(result).forEach(function(id) {
3014
            result[id].type = fixStatsType(result[id]);
3015
            results.set(id, result[id]);
3016
          });
3017
        });
3018
        if (cb) {
3019
          window.setTimeout(cb, 0, results);
3020
        }
3021
        resolve(results);
3022
      });
3023
    });
3024
  };
3025
  return RTCPeerConnection;
3026
};
3027
3028
},{"sdp":1}],9:[function(require,module,exports){
3029
/*
3030
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3031
 *
3032
 *  Use of this source code is governed by a BSD-style license
3033
 *  that can be found in the LICENSE file in the root of the source
3034
 *  tree.
3035
 */
3036
 /* eslint-env node */
3037
'use strict';
3038
3039
var utils = require('../utils');
3040
3041
var firefoxShim = {
3042
  shimOnTrack: function(window) {
3043
    if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
3044
        window.RTCPeerConnection.prototype)) {
3045
      Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
3046
        get: function() {
3047
          return this._ontrack;
3048
        },
3049
        set: function(f) {
3050
          if (this._ontrack) {
3051
            this.removeEventListener('track', this._ontrack);
3052
            this.removeEventListener('addstream', this._ontrackpoly);
3053
          }
3054
          this.addEventListener('track', this._ontrack = f);
3055
          this.addEventListener('addstream', this._ontrackpoly = function(e) {
3056
            e.stream.getTracks().forEach(function(track) {
3057
              var event = new Event('track');
3058
              event.track = track;
3059
              event.receiver = {track: track};
3060
              event.streams = [e.stream];
3061
              this.dispatchEvent(event);
3062
            }.bind(this));
3063
          }.bind(this));
3064
        }
3065
      });
3066
    }
3067
  },
3068
3069
  shimSourceObject: function(window) {
3070
    // Firefox has supported mozSrcObject since FF22, unprefixed in 42.
3071
    if (typeof window === 'object') {
3072
      if (window.HTMLMediaElement &&
3073
        !('srcObject' in window.HTMLMediaElement.prototype)) {
3074
        // Shim the srcObject property, once, when HTMLMediaElement is found.
3075
        Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
3076
          get: function() {
3077
            return this.mozSrcObject;
3078
          },
3079
          set: function(stream) {
3080
            this.mozSrcObject = stream;
3081
          }
3082
        });
3083
      }
3084
    }
3085
  },
3086
3087
  shimPeerConnection: function(window) {
3088
    var browserDetails = utils.detectBrowser(window);
3089
3090
    if (typeof window !== 'object' || !(window.RTCPeerConnection ||
3091
        window.mozRTCPeerConnection)) {
3092
      return; // probably media.peerconnection.enabled=false in about:config
3093
    }
3094
    // The RTCPeerConnection object.
3095
    if (!window.RTCPeerConnection) {
3096
      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
3097
        if (browserDetails.version < 38) {
3098
          // .urls is not supported in FF < 38.
3099
          // create RTCIceServers with a single url.
3100
          if (pcConfig && pcConfig.iceServers) {
3101
            var newIceServers = [];
3102
            for (var i = 0; i < pcConfig.iceServers.length; i++) {
3103
              var server = pcConfig.iceServers[i];
3104
              if (server.hasOwnProperty('urls')) {
3105
                for (var j = 0; j < server.urls.length; j++) {
3106
                  var newServer = {
3107
                    url: server.urls[j]
3108
                  };
3109
                  if (server.urls[j].indexOf('turn') === 0) {
3110
                    newServer.username = server.username;
3111
                    newServer.credential = server.credential;
3112
                  }
3113
                  newIceServers.push(newServer);
3114
                }
3115
              } else {
3116
                newIceServers.push(pcConfig.iceServers[i]);
3117
              }
3118
            }
3119
            pcConfig.iceServers = newIceServers;
3120
          }
3121
        }
3122
        return new window.mozRTCPeerConnection(pcConfig, pcConstraints);
3123
      };
3124
      window.RTCPeerConnection.prototype =
3125
          window.mozRTCPeerConnection.prototype;
3126
3127
      // wrap static methods. Currently just generateCertificate.
3128
      if (window.mozRTCPeerConnection.generateCertificate) {
3129
        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
3130
          get: function() {
3131
            return window.mozRTCPeerConnection.generateCertificate;
3132
          }
3133
        });
3134
      }
3135
3136
      window.RTCSessionDescription = window.mozRTCSessionDescription;
3137
      window.RTCIceCandidate = window.mozRTCIceCandidate;
3138
    }
3139
3140
    // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
3141
    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
3142
        .forEach(function(method) {
3143
          var nativeMethod = window.RTCPeerConnection.prototype[method];
3144
          window.RTCPeerConnection.prototype[method] = function() {
3145
            arguments[0] = new ((method === 'addIceCandidate') ?
3146
                window.RTCIceCandidate :
3147
                window.RTCSessionDescription)(arguments[0]);
3148
            return nativeMethod.apply(this, arguments);
3149
          };
3150
        });
3151
3152
    // support for addIceCandidate(null or undefined)
3153
    var nativeAddIceCandidate =
3154
        window.RTCPeerConnection.prototype.addIceCandidate;
3155
    window.RTCPeerConnection.prototype.addIceCandidate = function() {
3156
      if (!arguments[0]) {
3157
        if (arguments[1]) {
3158
          arguments[1].apply(null);
3159
        }
3160
        return Promise.resolve();
3161
      }
3162
      return nativeAddIceCandidate.apply(this, arguments);
3163
    };
3164
3165
    // shim getStats with maplike support
3166
    var makeMapStats = function(stats) {
3167
      var map = new Map();
3168
      Object.keys(stats).forEach(function(key) {
3169
        map.set(key, stats[key]);
3170
        map[key] = stats[key];
3171
      });
3172
      return map;
3173
    };
3174
3175
    var modernStatsTypes = {
3176
      inboundrtp: 'inbound-rtp',
3177
      outboundrtp: 'outbound-rtp',
3178
      candidatepair: 'candidate-pair',
3179
      localcandidate: 'local-candidate',
3180
      remotecandidate: 'remote-candidate'
3181
    };
3182
3183
    var nativeGetStats = window.RTCPeerConnection.prototype.getStats;
3184
    window.RTCPeerConnection.prototype.getStats = function(
3185
      selector,
3186
      onSucc,
3187
      onErr
3188
    ) {
3189
      return nativeGetStats.apply(this, [selector || null])
3190
        .then(function(stats) {
3191
          if (browserDetails.version < 48) {
3192
            stats = makeMapStats(stats);
3193
          }
3194
          if (browserDetails.version < 53 && !onSucc) {
3195
            // Shim only promise getStats with spec-hyphens in type names
3196
            // Leave callback version alone; misc old uses of forEach before Map
3197
            try {
3198
              stats.forEach(function(stat) {
3199
                stat.type = modernStatsTypes[stat.type] || stat.type;
3200
              });
3201
            } catch (e) {
3202
              if (e.name !== 'TypeError') {
3203
                throw e;
3204
              }
3205
              // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
3206
              stats.forEach(function(stat, i) {
3207
                stats.set(i, Object.assign({}, stat, {
3208
                  type: modernStatsTypes[stat.type] || stat.type
3209
                }));
3210
              });
3211
            }
3212
          }
3213
          return stats;
3214
        })
3215
        .then(onSucc, onErr);
3216
    };
3217
  }
3218
};
3219
3220
// Expose public methods.
3221
module.exports = {
3222
  shimOnTrack: firefoxShim.shimOnTrack,
3223
  shimSourceObject: firefoxShim.shimSourceObject,
3224
  shimPeerConnection: firefoxShim.shimPeerConnection,
3225
  shimGetUserMedia: require('./getusermedia')
3226
};
3227
3228
},{"../utils":12,"./getusermedia":10}],10:[function(require,module,exports){
3229
/*
3230
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3231
 *
3232
 *  Use of this source code is governed by a BSD-style license
3233
 *  that can be found in the LICENSE file in the root of the source
3234
 *  tree.
3235
 */
3236
 /* eslint-env node */
3237
'use strict';
3238
3239
var utils = require('../utils');
3240
var logging = utils.log;
3241
3242
// Expose public methods.
3243
module.exports = function(window) {
3244
  var browserDetails = utils.detectBrowser(window);
3245
  var navigator = window && window.navigator;
3246
  var MediaStreamTrack = window && window.MediaStreamTrack;
3247
3248
  var shimError_ = function(e) {
3249
    return {
3250
      name: {
3251
        InternalError: 'NotReadableError',
3252
        NotSupportedError: 'TypeError',
3253
        PermissionDeniedError: 'NotAllowedError',
3254
        SecurityError: 'NotAllowedError'
3255
      }[e.name] || e.name,
3256
      message: {
3257
        'The operation is insecure.': 'The request is not allowed by the ' +
3258
        'user agent or the platform in the current context.'
3259
      }[e.message] || e.message,
3260
      constraint: e.constraint,
3261
      toString: function() {
3262
        return this.name + (this.message && ': ') + this.message;
3263
      }
3264
    };
3265
  };
3266
3267
  // getUserMedia constraints shim.
3268
  var getUserMedia_ = function(constraints, onSuccess, onError) {
3269
    var constraintsToFF37_ = function(c) {
3270
      if (typeof c !== 'object' || c.require) {
3271
        return c;
3272
      }
3273
      var require = [];
3274
      Object.keys(c).forEach(function(key) {
3275
        if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
3276
          return;
3277
        }
3278
        var r = c[key] = (typeof c[key] === 'object') ?
3279
            c[key] : {ideal: c[key]};
3280
        if (r.min !== undefined ||
3281
            r.max !== undefined || r.exact !== undefined) {
3282
          require.push(key);
3283
        }
3284
        if (r.exact !== undefined) {
3285
          if (typeof r.exact === 'number') {
3286
            r. min = r.max = r.exact;
3287
          } else {
3288
            c[key] = r.exact;
3289
          }
3290
          delete r.exact;
3291
        }
3292
        if (r.ideal !== undefined) {
3293
          c.advanced = c.advanced || [];
3294
          var oc = {};
3295
          if (typeof r.ideal === 'number') {
3296
            oc[key] = {min: r.ideal, max: r.ideal};
3297
          } else {
3298
            oc[key] = r.ideal;
3299
          }
3300
          c.advanced.push(oc);
3301
          delete r.ideal;
3302
          if (!Object.keys(r).length) {
3303
            delete c[key];
3304
          }
3305
        }
3306
      });
3307
      if (require.length) {
3308
        c.require = require;
3309
      }
3310
      return c;
3311
    };
3312
    constraints = JSON.parse(JSON.stringify(constraints));
3313
    if (browserDetails.version < 38) {
3314
      logging('spec: ' + JSON.stringify(constraints));
3315
      if (constraints.audio) {
3316
        constraints.audio = constraintsToFF37_(constraints.audio);
3317
      }
3318
      if (constraints.video) {
3319
        constraints.video = constraintsToFF37_(constraints.video);
3320
      }
3321
      logging('ff37: ' + JSON.stringify(constraints));
3322
    }
3323
    return navigator.mozGetUserMedia(constraints, onSuccess, function(e) {
3324
      onError(shimError_(e));
3325
    });
3326
  };
3327
3328
  // Returns the result of getUserMedia as a Promise.
3329
  var getUserMediaPromise_ = function(constraints) {
3330
    return new Promise(function(resolve, reject) {
3331
      getUserMedia_(constraints, resolve, reject);
3332
    });
3333
  };
3334
3335
  // Shim for mediaDevices on older versions.
3336
  if (!navigator.mediaDevices) {
3337
    navigator.mediaDevices = {getUserMedia: getUserMediaPromise_,
3338
      addEventListener: function() { },
3339
      removeEventListener: function() { }
3340
    };
3341
  }
3342
  navigator.mediaDevices.enumerateDevices =
3343
      navigator.mediaDevices.enumerateDevices || function() {
3344
        return new Promise(function(resolve) {
3345
          var infos = [
3346
            {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
3347
            {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
3348
          ];
3349
          resolve(infos);
3350
        });
3351
      };
3352
3353
  if (browserDetails.version < 41) {
3354
    // Work around http://bugzil.la/1169665
3355
    var orgEnumerateDevices =
3356
        navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
3357
    navigator.mediaDevices.enumerateDevices = function() {
3358
      return orgEnumerateDevices().then(undefined, function(e) {
3359
        if (e.name === 'NotFoundError') {
3360
          return [];
3361
        }
3362
        throw e;
3363
      });
3364
    };
3365
  }
3366
  if (browserDetails.version < 49) {
3367
    var origGetUserMedia = navigator.mediaDevices.getUserMedia.
3368
        bind(navigator.mediaDevices);
3369
    navigator.mediaDevices.getUserMedia = function(c) {
3370
      return origGetUserMedia(c).then(function(stream) {
3371
        // Work around https://bugzil.la/802326
3372
        if (c.audio && !stream.getAudioTracks().length ||
3373
            c.video && !stream.getVideoTracks().length) {
3374
          stream.getTracks().forEach(function(track) {
3375
            track.stop();
3376
          });
3377
          throw new DOMException('The object can not be found here.',
3378
                                 'NotFoundError');
3379
        }
3380
        return stream;
3381
      }, function(e) {
3382
        return Promise.reject(shimError_(e));
3383
      });
3384
    };
3385
  }
3386
  if (!(browserDetails.version > 55 &&
3387
      'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {
3388
    var remap = function(obj, a, b) {
3389
      if (a in obj && !(b in obj)) {
3390
        obj[b] = obj[a];
3391
        delete obj[a];
3392
      }
3393
    };
3394
3395
    var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.
3396
        bind(navigator.mediaDevices);
3397
    navigator.mediaDevices.getUserMedia = function(c) {
3398
      if (typeof c === 'object' && typeof c.audio === 'object') {
3399
        c = JSON.parse(JSON.stringify(c));
3400
        remap(c.audio, 'autoGainControl', 'mozAutoGainControl');
3401
        remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');
3402
      }
3403
      return nativeGetUserMedia(c);
3404
    };
3405
3406
    if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {
3407
      var nativeGetSettings = MediaStreamTrack.prototype.getSettings;
3408
      MediaStreamTrack.prototype.getSettings = function() {
3409
        var obj = nativeGetSettings.apply(this, arguments);
3410
        remap(obj, 'mozAutoGainControl', 'autoGainControl');
3411
        remap(obj, 'mozNoiseSuppression', 'noiseSuppression');
3412
        return obj;
3413
      };
3414
    }
3415
3416
    if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {
3417
      var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints;
3418
      MediaStreamTrack.prototype.applyConstraints = function(c) {
3419
        if (this.kind === 'audio' && typeof c === 'object') {
3420
          c = JSON.parse(JSON.stringify(c));
3421
          remap(c, 'autoGainControl', 'mozAutoGainControl');
3422
          remap(c, 'noiseSuppression', 'mozNoiseSuppression');
3423
        }
3424
        return nativeApplyConstraints.apply(this, [c]);
3425
      };
3426
    }
3427
  }
3428
  navigator.getUserMedia = function(constraints, onSuccess, onError) {
3429
    if (browserDetails.version < 44) {
3430
      return getUserMedia_(constraints, onSuccess, onError);
3431
    }
3432
    // Replace Firefox 44+'s deprecation warning with unprefixed version.
3433
    console.warn('navigator.getUserMedia has been replaced by ' +
3434
                 'navigator.mediaDevices.getUserMedia');
3435
    navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
3436
  };
3437
};
3438
3439
},{"../utils":12}],11:[function(require,module,exports){
3440
/*
3441
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3442
 *
3443
 *  Use of this source code is governed by a BSD-style license
3444
 *  that can be found in the LICENSE file in the root of the source
3445
 *  tree.
3446
 */
3447
'use strict';
3448
var utils = require('../utils');
3449
3450
var safariShim = {
3451
  // TODO: DrAlex, should be here, double check against LayoutTests
3452
3453
  // TODO: once the back-end for the mac port is done, add.
3454
  // TODO: check for webkitGTK+
3455
  // shimPeerConnection: function() { },
3456
3457
  shimLocalStreamsAPI: function(window) {
3458
    if (typeof window !== 'object' || !window.RTCPeerConnection) {
3459
      return;
3460
    }
3461
    if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {
3462
      window.RTCPeerConnection.prototype.getLocalStreams = function() {
3463
        if (!this._localStreams) {
3464
          this._localStreams = [];
3465
        }
3466
        return this._localStreams;
3467
      };
3468
    }
3469
    if (!('getStreamById' in window.RTCPeerConnection.prototype)) {
3470
      window.RTCPeerConnection.prototype.getStreamById = function(id) {
3471
        var result = null;
3472
        if (this._localStreams) {
3473
          this._localStreams.forEach(function(stream) {
3474
            if (stream.id === id) {
3475
              result = stream;
3476
            }
3477
          });
3478
        }
3479
        if (this._remoteStreams) {
3480
          this._remoteStreams.forEach(function(stream) {
3481
            if (stream.id === id) {
3482
              result = stream;
3483
            }
3484
          });
3485
        }
3486
        return result;
3487
      };
3488
    }
3489
    if (!('addStream' in window.RTCPeerConnection.prototype)) {
3490
      var _addTrack = window.RTCPeerConnection.prototype.addTrack;
3491
      window.RTCPeerConnection.prototype.addStream = function(stream) {
3492
        if (!this._localStreams) {
3493
          this._localStreams = [];
3494
        }
3495
        if (this._localStreams.indexOf(stream) === -1) {
3496
          this._localStreams.push(stream);
3497
        }
3498
        var self = this;
3499
        stream.getTracks().forEach(function(track) {
3500
          _addTrack.call(self, track, stream);
3501
        });
3502
      };
3503
3504
      window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
3505
        if (stream) {
3506
          if (!this._localStreams) {
3507
            this._localStreams = [stream];
3508
          } else if (this._localStreams.indexOf(stream) === -1) {
3509
            this._localStreams.push(stream);
3510
          }
3511
        }
3512
        _addTrack.call(this, track, stream);
3513
      };
3514
    }
3515
    if (!('removeStream' in window.RTCPeerConnection.prototype)) {
3516
      window.RTCPeerConnection.prototype.removeStream = function(stream) {
3517
        if (!this._localStreams) {
3518
          this._localStreams = [];
3519
        }
3520
        var index = this._localStreams.indexOf(stream);
3521
        if (index === -1) {
3522
          return;
3523
        }
3524
        this._localStreams.splice(index, 1);
3525
        var self = this;
3526
        var tracks = stream.getTracks();
3527
        this.getSenders().forEach(function(sender) {
3528
          if (tracks.indexOf(sender.track) !== -1) {
3529
            self.removeTrack(sender);
3530
          }
3531
        });
3532
      };
3533
    }
3534
  },
3535
  shimRemoteStreamsAPI: function(window) {
3536
    if (typeof window !== 'object' || !window.RTCPeerConnection) {
3537
      return;
3538
    }
3539
    if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
3540
      window.RTCPeerConnection.prototype.getRemoteStreams = function() {
3541
        return this._remoteStreams ? this._remoteStreams : [];
3542
      };
3543
    }
3544
    if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
3545
      Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
3546
        get: function() {
3547
          return this._onaddstream;
3548
        },
3549
        set: function(f) {
3550
          if (this._onaddstream) {
3551
            this.removeEventListener('addstream', this._onaddstream);
3552
            this.removeEventListener('track', this._onaddstreampoly);
3553
          }
3554
          this.addEventListener('addstream', this._onaddstream = f);
3555
          this.addEventListener('track', this._onaddstreampoly = function(e) {
3556
            var stream = e.streams[0];
3557
            if (!this._remoteStreams) {
3558
              this._remoteStreams = [];
3559
            }
3560
            if (this._remoteStreams.indexOf(stream) >= 0) {
3561
              return;
3562
            }
3563
            this._remoteStreams.push(stream);
3564
            var event = new Event('addstream');
3565
            event.stream = e.streams[0];
3566
            this.dispatchEvent(event);
3567
          }.bind(this));
3568
        }
3569
      });
3570
    }
3571
  },
3572
  shimCallbacksAPI: function(window) {
3573
    if (typeof window !== 'object' || !window.RTCPeerConnection) {
3574
      return;
3575
    }
3576
    var prototype = window.RTCPeerConnection.prototype;
3577
    var createOffer = prototype.createOffer;
3578
    var createAnswer = prototype.createAnswer;
3579
    var setLocalDescription = prototype.setLocalDescription;
3580
    var setRemoteDescription = prototype.setRemoteDescription;
3581
    var addIceCandidate = prototype.addIceCandidate;
3582
3583
    prototype.createOffer = function(successCallback, failureCallback) {
3584
      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
3585
      var promise = createOffer.apply(this, [options]);
3586
      if (!failureCallback) {
3587
        return promise;
3588
      }
3589
      promise.then(successCallback, failureCallback);
3590
      return Promise.resolve();
3591
    };
3592
3593
    prototype.createAnswer = function(successCallback, failureCallback) {
3594
      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
3595
      var promise = createAnswer.apply(this, [options]);
3596
      if (!failureCallback) {
3597
        return promise;
3598
      }
3599
      promise.then(successCallback, failureCallback);
3600
      return Promise.resolve();
3601
    };
3602
3603
    var withCallback = function(description, successCallback, failureCallback) {
3604
      var promise = setLocalDescription.apply(this, [description]);
3605
      if (!failureCallback) {
3606
        return promise;
3607
      }
3608
      promise.then(successCallback, failureCallback);
3609
      return Promise.resolve();
3610
    };
3611
    prototype.setLocalDescription = withCallback;
3612
3613
    withCallback = function(description, successCallback, failureCallback) {
3614
      var promise = setRemoteDescription.apply(this, [description]);
3615
      if (!failureCallback) {
3616
        return promise;
3617
      }
3618
      promise.then(successCallback, failureCallback);
3619
      return Promise.resolve();
3620
    };
3621
    prototype.setRemoteDescription = withCallback;
3622
3623
    withCallback = function(candidate, successCallback, failureCallback) {
3624
      var promise = addIceCandidate.apply(this, [candidate]);
3625
      if (!failureCallback) {
3626
        return promise;
3627
      }
3628
      promise.then(successCallback, failureCallback);
3629
      return Promise.resolve();
3630
    };
3631
    prototype.addIceCandidate = withCallback;
3632
  },
3633
  shimGetUserMedia: function(window) {
3634
    var navigator = window && window.navigator;
3635
3636
    if (!navigator.getUserMedia) {
3637
      if (navigator.webkitGetUserMedia) {
3638
        navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
3639
      } else if (navigator.mediaDevices &&
3640
          navigator.mediaDevices.getUserMedia) {
3641
        navigator.getUserMedia = function(constraints, cb, errcb) {
3642
          navigator.mediaDevices.getUserMedia(constraints)
3643
          .then(cb, errcb);
3644
        }.bind(navigator);
3645
      }
3646
    }
3647
  },
3648
  shimRTCIceServerUrls: function(window) {
3649
    // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
3650
    var OrigPeerConnection = window.RTCPeerConnection;
3651
    window.RTCPeerConnection = function(pcConfig, pcConstraints) {
3652
      if (pcConfig && pcConfig.iceServers) {
3653
        var newIceServers = [];
3654
        for (var i = 0; i < pcConfig.iceServers.length; i++) {
3655
          var server = pcConfig.iceServers[i];
3656
          if (!server.hasOwnProperty('urls') &&
3657
              server.hasOwnProperty('url')) {
3658
            utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
3659
            server = JSON.parse(JSON.stringify(server));
3660
            server.urls = server.url;
3661
            delete server.url;
3662
            newIceServers.push(server);
3663
          } else {
3664
            newIceServers.push(pcConfig.iceServers[i]);
3665
          }
3666
        }
3667
        pcConfig.iceServers = newIceServers;
3668
      }
3669
      return new OrigPeerConnection(pcConfig, pcConstraints);
3670
    };
3671
    window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
3672
    // wrap static methods. Currently just generateCertificate.
3673
    Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
3674
      get: function() {
3675
        return OrigPeerConnection.generateCertificate;
3676
      }
3677
    });
3678
  }
3679
};
3680
3681
// Expose public methods.
3682
module.exports = {
3683
  shimCallbacksAPI: safariShim.shimCallbacksAPI,
3684
  shimLocalStreamsAPI: safariShim.shimLocalStreamsAPI,
3685
  shimRemoteStreamsAPI: safariShim.shimRemoteStreamsAPI,
3686
  shimGetUserMedia: safariShim.shimGetUserMedia,
3687
  shimRTCIceServerUrls: safariShim.shimRTCIceServerUrls
3688
  // TODO
3689
  // shimPeerConnection: safariShim.shimPeerConnection
3690
};
3691
3692
},{"../utils":12}],12:[function(require,module,exports){
3693
/*
3694
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3695
 *
3696
 *  Use of this source code is governed by a BSD-style license
3697
 *  that can be found in the LICENSE file in the root of the source
3698
 *  tree.
3699
 */
3700
 /* eslint-env node */
3701
'use strict';
3702
3703
var logDisabled_ = true;
3704
var deprecationWarnings_ = true;
3705
3706
// Utility methods.
3707
var utils = {
3708
  disableLog: function(bool) {
3709
    if (typeof bool !== 'boolean') {
3710
      return new Error('Argument type: ' + typeof bool +
3711
          '. Please use a boolean.');
3712
    }
3713
    logDisabled_ = bool;
3714
    return (bool) ? 'adapter.js logging disabled' :
3715
        'adapter.js logging enabled';
3716
  },
3717
3718
  /**
3719
   * Disable or enable deprecation warnings
3720
   * @param {!boolean} bool set to true to disable warnings.
3721
   */
3722
  disableWarnings: function(bool) {
3723
    if (typeof bool !== 'boolean') {
3724
      return new Error('Argument type: ' + typeof bool +
3725
          '. Please use a boolean.');
3726
    }
3727
    deprecationWarnings_ = !bool;
3728
    return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
3729
  },
3730
3731
  log: function() {
3732
    if (typeof window === 'object') {
3733
      if (logDisabled_) {
3734
        return;
3735
      }
3736
      if (typeof console !== 'undefined' && typeof console.log === 'function') {
3737
        console.log.apply(console, arguments);
3738
      }
3739
    }
3740
  },
3741
3742
  /**
3743
   * Shows a deprecation warning suggesting the modern and spec-compatible API.
3744
   */
3745
  deprecated: function(oldMethod, newMethod) {
3746
    if (!deprecationWarnings_) {
3747
      return;
3748
    }
3749
    console.warn(oldMethod + ' is deprecated, please use ' + newMethod +
3750
        ' instead.');
3751
  },
3752
3753
  /**
3754
   * Extract browser version out of the provided user agent string.
3755
   *
3756
   * @param {!string} uastring userAgent string.
3757
   * @param {!string} expr Regular expression used as match criteria.
3758
   * @param {!number} pos position in the version string to be returned.
3759
   * @return {!number} browser version.
3760
   */
3761
  extractVersion: function(uastring, expr, pos) {
3762
    var match = uastring.match(expr);
3763
    return match && match.length >= pos && parseInt(match[pos], 10);
3764
  },
3765
3766
  /**
3767
   * Browser detector.
3768
   *
3769
   * @return {object} result containing browser and version
3770
   *     properties.
3771
   */
3772
  detectBrowser: function(window) {
3773
    var navigator = window && window.navigator;
3774
3775
    // Returned result object.
3776
    var result = {};
3777
    result.browser = null;
3778
    result.version = null;
3779
3780
    // Fail early if it's not a browser
3781
    if (typeof window === 'undefined' || !window.navigator) {
3782
      result.browser = 'Not a browser.';
3783
      return result;
3784
    }
3785
3786
    // Firefox.
3787
    if (navigator.mozGetUserMedia) {
3788
      result.browser = 'firefox';
3789
      result.version = this.extractVersion(navigator.userAgent,
3790
          /Firefox\/(\d+)\./, 1);
3791
    } else if (navigator.webkitGetUserMedia) {
3792
      // Chrome, Chromium, Webview, Opera, all use the chrome shim for now
3793
      if (window.webkitRTCPeerConnection) {
3794
        result.browser = 'chrome';
3795
        result.version = this.extractVersion(navigator.userAgent,
3796
          /Chrom(e|ium)\/(\d+)\./, 2);
3797
      } else { // Safari (in an unpublished version) or unknown webkit-based.
3798
        if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) {
3799
          result.browser = 'safari';
3800
          result.version = this.extractVersion(navigator.userAgent,
3801
            /AppleWebKit\/(\d+)\./, 1);
3802
        } else { // unknown webkit-based browser.
3803
          result.browser = 'Unsupported webkit-based browser ' +
3804
              'with GUM support but no WebRTC support.';
3805
          return result;
3806
        }
3807
      }
3808
    } else if (navigator.mediaDevices &&
3809
        navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge.
3810
      result.browser = 'edge';
3811
      result.version = this.extractVersion(navigator.userAgent,
3812
          /Edge\/(\d+).(\d+)$/, 2);
3813
    } else if (navigator.mediaDevices &&
3814
        navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) {
3815
        // Safari, with webkitGetUserMedia removed.
3816
      result.browser = 'safari';
3817
      result.version = this.extractVersion(navigator.userAgent,
3818
          /AppleWebKit\/(\d+)\./, 1);
3819
    } else { // Default fallthrough: not supported.
3820
      result.browser = 'Not a supported browser.';
3821
      return result;
3822
    }
3823
3824
    return result;
3825
  },
3826
3827
  // shimCreateObjectURL must be called before shimSourceObject to avoid loop.
3828
3829
  shimCreateObjectURL: function(window) {
3830
    var URL = window && window.URL;
3831
3832
    if (!(typeof window === 'object' && window.HTMLMediaElement &&
3833
          'srcObject' in window.HTMLMediaElement.prototype)) {
3834
      // Only shim CreateObjectURL using srcObject if srcObject exists.
3835
      return undefined;
3836
    }
3837
3838
    var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
3839
    var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
3840
    var streams = new Map(), newId = 0;
3841
3842
    URL.createObjectURL = function(stream) {
3843
      if ('getTracks' in stream) {
3844
        var url = 'polyblob:' + (++newId);
3845
        streams.set(url, stream);
3846
        utils.deprecated('URL.createObjectURL(stream)',
3847
            'elem.srcObject = stream');
3848
        return url;
3849
      }
3850
      return nativeCreateObjectURL(stream);
3851
    };
3852
    URL.revokeObjectURL = function(url) {
3853
      nativeRevokeObjectURL(url);
3854
      streams.delete(url);
3855
    };
3856
3857
    var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
3858
                                              'src');
3859
    Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
3860
      get: function() {
3861
        return dsc.get.apply(this);
3862
      },
3863
      set: function(url) {
3864
        this.srcObject = streams.get(url) || null;
3865
        return dsc.set.apply(this, [url]);
3866
      }
3867
    });
3868
3869
    var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute;
3870
    window.HTMLMediaElement.prototype.setAttribute = function() {
3871
      if (arguments.length === 2 &&
3872
          ('' + arguments[0]).toLowerCase() === 'src') {
3873
        this.srcObject = streams.get(arguments[1]) || null;
3874
      }
3875
      return nativeSetAttribute.apply(this, arguments);
3876
    };
3877
  }
3878
};
3879
3880
// Export.
3881
module.exports = {
3882
  log: utils.log,
3883
  deprecated: utils.deprecated,
3884
  disableLog: utils.disableLog,
3885
  disableWarnings: utils.disableWarnings,
3886
  extractVersion: utils.extractVersion,
3887
  shimCreateObjectURL: utils.shimCreateObjectURL,
3888
  detectBrowser: utils.detectBrowser.bind(utils)
3889
};
3890
3891
},{}]},{},[2])(2)
3892
});
3893