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