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