GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 4ae00e...0ca11f )
by Jesus
01:28
created

adapter.js ➔ ... ➔ RTCPeerConnection.createOffer   F

Complexity

Conditions 21
Paths 16320

Size

Total Lines 155

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 21
c 1
b 0
f 0
nc 16320
nop 0
dl 0
loc 155
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

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

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

1
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
0 ignored issues
show
Unused Code introduced by
The variable define seems to be never used. Consider removing it.
Loading history...
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
Bug introduced by
The variable self seems to be never declared. If this is a global, consider adding a /** global: self */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Unused Code introduced by
The variable module seems to be never used. Consider removing it.
Loading history...
2
 /* eslint-env node */
3
'use strict';
4
5
// SDP helpers.
6
var SDPUtils = {};
7
8
// Generate an alphanumeric identifier for cname or mids.
9
// TODO: use UUIDs instead? https://gist.github.com/jed/982883
10
SDPUtils.generateIdentifier = function() {
11
  return Math.random().toString(36).substr(2, 10);
12
};
13
14
// The RTCP CNAME used by all peerconnections from the same JS.
15
SDPUtils.localCName = SDPUtils.generateIdentifier();
16
17
// Splits SDP into lines, dealing with both CRLF and LF.
18
SDPUtils.splitLines = function(blob) {
19
  return blob.trim().split('\n').map(function(line) {
20
    return line.trim();
21
  });
22
};
23
// Splits SDP into sessionpart and mediasections. Ensures CRLF.
24
SDPUtils.splitSections = function(blob) {
25
  var parts = blob.split('\nm=');
26
  return parts.map(function(part, index) {
27
    return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
28
  });
29
};
30
31
// Returns lines that start with a certain prefix.
32
SDPUtils.matchPrefix = function(blob, prefix) {
33
  return SDPUtils.splitLines(blob).filter(function(line) {
34
    return line.indexOf(prefix) === 0;
35
  });
36
};
37
38
// Parses an ICE candidate line. Sample input:
39
// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
40
// rport 55996"
41
SDPUtils.parseCandidate = function(line) {
42
  var parts;
43
  // Parse both variants.
44
  if (line.indexOf('a=candidate:') === 0) {
45
    parts = line.substring(12).split(' ');
46
  } else {
47
    parts = line.substring(10).split(' ');
48
  }
49
50
  var candidate = {
51
    foundation: parts[0],
52
    component: parts[1],
53
    protocol: parts[2].toLowerCase(),
54
    priority: parseInt(parts[3], 10),
55
    ip: parts[4],
56
    port: parseInt(parts[5], 10),
57
    // skip parts[6] == 'typ'
58
    type: parts[7]
59
  };
60
61
  for (var i = 8; i < parts.length; i += 2) {
62
    switch (parts[i]) {
63
      case 'raddr':
64
        candidate.relatedAddress = parts[i + 1];
65
        break;
66
      case 'rport':
67
        candidate.relatedPort = parseInt(parts[i + 1], 10);
68
        break;
69
      case 'tcptype':
70
        candidate.tcpType = parts[i + 1];
71
        break;
72
      default: // extension handling, in particular ufrag
73
        candidate[parts[i]] = parts[i + 1];
74
        break;
75
    }
76
  }
77
  return candidate;
78
};
79
80
// Translates a candidate object into SDP candidate attribute.
81
SDPUtils.writeCandidate = function(candidate) {
82
  var sdp = [];
83
  sdp.push(candidate.foundation);
84
  sdp.push(candidate.component);
85
  sdp.push(candidate.protocol.toUpperCase());
86
  sdp.push(candidate.priority);
87
  sdp.push(candidate.ip);
88
  sdp.push(candidate.port);
89
90
  var type = candidate.type;
91
  sdp.push('typ');
92
  sdp.push(type);
93
  if (type !== 'host' && candidate.relatedAddress &&
94
      candidate.relatedPort) {
95
    sdp.push('raddr');
96
    sdp.push(candidate.relatedAddress); // was: relAddr
97
    sdp.push('rport');
98
    sdp.push(candidate.relatedPort); // was: relPort
99
  }
100
  if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
101
    sdp.push('tcptype');
102
    sdp.push(candidate.tcpType);
103
  }
104
  return 'candidate:' + sdp.join(' ');
105
};
106
107
// Parses an ice-options line, returns an array of option tags.
108
// a=ice-options:foo bar
109
SDPUtils.parseIceOptions = function(line) {
110
  return line.substr(14).split(' ');
111
}
112
113
// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
114
// a=rtpmap:111 opus/48000/2
115
SDPUtils.parseRtpMap = function(line) {
116
  var parts = line.substr(9).split(' ');
117
  var parsed = {
118
    payloadType: parseInt(parts.shift(), 10) // was: id
119
  };
120
121
  parts = parts[0].split('/');
122
123
  parsed.name = parts[0];
124
  parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
125
  // was: channels
126
  parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
127
  return parsed;
128
};
129
130
// Generate an a=rtpmap line from RTCRtpCodecCapability or
131
// RTCRtpCodecParameters.
132
SDPUtils.writeRtpMap = function(codec) {
133
  var pt = codec.payloadType;
134
  if (codec.preferredPayloadType !== undefined) {
135
    pt = codec.preferredPayloadType;
136
  }
137
  return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
138
      (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
139
};
140
141
// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
142
// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
143
// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
144
SDPUtils.parseExtmap = function(line) {
145
  var parts = line.substr(9).split(' ');
146
  return {
147
    id: parseInt(parts[0], 10),
148
    direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
149
    uri: parts[1]
150
  };
151
};
152
153
// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
154
// RTCRtpHeaderExtension.
155
SDPUtils.writeExtmap = function(headerExtension) {
156
  return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
157
      (headerExtension.direction && headerExtension.direction !== 'sendrecv'
158
          ? '/' + headerExtension.direction
159
          : '') +
160
      ' ' + headerExtension.uri + '\r\n';
161
};
162
163
// Parses an ftmp line, returns dictionary. Sample input:
164
// a=fmtp:96 vbr=on;cng=on
165
// Also deals with vbr=on; cng=on
166
SDPUtils.parseFmtp = function(line) {
167
  var parsed = {};
168
  var kv;
169
  var parts = line.substr(line.indexOf(' ') + 1).split(';');
170
  for (var j = 0; j < parts.length; j++) {
171
    kv = parts[j].trim().split('=');
172
    parsed[kv[0].trim()] = kv[1];
173
  }
174
  return parsed;
175
};
176
177
// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
178
SDPUtils.writeFmtp = function(codec) {
179
  var line = '';
180
  var pt = codec.payloadType;
181
  if (codec.preferredPayloadType !== undefined) {
182
    pt = codec.preferredPayloadType;
183
  }
184
  if (codec.parameters && Object.keys(codec.parameters).length) {
185
    var params = [];
186
    Object.keys(codec.parameters).forEach(function(param) {
187
      params.push(param + '=' + codec.parameters[param]);
188
    });
189
    line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
190
  }
191
  return line;
192
};
193
194
// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
195
// a=rtcp-fb:98 nack rpsi
196
SDPUtils.parseRtcpFb = function(line) {
197
  var parts = line.substr(line.indexOf(' ') + 1).split(' ');
198
  return {
199
    type: parts.shift(),
200
    parameter: parts.join(' ')
201
  };
202
};
203
// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
204
SDPUtils.writeRtcpFb = function(codec) {
205
  var lines = '';
206
  var pt = codec.payloadType;
207
  if (codec.preferredPayloadType !== undefined) {
208
    pt = codec.preferredPayloadType;
209
  }
210
  if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
211
    // FIXME: special handling for trr-int?
212
    codec.rtcpFeedback.forEach(function(fb) {
213
      lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +
214
      (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +
215
          '\r\n';
216
    });
217
  }
218
  return lines;
219
};
220
221
// Parses an RFC 5576 ssrc media attribute. Sample input:
222
// a=ssrc:3735928559 cname:something
223
SDPUtils.parseSsrcMedia = function(line) {
224
  var sp = line.indexOf(' ');
225
  var parts = {
226
    ssrc: parseInt(line.substr(7, sp - 7), 10)
227
  };
228
  var colon = line.indexOf(':', sp);
229
  if (colon > -1) {
230
    parts.attribute = line.substr(sp + 1, colon - sp - 1);
231
    parts.value = line.substr(colon + 1);
232
  } else {
233
    parts.attribute = line.substr(sp + 1);
234
  }
235
  return parts;
236
};
237
238
// Extracts the MID (RFC 5888) from a media section.
239
// returns the MID or undefined if no mid line was found.
240
SDPUtils.getMid = function(mediaSection) {
241
  var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
242
  if (mid) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if mid is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
243
    return mid.substr(6);
244
  }
245
}
246
247
SDPUtils.parseFingerprint = function(line) {
248
  var parts = line.substr(14).split(' ');
249
  return {
250
    algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
251
    value: parts[1]
252
  };
253
};
254
255
// Extracts DTLS parameters from SDP media section or sessionpart.
256
// FIXME: for consistency with other functions this should only
257
//   get the fingerprint line as input. See also getIceParameters.
258
SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
259
  var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
260
      'a=fingerprint:');
261
  // Note: a=setup line is ignored since we use the 'auto' role.
262
  // Note2: 'algorithm' is not case sensitive except in Edge.
263
  return {
264
    role: 'auto',
265
    fingerprints: lines.map(SDPUtils.parseFingerprint)
266
  };
267
};
268
269
// Serializes DTLS parameters to SDP.
270
SDPUtils.writeDtlsParameters = function(params, setupType) {
271
  var sdp = 'a=setup:' + setupType + '\r\n';
272
  params.fingerprints.forEach(function(fp) {
273
    sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
274
  });
275
  return sdp;
276
};
277
// Parses ICE information from SDP media section or sessionpart.
278
// FIXME: for consistency with other functions this should only
279
//   get the ice-ufrag and ice-pwd lines as input.
280
SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
281
  var lines = SDPUtils.splitLines(mediaSection);
282
  // Search in session part, too.
283
  lines = lines.concat(SDPUtils.splitLines(sessionpart));
284
  var iceParameters = {
285
    usernameFragment: lines.filter(function(line) {
286
      return line.indexOf('a=ice-ufrag:') === 0;
287
    })[0].substr(12),
288
    password: lines.filter(function(line) {
289
      return line.indexOf('a=ice-pwd:') === 0;
290
    })[0].substr(10)
291
  };
292
  return iceParameters;
293
};
294
295
// Serializes ICE parameters to SDP.
296
SDPUtils.writeIceParameters = function(params) {
297
  return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
298
      'a=ice-pwd:' + params.password + '\r\n';
299
};
300
301
// Parses the SDP media section and returns RTCRtpParameters.
302
SDPUtils.parseRtpParameters = function(mediaSection) {
303
  var description = {
304
    codecs: [],
305
    headerExtensions: [],
306
    fecMechanisms: [],
307
    rtcp: []
308
  };
309
  var lines = SDPUtils.splitLines(mediaSection);
310
  var mline = lines[0].split(' ');
311
  for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
312
    var pt = mline[i];
313
    var rtpmapline = SDPUtils.matchPrefix(
314
        mediaSection, 'a=rtpmap:' + pt + ' ')[0];
315
    if (rtpmapline) {
316
      var codec = SDPUtils.parseRtpMap(rtpmapline);
317
      var fmtps = SDPUtils.matchPrefix(
318
          mediaSection, 'a=fmtp:' + pt + ' ');
319
      // Only the first a=fmtp:<pt> is considered.
320
      codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
321
      codec.rtcpFeedback = SDPUtils.matchPrefix(
322
          mediaSection, 'a=rtcp-fb:' + pt + ' ')
323
        .map(SDPUtils.parseRtcpFb);
324
      description.codecs.push(codec);
325
      // parse FEC mechanisms from rtpmap lines.
326
      switch (codec.name.toUpperCase()) {
327
        case 'RED':
328
        case 'ULPFEC':
329
          description.fecMechanisms.push(codec.name.toUpperCase());
330
          break;
331
        default: // only RED and ULPFEC are recognized as FEC mechanisms.
332
          break;
333
      }
334
    }
335
  }
336
  SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
337
    description.headerExtensions.push(SDPUtils.parseExtmap(line));
338
  });
339
  // FIXME: parse rtcp.
340
  return description;
341
};
342
343
// Generates parts of the SDP media section describing the capabilities /
344
// parameters.
345
SDPUtils.writeRtpDescription = function(kind, caps) {
346
  var sdp = '';
347
348
  // Build the mline.
349
  sdp += 'm=' + kind + ' ';
350
  sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
351
  sdp += ' UDP/TLS/RTP/SAVPF ';
352
  sdp += caps.codecs.map(function(codec) {
353
    if (codec.preferredPayloadType !== undefined) {
354
      return codec.preferredPayloadType;
355
    }
356
    return codec.payloadType;
357
  }).join(' ') + '\r\n';
358
359
  sdp += 'c=IN IP4 0.0.0.0\r\n';
360
  sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
361
362
  // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
363
  caps.codecs.forEach(function(codec) {
364
    sdp += SDPUtils.writeRtpMap(codec);
365
    sdp += SDPUtils.writeFmtp(codec);
366
    sdp += SDPUtils.writeRtcpFb(codec);
367
  });
368
  var maxptime = 0;
369
  caps.codecs.forEach(function(codec) {
370
    if (codec.maxptime > maxptime) {
371
      maxptime = codec.maxptime;
372
    }
373
  });
374
  if (maxptime > 0) {
375
    sdp += 'a=maxptime:' + maxptime + '\r\n';
376
  }
377
  sdp += 'a=rtcp-mux\r\n';
378
379
  caps.headerExtensions.forEach(function(extension) {
380
    sdp += SDPUtils.writeExtmap(extension);
381
  });
382
  // FIXME: write fecMechanisms.
383
  return sdp;
384
};
385
386
// Parses the SDP media section and returns an array of
387
// RTCRtpEncodingParameters.
388
SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
389
  var encodingParameters = [];
390
  var description = SDPUtils.parseRtpParameters(mediaSection);
391
  var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
392
  var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
393
394
  // filter a=ssrc:... cname:, ignore PlanB-msid
395
  var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
396
  .map(function(line) {
397
    return SDPUtils.parseSsrcMedia(line);
398
  })
399
  .filter(function(parts) {
400
    return parts.attribute === 'cname';
401
  });
402
  var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
403
  var secondarySsrc;
404
405
  var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
406
  .map(function(line) {
407
    var parts = line.split(' ');
408
    parts.shift();
409
    return parts.map(function(part) {
410
      return parseInt(part, 10);
411
    });
412
  });
413
  if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
414
    secondarySsrc = flows[0][1];
415
  }
416
417
  description.codecs.forEach(function(codec) {
418
    if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
419
      var encParam = {
420
        ssrc: primarySsrc,
421
        codecPayloadType: parseInt(codec.parameters.apt, 10),
422
        rtx: {
423
          ssrc: secondarySsrc
0 ignored issues
show
Bug introduced by
The variable secondarySsrc does not seem to be initialized in case flows.length > 0 && flow...ows.0.0 === primarySsrc on line 413 is false. Are you sure this can never be the case?
Loading history...
424
        }
425
      };
426
      encodingParameters.push(encParam);
427
      if (hasRed) {
428
        encParam = JSON.parse(JSON.stringify(encParam));
429
        encParam.fec = {
430
          ssrc: secondarySsrc,
431
          mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
432
        };
433
        encodingParameters.push(encParam);
434
      }
435
    }
436
  });
437
  if (encodingParameters.length === 0 && primarySsrc) {
438
    encodingParameters.push({
439
      ssrc: primarySsrc
440
    });
441
  }
442
443
  // we support both b=AS and b=TIAS but interpret AS as TIAS.
444
  var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
445
  if (bandwidth.length) {
446
    if (bandwidth[0].indexOf('b=TIAS:') === 0) {
447
      bandwidth = parseInt(bandwidth[0].substr(7), 10);
448
    } else if (bandwidth[0].indexOf('b=AS:') === 0) {
449
      bandwidth = parseInt(bandwidth[0].substr(5), 10);
450
    }
451
    encodingParameters.forEach(function(params) {
452
      params.maxBitrate = bandwidth;
453
    });
454
  }
455
  return encodingParameters;
456
};
457
458
// parses http://draft.ortc.org/#rtcrtcpparameters*
459
SDPUtils.parseRtcpParameters = function(mediaSection) {
460
  var rtcpParameters = {};
461
462
  var cname;
463
  // Gets the first SSRC. Note that with RTX there might be multiple
464
  // SSRCs.
465
  var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
466
      .map(function(line) {
467
        return SDPUtils.parseSsrcMedia(line);
468
      })
469
      .filter(function(obj) {
470
        return obj.attribute === 'cname';
471
      })[0];
472
  if (remoteSsrc) {
473
    rtcpParameters.cname = remoteSsrc.value;
474
    rtcpParameters.ssrc = remoteSsrc.ssrc;
475
  }
476
477
  // Edge uses the compound attribute instead of reducedSize
478
  // compound is !reducedSize
479
  var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
480
  rtcpParameters.reducedSize = rsize.length > 0;
481
  rtcpParameters.compound = rsize.length === 0;
482
483
  // parses the rtcp-mux attrіbute.
484
  // Note that Edge does not support unmuxed RTCP.
485
  var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
486
  rtcpParameters.mux = mux.length > 0;
487
488
  return rtcpParameters;
489
};
490
491
// parses either a=msid: or a=ssrc:... msid lines and returns
492
// the id of the MediaStream and MediaStreamTrack.
493
SDPUtils.parseMsid = function(mediaSection) {
494
  var parts;
495
  var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
496
  if (spec.length === 1) {
497
    parts = spec[0].substr(7).split(' ');
498
    return {stream: parts[0], track: parts[1]};
499
  }
500
  var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
501
  .map(function(line) {
502
    return SDPUtils.parseSsrcMedia(line);
503
  })
504
  .filter(function(parts) {
505
    return parts.attribute === 'msid';
506
  });
507
  if (planB.length > 0) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if planB.length > 0 is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
508
    parts = planB[0].value.split(' ');
509
    return {stream: parts[0], track: parts[1]};
510
  }
511
};
512
513
SDPUtils.writeSessionBoilerplate = function() {
514
  // FIXME: sess-id should be an NTP timestamp.
515
  return 'v=0\r\n' +
516
      'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
517
      's=-\r\n' +
518
      't=0 0\r\n';
519
};
520
521
SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
522
  var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
523
524
  // Map ICE parameters (ufrag, pwd) to SDP.
525
  sdp += SDPUtils.writeIceParameters(
526
      transceiver.iceGatherer.getLocalParameters());
527
528
  // Map DTLS parameters to SDP.
529
  sdp += SDPUtils.writeDtlsParameters(
530
      transceiver.dtlsTransport.getLocalParameters(),
531
      type === 'offer' ? 'actpass' : 'active');
532
533
  sdp += 'a=mid:' + transceiver.mid + '\r\n';
534
535
  if (transceiver.direction) {
536
    sdp += 'a=' + transceiver.direction + '\r\n';
537
  } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
538
    sdp += 'a=sendrecv\r\n';
539
  } else if (transceiver.rtpSender) {
540
    sdp += 'a=sendonly\r\n';
541
  } else if (transceiver.rtpReceiver) {
542
    sdp += 'a=recvonly\r\n';
543
  } else {
544
    sdp += 'a=inactive\r\n';
545
  }
546
547
  if (transceiver.rtpSender) {
548
    // spec.
549
    var msid = 'msid:' + stream.id + ' ' +
550
        transceiver.rtpSender.track.id + '\r\n';
551
    sdp += 'a=' + msid;
552
553
    // for Chrome.
554
    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
555
        ' ' + msid;
556
    if (transceiver.sendEncodingParameters[0].rtx) {
557
      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
558
          ' ' + msid;
559
      sdp += 'a=ssrc-group:FID ' +
560
          transceiver.sendEncodingParameters[0].ssrc + ' ' +
561
          transceiver.sendEncodingParameters[0].rtx.ssrc +
562
          '\r\n';
563
    }
564
  }
565
  // FIXME: this should be written by writeRtpDescription.
566
  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
567
      ' cname:' + SDPUtils.localCName + '\r\n';
568
  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
569
    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
570
        ' cname:' + SDPUtils.localCName + '\r\n';
571
  }
572
  return sdp;
573
};
574
575
// Gets the direction from the mediaSection or the sessionpart.
576
SDPUtils.getDirection = function(mediaSection, sessionpart) {
577
  // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
578
  var lines = SDPUtils.splitLines(mediaSection);
579
  for (var i = 0; i < lines.length; i++) {
580
    switch (lines[i]) {
581
      case 'a=sendrecv':
582
      case 'a=sendonly':
583
      case 'a=recvonly':
584
      case 'a=inactive':
585
        return lines[i].substr(2);
586
      default:
587
        // FIXME: What should happen here?
588
    }
589
  }
590
  if (sessionpart) {
591
    return SDPUtils.getDirection(sessionpart);
592
  }
593
  return 'sendrecv';
594
};
595
596
SDPUtils.getKind = function(mediaSection) {
597
  var lines = SDPUtils.splitLines(mediaSection);
598
  var mline = lines[0].split(' ');
599
  return mline[0].substr(2);
600
};
601
602
SDPUtils.isRejected = function(mediaSection) {
603
  return mediaSection.split(' ', 2)[1] === '0';
604
};
605
606
// Expose public methods.
607
module.exports = SDPUtils;
608
609
},{}],2:[function(require,module,exports){
610
(function (global){
611
/*
612
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
613
 *
614
 *  Use of this source code is governed by a BSD-style license
615
 *  that can be found in the LICENSE file in the root of the source
616
 *  tree.
617
 */
618
 /* eslint-env node */
619
620
'use strict';
621
622
var adapterFactory = require('./adapter_factory.js');
623
module.exports = adapterFactory({window: global.window});
624
625
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
0 ignored issues
show
Bug introduced by
The variable self seems to be never declared. If this is a global, consider adding a /** global: self */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
626
},{"./adapter_factory.js":3}],3:[function(require,module,exports){
627
/*
628
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
629
 *
630
 *  Use of this source code is governed by a BSD-style license
631
 *  that can be found in the LICENSE file in the root of the source
632
 *  tree.
633
 */
634
 /* eslint-env node */
635
636
'use strict';
637
638
// Shimming starts here.
639
module.exports = function(dependencies) {
640
  var window = dependencies && dependencies.window;
641
642
  // Utils.
643
  var utils = require('./utils');
644
  var logging = utils.log;
645
  var browserDetails = utils.detectBrowser(window);
646
647
  // Export to the adapter global object visible in the browser.
648
  var adapter = {
649
    browserDetails: browserDetails,
650
    extractVersion: utils.extractVersion,
651
    disableLog: utils.disableLog
652
  };
653
654
  // Uncomment the line below if you want logging to occur, including logging
655
  // for the switch statement below. Can also be turned on in the browser via
656
  // adapter.disableLog(false), but then logging from the switch statement below
657
  // will not appear.
658
  // require('./utils').disableLog(false);
659
660
  // Browser shims.
661
  var chromeShim = require('./chrome/chrome_shim') || null;
662
  var edgeShim = require('./edge/edge_shim') || null;
663
  var firefoxShim = require('./firefox/firefox_shim') || null;
664
  var safariShim = require('./safari/safari_shim') || null;
665
666
  // Shim browser if found.
667
  switch (browserDetails.browser) {
668
    case 'chrome':
669
      if (!chromeShim || !chromeShim.shimPeerConnection) {
670
        logging('Chrome shim is not included in this adapter release.');
671
        return adapter;
672
      }
673
      logging('adapter.js shimming chrome.');
674
      // Export to the adapter global object visible in the browser.
675
      adapter.browserShim = chromeShim;
676
677
      chromeShim.shimGetUserMedia(window);
678
      chromeShim.shimMediaStream(window);
679
      utils.shimCreateObjectURL(window);
680
      chromeShim.shimSourceObject(window);
681
      chromeShim.shimPeerConnection(window);
682
      chromeShim.shimOnTrack(window);
683
      chromeShim.shimGetSendersWithDtmf(window);
684
      break;
685
    case 'firefox':
686
      if (!firefoxShim || !firefoxShim.shimPeerConnection) {
687
        logging('Firefox shim is not included in this adapter release.');
688
        return adapter;
689
      }
690
      logging('adapter.js shimming firefox.');
691
      // Export to the adapter global object visible in the browser.
692
      adapter.browserShim = firefoxShim;
693
694
      firefoxShim.shimGetUserMedia(window);
695
      utils.shimCreateObjectURL(window);
696
      firefoxShim.shimSourceObject(window);
697
      firefoxShim.shimPeerConnection(window);
698
      firefoxShim.shimOnTrack(window);
699
      break;
700
    case 'edge':
701
      if (!edgeShim || !edgeShim.shimPeerConnection) {
702
        logging('MS edge shim is not included in this adapter release.');
703
        return adapter;
704
      }
705
      logging('adapter.js shimming edge.');
706
      // Export to the adapter global object visible in the browser.
707
      adapter.browserShim = edgeShim;
708
709
      edgeShim.shimGetUserMedia(window);
710
      utils.shimCreateObjectURL(window);
711
      edgeShim.shimPeerConnection(window);
712
      edgeShim.shimReplaceTrack(window);
713
      break;
714
    case 'safari':
715
      if (!safariShim) {
716
        logging('Safari shim is not included in this adapter release.');
717
        return adapter;
718
      }
719
      logging('adapter.js shimming safari.');
720
      // Export to the adapter global object visible in the browser.
721
      adapter.browserShim = safariShim;
722
      // shim window.URL.createObjectURL Safari (technical preview)
723
      utils.shimCreateObjectURL(window);
724
      safariShim.shimCallbacksAPI(window);
725
      safariShim.shimLocalStreamsAPI(window);
726
      safariShim.shimRemoteStreamsAPI(window);
727
      safariShim.shimGetUserMedia(window);
728
      break;
729
    default:
730
      logging('Unsupported browser!');
731
      break;
732
  }
733
734
  return adapter;
735
};
736
737
},{"./chrome/chrome_shim":4,"./edge/edge_shim":6,"./firefox/firefox_shim":9,"./safari/safari_shim":11,"./utils":12}],4:[function(require,module,exports){
738
739
/*
740
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
741
 *
742
 *  Use of this source code is governed by a BSD-style license
743
 *  that can be found in the LICENSE file in the root of the source
744
 *  tree.
745
 */
746
 /* eslint-env node */
747
'use strict';
748
var utils = require('../utils.js');
749
var logging = utils.log;
750
751
var chromeShim = {
752
  shimMediaStream: function(window) {
753
    window.MediaStream = window.MediaStream || window.webkitMediaStream;
754
  },
755
756
  shimOnTrack: function(window) {
757
    if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
758
        window.RTCPeerConnection.prototype)) {
759
      Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
760
        get: function() {
761
          return this._ontrack;
762
        },
763
        set: function(f) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
764
          var self = this;
765
          if (this._ontrack) {
766
            this.removeEventListener('track', this._ontrack);
767
            this.removeEventListener('addstream', this._ontrackpoly);
768
          }
769
          this.addEventListener('track', this._ontrack = f);
770
          this.addEventListener('addstream', this._ontrackpoly = function(e) {
771
            // onaddstream does not fire when a track is added to an existing
772
            // stream. But stream.onaddtrack is implemented so we use that.
773
            e.stream.addEventListener('addtrack', function(te) {
774
              var receiver;
775
              if (window.RTCPeerConnection.prototype.getReceivers) {
776
                receiver = self.getReceivers().find(function(r) {
777
                  return r.track.id === te.track.id;
778
                });
779
              } else {
780
                receiver = {track: te.track};
781
              }
782
783
              var event = new Event('track');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
784
              event.track = te.track;
785
              event.receiver = receiver;
786
              event.streams = [e.stream];
787
              self.dispatchEvent(event);
788
            });
789
            e.stream.getTracks().forEach(function(track) {
790
              var receiver;
791
              if (window.RTCPeerConnection.prototype.getReceivers) {
792
                receiver = self.getReceivers().find(function(r) {
793
                  return r.track.id === track.id;
794
                });
795
              } else {
796
                receiver = {track: track};
797
              }
798
              var event = new Event('track');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
799
              event.track = track;
800
              event.receiver = receiver;
801
              event.streams = [e.stream];
802
              this.dispatchEvent(event);
803
            }.bind(this));
804
          }.bind(this));
805
        }
806
      });
807
    }
808
  },
809
810
  shimGetSendersWithDtmf: function(window) {
811
    if (typeof window === 'object' && window.RTCPeerConnection &&
812
        !('getSenders' in window.RTCPeerConnection.prototype) &&
813
        'createDTMFSender' in window.RTCPeerConnection.prototype) {
814
      window.RTCPeerConnection.prototype.getSenders = function() {
815
        return this._senders || [];
816
      };
817
      var origAddStream = window.RTCPeerConnection.prototype.addStream;
818
      var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
819
820
      if (!window.RTCPeerConnection.prototype.addTrack) {
821
        window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
822
          var pc = this;
823
          if (pc.signalingState === 'closed') {
824
            throw new DOMException(
0 ignored issues
show
Bug introduced by
The variable DOMException seems to be never declared. If this is a global, consider adding a /** global: DOMException */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
825
              'The RTCPeerConnection\'s signalingState is \'closed\'.',
826
              'InvalidStateError');
827
          }
828
          var streams = [].slice.call(arguments, 1);
829
          if (streams.length !== 1 ||
830
              !streams[0].getTracks().find(function(t) {
831
                return t === track;
832
              })) {
833
            // this is not fully correct but all we can manage without
834
            // [[associated MediaStreams]] internal slot.
835
            throw new DOMException(
0 ignored issues
show
Bug introduced by
The variable DOMException seems to be never declared. If this is a global, consider adding a /** global: DOMException */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
836
              'The adapter.js addTrack polyfill only supports a single ' +
837
              ' stream which is associated with the specified track.',
838
              'NotSupportedError');
839
          }
840
841
          pc._senders = pc._senders || [];
842
          var alreadyExists = pc._senders.find(function(t) {
843
            return t.track === track;
844
          });
845
          if (alreadyExists) {
846
            throw new DOMException('Track already exists.',
0 ignored issues
show
Bug introduced by
The variable DOMException seems to be never declared. If this is a global, consider adding a /** global: DOMException */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
847
                'InvalidAccessError');
848
          }
849
850
          pc._streams = pc._streams || {};
851
          var oldStream = pc._streams[stream.id];
852
          if (oldStream) {
853
            oldStream.addTrack(track);
854
            pc.removeStream(oldStream);
855
            pc.addStream(oldStream);
856
          } else {
857
            var newStream = new window.MediaStream([track]);
858
            pc._streams[stream.id] = newStream;
859
            pc.addStream(newStream);
860
          }
861
862
          var sender = {
863
            track: track,
864
            get dtmf() {
865
              if (this._dtmf === undefined) {
866
                if (track.kind === 'audio') {
867
                  this._dtmf = pc.createDTMFSender(track);
868
                } else {
869
                  this._dtmf = null;
870
                }
871
              }
872
              return this._dtmf;
873
            }
874
          };
875
          pc._senders.push(sender);
876
          return sender;
877
        };
878
      }
879
      window.RTCPeerConnection.prototype.addStream = function(stream) {
880
        var pc = this;
881
        pc._senders = pc._senders || [];
882
        origAddStream.apply(pc, [stream]);
883
        stream.getTracks().forEach(function(track) {
884
          pc._senders.push({
885
            track: track,
886
            get dtmf() {
887
              if (this._dtmf === undefined) {
888
                if (track.kind === 'audio') {
889
                  this._dtmf = pc.createDTMFSender(track);
890
                } else {
891
                  this._dtmf = null;
892
                }
893
              }
894
              return this._dtmf;
895
            }
896
          });
897
        });
898
      };
899
900
      window.RTCPeerConnection.prototype.removeStream = function(stream) {
901
        var pc = this;
902
        pc._senders = pc._senders || [];
903
        origRemoveStream.apply(pc, [stream]);
904
        stream.getTracks().forEach(function(track) {
905
          var sender = pc._senders.find(function(s) {
906
            return s.track === track;
907
          });
908
          if (sender) {
909
            pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender
910
          }
911
        });
912
      };
913
    } else if (typeof window === 'object' && window.RTCPeerConnection &&
914
               'getSenders' in window.RTCPeerConnection.prototype &&
915
               'createDTMFSender' in window.RTCPeerConnection.prototype &&
916
               window.RTCRtpSender &&
917
               !('dtmf' in window.RTCRtpSender.prototype)) {
918
      var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
919
      window.RTCPeerConnection.prototype.getSenders = function() {
920
        var pc = this;
921
        var senders = origGetSenders.apply(pc, []);
922
        senders.forEach(function(sender) {
923
          sender._pc = pc;
924
        });
925
        return senders;
926
      };
927
928
      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
929
        get: function() {
930
          if (this._dtmf === undefined) {
931
            if (this.track.kind === 'audio') {
932
              this._dtmf = this._pc.createDTMFSender(this.track);
933
            } else {
934
              this._dtmf = null;
935
            }
936
          }
937
          return this._dtmf;
938
        },
939
      });
940
    }
941
  },
942
943
  shimSourceObject: function(window) {
944
    var URL = window && window.URL;
945
946
    if (typeof window === 'object') {
947
      if (window.HTMLMediaElement &&
948
        !('srcObject' in window.HTMLMediaElement.prototype)) {
949
        // Shim the srcObject property, once, when HTMLMediaElement is found.
950
        Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
951
          get: function() {
952
            return this._srcObject;
953
          },
954
          set: function(stream) {
955
            var self = this;
956
            // Use _srcObject as a private property for this shim
957
            this._srcObject = stream;
958
            if (this.src) {
959
              URL.revokeObjectURL(this.src);
960
            }
961
962
            if (!stream) {
963
              this.src = '';
964
              return undefined;
965
            }
966
            this.src = URL.createObjectURL(stream);
967
            // We need to recreate the blob url when a track is added or
968
            // removed. Doing it manually since we want to avoid a recursion.
969
            stream.addEventListener('addtrack', function() {
970
              if (self.src) {
971
                URL.revokeObjectURL(self.src);
972
              }
973
              self.src = URL.createObjectURL(stream);
974
            });
975
            stream.addEventListener('removetrack', function() {
976
              if (self.src) {
977
                URL.revokeObjectURL(self.src);
978
              }
979
              self.src = URL.createObjectURL(stream);
980
            });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
981
          }
982
        });
983
      }
984
    }
985
  },
986
987
  shimPeerConnection: function(window) {
988
    var browserDetails = utils.detectBrowser(window);
989
990
    // The RTCPeerConnection object.
991
    if (!window.RTCPeerConnection) {
992
      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
993
        // Translate iceTransportPolicy to iceTransports,
994
        // see https://code.google.com/p/webrtc/issues/detail?id=4869
995
        // this was fixed in M56 along with unprefixing RTCPeerConnection.
996
        logging('PeerConnection');
997
        if (pcConfig && pcConfig.iceTransportPolicy) {
998
          pcConfig.iceTransports = pcConfig.iceTransportPolicy;
999
        }
1000
1001
        return new window.webkitRTCPeerConnection(pcConfig, pcConstraints);
1002
      };
1003
      window.RTCPeerConnection.prototype =
1004
          window.webkitRTCPeerConnection.prototype;
1005
      // wrap static methods. Currently just generateCertificate.
1006
      if (window.webkitRTCPeerConnection.generateCertificate) {
1007
        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
1008
          get: function() {
1009
            return window.webkitRTCPeerConnection.generateCertificate;
1010
          }
1011
        });
1012
      }
1013
    } else {
1014
      // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
1015
      var OrigPeerConnection = window.RTCPeerConnection;
1016
      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
1017
        if (pcConfig && pcConfig.iceServers) {
1018
          var newIceServers = [];
1019
          for (var i = 0; i < pcConfig.iceServers.length; i++) {
1020
            var server = pcConfig.iceServers[i];
1021
            if (!server.hasOwnProperty('urls') &&
1022
                server.hasOwnProperty('url')) {
1023
              console.warn('RTCIceServer.url is deprecated! Use urls instead.');
1024
              server = JSON.parse(JSON.stringify(server));
1025
              server.urls = server.url;
1026
              newIceServers.push(server);
1027
            } else {
1028
              newIceServers.push(pcConfig.iceServers[i]);
1029
            }
1030
          }
1031
          pcConfig.iceServers = newIceServers;
1032
        }
1033
        return new OrigPeerConnection(pcConfig, pcConstraints);
1034
      };
1035
      window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
1036
      // wrap static methods. Currently just generateCertificate.
1037
      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
1038
        get: function() {
1039
          return OrigPeerConnection.generateCertificate;
1040
        }
1041
      });
1042
    }
1043
1044
    var origGetStats = window.RTCPeerConnection.prototype.getStats;
1045
    window.RTCPeerConnection.prototype.getStats = function(selector,
1046
        successCallback, errorCallback) {
1047
      var self = this;
1048
      var args = arguments;
1049
1050
      // If selector is a function then we are in the old style stats so just
1051
      // pass back the original getStats format to avoid breaking old users.
1052
      if (arguments.length > 0 && typeof selector === 'function') {
1053
        return origGetStats.apply(this, arguments);
1054
      }
1055
1056
      // When spec-style getStats is supported, return those when called with
1057
      // either no arguments or the selector argument is null.
1058
      if (origGetStats.length === 0 && (arguments.length === 0 ||
1059
          typeof arguments[0] !== 'function')) {
1060
        return origGetStats.apply(this, []);
1061
      }
1062
1063
      var fixChromeStats_ = function(response) {
1064
        var standardReport = {};
1065
        var reports = response.result();
1066
        reports.forEach(function(report) {
1067
          var standardStats = {
1068
            id: report.id,
1069
            timestamp: report.timestamp,
1070
            type: {
1071
              localcandidate: 'local-candidate',
1072
              remotecandidate: 'remote-candidate'
1073
            }[report.type] || report.type
1074
          };
1075
          report.names().forEach(function(name) {
1076
            standardStats[name] = report.stat(name);
1077
          });
1078
          standardReport[standardStats.id] = standardStats;
1079
        });
1080
1081
        return standardReport;
1082
      };
1083
1084
      // shim getStats with maplike support
1085
      var makeMapStats = function(stats) {
1086
        return new Map(Object.keys(stats).map(function(key) {
1087
          return [key, stats[key]];
1088
        }));
1089
      };
1090
1091
      if (arguments.length >= 2) {
1092
        var successCallbackWrapper_ = function(response) {
1093
          args[1](makeMapStats(fixChromeStats_(response)));
1094
        };
1095
1096
        return origGetStats.apply(this, [successCallbackWrapper_,
1097
          arguments[0]]);
1098
      }
1099
1100
      // promise-support
1101
      return new Promise(function(resolve, reject) {
1102
        origGetStats.apply(self, [
1103
          function(response) {
1104
            resolve(makeMapStats(fixChromeStats_(response)));
1105
          }, reject]);
1106
      }).then(successCallback, errorCallback);
1107
    };
1108
1109
    // add promise support -- natively available in Chrome 51
1110
    if (browserDetails.version < 51) {
1111
      ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
1112
          .forEach(function(method) {
1113
            var nativeMethod = window.RTCPeerConnection.prototype[method];
1114
            window.RTCPeerConnection.prototype[method] = function() {
1115
              var args = arguments;
1116
              var self = this;
1117
              var promise = new Promise(function(resolve, reject) {
1118
                nativeMethod.apply(self, [args[0], resolve, reject]);
1119
              });
1120
              if (args.length < 2) {
1121
                return promise;
1122
              }
1123
              return promise.then(function() {
1124
                args[1].apply(null, []);
1125
              },
1126
              function(err) {
1127
                if (args.length >= 3) {
1128
                  args[2].apply(null, [err]);
1129
                }
1130
              });
1131
            };
1132
          });
1133
    }
1134
1135
    // promise support for createOffer and createAnswer. Available (without
1136
    // bugs) since M52: crbug/619289
1137
    if (browserDetails.version < 52) {
1138
      ['createOffer', 'createAnswer'].forEach(function(method) {
1139
        var nativeMethod = window.RTCPeerConnection.prototype[method];
1140
        window.RTCPeerConnection.prototype[method] = function() {
1141
          var self = this;
1142
          if (arguments.length < 1 || (arguments.length === 1 &&
1143
              typeof arguments[0] === 'object')) {
1144
            var opts = arguments.length === 1 ? arguments[0] : undefined;
1145
            return new Promise(function(resolve, reject) {
1146
              nativeMethod.apply(self, [resolve, reject, opts]);
1147
            });
1148
          }
1149
          return nativeMethod.apply(this, arguments);
1150
        };
1151
      });
1152
    }
1153
1154
    // shim implicit creation of RTCSessionDescription/RTCIceCandidate
1155
    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
1156
        .forEach(function(method) {
1157
          var nativeMethod = window.RTCPeerConnection.prototype[method];
1158
          window.RTCPeerConnection.prototype[method] = function() {
1159
            arguments[0] = new ((method === 'addIceCandidate') ?
1160
                window.RTCIceCandidate :
1161
                window.RTCSessionDescription)(arguments[0]);
1162
            return nativeMethod.apply(this, arguments);
1163
          };
1164
        });
1165
1166
    // support for addIceCandidate(null or undefined)
1167
    var nativeAddIceCandidate =
1168
        window.RTCPeerConnection.prototype.addIceCandidate;
1169
    window.RTCPeerConnection.prototype.addIceCandidate = function() {
1170
      if (!arguments[0]) {
1171
        if (arguments[1]) {
1172
          arguments[1].apply(null);
1173
        }
1174
        return Promise.resolve();
1175
      }
1176
      return nativeAddIceCandidate.apply(this, arguments);
1177
    };
1178
  }
1179
};
1180
1181
1182
// Expose public methods.
1183
module.exports = {
1184
  shimMediaStream: chromeShim.shimMediaStream,
1185
  shimOnTrack: chromeShim.shimOnTrack,
1186
  shimGetSendersWithDtmf: chromeShim.shimGetSendersWithDtmf,
1187
  shimSourceObject: chromeShim.shimSourceObject,
1188
  shimPeerConnection: chromeShim.shimPeerConnection,
1189
  shimGetUserMedia: require('./getusermedia')
1190
};
1191
1192
},{"../utils.js":12,"./getusermedia":5}],5:[function(require,module,exports){
1193
/*
1194
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
1195
 *
1196
 *  Use of this source code is governed by a BSD-style license
1197
 *  that can be found in the LICENSE file in the root of the source
1198
 *  tree.
1199
 */
1200
 /* eslint-env node */
1201
'use strict';
1202
var utils = require('../utils.js');
1203
var logging = utils.log;
1204
1205
// Expose public methods.
1206
module.exports = function(window) {
1207
  var browserDetails = utils.detectBrowser(window);
1208
  var navigator = window && window.navigator;
1209
1210
  var constraintsToChrome_ = function(c) {
1211
    if (typeof c !== 'object' || c.mandatory || c.optional) {
1212
      return c;
1213
    }
1214
    var cc = {};
1215
    Object.keys(c).forEach(function(key) {
1216
      if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
1217
        return;
1218
      }
1219
      var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
1220
      if (r.exact !== undefined && typeof r.exact === 'number') {
1221
        r.min = r.max = r.exact;
1222
      }
1223
      var oldname_ = function(prefix, name) {
1224
        if (prefix) {
1225
          return prefix + name.charAt(0).toUpperCase() + name.slice(1);
1226
        }
1227
        return (name === 'deviceId') ? 'sourceId' : name;
1228
      };
1229
      if (r.ideal !== undefined) {
1230
        cc.optional = cc.optional || [];
1231
        var oc = {};
1232
        if (typeof r.ideal === 'number') {
1233
          oc[oldname_('min', key)] = r.ideal;
1234
          cc.optional.push(oc);
1235
          oc = {};
1236
          oc[oldname_('max', key)] = r.ideal;
1237
          cc.optional.push(oc);
1238
        } else {
1239
          oc[oldname_('', key)] = r.ideal;
1240
          cc.optional.push(oc);
1241
        }
1242
      }
1243
      if (r.exact !== undefined && typeof r.exact !== 'number') {
1244
        cc.mandatory = cc.mandatory || {};
1245
        cc.mandatory[oldname_('', key)] = r.exact;
1246
      } else {
1247
        ['min', 'max'].forEach(function(mix) {
1248
          if (r[mix] !== undefined) {
1249
            cc.mandatory = cc.mandatory || {};
1250
            cc.mandatory[oldname_(mix, key)] = r[mix];
1251
          }
1252
        });
1253
      }
1254
    });
1255
    if (c.advanced) {
1256
      cc.optional = (cc.optional || []).concat(c.advanced);
1257
    }
1258
    return cc;
1259
  };
1260
1261
  var shimConstraints_ = function(constraints, func) {
1262
    constraints = JSON.parse(JSON.stringify(constraints));
1263
    if (constraints && typeof constraints.audio === 'object') {
1264
      var remap = function(obj, a, b) {
1265
        if (a in obj && !(b in obj)) {
1266
          obj[b] = obj[a];
1267
          delete obj[a];
1268
        }
1269
      };
1270
      constraints = JSON.parse(JSON.stringify(constraints));
1271
      remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');
1272
      remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');
1273
      constraints.audio = constraintsToChrome_(constraints.audio);
1274
    }
1275
    if (constraints && typeof constraints.video === 'object') {
1276
      // Shim facingMode for mobile & surface pro.
1277
      var face = constraints.video.facingMode;
1278
      face = face && ((typeof face === 'object') ? face : {ideal: face});
1279
      var getSupportedFacingModeLies = browserDetails.version < 61;
1280
1281
      if ((face && (face.exact === 'user' || face.exact === 'environment' ||
1282
                    face.ideal === 'user' || face.ideal === 'environment')) &&
1283
          !(navigator.mediaDevices.getSupportedConstraints &&
1284
            navigator.mediaDevices.getSupportedConstraints().facingMode &&
1285
            !getSupportedFacingModeLies)) {
1286
        delete constraints.video.facingMode;
1287
        var matches;
1288
        if (face.exact === 'environment' || face.ideal === 'environment') {
1289
          matches = ['back', 'rear'];
1290
        } else if (face.exact === 'user' || face.ideal === 'user') {
1291
          matches = ['front'];
1292
        }
1293
        if (matches) {
1294
          // Look for matches in label, or use last cam for back (typical).
1295
          return navigator.mediaDevices.enumerateDevices()
1296
          .then(function(devices) {
1297
            devices = devices.filter(function(d) {
1298
              return d.kind === 'videoinput';
1299
            });
1300
            var dev = devices.find(function(d) {
1301
              return matches.some(function(match) {
1302
                return d.label.toLowerCase().indexOf(match) !== -1;
1303
              });
1304
            });
1305
            if (!dev && devices.length && matches.indexOf('back') !== -1) {
1306
              dev = devices[devices.length - 1]; // more likely the back cam
1307
            }
1308
            if (dev) {
1309
              constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :
1310
                                                        {ideal: dev.deviceId};
1311
            }
1312
            constraints.video = constraintsToChrome_(constraints.video);
1313
            logging('chrome: ' + JSON.stringify(constraints));
1314
            return func(constraints);
1315
          });
1316
        }
1317
      }
1318
      constraints.video = constraintsToChrome_(constraints.video);
1319
    }
1320
    logging('chrome: ' + JSON.stringify(constraints));
1321
    return func(constraints);
1322
  };
1323
1324
  var shimError_ = function(e) {
1325
    return {
1326
      name: {
1327
        PermissionDeniedError: 'NotAllowedError',
1328
        InvalidStateError: 'NotReadableError',
1329
        DevicesNotFoundError: 'NotFoundError',
1330
        ConstraintNotSatisfiedError: 'OverconstrainedError',
1331
        TrackStartError: 'NotReadableError',
1332
        MediaDeviceFailedDueToShutdown: 'NotReadableError',
1333
        MediaDeviceKillSwitchOn: 'NotReadableError'
1334
      }[e.name] || e.name,
1335
      message: e.message,
1336
      constraint: e.constraintName,
1337
      toString: function() {
1338
        return this.name + (this.message && ': ') + this.message;
1339
      }
1340
    };
1341
  };
1342
1343
  var getUserMedia_ = function(constraints, onSuccess, onError) {
1344
    shimConstraints_(constraints, function(c) {
1345
      navigator.webkitGetUserMedia(c, onSuccess, function(e) {
1346
        onError(shimError_(e));
1347
      });
1348
    });
1349
  };
1350
1351
  navigator.getUserMedia = getUserMedia_;
1352
1353
  // Returns the result of getUserMedia as a Promise.
1354
  var getUserMediaPromise_ = function(constraints) {
1355
    return new Promise(function(resolve, reject) {
1356
      navigator.getUserMedia(constraints, resolve, reject);
1357
    });
1358
  };
1359
1360
  if (!navigator.mediaDevices) {
1361
    navigator.mediaDevices = {
1362
      getUserMedia: getUserMediaPromise_,
1363
      enumerateDevices: function() {
1364
        return new Promise(function(resolve) {
1365
          var kinds = {audio: 'audioinput', video: 'videoinput'};
1366
          return window.MediaStreamTrack.getSources(function(devices) {
1367
            resolve(devices.map(function(device) {
1368
              return {label: device.label,
1369
                kind: kinds[device.kind],
1370
                deviceId: device.id,
1371
                groupId: ''};
1372
            }));
1373
          });
1374
        });
1375
      },
1376
      getSupportedConstraints: function() {
1377
        return {
1378
          deviceId: true, echoCancellation: true, facingMode: true,
1379
          frameRate: true, height: true, width: true
1380
        };
1381
      }
1382
    };
1383
  }
1384
1385
  // A shim for getUserMedia method on the mediaDevices object.
1386
  // TODO(KaptenJansson) remove once implemented in Chrome stable.
1387
  if (!navigator.mediaDevices.getUserMedia) {
1388
    navigator.mediaDevices.getUserMedia = function(constraints) {
1389
      return getUserMediaPromise_(constraints);
1390
    };
1391
  } else {
1392
    // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
1393
    // function which returns a Promise, it does not accept spec-style
1394
    // constraints.
1395
    var origGetUserMedia = navigator.mediaDevices.getUserMedia.
1396
        bind(navigator.mediaDevices);
1397
    navigator.mediaDevices.getUserMedia = function(cs) {
1398
      return shimConstraints_(cs, function(c) {
1399
        return origGetUserMedia(c).then(function(stream) {
1400
          if (c.audio && !stream.getAudioTracks().length ||
1401
              c.video && !stream.getVideoTracks().length) {
1402
            stream.getTracks().forEach(function(track) {
1403
              track.stop();
1404
            });
1405
            throw new DOMException('', 'NotFoundError');
0 ignored issues
show
Bug introduced by
The variable DOMException seems to be never declared. If this is a global, consider adding a /** global: DOMException */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1406
          }
1407
          return stream;
1408
        }, function(e) {
1409
          return Promise.reject(shimError_(e));
1410
        });
1411
      });
1412
    };
1413
  }
1414
1415
  // Dummy devicechange event methods.
1416
  // TODO(KaptenJansson) remove once implemented in Chrome stable.
1417
  if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
1418
    navigator.mediaDevices.addEventListener = function() {
1419
      logging('Dummy mediaDevices.addEventListener called.');
1420
    };
1421
  }
1422
  if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
1423
    navigator.mediaDevices.removeEventListener = function() {
1424
      logging('Dummy mediaDevices.removeEventListener called.');
1425
    };
1426
  }
1427
};
1428
1429
},{"../utils.js":12}],6:[function(require,module,exports){
1430
/*
1431
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
1432
 *
1433
 *  Use of this source code is governed by a BSD-style license
1434
 *  that can be found in the LICENSE file in the root of the source
1435
 *  tree.
1436
 */
1437
 /* eslint-env node */
1438
'use strict';
1439
1440
var utils = require('../utils');
1441
var shimRTCPeerConnection = require('./rtcpeerconnection_shim');
1442
1443
module.exports = {
1444
  shimGetUserMedia: require('./getusermedia'),
1445
  shimPeerConnection: function(window) {
1446
    var browserDetails = utils.detectBrowser(window);
1447
1448
    if (window.RTCIceGatherer) {
1449
      // ORTC defines an RTCIceCandidate object but no constructor.
1450
      // Not implemented in Edge.
1451
      if (!window.RTCIceCandidate) {
1452
        window.RTCIceCandidate = function(args) {
1453
          return args;
1454
        };
1455
      }
1456
      // ORTC does not have a session description object but
1457
      // other browsers (i.e. Chrome) that will support both PC and ORTC
1458
      // in the future might have this defined already.
1459
      if (!window.RTCSessionDescription) {
1460
        window.RTCSessionDescription = function(args) {
1461
          return args;
1462
        };
1463
      }
1464
      // this adds an additional event listener to MediaStrackTrack that signals
1465
      // when a tracks enabled property was changed. Workaround for a bug in
1466
      // addStream, see below. No longer required in 15025+
1467
      if (browserDetails.version < 15025) {
1468
        var origMSTEnabled = Object.getOwnPropertyDescriptor(
1469
            window.MediaStreamTrack.prototype, 'enabled');
1470
        Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', {
1471
          set: function(value) {
1472
            origMSTEnabled.set.call(this, value);
1473
            var ev = new Event('enabled');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1474
            ev.enabled = value;
1475
            this.dispatchEvent(ev);
1476
          }
1477
        });
1478
      }
1479
    }
1480
    window.RTCPeerConnection =
1481
        shimRTCPeerConnection(window, browserDetails.version);
1482
  },
1483
  shimReplaceTrack: function(window) {
1484
    // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
1485
    if (window.RTCRtpSender &&
1486
        !('replaceTrack' in window.RTCRtpSender.prototype)) {
1487
      window.RTCRtpSender.prototype.replaceTrack =
1488
          window.RTCRtpSender.prototype.setTrack;
1489
    }
1490
  }
1491
};
1492
1493
},{"../utils":12,"./getusermedia":7,"./rtcpeerconnection_shim":8}],7:[function(require,module,exports){
1494
/*
1495
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
1496
 *
1497
 *  Use of this source code is governed by a BSD-style license
1498
 *  that can be found in the LICENSE file in the root of the source
1499
 *  tree.
1500
 */
1501
 /* eslint-env node */
1502
'use strict';
1503
1504
// Expose public methods.
1505
module.exports = function(window) {
1506
  var navigator = window && window.navigator;
1507
1508
  var shimError_ = function(e) {
1509
    return {
1510
      name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name,
1511
      message: e.message,
1512
      constraint: e.constraint,
1513
      toString: function() {
1514
        return this.name;
1515
      }
1516
    };
1517
  };
1518
1519
  // getUserMedia error shim.
1520
  var origGetUserMedia = navigator.mediaDevices.getUserMedia.
1521
      bind(navigator.mediaDevices);
1522
  navigator.mediaDevices.getUserMedia = function(c) {
1523
    return origGetUserMedia(c).catch(function(e) {
1524
      return Promise.reject(shimError_(e));
1525
    });
1526
  };
1527
};
1528
1529
},{}],8:[function(require,module,exports){
1530
/*
1531
 *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
1532
 *
1533
 *  Use of this source code is governed by a BSD-style license
1534
 *  that can be found in the LICENSE file in the root of the source
1535
 *  tree.
1536
 */
1537
 /* eslint-env node */
1538
'use strict';
1539
1540
var SDPUtils = require('sdp');
1541
1542
// sort tracks such that they follow an a-v-a-v...
1543
// pattern.
1544
function sortTracks(tracks) {
1545
  var audioTracks = tracks.filter(function(track) {
1546
    return track.kind === 'audio';
1547
  });
1548
  var videoTracks = tracks.filter(function(track) {
1549
    return track.kind === 'video';
1550
  });
1551
  tracks = [];
1552
  while (audioTracks.length || videoTracks.length) {
1553
    if (audioTracks.length) {
1554
      tracks.push(audioTracks.shift());
1555
    }
1556
    if (videoTracks.length) {
1557
      tracks.push(videoTracks.shift());
1558
    }
1559
  }
1560
  return tracks;
1561
}
1562
1563
// Edge does not like
1564
// 1) stun:
1565
// 2) turn: that does not have all of turn:host:port?transport=udp
1566
// 3) turn: with ipv6 addresses
1567
// 4) turn: occurring muliple times
1568
function filterIceServers(iceServers, edgeVersion) {
1569
  var hasTurn = false;
1570
  iceServers = JSON.parse(JSON.stringify(iceServers));
1571
  return iceServers.filter(function(server) {
1572
    if (server && (server.urls || server.url)) {
1573
      var urls = server.urls || server.url;
1574
      if (server.url && !server.urls) {
1575
        console.warn('RTCIceServer.url is deprecated! Use urls instead.');
1576
      }
1577
      var isString = typeof urls === 'string';
1578
      if (isString) {
1579
        urls = [urls];
1580
      }
1581
      urls = urls.filter(function(url) {
1582
        var validTurn = url.indexOf('turn:') === 0 &&
1583
            url.indexOf('transport=udp') !== -1 &&
1584
            url.indexOf('turn:[') === -1 &&
1585
            !hasTurn;
1586
1587
        if (validTurn) {
1588
          hasTurn = true;
1589
          return true;
1590
        }
1591
        return url.indexOf('stun:') === 0 && edgeVersion >= 14393;
1592
      });
1593
1594
      delete server.url;
1595
      server.urls = isString ? urls[0] : urls;
1596
      return !!urls.length;
1597
    }
1598
    return false;
1599
  });
1600
}
1601
1602
// Determines the intersection of local and remote capabilities.
1603
function getCommonCapabilities(localCapabilities, remoteCapabilities) {
1604
  var commonCapabilities = {
1605
    codecs: [],
1606
    headerExtensions: [],
1607
    fecMechanisms: []
1608
  };
1609
1610
  var findCodecByPayloadType = function(pt, codecs) {
1611
    pt = parseInt(pt, 10);
1612
    for (var i = 0; i < codecs.length; i++) {
1613
      if (codecs[i].payloadType === pt ||
1614
          codecs[i].preferredPayloadType === pt) {
1615
        return codecs[i];
1616
      }
1617
    }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1618
  };
1619
1620
  var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
1621
    var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
1622
    var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
1623
    return lCodec && rCodec &&
1624
        lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
1625
  };
1626
1627
  localCapabilities.codecs.forEach(function(lCodec) {
1628
    for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
1629
      var rCodec = remoteCapabilities.codecs[i];
1630
      if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
1631
          lCodec.clockRate === rCodec.clockRate) {
1632
        if (lCodec.name.toLowerCase() === 'rtx' &&
1633
            lCodec.parameters && rCodec.parameters.apt) {
1634
          // for RTX we need to find the local rtx that has a apt
1635
          // which points to the same local codec as the remote one.
1636
          if (!rtxCapabilityMatches(lCodec, rCodec,
1637
              localCapabilities.codecs, remoteCapabilities.codecs)) {
1638
            continue;
1639
          }
1640
        }
1641
        rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
1642
        // number of channels is the highest common number of channels
1643
        rCodec.numChannels = Math.min(lCodec.numChannels,
1644
            rCodec.numChannels);
1645
        // push rCodec so we reply with offerer payload type
1646
        commonCapabilities.codecs.push(rCodec);
1647
1648
        // determine common feedback mechanisms
1649
        rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {
1650
          for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
1651
            if (lCodec.rtcpFeedback[j].type === fb.type &&
1652
                lCodec.rtcpFeedback[j].parameter === fb.parameter) {
1653
              return true;
1654
            }
1655
          }
1656
          return false;
1657
        });
1658
        // FIXME: also need to determine .parameters
1659
        //  see https://github.com/openpeer/ortc/issues/569
1660
        break;
1661
      }
1662
    }
1663
  });
1664
1665
  localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {
1666
    for (var i = 0; i < remoteCapabilities.headerExtensions.length;
1667
         i++) {
1668
      var rHeaderExtension = remoteCapabilities.headerExtensions[i];
1669
      if (lHeaderExtension.uri === rHeaderExtension.uri) {
1670
        commonCapabilities.headerExtensions.push(rHeaderExtension);
1671
        break;
1672
      }
1673
    }
1674
  });
1675
1676
  // FIXME: fecMechanisms
1677
  return commonCapabilities;
1678
}
1679
1680
// is action=setLocalDescription with type allowed in signalingState
1681
function isActionAllowedInSignalingState(action, type, signalingState) {
1682
  return {
1683
    offer: {
1684
      setLocalDescription: ['stable', 'have-local-offer'],
1685
      setRemoteDescription: ['stable', 'have-remote-offer']
1686
    },
1687
    answer: {
1688
      setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],
1689
      setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']
1690
    }
1691
  }[type][action].indexOf(signalingState) !== -1;
1692
}
1693
1694
module.exports = function(window, edgeVersion) {
1695
  var RTCPeerConnection = function(config) {
1696
    var self = this;
1697
1698
    var _eventTarget = document.createDocumentFragment();
1699
    ['addEventListener', 'removeEventListener', 'dispatchEvent']
1700
        .forEach(function(method) {
1701
          self[method] = _eventTarget[method].bind(_eventTarget);
1702
        });
1703
1704
    this.needNegotiation = false;
1705
1706
    this.onicecandidate = null;
1707
    this.onaddstream = null;
1708
    this.ontrack = null;
1709
    this.onremovestream = null;
1710
    this.onsignalingstatechange = null;
1711
    this.oniceconnectionstatechange = null;
1712
    this.onicegatheringstatechange = null;
1713
    this.onnegotiationneeded = null;
1714
    this.ondatachannel = null;
1715
    this.canTrickleIceCandidates = null;
1716
1717
    this.localStreams = [];
1718
    this.remoteStreams = [];
1719
    this.getLocalStreams = function() {
1720
      return self.localStreams;
1721
    };
1722
    this.getRemoteStreams = function() {
1723
      return self.remoteStreams;
1724
    };
1725
1726
    this.localDescription = new window.RTCSessionDescription({
1727
      type: '',
1728
      sdp: ''
1729
    });
1730
    this.remoteDescription = new window.RTCSessionDescription({
1731
      type: '',
1732
      sdp: ''
1733
    });
1734
    this.signalingState = 'stable';
1735
    this.iceConnectionState = 'new';
1736
    this.iceGatheringState = 'new';
1737
1738
    this.iceOptions = {
1739
      gatherPolicy: 'all',
1740
      iceServers: []
1741
    };
1742
    if (config && config.iceTransportPolicy) {
1743
      switch (config.iceTransportPolicy) {
1744
        case 'all':
1745
        case 'relay':
1746
          this.iceOptions.gatherPolicy = config.iceTransportPolicy;
1747
          break;
1748
        default:
1749
          // don't set iceTransportPolicy.
1750
          break;
1751
      }
1752
    }
1753
    this.usingBundle = config && config.bundlePolicy === 'max-bundle';
1754
1755
    if (config && config.iceServers) {
1756
      this.iceOptions.iceServers = filterIceServers(config.iceServers,
1757
          edgeVersion);
1758
    }
1759
    this._config = config || {};
1760
1761
    // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
1762
    // everything that is needed to describe a SDP m-line.
1763
    this.transceivers = [];
1764
1765
    // since the iceGatherer is currently created in createOffer but we
1766
    // must not emit candidates until after setLocalDescription we buffer
1767
    // them in this array.
1768
    this._localIceCandidatesBuffer = [];
1769
  };
1770
1771
  RTCPeerConnection.prototype._emitGatheringStateChange = function() {
1772
    var event = new Event('icegatheringstatechange');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1773
    this.dispatchEvent(event);
1774
    if (this.onicegatheringstatechange !== null) {
1775
      this.onicegatheringstatechange(event);
1776
    }
1777
  };
1778
1779
  RTCPeerConnection.prototype._emitBufferedCandidates = function() {
1780
    var self = this;
1781
    var sections = SDPUtils.splitSections(self.localDescription.sdp);
1782
    // FIXME: need to apply ice candidates in a way which is async but
1783
    // in-order
1784
    this._localIceCandidatesBuffer.forEach(function(event) {
1785
      var end = !event.candidate || Object.keys(event.candidate).length === 0;
1786
      if (end) {
1787
        for (var j = 1; j < sections.length; j++) {
1788
          if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
1789
            sections[j] += 'a=end-of-candidates\r\n';
1790
          }
1791
        }
1792
      } else {
1793
        sections[event.candidate.sdpMLineIndex + 1] +=
1794
            'a=' + event.candidate.candidate + '\r\n';
1795
      }
1796
      self.localDescription.sdp = sections.join('');
1797
      self.dispatchEvent(event);
1798
      if (self.onicecandidate !== null) {
1799
        self.onicecandidate(event);
1800
      }
1801
      if (!event.candidate && self.iceGatheringState !== 'complete') {
1802
        var complete = self.transceivers.every(function(transceiver) {
1803
          return transceiver.iceGatherer &&
1804
              transceiver.iceGatherer.state === 'completed';
1805
        });
1806
        if (complete && self.iceGatheringStateChange !== 'complete') {
1807
          self.iceGatheringState = 'complete';
1808
          self._emitGatheringStateChange();
1809
        }
1810
      }
1811
    });
1812
    this._localIceCandidatesBuffer = [];
1813
  };
1814
1815
  RTCPeerConnection.prototype.getConfiguration = function() {
1816
    return this._config;
1817
  };
1818
1819
  // internal helper to create a transceiver object.
1820
  // (whih is not yet the same as the WebRTC 1.0 transceiver)
1821
  RTCPeerConnection.prototype._createTransceiver = function(kind) {
1822
    var hasBundleTransport = this.transceivers.length > 0;
1823
    var transceiver = {
1824
      track: null,
1825
      iceGatherer: null,
1826
      iceTransport: null,
1827
      dtlsTransport: null,
1828
      localCapabilities: null,
1829
      remoteCapabilities: null,
1830
      rtpSender: null,
1831
      rtpReceiver: null,
1832
      kind: kind,
1833
      mid: null,
1834
      sendEncodingParameters: null,
1835
      recvEncodingParameters: null,
1836
      stream: null,
1837
      wantReceive: true
1838
    };
1839
    if (this.usingBundle && hasBundleTransport) {
1840
      transceiver.iceTransport = this.transceivers[0].iceTransport;
1841
      transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
1842
    } else {
1843
      var transports = this._createIceAndDtlsTransports();
1844
      transceiver.iceTransport = transports.iceTransport;
1845
      transceiver.dtlsTransport = transports.dtlsTransport;
1846
    }
1847
    this.transceivers.push(transceiver);
1848
    return transceiver;
1849
  };
1850
1851
  RTCPeerConnection.prototype.addTrack = function(track, stream) {
1852
    var transceiver;
1853
    for (var i = 0; i < this.transceivers.length; i++) {
1854
      if (!this.transceivers[i].track &&
1855
          this.transceivers[i].kind === track.kind) {
1856
        transceiver = this.transceivers[i];
1857
      }
1858
    }
1859
    if (!transceiver) {
1860
      transceiver = this._createTransceiver(track.kind);
1861
    }
1862
1863
    transceiver.track = track;
1864
    transceiver.stream = stream;
1865
    transceiver.rtpSender = new window.RTCRtpSender(track,
1866
        transceiver.dtlsTransport);
1867
1868
    this._maybeFireNegotiationNeeded();
1869
    return transceiver.rtpSender;
1870
  };
1871
1872
  RTCPeerConnection.prototype.addStream = function(stream) {
1873
    var self = this;
1874
    if (edgeVersion >= 15025) {
1875
      this.localStreams.push(stream);
1876
      stream.getTracks().forEach(function(track) {
1877
        self.addTrack(track, stream);
1878
      });
1879
    } else {
1880
      // Clone is necessary for local demos mostly, attaching directly
1881
      // to two different senders does not work (build 10547).
1882
      // Fixed in 15025 (or earlier)
1883
      var clonedStream = stream.clone();
1884
      stream.getTracks().forEach(function(track, idx) {
1885
        var clonedTrack = clonedStream.getTracks()[idx];
1886
        track.addEventListener('enabled', function(event) {
1887
          clonedTrack.enabled = event.enabled;
1888
        });
1889
      });
1890
      clonedStream.getTracks().forEach(function(track) {
1891
        self.addTrack(track, clonedStream);
1892
      });
1893
      this.localStreams.push(clonedStream);
1894
    }
1895
    this._maybeFireNegotiationNeeded();
1896
  };
1897
1898
  RTCPeerConnection.prototype.removeStream = function(stream) {
1899
    var idx = this.localStreams.indexOf(stream);
1900
    if (idx > -1) {
1901
      this.localStreams.splice(idx, 1);
1902
      this._maybeFireNegotiationNeeded();
1903
    }
1904
  };
1905
1906
  RTCPeerConnection.prototype.getSenders = function() {
1907
    return this.transceivers.filter(function(transceiver) {
1908
      return !!transceiver.rtpSender;
1909
    })
1910
    .map(function(transceiver) {
1911
      return transceiver.rtpSender;
1912
    });
1913
  };
1914
1915
  RTCPeerConnection.prototype.getReceivers = function() {
1916
    return this.transceivers.filter(function(transceiver) {
1917
      return !!transceiver.rtpReceiver;
1918
    })
1919
    .map(function(transceiver) {
1920
      return transceiver.rtpReceiver;
1921
    });
1922
  };
1923
1924
  // Create ICE gatherer and hook it up.
1925
  RTCPeerConnection.prototype._createIceGatherer = function(mid,
1926
      sdpMLineIndex) {
1927
    var self = this;
1928
    var iceGatherer = new window.RTCIceGatherer(self.iceOptions);
1929
    iceGatherer.onlocalcandidate = function(evt) {
1930
      var event = new Event('icecandidate');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1931
      event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
1932
1933
      var cand = evt.candidate;
1934
      var end = !cand || Object.keys(cand).length === 0;
1935
      // Edge emits an empty object for RTCIceCandidateComplete‥
1936
      if (end) {
1937
        // polyfill since RTCIceGatherer.state is not implemented in
1938
        // Edge 10547 yet.
1939
        if (iceGatherer.state === undefined) {
1940
          iceGatherer.state = 'completed';
1941
        }
1942
      } else {
1943
        // RTCIceCandidate doesn't have a component, needs to be added
1944
        cand.component = 1;
1945
        event.candidate.candidate = SDPUtils.writeCandidate(cand);
1946
      }
1947
1948
      // update local description.
1949
      var sections = SDPUtils.splitSections(self.localDescription.sdp);
1950
      if (!end) {
1951
        sections[event.candidate.sdpMLineIndex + 1] +=
1952
            'a=' + event.candidate.candidate + '\r\n';
1953
      } else {
1954
        sections[event.candidate.sdpMLineIndex + 1] +=
1955
            'a=end-of-candidates\r\n';
1956
      }
1957
      self.localDescription.sdp = sections.join('');
1958
      var transceivers = self._pendingOffer ? self._pendingOffer :
1959
          self.transceivers;
1960
      var complete = transceivers.every(function(transceiver) {
1961
        return transceiver.iceGatherer &&
1962
            transceiver.iceGatherer.state === 'completed';
1963
      });
1964
1965
      // Emit candidate if localDescription is set.
1966
      // Also emits null candidate when all gatherers are complete.
1967
      switch (self.iceGatheringState) {
1968
        case 'new':
1969
          if (!end) {
1970
            self._localIceCandidatesBuffer.push(event);
1971
          }
1972
          if (end && complete) {
1973
            self._localIceCandidatesBuffer.push(
1974
                new Event('icecandidate'));
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1975
          }
1976
          break;
1977
        case 'gathering':
1978
          self._emitBufferedCandidates();
1979
          if (!end) {
1980
            self.dispatchEvent(event);
1981
            if (self.onicecandidate !== null) {
1982
              self.onicecandidate(event);
1983
            }
1984
          }
1985
          if (complete) {
1986
            self.dispatchEvent(new Event('icecandidate'));
1987
            if (self.onicecandidate !== null) {
1988
              self.onicecandidate(new Event('icecandidate'));
1989
            }
1990
            self.iceGatheringState = 'complete';
1991
            self._emitGatheringStateChange();
1992
          }
1993
          break;
1994
        case 'complete':
1995
          // should not happen... currently!
1996
          break;
1997
        default: // no-op.
1998
          break;
1999
      }
2000
    };
2001
    return iceGatherer;
2002
  };
2003
2004
  // Create ICE transport and DTLS transport.
2005
  RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
2006
    var self = this;
2007
    var iceTransport = new window.RTCIceTransport(null);
2008
    iceTransport.onicestatechange = function() {
2009
      self._updateConnectionState();
2010
    };
2011
2012
    var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
2013
    dtlsTransport.ondtlsstatechange = function() {
2014
      self._updateConnectionState();
2015
    };
2016
    dtlsTransport.onerror = function() {
2017
      // onerror does not set state to failed by itself.
2018
      Object.defineProperty(dtlsTransport, 'state',
2019
          {value: 'failed', writable: true});
2020
      self._updateConnectionState();
2021
    };
2022
2023
    return {
2024
      iceTransport: iceTransport,
2025
      dtlsTransport: dtlsTransport
2026
    };
2027
  };
2028
2029
  // Destroy ICE gatherer, ICE transport and DTLS transport.
2030
  // Without triggering the callbacks.
2031
  RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
2032
      sdpMLineIndex) {
2033
    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
2034
    if (iceGatherer) {
2035
      delete iceGatherer.onlocalcandidate;
2036
      delete this.transceivers[sdpMLineIndex].iceGatherer;
2037
    }
2038
    var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
2039
    if (iceTransport) {
2040
      delete iceTransport.onicestatechange;
2041
      delete this.transceivers[sdpMLineIndex].iceTransport;
2042
    }
2043
    var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
2044
    if (dtlsTransport) {
2045
      delete dtlsTransport.ondtlssttatechange;
2046
      delete dtlsTransport.onerror;
2047
      delete this.transceivers[sdpMLineIndex].dtlsTransport;
2048
    }
2049
  };
2050
2051
  // Start the RTP Sender and Receiver for a transceiver.
2052
  RTCPeerConnection.prototype._transceive = function(transceiver,
2053
      send, recv) {
2054
    var params = getCommonCapabilities(transceiver.localCapabilities,
2055
        transceiver.remoteCapabilities);
2056
    if (send && transceiver.rtpSender) {
2057
      params.encodings = transceiver.sendEncodingParameters;
2058
      params.rtcp = {
2059
        cname: SDPUtils.localCName,
2060
        compound: transceiver.rtcpParameters.compound
2061
      };
2062
      if (transceiver.recvEncodingParameters.length) {
2063
        params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
2064
      }
2065
      transceiver.rtpSender.send(params);
2066
    }
2067
    if (recv && transceiver.rtpReceiver) {
2068
      // remove RTX field in Edge 14942
2069
      if (transceiver.kind === 'video'
2070
          && transceiver.recvEncodingParameters
2071
          && edgeVersion < 15019) {
2072
        transceiver.recvEncodingParameters.forEach(function(p) {
2073
          delete p.rtx;
2074
        });
2075
      }
2076
      params.encodings = transceiver.recvEncodingParameters;
2077
      params.rtcp = {
2078
        cname: transceiver.rtcpParameters.cname,
2079
        compound: transceiver.rtcpParameters.compound
2080
      };
2081
      if (transceiver.sendEncodingParameters.length) {
2082
        params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
2083
      }
2084
      transceiver.rtpReceiver.receive(params);
2085
    }
2086
  };
2087
2088
  RTCPeerConnection.prototype.setLocalDescription = function(description) {
2089
    var self = this;
2090
2091
    if (!isActionAllowedInSignalingState('setLocalDescription',
2092
        description.type, this.signalingState)) {
2093
      var e = new Error('Can not set local ' + description.type +
2094
          ' in state ' + this.signalingState);
2095
      e.name = 'InvalidStateError';
2096
      if (arguments.length > 2 && typeof arguments[2] === 'function') {
2097
        window.setTimeout(arguments[2], 0, e);
2098
      }
2099
      return Promise.reject(e);
2100
    }
2101
2102
    var sections;
2103
    var sessionpart;
2104
    if (description.type === 'offer') {
2105
      // FIXME: What was the purpose of this empty if statement?
2106
      // if (!this._pendingOffer) {
2107
      // } else {
2108
      if (this._pendingOffer) {
2109
        // VERY limited support for SDP munging. Limited to:
2110
        // * changing the order of codecs
2111
        sections = SDPUtils.splitSections(description.sdp);
2112
        sessionpart = sections.shift();
0 ignored issues
show
Unused Code introduced by
The assignment to variable sessionpart seems to be never used. Consider removing it.
Loading history...
2113
        sections.forEach(function(mediaSection, sdpMLineIndex) {
2114
          var caps = SDPUtils.parseRtpParameters(mediaSection);
2115
          self._pendingOffer[sdpMLineIndex].localCapabilities = caps;
2116
        });
2117
        this.transceivers = this._pendingOffer;
2118
        delete this._pendingOffer;
2119
      }
2120
    } else if (description.type === 'answer') {
2121
      sections = SDPUtils.splitSections(self.remoteDescription.sdp);
2122
      sessionpart = sections.shift();
2123
      var isIceLite = SDPUtils.matchPrefix(sessionpart,
2124
          'a=ice-lite').length > 0;
2125
      sections.forEach(function(mediaSection, sdpMLineIndex) {
2126
        var transceiver = self.transceivers[sdpMLineIndex];
2127
        var iceGatherer = transceiver.iceGatherer;
2128
        var iceTransport = transceiver.iceTransport;
2129
        var dtlsTransport = transceiver.dtlsTransport;
2130
        var localCapabilities = transceiver.localCapabilities;
2131
        var remoteCapabilities = transceiver.remoteCapabilities;
2132
2133
        var rejected = SDPUtils.isRejected(mediaSection);
2134
2135
        if (!rejected && !transceiver.isDatachannel) {
2136
          var remoteIceParameters = SDPUtils.getIceParameters(
2137
              mediaSection, sessionpart);
2138
          var remoteDtlsParameters = SDPUtils.getDtlsParameters(
2139
              mediaSection, sessionpart);
2140
          if (isIceLite) {
2141
            remoteDtlsParameters.role = 'server';
2142
          }
2143
2144
          if (!self.usingBundle || sdpMLineIndex === 0) {
2145
            iceTransport.start(iceGatherer, remoteIceParameters,
2146
                isIceLite ? 'controlling' : 'controlled');
2147
            dtlsTransport.start(remoteDtlsParameters);
2148
          }
2149
2150
          // Calculate intersection of capabilities.
2151
          var params = getCommonCapabilities(localCapabilities,
2152
              remoteCapabilities);
2153
2154
          // Start the RTCRtpSender. The RTCRtpReceiver for this
2155
          // transceiver has already been started in setRemoteDescription.
2156
          self._transceive(transceiver,
2157
              params.codecs.length > 0,
2158
              false);
2159
        }
2160
      });
2161
    }
2162
2163
    this.localDescription = {
2164
      type: description.type,
2165
      sdp: description.sdp
2166
    };
2167
    switch (description.type) {
2168
      case 'offer':
2169
        this._updateSignalingState('have-local-offer');
2170
        break;
2171
      case 'answer':
2172
        this._updateSignalingState('stable');
2173
        break;
2174
      default:
2175
        throw new TypeError('unsupported type "' + description.type +
2176
            '"');
2177
    }
2178
2179
    // If a success callback was provided, emit ICE candidates after it
2180
    // has been executed. Otherwise, emit callback after the Promise is
2181
    // resolved.
2182
    var hasCallback = arguments.length > 1 &&
2183
      typeof arguments[1] === 'function';
2184
    if (hasCallback) {
2185
      var cb = arguments[1];
2186
      window.setTimeout(function() {
2187
        cb();
2188
        if (self.iceGatheringState === 'new') {
2189
          self.iceGatheringState = 'gathering';
2190
          self._emitGatheringStateChange();
2191
        }
2192
        self._emitBufferedCandidates();
2193
      }, 0);
2194
    }
2195
    var p = Promise.resolve();
2196
    p.then(function() {
2197
      if (!hasCallback) {
2198
        if (self.iceGatheringState === 'new') {
2199
          self.iceGatheringState = 'gathering';
2200
          self._emitGatheringStateChange();
2201
        }
2202
        // Usually candidates will be emitted earlier.
2203
        window.setTimeout(self._emitBufferedCandidates.bind(self), 500);
2204
      }
2205
    });
2206
    return p;
2207
  };
2208
2209
  RTCPeerConnection.prototype.setRemoteDescription = function(description) {
2210
    var self = this;
2211
2212
    if (!isActionAllowedInSignalingState('setRemoteDescription',
2213
        description.type, this.signalingState)) {
2214
      var e = new Error('Can not set remote ' + description.type +
2215
          ' in state ' + this.signalingState);
2216
      e.name = 'InvalidStateError';
2217
      if (arguments.length > 2 && typeof arguments[2] === 'function') {
2218
        window.setTimeout(arguments[2], 0, e);
2219
      }
2220
      return Promise.reject(e);
2221
    }
2222
2223
    var streams = {};
2224
    var receiverList = [];
2225
    var sections = SDPUtils.splitSections(description.sdp);
2226
    var sessionpart = sections.shift();
2227
    var isIceLite = SDPUtils.matchPrefix(sessionpart,
2228
        'a=ice-lite').length > 0;
2229
    var usingBundle = SDPUtils.matchPrefix(sessionpart,
2230
        'a=group:BUNDLE ').length > 0;
2231
    this.usingBundle = usingBundle;
2232
    var iceOptions = SDPUtils.matchPrefix(sessionpart,
2233
        'a=ice-options:')[0];
2234
    if (iceOptions) {
2235
      this.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
2236
          .indexOf('trickle') >= 0;
2237
    } else {
2238
      this.canTrickleIceCandidates = false;
2239
    }
2240
2241
    sections.forEach(function(mediaSection, sdpMLineIndex) {
2242
      var lines = SDPUtils.splitLines(mediaSection);
2243
      var kind = SDPUtils.getKind(mediaSection);
2244
      var rejected = SDPUtils.isRejected(mediaSection);
2245
      var protocol = lines[0].substr(2).split(' ')[2];
2246
2247
      var direction = SDPUtils.getDirection(mediaSection, sessionpart);
2248
      var remoteMsid = SDPUtils.parseMsid(mediaSection);
2249
2250
      var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier();
2251
2252
      // Reject datachannels which are not implemented yet.
2253
      if (kind === 'application' && protocol === 'DTLS/SCTP') {
2254
        self.transceivers[sdpMLineIndex] = {
2255
          mid: mid,
2256
          isDatachannel: true
2257
        };
2258
        return;
2259
      }
2260
2261
      var transceiver;
2262
      var iceGatherer;
2263
      var iceTransport;
2264
      var dtlsTransport;
2265
      var rtpReceiver;
2266
      var sendEncodingParameters;
2267
      var recvEncodingParameters;
2268
      var localCapabilities;
2269
2270
      var track;
2271
      // FIXME: ensure the mediaSection has rtcp-mux set.
2272
      var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
2273
      var remoteIceParameters;
2274
      var remoteDtlsParameters;
2275
      if (!rejected) {
2276
        remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
2277
            sessionpart);
2278
        remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
2279
            sessionpart);
2280
        remoteDtlsParameters.role = 'client';
2281
      }
2282
      recvEncodingParameters =
2283
          SDPUtils.parseRtpEncodingParameters(mediaSection);
2284
2285
      var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
2286
2287
      var isComplete = SDPUtils.matchPrefix(mediaSection,
2288
          'a=end-of-candidates', sessionpart).length > 0;
2289
      var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
2290
          .map(function(cand) {
2291
            return SDPUtils.parseCandidate(cand);
2292
          })
2293
          .filter(function(cand) {
2294
            return cand.component === '1' || cand.component === 1;
2295
          });
2296
2297
      // Check if we can use BUNDLE and dispose transports.
2298
      if ((description.type === 'offer' || description.type === 'answer') &&
2299
          !rejected && usingBundle && sdpMLineIndex > 0 &&
2300
          self.transceivers[sdpMLineIndex]) {
2301
        self._disposeIceAndDtlsTransports(sdpMLineIndex);
2302
        self.transceivers[sdpMLineIndex].iceGatherer =
2303
            self.transceivers[0].iceGatherer;
2304
        self.transceivers[sdpMLineIndex].iceTransport =
2305
            self.transceivers[0].iceTransport;
2306
        self.transceivers[sdpMLineIndex].dtlsTransport =
2307
            self.transceivers[0].dtlsTransport;
2308
        if (self.transceivers[sdpMLineIndex].rtpSender) {
2309
          self.transceivers[sdpMLineIndex].rtpSender.setTransport(
2310
              self.transceivers[0].dtlsTransport);
2311
        }
2312
        if (self.transceivers[sdpMLineIndex].rtpReceiver) {
2313
          self.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
2314
              self.transceivers[0].dtlsTransport);
2315
        }
2316
      }
2317
      if (description.type === 'offer' && !rejected) {
2318
        transceiver = self.transceivers[sdpMLineIndex] ||
2319
            self._createTransceiver(kind);
2320
        transceiver.mid = mid;
2321
2322
        if (!transceiver.iceGatherer) {
2323
          transceiver.iceGatherer = usingBundle && sdpMLineIndex > 0 ?
2324
              self.transceivers[0].iceGatherer :
2325
              self._createIceGatherer(mid, sdpMLineIndex);
2326
        }
2327
2328
        if (isComplete && (!usingBundle || sdpMLineIndex === 0)) {
2329
          transceiver.iceTransport.setRemoteCandidates(cands);
2330
        }
2331
2332
        localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);
2333
2334
        // filter RTX until additional stuff needed for RTX is implemented
2335
        // in adapter.js
2336
        if (edgeVersion < 15019) {
2337
          localCapabilities.codecs = localCapabilities.codecs.filter(
2338
              function(codec) {
2339
                return codec.name !== 'rtx';
2340
              });
2341
        }
2342
2343
        sendEncodingParameters = [{
2344
          ssrc: (2 * sdpMLineIndex + 2) * 1001
2345
        }];
2346
2347
        if (direction === 'sendrecv' || direction === 'sendonly') {
2348
          rtpReceiver = new window.RTCRtpReceiver(transceiver.dtlsTransport,
2349
              kind);
2350
2351
          track = rtpReceiver.track;
2352
          // FIXME: does not work with Plan B.
2353
          if (remoteMsid) {
2354
            if (!streams[remoteMsid.stream]) {
2355
              streams[remoteMsid.stream] = new window.MediaStream();
2356
              Object.defineProperty(streams[remoteMsid.stream], 'id', {
2357
                get: function() {
2358
                  return remoteMsid.stream;
2359
                }
2360
              });
2361
            }
2362
            Object.defineProperty(track, 'id', {
2363
              get: function() {
2364
                return remoteMsid.track;
2365
              }
2366
            });
2367
            streams[remoteMsid.stream].addTrack(track);
2368
            receiverList.push([track, rtpReceiver,
2369
              streams[remoteMsid.stream]]);
2370
          } else {
2371
            if (!streams.default) {
2372
              streams.default = new window.MediaStream();
2373
            }
2374
            streams.default.addTrack(track);
2375
            receiverList.push([track, rtpReceiver, streams.default]);
2376
          }
2377
        }
2378
2379
        transceiver.localCapabilities = localCapabilities;
2380
        transceiver.remoteCapabilities = remoteCapabilities;
2381
        transceiver.rtpReceiver = rtpReceiver;
0 ignored issues
show
Bug introduced by
The variable rtpReceiver does not seem to be initialized in case direction === "sendrecv"...irection === "sendonly" on line 2347 is false. Are you sure this can never be the case?
Loading history...
2382
        transceiver.rtcpParameters = rtcpParameters;
2383
        transceiver.sendEncodingParameters = sendEncodingParameters;
2384
        transceiver.recvEncodingParameters = recvEncodingParameters;
2385
2386
        // Start the RTCRtpReceiver now. The RTPSender is started in
2387
        // setLocalDescription.
2388
        self._transceive(self.transceivers[sdpMLineIndex],
2389
            false,
2390
            direction === 'sendrecv' || direction === 'sendonly');
2391
      } else if (description.type === 'answer' && !rejected) {
2392
        transceiver = self.transceivers[sdpMLineIndex];
2393
        iceGatherer = transceiver.iceGatherer;
2394
        iceTransport = transceiver.iceTransport;
2395
        dtlsTransport = transceiver.dtlsTransport;
2396
        rtpReceiver = transceiver.rtpReceiver;
2397
        sendEncodingParameters = transceiver.sendEncodingParameters;
0 ignored issues
show
Unused Code introduced by
The assignment to variable sendEncodingParameters seems to be never used. Consider removing it.
Loading history...
2398
        localCapabilities = transceiver.localCapabilities;
0 ignored issues
show
Unused Code introduced by
The assignment to variable localCapabilities seems to be never used. Consider removing it.
Loading history...
2399
2400
        self.transceivers[sdpMLineIndex].recvEncodingParameters =
2401
            recvEncodingParameters;
2402
        self.transceivers[sdpMLineIndex].remoteCapabilities =
2403
            remoteCapabilities;
2404
        self.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
2405
2406
        if ((isIceLite || isComplete) && cands.length) {
2407
          iceTransport.setRemoteCandidates(cands);
2408
        }
2409
        if (!usingBundle || sdpMLineIndex === 0) {
2410
          iceTransport.start(iceGatherer, remoteIceParameters,
0 ignored issues
show
Bug introduced by
The variable remoteIceParameters does not seem to be initialized in case !rejected on line 2275 is false. Are you sure the function start handles undefined variables?
Loading history...
2411
              'controlling');
2412
          dtlsTransport.start(remoteDtlsParameters);
0 ignored issues
show
Bug introduced by
The variable remoteDtlsParameters does not seem to be initialized in case !rejected on line 2275 is false. Are you sure the function start handles undefined variables?
Loading history...
2413
        }
2414
2415
        self._transceive(transceiver,
2416
            direction === 'sendrecv' || direction === 'recvonly',
2417
            direction === 'sendrecv' || direction === 'sendonly');
2418
2419
        if (rtpReceiver &&
2420
            (direction === 'sendrecv' || direction === 'sendonly')) {
2421
          track = rtpReceiver.track;
2422
          if (remoteMsid) {
2423
            if (!streams[remoteMsid.stream]) {
2424
              streams[remoteMsid.stream] = new window.MediaStream();
2425
            }
2426
            streams[remoteMsid.stream].addTrack(track);
2427
            receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);
2428
          } else {
2429
            if (!streams.default) {
2430
              streams.default = new window.MediaStream();
2431
            }
2432
            streams.default.addTrack(track);
2433
            receiverList.push([track, rtpReceiver, streams.default]);
2434
          }
2435
        } else {
2436
          // FIXME: actually the receiver should be created later.
2437
          delete transceiver.rtpReceiver;
2438
        }
2439
      }
2440
    });
2441
2442
    this.remoteDescription = {
2443
      type: description.type,
2444
      sdp: description.sdp
2445
    };
2446
    switch (description.type) {
2447
      case 'offer':
2448
        this._updateSignalingState('have-remote-offer');
2449
        break;
2450
      case 'answer':
2451
        this._updateSignalingState('stable');
2452
        break;
2453
      default:
2454
        throw new TypeError('unsupported type "' + description.type +
2455
            '"');
2456
    }
2457
    Object.keys(streams).forEach(function(sid) {
2458
      var stream = streams[sid];
2459
      if (stream.getTracks().length) {
2460
        self.remoteStreams.push(stream);
2461
        var event = new Event('addstream');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2462
        event.stream = stream;
2463
        self.dispatchEvent(event);
2464
        if (self.onaddstream !== null) {
2465
          window.setTimeout(function() {
2466
            self.onaddstream(event);
2467
          }, 0);
2468
        }
2469
2470
        receiverList.forEach(function(item) {
2471
          var track = item[0];
2472
          var receiver = item[1];
2473
          if (stream.id !== item[2].id) {
2474
            return;
2475
          }
2476
          var trackEvent = new Event('track');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2477
          trackEvent.track = track;
2478
          trackEvent.receiver = receiver;
2479
          trackEvent.streams = [stream];
2480
          self.dispatchEvent(trackEvent);
2481
          if (self.ontrack !== null) {
2482
            window.setTimeout(function() {
2483
              self.ontrack(trackEvent);
2484
            }, 0);
2485
          }
2486
        });
2487
      }
2488
    });
2489
2490
    // check whether addIceCandidate({}) was called within four seconds after
2491
    // setRemoteDescription.
2492
    window.setTimeout(function() {
2493
      if (!(self && self.transceivers)) {
2494
        return;
2495
      }
2496
      self.transceivers.forEach(function(transceiver) {
2497
        if (transceiver.iceTransport &&
2498
            transceiver.iceTransport.state === 'new' &&
2499
            transceiver.iceTransport.getRemoteCandidates().length > 0) {
2500
          console.warn('Timeout for addRemoteCandidate. Consider sending ' +
2501
              'an end-of-candidates notification');
2502
          transceiver.iceTransport.addRemoteCandidate({});
2503
        }
2504
      });
2505
    }, 4000);
2506
2507
    if (arguments.length > 1 && typeof arguments[1] === 'function') {
2508
      window.setTimeout(arguments[1], 0);
2509
    }
2510
    return Promise.resolve();
2511
  };
2512
2513
  RTCPeerConnection.prototype.close = function() {
2514
    this.transceivers.forEach(function(transceiver) {
2515
      /* not yet
2516
      if (transceiver.iceGatherer) {
2517
        transceiver.iceGatherer.close();
2518
      }
2519
      */
2520
      if (transceiver.iceTransport) {
2521
        transceiver.iceTransport.stop();
2522
      }
2523
      if (transceiver.dtlsTransport) {
2524
        transceiver.dtlsTransport.stop();
2525
      }
2526
      if (transceiver.rtpSender) {
2527
        transceiver.rtpSender.stop();
2528
      }
2529
      if (transceiver.rtpReceiver) {
2530
        transceiver.rtpReceiver.stop();
2531
      }
2532
    });
2533
    // FIXME: clean up tracks, local streams, remote streams, etc
2534
    this._updateSignalingState('closed');
2535
  };
2536
2537
  // Update the signaling state.
2538
  RTCPeerConnection.prototype._updateSignalingState = function(newState) {
2539
    this.signalingState = newState;
2540
    var event = new Event('signalingstatechange');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2541
    this.dispatchEvent(event);
2542
    if (this.onsignalingstatechange !== null) {
2543
      this.onsignalingstatechange(event);
2544
    }
2545
  };
2546
2547
  // Determine whether to fire the negotiationneeded event.
2548
  RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
2549
    var self = this;
2550
    if (this.signalingState !== 'stable' || this.needNegotiation === true) {
2551
      return;
2552
    }
2553
    this.needNegotiation = true;
2554
    window.setTimeout(function() {
2555
      if (self.needNegotiation === false) {
2556
        return;
2557
      }
2558
      self.needNegotiation = false;
2559
      var event = new Event('negotiationneeded');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2560
      self.dispatchEvent(event);
2561
      if (self.onnegotiationneeded !== null) {
2562
        self.onnegotiationneeded(event);
2563
      }
2564
    }, 0);
2565
  };
2566
2567
  // Update the connection state.
2568
  RTCPeerConnection.prototype._updateConnectionState = function() {
2569
    var self = this;
2570
    var newState;
2571
    var states = {
2572
      'new': 0,
2573
      closed: 0,
2574
      connecting: 0,
2575
      checking: 0,
2576
      connected: 0,
2577
      completed: 0,
2578
      disconnected: 0,
2579
      failed: 0
2580
    };
2581
    this.transceivers.forEach(function(transceiver) {
2582
      states[transceiver.iceTransport.state]++;
2583
      states[transceiver.dtlsTransport.state]++;
2584
    });
2585
    // ICETransport.completed and connected are the same for this purpose.
2586
    states.connected += states.completed;
2587
2588
    newState = 'new';
2589
    if (states.failed > 0) {
2590
      newState = 'failed';
2591
    } else if (states.connecting > 0 || states.checking > 0) {
2592
      newState = 'connecting';
2593
    } else if (states.disconnected > 0) {
2594
      newState = 'disconnected';
2595
    } else if (states.new > 0) {
2596
      newState = 'new';
2597
    } else if (states.connected > 0 || states.completed > 0) {
2598
      newState = 'connected';
2599
    }
2600
2601
    if (newState !== self.iceConnectionState) {
2602
      self.iceConnectionState = newState;
2603
      var event = new Event('iceconnectionstatechange');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2604
      this.dispatchEvent(event);
2605
      if (this.oniceconnectionstatechange !== null) {
2606
        this.oniceconnectionstatechange(event);
2607
      }
2608
    }
2609
  };
2610
2611
  RTCPeerConnection.prototype.createOffer = function() {
2612
    var self = this;
2613
    if (this._pendingOffer) {
2614
      throw new Error('createOffer called while there is a pending offer.');
2615
    }
2616
    var offerOptions;
2617
    if (arguments.length === 1 && typeof arguments[0] !== 'function') {
2618
      offerOptions = arguments[0];
2619
    } else if (arguments.length === 3) {
2620
      offerOptions = arguments[2];
2621
    }
2622
2623
    var numAudioTracks = this.transceivers.filter(function(t) {
2624
      return t.kind === 'audio';
2625
    }).length;
2626
    var numVideoTracks = this.transceivers.filter(function(t) {
2627
      return t.kind === 'video';
2628
    }).length;
2629
2630
    // Determine number of audio and video tracks we need to send/recv.
2631
    if (offerOptions) {
2632
      // Reject Chrome legacy constraints.
2633
      if (offerOptions.mandatory || offerOptions.optional) {
2634
        throw new TypeError(
2635
            'Legacy mandatory/optional constraints not supported.');
2636
      }
2637
      if (offerOptions.offerToReceiveAudio !== undefined) {
2638
        if (offerOptions.offerToReceiveAudio === true) {
2639
          numAudioTracks = 1;
2640
        } else if (offerOptions.offerToReceiveAudio === false) {
2641
          numAudioTracks = 0;
2642
        } else {
2643
          numAudioTracks = offerOptions.offerToReceiveAudio;
2644
        }
2645
      }
2646
      if (offerOptions.offerToReceiveVideo !== undefined) {
2647
        if (offerOptions.offerToReceiveVideo === true) {
2648
          numVideoTracks = 1;
2649
        } else if (offerOptions.offerToReceiveVideo === false) {
2650
          numVideoTracks = 0;
2651
        } else {
2652
          numVideoTracks = offerOptions.offerToReceiveVideo;
2653
        }
2654
      }
2655
    }
2656
2657
    this.transceivers.forEach(function(transceiver) {
2658
      if (transceiver.kind === 'audio') {
2659
        numAudioTracks--;
2660
        if (numAudioTracks < 0) {
2661
          transceiver.wantReceive = false;
2662
        }
2663
      } else if (transceiver.kind === 'video') {
2664
        numVideoTracks--;
2665
        if (numVideoTracks < 0) {
2666
          transceiver.wantReceive = false;
2667
        }
2668
      }
2669
    });
2670
2671
    // Create M-lines for recvonly streams.
2672
    while (numAudioTracks > 0 || numVideoTracks > 0) {
2673
      if (numAudioTracks > 0) {
2674
        this._createTransceiver('audio');
2675
        numAudioTracks--;
2676
      }
2677
      if (numVideoTracks > 0) {
2678
        this._createTransceiver('video');
2679
        numVideoTracks--;
2680
      }
2681
    }
2682
    // reorder tracks
2683
    var transceivers = sortTracks(this.transceivers);
2684
2685
    var sdp = SDPUtils.writeSessionBoilerplate();
2686
    transceivers.forEach(function(transceiver, sdpMLineIndex) {
2687
      // For each track, create an ice gatherer, ice transport,
2688
      // dtls transport, potentially rtpsender and rtpreceiver.
2689
      var track = transceiver.track;
2690
      var kind = transceiver.kind;
2691
      var mid = SDPUtils.generateIdentifier();
2692
      transceiver.mid = mid;
2693
2694
      if (!transceiver.iceGatherer) {
2695
        transceiver.iceGatherer = self.usingBundle && sdpMLineIndex > 0 ?
2696
            transceivers[0].iceGatherer :
2697
            self._createIceGatherer(mid, sdpMLineIndex);
2698
      }
2699
2700
      var localCapabilities = window.RTCRtpSender.getCapabilities(kind);
2701
      // filter RTX until additional stuff needed for RTX is implemented
2702
      // in adapter.js
2703
      if (edgeVersion < 15019) {
2704
        localCapabilities.codecs = localCapabilities.codecs.filter(
2705
            function(codec) {
2706
              return codec.name !== 'rtx';
2707
            });
2708
      }
2709
      localCapabilities.codecs.forEach(function(codec) {
2710
        // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
2711
        // by adding level-asymmetry-allowed=1
2712
        if (codec.name === 'H264' &&
2713
            codec.parameters['level-asymmetry-allowed'] === undefined) {
2714
          codec.parameters['level-asymmetry-allowed'] = '1';
2715
        }
2716
      });
2717
2718
      // generate an ssrc now, to be used later in rtpSender.send
2719
      var sendEncodingParameters = [{
2720
        ssrc: (2 * sdpMLineIndex + 1) * 1001
2721
      }];
2722
      if (track) {
2723
        // add RTX
2724
        if (edgeVersion >= 15019 && kind === 'video') {
2725
          sendEncodingParameters[0].rtx = {
2726
            ssrc: (2 * sdpMLineIndex + 1) * 1001 + 1
2727
          };
2728
        }
2729
      }
2730
2731
      if (transceiver.wantReceive) {
2732
        transceiver.rtpReceiver = new window.RTCRtpReceiver(
2733
          transceiver.dtlsTransport,
2734
          kind
2735
        );
2736
      }
2737
2738
      transceiver.localCapabilities = localCapabilities;
2739
      transceiver.sendEncodingParameters = sendEncodingParameters;
2740
    });
2741
2742
    // always offer BUNDLE and dispose on return if not supported.
2743
    if (this._config.bundlePolicy !== 'max-compat') {
2744
      sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) {
2745
        return t.mid;
2746
      }).join(' ') + '\r\n';
2747
    }
2748
    sdp += 'a=ice-options:trickle\r\n';
2749
2750
    transceivers.forEach(function(transceiver, sdpMLineIndex) {
0 ignored issues
show
Unused Code introduced by
The parameter sdpMLineIndex is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
2751
      sdp += SDPUtils.writeMediaSection(transceiver,
2752
          transceiver.localCapabilities, 'offer', transceiver.stream);
2753
      sdp += 'a=rtcp-rsize\r\n';
2754
    });
2755
2756
    this._pendingOffer = transceivers;
2757
    var desc = new window.RTCSessionDescription({
2758
      type: 'offer',
2759
      sdp: sdp
2760
    });
2761
    if (arguments.length && typeof arguments[0] === 'function') {
2762
      window.setTimeout(arguments[0], 0, desc);
2763
    }
2764
    return Promise.resolve(desc);
2765
  };
2766
2767
  RTCPeerConnection.prototype.createAnswer = function() {
2768
    var sdp = SDPUtils.writeSessionBoilerplate();
2769
    if (this.usingBundle) {
2770
      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
2771
        return t.mid;
2772
      }).join(' ') + '\r\n';
2773
    }
2774
    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
2775
      if (transceiver.isDatachannel) {
2776
        sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
2777
            'c=IN IP4 0.0.0.0\r\n' +
2778
            'a=mid:' + transceiver.mid + '\r\n';
2779
        return;
2780
      }
2781
2782
      // FIXME: look at direction.
2783
      if (transceiver.stream) {
2784
        var localTrack;
2785
        if (transceiver.kind === 'audio') {
2786
          localTrack = transceiver.stream.getAudioTracks()[0];
2787
        } else if (transceiver.kind === 'video') {
2788
          localTrack = transceiver.stream.getVideoTracks()[0];
2789
        }
2790
        if (localTrack) {
2791
          // add RTX
2792
          if (edgeVersion >= 15019 && transceiver.kind === 'video') {
2793
            transceiver.sendEncodingParameters[0].rtx = {
2794
              ssrc: (2 * sdpMLineIndex + 2) * 1001 + 1
2795
            };
2796
          }
2797
        }
2798
      }
2799
2800
      // Calculate intersection of capabilities.
2801
      var commonCapabilities = getCommonCapabilities(
2802
          transceiver.localCapabilities,
2803
          transceiver.remoteCapabilities);
2804
2805
      var hasRtx = commonCapabilities.codecs.filter(function(c) {
2806
        return c.name.toLowerCase() === 'rtx';
2807
      }).length;
2808
      if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
2809
        delete transceiver.sendEncodingParameters[0].rtx;
2810
      }
2811
2812
      sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
2813
          'answer', transceiver.stream);
2814
      if (transceiver.rtcpParameters &&
2815
          transceiver.rtcpParameters.reducedSize) {
2816
        sdp += 'a=rtcp-rsize\r\n';
2817
      }
2818
    });
2819
2820
    var desc = new window.RTCSessionDescription({
2821
      type: 'answer',
2822
      sdp: sdp
2823
    });
2824
    if (arguments.length && typeof arguments[0] === 'function') {
2825
      window.setTimeout(arguments[0], 0, desc);
2826
    }
2827
    return Promise.resolve(desc);
2828
  };
2829
2830
  RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
2831
    if (!candidate) {
2832
      for (var j = 0; j < this.transceivers.length; j++) {
2833
        this.transceivers[j].iceTransport.addRemoteCandidate({});
2834
        if (this.usingBundle) {
2835
          return Promise.resolve();
2836
        }
2837
      }
2838
    } else {
2839
      var mLineIndex = candidate.sdpMLineIndex;
2840
      if (candidate.sdpMid) {
2841
        for (var i = 0; i < this.transceivers.length; i++) {
2842
          if (this.transceivers[i].mid === candidate.sdpMid) {
2843
            mLineIndex = i;
2844
            break;
2845
          }
2846
        }
2847
      }
2848
      var transceiver = this.transceivers[mLineIndex];
2849
      if (transceiver) {
2850
        var cand = Object.keys(candidate.candidate).length > 0 ?
2851
            SDPUtils.parseCandidate(candidate.candidate) : {};
2852
        // Ignore Chrome's invalid candidates since Edge does not like them.
2853
        if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
2854
          return Promise.resolve();
2855
        }
2856
        // Ignore RTCP candidates, we assume RTCP-MUX.
2857
        if (cand.component &&
2858
            !(cand.component === '1' || cand.component === 1)) {
2859
          return Promise.resolve();
2860
        }
2861
        transceiver.iceTransport.addRemoteCandidate(cand);
2862
2863
        // update the remoteDescription.
2864
        var sections = SDPUtils.splitSections(this.remoteDescription.sdp);
2865
        sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim()
2866
            : 'a=end-of-candidates') + '\r\n';
2867
        this.remoteDescription.sdp = sections.join('');
2868
      }
2869
    }
2870
    if (arguments.length > 1 && typeof arguments[1] === 'function') {
2871
      window.setTimeout(arguments[1], 0);
2872
    }
2873
    return Promise.resolve();
2874
  };
2875
2876
  RTCPeerConnection.prototype.getStats = function() {
2877
    var promises = [];
2878
    this.transceivers.forEach(function(transceiver) {
2879
      ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
2880
        'dtlsTransport'].forEach(function(method) {
2881
          if (transceiver[method]) {
2882
            promises.push(transceiver[method].getStats());
2883
          }
2884
        });
2885
    });
2886
    var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
2887
        arguments[1];
2888
    var fixStatsType = function(stat) {
2889
      return {
2890
        inboundrtp: 'inbound-rtp',
2891
        outboundrtp: 'outbound-rtp',
2892
        candidatepair: 'candidate-pair',
2893
        localcandidate: 'local-candidate',
2894
        remotecandidate: 'remote-candidate'
2895
      }[stat.type] || stat.type;
2896
    };
2897
    return new Promise(function(resolve) {
2898
      // shim getStats with maplike support
2899
      var results = new Map();
2900
      Promise.all(promises).then(function(res) {
2901
        res.forEach(function(result) {
2902
          Object.keys(result).forEach(function(id) {
2903
            result[id].type = fixStatsType(result[id]);
2904
            results.set(id, result[id]);
2905
          });
2906
        });
2907
        if (cb) {
2908
          window.setTimeout(cb, 0, results);
2909
        }
2910
        resolve(results);
2911
      });
2912
    });
2913
  };
2914
  return RTCPeerConnection;
2915
};
2916
2917
},{"sdp":1}],9:[function(require,module,exports){
2918
/*
2919
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
2920
 *
2921
 *  Use of this source code is governed by a BSD-style license
2922
 *  that can be found in the LICENSE file in the root of the source
2923
 *  tree.
2924
 */
2925
 /* eslint-env node */
2926
'use strict';
2927
2928
var utils = require('../utils');
2929
2930
var firefoxShim = {
2931
  shimOnTrack: function(window) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2932
    if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
2933
        window.RTCPeerConnection.prototype)) {
2934
      Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
2935
        get: function() {
2936
          return this._ontrack;
2937
        },
2938
        set: function(f) {
2939
          if (this._ontrack) {
2940
            this.removeEventListener('track', this._ontrack);
2941
            this.removeEventListener('addstream', this._ontrackpoly);
2942
          }
2943
          this.addEventListener('track', this._ontrack = f);
2944
          this.addEventListener('addstream', this._ontrackpoly = function(e) {
2945
            e.stream.getTracks().forEach(function(track) {
2946
              var event = new Event('track');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2947
              event.track = track;
2948
              event.receiver = {track: track};
2949
              event.streams = [e.stream];
2950
              this.dispatchEvent(event);
2951
            }.bind(this));
2952
          }.bind(this));
2953
        }
2954
      });
2955
    }
2956
  },
2957
2958
  shimSourceObject: function(window) {
2959
    // Firefox has supported mozSrcObject since FF22, unprefixed in 42.
2960
    if (typeof window === 'object') {
2961
      if (window.HTMLMediaElement &&
2962
        !('srcObject' in window.HTMLMediaElement.prototype)) {
2963
        // Shim the srcObject property, once, when HTMLMediaElement is found.
2964
        Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
2965
          get: function() {
2966
            return this.mozSrcObject;
2967
          },
2968
          set: function(stream) {
2969
            this.mozSrcObject = stream;
2970
          }
2971
        });
2972
      }
2973
    }
2974
  },
2975
2976
  shimPeerConnection: function(window) {
2977
    var browserDetails = utils.detectBrowser(window);
2978
2979
    if (typeof window !== 'object' || !(window.RTCPeerConnection ||
2980
        window.mozRTCPeerConnection)) {
2981
      return; // probably media.peerconnection.enabled=false in about:config
2982
    }
2983
    // The RTCPeerConnection object.
2984
    if (!window.RTCPeerConnection) {
2985
      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
2986
        if (browserDetails.version < 38) {
2987
          // .urls is not supported in FF < 38.
2988
          // create RTCIceServers with a single url.
2989
          if (pcConfig && pcConfig.iceServers) {
2990
            var newIceServers = [];
2991
            for (var i = 0; i < pcConfig.iceServers.length; i++) {
2992
              var server = pcConfig.iceServers[i];
2993
              if (server.hasOwnProperty('urls')) {
2994
                for (var j = 0; j < server.urls.length; j++) {
2995
                  var newServer = {
2996
                    url: server.urls[j]
2997
                  };
2998
                  if (server.urls[j].indexOf('turn') === 0) {
2999
                    newServer.username = server.username;
3000
                    newServer.credential = server.credential;
3001
                  }
3002
                  newIceServers.push(newServer);
3003
                }
3004
              } else {
3005
                newIceServers.push(pcConfig.iceServers[i]);
3006
              }
3007
            }
3008
            pcConfig.iceServers = newIceServers;
3009
          }
3010
        }
3011
        return new window.mozRTCPeerConnection(pcConfig, pcConstraints);
3012
      };
3013
      window.RTCPeerConnection.prototype =
3014
          window.mozRTCPeerConnection.prototype;
3015
3016
      // wrap static methods. Currently just generateCertificate.
3017
      if (window.mozRTCPeerConnection.generateCertificate) {
3018
        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
3019
          get: function() {
3020
            return window.mozRTCPeerConnection.generateCertificate;
3021
          }
3022
        });
3023
      }
3024
3025
      window.RTCSessionDescription = window.mozRTCSessionDescription;
3026
      window.RTCIceCandidate = window.mozRTCIceCandidate;
3027
    }
3028
3029
    // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
3030
    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
3031
        .forEach(function(method) {
3032
          var nativeMethod = window.RTCPeerConnection.prototype[method];
3033
          window.RTCPeerConnection.prototype[method] = function() {
3034
            arguments[0] = new ((method === 'addIceCandidate') ?
3035
                window.RTCIceCandidate :
3036
                window.RTCSessionDescription)(arguments[0]);
3037
            return nativeMethod.apply(this, arguments);
3038
          };
3039
        });
3040
3041
    // support for addIceCandidate(null or undefined)
3042
    var nativeAddIceCandidate =
3043
        window.RTCPeerConnection.prototype.addIceCandidate;
3044
    window.RTCPeerConnection.prototype.addIceCandidate = function() {
3045
      if (!arguments[0]) {
3046
        if (arguments[1]) {
3047
          arguments[1].apply(null);
3048
        }
3049
        return Promise.resolve();
3050
      }
3051
      return nativeAddIceCandidate.apply(this, arguments);
3052
    };
3053
3054
    // shim getStats with maplike support
3055
    var makeMapStats = function(stats) {
3056
      var map = new Map();
3057
      Object.keys(stats).forEach(function(key) {
3058
        map.set(key, stats[key]);
3059
        map[key] = stats[key];
3060
      });
3061
      return map;
3062
    };
3063
3064
    var modernStatsTypes = {
3065
      inboundrtp: 'inbound-rtp',
3066
      outboundrtp: 'outbound-rtp',
3067
      candidatepair: 'candidate-pair',
3068
      localcandidate: 'local-candidate',
3069
      remotecandidate: 'remote-candidate'
3070
    };
3071
3072
    var nativeGetStats = window.RTCPeerConnection.prototype.getStats;
3073
    window.RTCPeerConnection.prototype.getStats = function(
3074
      selector,
3075
      onSucc,
3076
      onErr
3077
    ) {
3078
      return nativeGetStats.apply(this, [selector || null])
3079
        .then(function(stats) {
3080
          if (browserDetails.version < 48) {
3081
            stats = makeMapStats(stats);
3082
          }
3083
          if (browserDetails.version < 53 && !onSucc) {
3084
            // Shim only promise getStats with spec-hyphens in type names
3085
            // Leave callback version alone; misc old uses of forEach before Map
3086
            try {
3087
              stats.forEach(function(stat) {
3088
                stat.type = modernStatsTypes[stat.type] || stat.type;
3089
              });
3090
            } catch (e) {
3091
              if (e.name !== 'TypeError') {
3092
                throw e;
3093
              }
3094
              // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
3095
              stats.forEach(function(stat, i) {
3096
                stats.set(i, Object.assign({}, stat, {
3097
                  type: modernStatsTypes[stat.type] || stat.type
3098
                }));
3099
              });
3100
            }
3101
          }
3102
          return stats;
3103
        })
3104
        .then(onSucc, onErr);
3105
    };
3106
  }
3107
};
3108
3109
// Expose public methods.
3110
module.exports = {
3111
  shimOnTrack: firefoxShim.shimOnTrack,
3112
  shimSourceObject: firefoxShim.shimSourceObject,
3113
  shimPeerConnection: firefoxShim.shimPeerConnection,
3114
  shimGetUserMedia: require('./getusermedia')
3115
};
3116
3117
},{"../utils":12,"./getusermedia":10}],10:[function(require,module,exports){
3118
/*
3119
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3120
 *
3121
 *  Use of this source code is governed by a BSD-style license
3122
 *  that can be found in the LICENSE file in the root of the source
3123
 *  tree.
3124
 */
3125
 /* eslint-env node */
3126
'use strict';
3127
3128
var utils = require('../utils');
3129
var logging = utils.log;
3130
3131
// Expose public methods.
3132
module.exports = function(window) {
3133
  var browserDetails = utils.detectBrowser(window);
3134
  var navigator = window && window.navigator;
3135
  var MediaStreamTrack = window && window.MediaStreamTrack;
3136
3137
  var shimError_ = function(e) {
3138
    return {
3139
      name: {
3140
        InternalError: 'NotReadableError',
3141
        NotSupportedError: 'TypeError',
3142
        PermissionDeniedError: 'NotAllowedError',
3143
        SecurityError: 'NotAllowedError'
3144
      }[e.name] || e.name,
3145
      message: {
3146
        'The operation is insecure.': 'The request is not allowed by the ' +
3147
        'user agent or the platform in the current context.'
3148
      }[e.message] || e.message,
3149
      constraint: e.constraint,
3150
      toString: function() {
3151
        return this.name + (this.message && ': ') + this.message;
3152
      }
3153
    };
3154
  };
3155
3156
  // getUserMedia constraints shim.
3157
  var getUserMedia_ = function(constraints, onSuccess, onError) {
3158
    var constraintsToFF37_ = function(c) {
3159
      if (typeof c !== 'object' || c.require) {
3160
        return c;
3161
      }
3162
      var require = [];
3163
      Object.keys(c).forEach(function(key) {
3164
        if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
3165
          return;
3166
        }
3167
        var r = c[key] = (typeof c[key] === 'object') ?
3168
            c[key] : {ideal: c[key]};
3169
        if (r.min !== undefined ||
3170
            r.max !== undefined || r.exact !== undefined) {
3171
          require.push(key);
3172
        }
3173
        if (r.exact !== undefined) {
3174
          if (typeof r.exact === 'number') {
3175
            r. min = r.max = r.exact;
3176
          } else {
3177
            c[key] = r.exact;
3178
          }
3179
          delete r.exact;
3180
        }
3181
        if (r.ideal !== undefined) {
3182
          c.advanced = c.advanced || [];
3183
          var oc = {};
3184
          if (typeof r.ideal === 'number') {
3185
            oc[key] = {min: r.ideal, max: r.ideal};
3186
          } else {
3187
            oc[key] = r.ideal;
3188
          }
3189
          c.advanced.push(oc);
3190
          delete r.ideal;
3191
          if (!Object.keys(r).length) {
3192
            delete c[key];
3193
          }
3194
        }
3195
      });
3196
      if (require.length) {
3197
        c.require = require;
3198
      }
3199
      return c;
3200
    };
3201
    constraints = JSON.parse(JSON.stringify(constraints));
3202
    if (browserDetails.version < 38) {
3203
      logging('spec: ' + JSON.stringify(constraints));
3204
      if (constraints.audio) {
3205
        constraints.audio = constraintsToFF37_(constraints.audio);
3206
      }
3207
      if (constraints.video) {
3208
        constraints.video = constraintsToFF37_(constraints.video);
3209
      }
3210
      logging('ff37: ' + JSON.stringify(constraints));
3211
    }
3212
    return navigator.mozGetUserMedia(constraints, onSuccess, function(e) {
3213
      onError(shimError_(e));
3214
    });
3215
  };
3216
3217
  // Returns the result of getUserMedia as a Promise.
3218
  var getUserMediaPromise_ = function(constraints) {
3219
    return new Promise(function(resolve, reject) {
3220
      getUserMedia_(constraints, resolve, reject);
3221
    });
3222
  };
3223
3224
  // Shim for mediaDevices on older versions.
3225
  if (!navigator.mediaDevices) {
3226
    navigator.mediaDevices = {getUserMedia: getUserMediaPromise_,
3227
      addEventListener: function() { },
3228
      removeEventListener: function() { }
3229
    };
3230
  }
3231
  navigator.mediaDevices.enumerateDevices =
3232
      navigator.mediaDevices.enumerateDevices || function() {
3233
        return new Promise(function(resolve) {
3234
          var infos = [
3235
            {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
3236
            {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
3237
          ];
3238
          resolve(infos);
3239
        });
3240
      };
3241
3242
  if (browserDetails.version < 41) {
3243
    // Work around http://bugzil.la/1169665
3244
    var orgEnumerateDevices =
3245
        navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
3246
    navigator.mediaDevices.enumerateDevices = function() {
3247
      return orgEnumerateDevices().then(undefined, function(e) {
3248
        if (e.name === 'NotFoundError') {
3249
          return [];
3250
        }
3251
        throw e;
3252
      });
3253
    };
3254
  }
3255
  if (browserDetails.version < 49) {
3256
    var origGetUserMedia = navigator.mediaDevices.getUserMedia.
3257
        bind(navigator.mediaDevices);
3258
    navigator.mediaDevices.getUserMedia = function(c) {
3259
      return origGetUserMedia(c).then(function(stream) {
3260
        // Work around https://bugzil.la/802326
3261
        if (c.audio && !stream.getAudioTracks().length ||
3262
            c.video && !stream.getVideoTracks().length) {
3263
          stream.getTracks().forEach(function(track) {
3264
            track.stop();
3265
          });
3266
          throw new DOMException('The object can not be found here.',
0 ignored issues
show
Bug introduced by
The variable DOMException seems to be never declared. If this is a global, consider adding a /** global: DOMException */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
3267
                                 'NotFoundError');
3268
        }
3269
        return stream;
3270
      }, function(e) {
3271
        return Promise.reject(shimError_(e));
3272
      });
3273
    };
3274
  }
3275
  if (!(browserDetails.version > 55 &&
3276
      'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {
3277
    var remap = function(obj, a, b) {
3278
      if (a in obj && !(b in obj)) {
3279
        obj[b] = obj[a];
3280
        delete obj[a];
3281
      }
3282
    };
3283
3284
    var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.
3285
        bind(navigator.mediaDevices);
3286
    navigator.mediaDevices.getUserMedia = function(c) {
3287
      if (typeof c === 'object' && typeof c.audio === 'object') {
3288
        c = JSON.parse(JSON.stringify(c));
3289
        remap(c.audio, 'autoGainControl', 'mozAutoGainControl');
3290
        remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');
3291
      }
3292
      return nativeGetUserMedia(c);
3293
    };
3294
3295
    if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {
3296
      var nativeGetSettings = MediaStreamTrack.prototype.getSettings;
3297
      MediaStreamTrack.prototype.getSettings = function() {
3298
        var obj = nativeGetSettings.apply(this, arguments);
3299
        remap(obj, 'mozAutoGainControl', 'autoGainControl');
3300
        remap(obj, 'mozNoiseSuppression', 'noiseSuppression');
3301
        return obj;
3302
      };
3303
    }
3304
3305
    if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {
3306
      var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints;
3307
      MediaStreamTrack.prototype.applyConstraints = function(c) {
3308
        if (this.kind === 'audio' && typeof c === 'object') {
3309
          c = JSON.parse(JSON.stringify(c));
3310
          remap(c, 'autoGainControl', 'mozAutoGainControl');
3311
          remap(c, 'noiseSuppression', 'mozNoiseSuppression');
3312
        }
3313
        return nativeApplyConstraints.apply(this, [c]);
3314
      };
3315
    }
3316
  }
3317
  navigator.getUserMedia = function(constraints, onSuccess, onError) {
3318
    if (browserDetails.version < 44) {
3319
      return getUserMedia_(constraints, onSuccess, onError);
3320
    }
3321
    // Replace Firefox 44+'s deprecation warning with unprefixed version.
3322
    console.warn('navigator.getUserMedia has been replaced by ' +
3323
                 'navigator.mediaDevices.getUserMedia');
3324
    navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3325
  };
3326
};
3327
3328
},{"../utils":12}],11:[function(require,module,exports){
3329
/*
3330
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3331
 *
3332
 *  Use of this source code is governed by a BSD-style license
3333
 *  that can be found in the LICENSE file in the root of the source
3334
 *  tree.
3335
 */
3336
'use strict';
3337
var safariShim = {
3338
  // TODO: DrAlex, should be here, double check against LayoutTests
3339
3340
  // TODO: once the back-end for the mac port is done, add.
3341
  // TODO: check for webkitGTK+
3342
  // shimPeerConnection: function() { },
3343
3344
  shimLocalStreamsAPI: function(window) {
3345
    if (typeof window !== 'object' || !window.RTCPeerConnection) {
3346
      return;
3347
    }
3348
    if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {
3349
      window.RTCPeerConnection.prototype.getLocalStreams = function() {
3350
        if (!this._localStreams) {
3351
          this._localStreams = [];
3352
        }
3353
        return this._localStreams;
3354
      };
3355
    }
3356
    if (!('getStreamById' in window.RTCPeerConnection.prototype)) {
3357
      window.RTCPeerConnection.prototype.getStreamById = function(id) {
3358
        var result = null;
3359
        if (this._localStreams) {
3360
          this._localStreams.forEach(function(stream) {
3361
            if (stream.id === id) {
3362
              result = stream;
3363
            }
3364
          });
3365
        }
3366
        if (this._remoteStreams) {
3367
          this._remoteStreams.forEach(function(stream) {
3368
            if (stream.id === id) {
3369
              result = stream;
3370
            }
3371
          });
3372
        }
3373
        return result;
3374
      };
3375
    }
3376
    if (!('addStream' in window.RTCPeerConnection.prototype)) {
3377
      var _addTrack = window.RTCPeerConnection.prototype.addTrack;
3378
      window.RTCPeerConnection.prototype.addStream = function(stream) {
3379
        if (!this._localStreams) {
3380
          this._localStreams = [];
3381
        }
3382
        if (this._localStreams.indexOf(stream) === -1) {
3383
          this._localStreams.push(stream);
3384
        }
3385
        var self = this;
3386
        stream.getTracks().forEach(function(track) {
3387
          _addTrack.call(self, track, stream);
3388
        });
3389
      };
3390
3391
      window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
3392
        if (stream) {
3393
          if (!this._localStreams) {
3394
            this._localStreams = [stream];
3395
          } else if (this._localStreams.indexOf(stream) === -1) {
3396
            this._localStreams.push(stream);
3397
          }
3398
        }
3399
        _addTrack.call(this, track, stream);
3400
      };
3401
    }
3402
    if (!('removeStream' in window.RTCPeerConnection.prototype)) {
3403
      window.RTCPeerConnection.prototype.removeStream = function(stream) {
3404
        if (!this._localStreams) {
3405
          this._localStreams = [];
3406
        }
3407
        var index = this._localStreams.indexOf(stream);
3408
        if (index === -1) {
3409
          return;
3410
        }
3411
        this._localStreams.splice(index, 1);
3412
        var self = this;
3413
        var tracks = stream.getTracks();
3414
        this.getSenders().forEach(function(sender) {
3415
          if (tracks.indexOf(sender.track) !== -1) {
3416
            self.removeTrack(sender);
3417
          }
3418
        });
3419
      };
3420
    }
3421
  },
3422
  shimRemoteStreamsAPI: function(window) {
3423
    if (typeof window !== 'object' || !window.RTCPeerConnection) {
3424
      return;
3425
    }
3426
    if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
3427
      window.RTCPeerConnection.prototype.getRemoteStreams = function() {
3428
        return this._remoteStreams ? this._remoteStreams : [];
3429
      };
3430
    }
3431
    if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
3432
      Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
3433
        get: function() {
3434
          return this._onaddstream;
3435
        },
3436
        set: function(f) {
3437
          if (this._onaddstream) {
3438
            this.removeEventListener('addstream', this._onaddstream);
3439
            this.removeEventListener('track', this._onaddstreampoly);
3440
          }
3441
          this.addEventListener('addstream', this._onaddstream = f);
3442
          this.addEventListener('track', this._onaddstreampoly = function(e) {
3443
            var stream = e.streams[0];
3444
            if (!this._remoteStreams) {
3445
              this._remoteStreams = [];
3446
            }
3447
            if (this._remoteStreams.indexOf(stream) >= 0) {
3448
              return;
3449
            }
3450
            this._remoteStreams.push(stream);
3451
            var event = new Event('addstream');
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
3452
            event.stream = e.streams[0];
3453
            this.dispatchEvent(event);
3454
          }.bind(this));
3455
        }
3456
      });
3457
    }
3458
  },
3459
  shimCallbacksAPI: function(window) {
3460
    if (typeof window !== 'object' || !window.RTCPeerConnection) {
3461
      return;
3462
    }
3463
    var prototype = window.RTCPeerConnection.prototype;
3464
    var createOffer = prototype.createOffer;
3465
    var createAnswer = prototype.createAnswer;
3466
    var setLocalDescription = prototype.setLocalDescription;
3467
    var setRemoteDescription = prototype.setRemoteDescription;
3468
    var addIceCandidate = prototype.addIceCandidate;
3469
3470
    prototype.createOffer = function(successCallback, failureCallback) {
3471
      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
3472
      var promise = createOffer.apply(this, [options]);
3473
      if (!failureCallback) {
3474
        return promise;
3475
      }
3476
      promise.then(successCallback, failureCallback);
3477
      return Promise.resolve();
3478
    };
3479
3480
    prototype.createAnswer = function(successCallback, failureCallback) {
3481
      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
3482
      var promise = createAnswer.apply(this, [options]);
3483
      if (!failureCallback) {
3484
        return promise;
3485
      }
3486
      promise.then(successCallback, failureCallback);
3487
      return Promise.resolve();
3488
    };
3489
3490
    var withCallback = function(description, successCallback, failureCallback) {
3491
      var promise = setLocalDescription.apply(this, [description]);
3492
      if (!failureCallback) {
3493
        return promise;
3494
      }
3495
      promise.then(successCallback, failureCallback);
3496
      return Promise.resolve();
3497
    };
3498
    prototype.setLocalDescription = withCallback;
3499
3500
    withCallback = function(description, successCallback, failureCallback) {
3501
      var promise = setRemoteDescription.apply(this, [description]);
3502
      if (!failureCallback) {
3503
        return promise;
3504
      }
3505
      promise.then(successCallback, failureCallback);
3506
      return Promise.resolve();
3507
    };
3508
    prototype.setRemoteDescription = withCallback;
3509
3510
    withCallback = function(candidate, successCallback, failureCallback) {
3511
      var promise = addIceCandidate.apply(this, [candidate]);
3512
      if (!failureCallback) {
3513
        return promise;
3514
      }
3515
      promise.then(successCallback, failureCallback);
3516
      return Promise.resolve();
3517
    };
3518
    prototype.addIceCandidate = withCallback;
3519
  },
3520
  shimGetUserMedia: function(window) {
3521
    var navigator = window && window.navigator;
3522
3523
    if (!navigator.getUserMedia) {
3524
      if (navigator.webkitGetUserMedia) {
3525
        navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
3526
      } else if (navigator.mediaDevices &&
3527
          navigator.mediaDevices.getUserMedia) {
3528
        navigator.getUserMedia = function(constraints, cb, errcb) {
3529
          navigator.mediaDevices.getUserMedia(constraints)
3530
          .then(cb, errcb);
3531
        }.bind(navigator);
0 ignored issues
show
unused-code introduced by
The call to bind does not seem necessary since the function does not use this. Consider calling it directly.
Loading history...
3532
      }
3533
    }
3534
  }
3535
};
3536
3537
// Expose public methods.
3538
module.exports = {
3539
  shimCallbacksAPI: safariShim.shimCallbacksAPI,
3540
  shimLocalStreamsAPI: safariShim.shimLocalStreamsAPI,
3541
  shimRemoteStreamsAPI: safariShim.shimRemoteStreamsAPI,
3542
  shimGetUserMedia: safariShim.shimGetUserMedia
3543
  // TODO
3544
  // shimPeerConnection: safariShim.shimPeerConnection
3545
};
3546
3547
},{}],12:[function(require,module,exports){
3548
/*
3549
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3550
 *
3551
 *  Use of this source code is governed by a BSD-style license
3552
 *  that can be found in the LICENSE file in the root of the source
3553
 *  tree.
3554
 */
3555
 /* eslint-env node */
3556
'use strict';
3557
3558
var logDisabled_ = true;
3559
3560
// Utility methods.
3561
var utils = {
3562
  disableLog: function(bool) {
3563
    if (typeof bool !== 'boolean') {
3564
      return new Error('Argument type: ' + typeof bool +
3565
          '. Please use a boolean.');
3566
    }
3567
    logDisabled_ = bool;
3568
    return (bool) ? 'adapter.js logging disabled' :
3569
        'adapter.js logging enabled';
3570
  },
3571
3572
  log: function() {
3573
    if (typeof window === 'object') {
3574
      if (logDisabled_) {
3575
        return;
3576
      }
3577
      if (typeof console !== 'undefined' && typeof console.log === 'function') {
3578
        console.log.apply(console, arguments);
3579
      }
3580
    }
3581
  },
3582
3583
  /**
3584
   * Extract browser version out of the provided user agent string.
3585
   *
3586
   * @param {!string} uastring userAgent string.
3587
   * @param {!string} expr Regular expression used as match criteria.
3588
   * @param {!number} pos position in the version string to be returned.
3589
   * @return {!number} browser version.
3590
   */
3591
  extractVersion: function(uastring, expr, pos) {
3592
    var match = uastring.match(expr);
3593
    return match && match.length >= pos && parseInt(match[pos], 10);
3594
  },
3595
3596
  /**
3597
   * Browser detector.
3598
   *
3599
   * @return {object} result containing browser and version
3600
   *     properties.
3601
   */
3602
  detectBrowser: function(window) {
3603
    var navigator = window && window.navigator;
3604
3605
    // Returned result object.
3606
    var result = {};
3607
    result.browser = null;
3608
    result.version = null;
3609
3610
    // Fail early if it's not a browser
3611
    if (typeof window === 'undefined' || !window.navigator) {
3612
      result.browser = 'Not a browser.';
3613
      return result;
3614
    }
3615
3616
    // Firefox.
3617
    if (navigator.mozGetUserMedia) {
3618
      result.browser = 'firefox';
3619
      result.version = this.extractVersion(navigator.userAgent,
3620
          /Firefox\/(\d+)\./, 1);
3621
    } else if (navigator.webkitGetUserMedia) {
3622
      // Chrome, Chromium, Webview, Opera, all use the chrome shim for now
3623
      if (window.webkitRTCPeerConnection) {
3624
        result.browser = 'chrome';
3625
        result.version = this.extractVersion(navigator.userAgent,
3626
          /Chrom(e|ium)\/(\d+)\./, 2);
3627
      } else { // Safari (in an unpublished version) or unknown webkit-based.
3628
        if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) {
3629
          result.browser = 'safari';
3630
          result.version = this.extractVersion(navigator.userAgent,
3631
            /AppleWebKit\/(\d+)\./, 1);
3632
        } else { // unknown webkit-based browser.
3633
          result.browser = 'Unsupported webkit-based browser ' +
3634
              'with GUM support but no WebRTC support.';
3635
          return result;
3636
        }
3637
      }
3638
    } else if (navigator.mediaDevices &&
3639
        navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge.
3640
      result.browser = 'edge';
3641
      result.version = this.extractVersion(navigator.userAgent,
3642
          /Edge\/(\d+).(\d+)$/, 2);
3643
    } else if (navigator.mediaDevices &&
3644
        navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) {
3645
        // Safari, with webkitGetUserMedia removed.
3646
      result.browser = 'safari';
3647
      result.version = this.extractVersion(navigator.userAgent,
3648
          /AppleWebKit\/(\d+)\./, 1);
3649
    } else { // Default fallthrough: not supported.
3650
      result.browser = 'Not a supported browser.';
3651
      return result;
3652
    }
3653
3654
    return result;
3655
  },
3656
3657
  // shimCreateObjectURL must be called before shimSourceObject to avoid loop.
3658
3659
  shimCreateObjectURL: function(window) {
3660
    var URL = window && window.URL;
3661
3662
    if (!(typeof window === 'object' && window.HTMLMediaElement &&
3663
          'srcObject' in window.HTMLMediaElement.prototype)) {
3664
      // Only shim CreateObjectURL using srcObject if srcObject exists.
3665
      return undefined;
3666
    }
3667
3668
    var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
3669
    var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
3670
    var streams = new Map(), newId = 0;
3671
3672
    URL.createObjectURL = function(stream) {
3673
      if ('getTracks' in stream) {
3674
        var url = 'polyblob:' + (++newId);
3675
        streams.set(url, stream);
3676
        console.log('URL.createObjectURL(stream) is deprecated! ' +
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
3677
                    'Use elem.srcObject = stream instead!');
3678
        return url;
3679
      }
3680
      return nativeCreateObjectURL(stream);
3681
    };
3682
    URL.revokeObjectURL = function(url) {
3683
      nativeRevokeObjectURL(url);
3684
      streams.delete(url);
3685
    };
3686
3687
    var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
3688
                                              'src');
3689
    Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
3690
      get: function() {
3691
        return dsc.get.apply(this);
3692
      },
3693
      set: function(url) {
3694
        this.srcObject = streams.get(url) || null;
3695
        return dsc.set.apply(this, [url]);
3696
      }
3697
    });
3698
3699
    var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute;
3700
    window.HTMLMediaElement.prototype.setAttribute = function() {
3701
      if (arguments.length === 2 &&
3702
          ('' + arguments[0]).toLowerCase() === 'src') {
3703
        this.srcObject = streams.get(arguments[1]) || null;
3704
      }
3705
      return nativeSetAttribute.apply(this, arguments);
3706
    };
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3707
  }
3708
};
3709
3710
// Export.
3711
module.exports = {
3712
  log: utils.log,
3713
  disableLog: utils.disableLog,
3714
  extractVersion: utils.extractVersion,
3715
  shimCreateObjectURL: utils.shimCreateObjectURL,
3716
  detectBrowser: utils.detectBrowser.bind(utils)
3717
};
3718
3719
},{}]},{},[2])(2)
3720
});