Passed
Push — master ( 3f7d62...9f7df0 )
by Jacob
01:30
created

amd/src/adapter.js   F

Complexity

Total Complexity 944
Complexity/F 2.67

Size

Lines of Code 3893
Function Count 353

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 0
nc 0
dl 0
loc 3893
rs 2.4
c 2
b 0
f 0
wmc 944
mnd 7
bc 754
fnc 353
bpm 2.1359
cpm 2.6742
noi 40

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