You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

adapter.screenshare.js 156KB

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