kurento-utils.js 152 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416
  1. (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.kurentoUtils = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
  2. var freeice = require('freeice');
  3. var inherits = require('inherits');
  4. var UAParser = require('ua-parser-js');
  5. var uuidv4 = require('uuid/v4');
  6. var hark = require('hark');
  7. var EventEmitter = require('events').EventEmitter;
  8. var recursive = require('merge').recursive.bind(undefined, true);
  9. var sdpTranslator = require('sdp-translator');
  10. var logger = typeof window === 'undefined' ? console : window.Logger || console;
  11. try {
  12. require('kurento-browser-extensions');
  13. } catch (error) {
  14. if (typeof getScreenConstraints === 'undefined') {
  15. logger.warn('screen sharing is not available');
  16. getScreenConstraints = function getScreenConstraints(sendSource, callback) {
  17. callback(new Error('This library is not enabled for screen sharing'));
  18. };
  19. }
  20. }
  21. var MEDIA_CONSTRAINTS = {
  22. audio: true,
  23. video: {
  24. width: 640,
  25. framerate: 15
  26. }
  27. };
  28. var ua = typeof window !== 'undefined' && window.navigator ? window.navigator.userAgent : '';
  29. var parser = new UAParser(ua);
  30. var browser = parser.getBrowser();
  31. function insertScriptSrcInHtmlDom(scriptSrc) {
  32. var script = document.createElement('script');
  33. script.src = scriptSrc;
  34. var ref = document.querySelector('script');
  35. ref.parentNode.insertBefore(script, ref);
  36. }
  37. function importScriptsDependsOnBrowser() {
  38. if (browser.name === 'IE') {
  39. insertScriptSrcInHtmlDom('https://cdn.temasys.io/adapterjs/0.15.x/adapter.debug.js');
  40. }
  41. }
  42. importScriptsDependsOnBrowser();
  43. var usePlanB = false;
  44. if (browser.name === 'Chrome' || browser.name === 'Chromium') {
  45. logger.debug(browser.name + ': using SDP PlanB');
  46. usePlanB = true;
  47. }
  48. function noop(error) {
  49. if (error)
  50. logger.error(error);
  51. }
  52. function trackStop(track) {
  53. track.stop && track.stop();
  54. }
  55. function streamStop(stream) {
  56. stream.getTracks().forEach(trackStop);
  57. }
  58. var dumpSDP = function (description) {
  59. if (typeof description === 'undefined' || description === null) {
  60. return '';
  61. }
  62. return 'type: ' + description.type + '\r\n' + description.sdp;
  63. };
  64. function bufferizeCandidates(pc, onerror) {
  65. var candidatesQueue = [];
  66. function setSignalingstatechangeAccordingWwebBrowser(functionToExecute, pc) {
  67. if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
  68. pc.onsignalingstatechange = functionToExecute;
  69. } else {
  70. pc.addEventListener('signalingstatechange', functionToExecute);
  71. }
  72. }
  73. var signalingstatechangeFunction = function () {
  74. if (pc.signalingState === 'stable') {
  75. while (candidatesQueue.length) {
  76. var entry = candidatesQueue.shift();
  77. pc.addIceCandidate(entry.candidate, entry.callback, entry.callback);
  78. }
  79. }
  80. };
  81. setSignalingstatechangeAccordingWwebBrowser(signalingstatechangeFunction, pc);
  82. return function (candidate, callback) {
  83. callback = callback || onerror;
  84. switch (pc.signalingState) {
  85. case 'closed':
  86. callback(new Error('PeerConnection object is closed'));
  87. break;
  88. case 'stable':
  89. if (pc.remoteDescription) {
  90. pc.addIceCandidate(candidate, callback, callback);
  91. break;
  92. }
  93. default:
  94. candidatesQueue.push({
  95. candidate: candidate,
  96. callback: callback
  97. });
  98. }
  99. };
  100. }
  101. function removeFIDFromOffer(sdp) {
  102. var n = sdp.indexOf('a=ssrc-group:FID');
  103. if (n > 0) {
  104. return sdp.slice(0, n);
  105. } else {
  106. return sdp;
  107. }
  108. }
  109. function getSimulcastInfo(videoStream) {
  110. var videoTracks = videoStream.getVideoTracks();
  111. if (!videoTracks.length) {
  112. logger.warn('No video tracks available in the video stream');
  113. return '';
  114. }
  115. var lines = [
  116. 'a=x-google-flag:conference',
  117. 'a=ssrc-group:SIM 1 2 3',
  118. 'a=ssrc:1 cname:localVideo',
  119. 'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id,
  120. 'a=ssrc:1 mslabel:' + videoStream.id,
  121. 'a=ssrc:1 label:' + videoTracks[0].id,
  122. 'a=ssrc:2 cname:localVideo',
  123. 'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id,
  124. 'a=ssrc:2 mslabel:' + videoStream.id,
  125. 'a=ssrc:2 label:' + videoTracks[0].id,
  126. 'a=ssrc:3 cname:localVideo',
  127. 'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id,
  128. 'a=ssrc:3 mslabel:' + videoStream.id,
  129. 'a=ssrc:3 label:' + videoTracks[0].id
  130. ];
  131. lines.push('');
  132. return lines.join('\n');
  133. }
  134. function sleep(milliseconds) {
  135. var start = new Date().getTime();
  136. for (var i = 0; i < 10000000; i++) {
  137. if (new Date().getTime() - start > milliseconds) {
  138. break;
  139. }
  140. }
  141. }
  142. function setIceCandidateAccordingWebBrowser(functionToExecute, pc) {
  143. if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
  144. pc.onicecandidate = functionToExecute;
  145. } else {
  146. pc.addEventListener('icecandidate', functionToExecute);
  147. }
  148. }
  149. function WebRtcPeer(mode, options, callback) {
  150. if (!(this instanceof WebRtcPeer)) {
  151. return new WebRtcPeer(mode, options, callback);
  152. }
  153. WebRtcPeer.super_.call(this);
  154. if (options instanceof Function) {
  155. callback = options;
  156. options = undefined;
  157. }
  158. options = options || {};
  159. callback = (callback || noop).bind(this);
  160. var self = this;
  161. var localVideo = options.localVideo;
  162. var remoteVideo = options.remoteVideo;
  163. var videoStream = options.videoStream;
  164. var audioStream = options.audioStream;
  165. var mediaConstraints = options.mediaConstraints;
  166. var pc = options.peerConnection;
  167. var sendSource = options.sendSource || 'webcam';
  168. var dataChannelConfig = options.dataChannelConfig;
  169. var useDataChannels = options.dataChannels || false;
  170. var dataChannel;
  171. var guid = uuidv4();
  172. var configuration = recursive({ iceServers: freeice() }, options.configuration);
  173. var onicecandidate = options.onicecandidate;
  174. if (onicecandidate)
  175. this.on('icecandidate', onicecandidate);
  176. var oncandidategatheringdone = options.oncandidategatheringdone;
  177. if (oncandidategatheringdone) {
  178. this.on('candidategatheringdone', oncandidategatheringdone);
  179. }
  180. var simulcast = options.simulcast;
  181. var multistream = options.multistream;
  182. var interop = new sdpTranslator.Interop();
  183. var candidatesQueueOut = [];
  184. var candidategatheringdone = false;
  185. Object.defineProperties(this, {
  186. 'peerConnection': {
  187. get: function () {
  188. return pc;
  189. }
  190. },
  191. 'id': {
  192. value: options.id || guid,
  193. writable: false
  194. },
  195. 'remoteVideo': {
  196. get: function () {
  197. return remoteVideo;
  198. }
  199. },
  200. 'localVideo': {
  201. get: function () {
  202. return localVideo;
  203. }
  204. },
  205. 'dataChannel': {
  206. get: function () {
  207. return dataChannel;
  208. }
  209. },
  210. 'currentFrame': {
  211. get: function () {
  212. if (!remoteVideo)
  213. return;
  214. if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)
  215. throw new Error('No video stream data available');
  216. var canvas = document.createElement('canvas');
  217. canvas.width = remoteVideo.videoWidth;
  218. canvas.height = remoteVideo.videoHeight;
  219. canvas.getContext('2d').drawImage(remoteVideo, 0, 0);
  220. return canvas;
  221. }
  222. }
  223. });
  224. if (!pc) {
  225. pc = new RTCPeerConnection(configuration);
  226. if (useDataChannels && !dataChannel) {
  227. var dcId = 'WebRtcPeer-' + self.id;
  228. var dcOptions = undefined;
  229. if (dataChannelConfig) {
  230. dcId = dataChannelConfig.id || dcId;
  231. dcOptions = dataChannelConfig.options;
  232. }
  233. dataChannel = pc.createDataChannel(dcId, dcOptions);
  234. if (dataChannelConfig) {
  235. dataChannel.onopen = dataChannelConfig.onopen;
  236. dataChannel.onclose = dataChannelConfig.onclose;
  237. dataChannel.onmessage = dataChannelConfig.onmessage;
  238. dataChannel.onbufferedamountlow = dataChannelConfig.onbufferedamountlow;
  239. dataChannel.onerror = dataChannelConfig.onerror || noop;
  240. }
  241. }
  242. }
  243. if (!pc.getLocalStreams && pc.getSenders) {
  244. pc.getLocalStreams = function () {
  245. var stream = new MediaStream();
  246. pc.getSenders().forEach(function (sender) {
  247. stream.addTrack(sender.track);
  248. });
  249. return [stream];
  250. };
  251. }
  252. if (!pc.getRemoteStreams && pc.getReceivers) {
  253. pc.getRemoteStreams = function () {
  254. var stream = new MediaStream();
  255. pc.getReceivers().forEach(function (sender) {
  256. stream.addTrack(sender.track);
  257. });
  258. return [stream];
  259. };
  260. }
  261. var iceCandidateFunction = function (event) {
  262. var candidate = event.candidate;
  263. if (EventEmitter.listenerCount(self, 'icecandidate') || EventEmitter.listenerCount(self, 'candidategatheringdone')) {
  264. if (candidate) {
  265. var cand;
  266. if (multistream && usePlanB) {
  267. cand = interop.candidateToUnifiedPlan(candidate);
  268. } else {
  269. cand = candidate;
  270. }
  271. if (typeof AdapterJS === 'undefined') {
  272. self.emit('icecandidate', cand);
  273. }
  274. candidategatheringdone = false;
  275. } else if (!candidategatheringdone) {
  276. if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
  277. EventEmitter.prototype.emit('candidategatheringdone', cand);
  278. } else {
  279. self.emit('candidategatheringdone');
  280. }
  281. candidategatheringdone = true;
  282. }
  283. } else if (!candidategatheringdone) {
  284. candidatesQueueOut.push(candidate);
  285. if (!candidate)
  286. candidategatheringdone = true;
  287. }
  288. };
  289. setIceCandidateAccordingWebBrowser(iceCandidateFunction, pc);
  290. pc.onaddstream = options.onaddstream;
  291. pc.onnegotiationneeded = options.onnegotiationneeded;
  292. this.on('newListener', function (event, listener) {
  293. if (event === 'icecandidate' || event === 'candidategatheringdone') {
  294. while (candidatesQueueOut.length) {
  295. var candidate = candidatesQueueOut.shift();
  296. if (!candidate === (event === 'candidategatheringdone')) {
  297. listener(candidate);
  298. }
  299. }
  300. }
  301. });
  302. var addIceCandidate = bufferizeCandidates(pc);
  303. this.addIceCandidate = function (iceCandidate, callback) {
  304. var candidate;
  305. if (multistream && usePlanB) {
  306. candidate = interop.candidateToPlanB(iceCandidate);
  307. } else {
  308. candidate = new RTCIceCandidate(iceCandidate);
  309. }
  310. logger.debug('Remote ICE candidate received', iceCandidate);
  311. callback = (callback || noop).bind(this);
  312. addIceCandidate(candidate, callback);
  313. };
  314. this.generateOffer = function (callback) {
  315. callback = callback.bind(this);
  316. if (mode === 'recvonly') {
  317. var useAudio = mediaConstraints && typeof mediaConstraints.audio === 'boolean' ? mediaConstraints.audio : true;
  318. var useVideo = mediaConstraints && typeof mediaConstraints.video === 'boolean' ? mediaConstraints.video : true;
  319. if (useAudio) {
  320. pc.addTransceiver('audio', { direction: 'recvonly' });
  321. }
  322. if (useVideo) {
  323. pc.addTransceiver('video', { direction: 'recvonly' });
  324. }
  325. }
  326. if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
  327. var setLocalDescriptionOnSuccess = function () {
  328. sleep(1000);
  329. var localDescription = pc.localDescription;
  330. logger.debug('Local description set\n', localDescription.sdp);
  331. if (multistream && usePlanB) {
  332. localDescription = interop.toUnifiedPlan(localDescription);
  333. logger.debug('offer::origPlanB->UnifiedPlan', dumpSDP(localDescription));
  334. }
  335. callback(null, localDescription.sdp, self.processAnswer.bind(self));
  336. };
  337. var createOfferOnSuccess = function (offer) {
  338. logger.debug('Created SDP offer');
  339. logger.debug('Local description set\n', pc.localDescription);
  340. pc.setLocalDescription(offer, setLocalDescriptionOnSuccess, callback);
  341. };
  342. pc.createOffer(createOfferOnSuccess, callback);
  343. } else {
  344. pc.createOffer().then(function (offer) {
  345. logger.debug('Created SDP offer');
  346. offer = mangleSdpToAddSimulcast(offer);
  347. return pc.setLocalDescription(offer);
  348. }).then(function () {
  349. var localDescription = pc.localDescription;
  350. logger.debug('Local description set\n', localDescription.sdp);
  351. if (multistream && usePlanB) {
  352. localDescription = interop.toUnifiedPlan(localDescription);
  353. logger.debug('offer::origPlanB->UnifiedPlan', dumpSDP(localDescription));
  354. }
  355. callback(null, localDescription.sdp, self.processAnswer.bind(self));
  356. }).catch(callback);
  357. }
  358. };
  359. this.getLocalSessionDescriptor = function () {
  360. return pc.localDescription;
  361. };
  362. this.getRemoteSessionDescriptor = function () {
  363. return pc.remoteDescription;
  364. };
  365. function setRemoteVideo() {
  366. if (remoteVideo) {
  367. remoteVideo.pause();
  368. var stream = pc.getRemoteStreams()[0];
  369. remoteVideo.srcObject = stream;
  370. logger.debug('Remote stream:', stream);
  371. if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
  372. remoteVideo = attachMediaStream(remoteVideo, stream);
  373. } else {
  374. remoteVideo.load();
  375. }
  376. }
  377. }
  378. this.showLocalVideo = function () {
  379. localVideo.srcObject = videoStream;
  380. localVideo.muted = true;
  381. if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
  382. localVideo = attachMediaStream(localVideo, videoStream);
  383. }
  384. };
  385. this.send = function (data) {
  386. if (dataChannel && dataChannel.readyState === 'open') {
  387. dataChannel.send(data);
  388. } else {
  389. logger.warn('Trying to send data over a non-existing or closed data channel');
  390. }
  391. };
  392. this.processAnswer = function (sdpAnswer, callback) {
  393. callback = (callback || noop).bind(this);
  394. var answer = new RTCSessionDescription({
  395. type: 'answer',
  396. sdp: sdpAnswer
  397. });
  398. if (multistream && usePlanB) {
  399. var planBAnswer = interop.toPlanB(answer);
  400. logger.debug('asnwer::planB', dumpSDP(planBAnswer));
  401. answer = planBAnswer;
  402. }
  403. logger.debug('SDP answer received, setting remote description');
  404. if (pc.signalingState === 'closed') {
  405. return callback('PeerConnection is closed');
  406. }
  407. pc.setRemoteDescription(answer).then(function () {
  408. setRemoteVideo();
  409. callback();
  410. }, callback);
  411. };
  412. this.processOffer = function (sdpOffer, callback) {
  413. callback = callback.bind(this);
  414. var offer = new RTCSessionDescription({
  415. type: 'offer',
  416. sdp: sdpOffer
  417. });
  418. if (multistream && usePlanB) {
  419. var planBOffer = interop.toPlanB(offer);
  420. logger.debug('offer::planB', dumpSDP(planBOffer));
  421. offer = planBOffer;
  422. }
  423. logger.debug('SDP offer received, setting remote description');
  424. if (pc.signalingState === 'closed') {
  425. return callback('PeerConnection is closed');
  426. }
  427. pc.setRemoteDescription(offer).then(function () {
  428. return setRemoteVideo();
  429. }).then(function () {
  430. return pc.createAnswer();
  431. }).then(function (answer) {
  432. answer = mangleSdpToAddSimulcast(answer);
  433. logger.debug('Created SDP answer');
  434. return pc.setLocalDescription(answer);
  435. }).then(function () {
  436. var localDescription = pc.localDescription;
  437. if (multistream && usePlanB) {
  438. localDescription = interop.toUnifiedPlan(localDescription);
  439. logger.debug('answer::origPlanB->UnifiedPlan', dumpSDP(localDescription));
  440. }
  441. logger.debug('Local description set\n', localDescription.sdp);
  442. callback(null, localDescription.sdp);
  443. }).catch(callback);
  444. };
  445. function mangleSdpToAddSimulcast(answer) {
  446. if (simulcast) {
  447. if (browser.name === 'Chrome' || browser.name === 'Chromium') {
  448. logger.debug('Adding multicast info');
  449. answer = new RTCSessionDescription({
  450. 'type': answer.type,
  451. 'sdp': removeFIDFromOffer(answer.sdp) + getSimulcastInfo(videoStream)
  452. });
  453. } else {
  454. logger.warn('Simulcast is only available in Chrome browser.');
  455. }
  456. }
  457. return answer;
  458. }
  459. function start() {
  460. if (pc.signalingState === 'closed') {
  461. callback('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue');
  462. }
  463. if (videoStream && localVideo) {
  464. self.showLocalVideo();
  465. }
  466. if (videoStream) {
  467. videoStream.getTracks().forEach(function (track) {
  468. pc.addTrack(track, videoStream);
  469. });
  470. }
  471. if (audioStream) {
  472. audioStream.getTracks().forEach(function (track) {
  473. pc.addTrack(track, audioStream);
  474. });
  475. }
  476. var browser = parser.getBrowser();
  477. if (mode === 'sendonly' && (browser.name === 'Chrome' || browser.name === 'Chromium') && browser.major === 39) {
  478. mode = 'sendrecv';
  479. }
  480. callback();
  481. }
  482. if (mode !== 'recvonly' && !videoStream && !audioStream) {
  483. function getMedia(constraints) {
  484. if (constraints === undefined) {
  485. constraints = MEDIA_CONSTRAINTS;
  486. }
  487. if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
  488. navigator.getUserMedia(constraints, function (stream) {
  489. videoStream = stream;
  490. start();
  491. }, callback);
  492. } else {
  493. navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
  494. videoStream = stream;
  495. start();
  496. }).catch(callback);
  497. }
  498. }
  499. if (sendSource === 'webcam') {
  500. getMedia(mediaConstraints);
  501. } else {
  502. getScreenConstraints(sendSource, function (error, constraints_) {
  503. if (error)
  504. return callback(error);
  505. constraints = [mediaConstraints];
  506. constraints.unshift(constraints_);
  507. getMedia(recursive.apply(undefined, constraints));
  508. }, guid);
  509. }
  510. } else {
  511. setTimeout(start, 0);
  512. }
  513. this.on('_dispose', function () {
  514. if (localVideo) {
  515. localVideo.pause();
  516. localVideo.srcObject = null;
  517. if (typeof AdapterJS === 'undefined') {
  518. localVideo.load();
  519. }
  520. localVideo.muted = false;
  521. }
  522. if (remoteVideo) {
  523. remoteVideo.pause();
  524. remoteVideo.srcObject = null;
  525. if (typeof AdapterJS === 'undefined') {
  526. remoteVideo.load();
  527. }
  528. }
  529. self.removeAllListeners();
  530. if (typeof window !== 'undefined' && window.cancelChooseDesktopMedia !== undefined) {
  531. window.cancelChooseDesktopMedia(guid);
  532. }
  533. });
  534. }
  535. inherits(WebRtcPeer, EventEmitter);
  536. function createEnableDescriptor(type) {
  537. var method = 'get' + type + 'Tracks';
  538. return {
  539. enumerable: true,
  540. get: function () {
  541. if (!this.peerConnection)
  542. return;
  543. var streams = this.peerConnection.getLocalStreams();
  544. if (!streams.length)
  545. return;
  546. for (var i = 0, stream; stream = streams[i]; i++) {
  547. var tracks = stream[method]();
  548. for (var j = 0, track; track = tracks[j]; j++)
  549. if (!track.enabled)
  550. return false;
  551. }
  552. return true;
  553. },
  554. set: function (value) {
  555. function trackSetEnable(track) {
  556. track.enabled = value;
  557. }
  558. this.peerConnection.getLocalStreams().forEach(function (stream) {
  559. stream[method]().forEach(trackSetEnable);
  560. });
  561. }
  562. };
  563. }
  564. Object.defineProperties(WebRtcPeer.prototype, {
  565. 'enabled': {
  566. enumerable: true,
  567. get: function () {
  568. return this.audioEnabled && this.videoEnabled;
  569. },
  570. set: function (value) {
  571. this.audioEnabled = this.videoEnabled = value;
  572. }
  573. },
  574. 'audioEnabled': createEnableDescriptor('Audio'),
  575. 'videoEnabled': createEnableDescriptor('Video')
  576. });
  577. WebRtcPeer.prototype.getLocalStream = function (index) {
  578. if (this.peerConnection) {
  579. return this.peerConnection.getLocalStreams()[index || 0];
  580. }
  581. };
  582. WebRtcPeer.prototype.getRemoteStream = function (index) {
  583. if (this.peerConnection) {
  584. return this.peerConnection.getRemoteStreams()[index || 0];
  585. }
  586. };
  587. WebRtcPeer.prototype.dispose = function () {
  588. logger.debug('Disposing WebRtcPeer');
  589. var pc = this.peerConnection;
  590. var dc = this.dataChannel;
  591. try {
  592. if (dc) {
  593. if (dc.readyState === 'closed')
  594. return;
  595. dc.close();
  596. }
  597. if (pc) {
  598. if (pc.signalingState === 'closed')
  599. return;
  600. pc.getLocalStreams().forEach(streamStop);
  601. pc.close();
  602. }
  603. } catch (err) {
  604. logger.warn('Exception disposing webrtc peer ' + err);
  605. }
  606. if (typeof AdapterJS === 'undefined') {
  607. this.emit('_dispose');
  608. }
  609. };
  610. function WebRtcPeerRecvonly(options, callback) {
  611. if (!(this instanceof WebRtcPeerRecvonly)) {
  612. return new WebRtcPeerRecvonly(options, callback);
  613. }
  614. WebRtcPeerRecvonly.super_.call(this, 'recvonly', options, callback);
  615. }
  616. inherits(WebRtcPeerRecvonly, WebRtcPeer);
  617. function WebRtcPeerSendonly(options, callback) {
  618. if (!(this instanceof WebRtcPeerSendonly)) {
  619. return new WebRtcPeerSendonly(options, callback);
  620. }
  621. WebRtcPeerSendonly.super_.call(this, 'sendonly', options, callback);
  622. }
  623. inherits(WebRtcPeerSendonly, WebRtcPeer);
  624. function WebRtcPeerSendrecv(options, callback) {
  625. if (!(this instanceof WebRtcPeerSendrecv)) {
  626. return new WebRtcPeerSendrecv(options, callback);
  627. }
  628. WebRtcPeerSendrecv.super_.call(this, 'sendrecv', options, callback);
  629. }
  630. inherits(WebRtcPeerSendrecv, WebRtcPeer);
  631. function harkUtils(stream, options) {
  632. return hark(stream, options);
  633. }
  634. exports.bufferizeCandidates = bufferizeCandidates;
  635. exports.WebRtcPeerRecvonly = WebRtcPeerRecvonly;
  636. exports.WebRtcPeerSendonly = WebRtcPeerSendonly;
  637. exports.WebRtcPeerSendrecv = WebRtcPeerSendrecv;
  638. exports.hark = harkUtils;
  639. },{"events":4,"freeice":5,"hark":8,"inherits":9,"kurento-browser-extensions":10,"merge":11,"sdp-translator":18,"ua-parser-js":21,"uuid/v4":24}],2:[function(require,module,exports){
  640. if (window.addEventListener)
  641. module.exports = require('./index');
  642. },{"./index":3}],3:[function(require,module,exports){
  643. var WebRtcPeer = require('./WebRtcPeer');
  644. exports.WebRtcPeer = WebRtcPeer;
  645. },{"./WebRtcPeer":1}],4:[function(require,module,exports){
  646. // Copyright Joyent, Inc. and other Node contributors.
  647. //
  648. // Permission is hereby granted, free of charge, to any person obtaining a
  649. // copy of this software and associated documentation files (the
  650. // "Software"), to deal in the Software without restriction, including
  651. // without limitation the rights to use, copy, modify, merge, publish,
  652. // distribute, sublicense, and/or sell copies of the Software, and to permit
  653. // persons to whom the Software is furnished to do so, subject to the
  654. // following conditions:
  655. //
  656. // The above copyright notice and this permission notice shall be included
  657. // in all copies or substantial portions of the Software.
  658. //
  659. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  660. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  661. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  662. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  663. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  664. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  665. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  666. var objectCreate = Object.create || objectCreatePolyfill
  667. var objectKeys = Object.keys || objectKeysPolyfill
  668. var bind = Function.prototype.bind || functionBindPolyfill
  669. function EventEmitter() {
  670. if (!this._events || !Object.prototype.hasOwnProperty.call(this, '_events')) {
  671. this._events = objectCreate(null);
  672. this._eventsCount = 0;
  673. }
  674. this._maxListeners = this._maxListeners || undefined;
  675. }
  676. module.exports = EventEmitter;
  677. // Backwards-compat with node 0.10.x
  678. EventEmitter.EventEmitter = EventEmitter;
  679. EventEmitter.prototype._events = undefined;
  680. EventEmitter.prototype._maxListeners = undefined;
  681. // By default EventEmitters will print a warning if more than 10 listeners are
  682. // added to it. This is a useful default which helps finding memory leaks.
  683. var defaultMaxListeners = 10;
  684. var hasDefineProperty;
  685. try {
  686. var o = {};
  687. if (Object.defineProperty) Object.defineProperty(o, 'x', { value: 0 });
  688. hasDefineProperty = o.x === 0;
  689. } catch (err) { hasDefineProperty = false }
  690. if (hasDefineProperty) {
  691. Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
  692. enumerable: true,
  693. get: function() {
  694. return defaultMaxListeners;
  695. },
  696. set: function(arg) {
  697. // check whether the input is a positive number (whose value is zero or
  698. // greater and not a NaN).
  699. if (typeof arg !== 'number' || arg < 0 || arg !== arg)
  700. throw new TypeError('"defaultMaxListeners" must be a positive number');
  701. defaultMaxListeners = arg;
  702. }
  703. });
  704. } else {
  705. EventEmitter.defaultMaxListeners = defaultMaxListeners;
  706. }
  707. // Obviously not all Emitters should be limited to 10. This function allows
  708. // that to be increased. Set to zero for unlimited.
  709. EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
  710. if (typeof n !== 'number' || n < 0 || isNaN(n))
  711. throw new TypeError('"n" argument must be a positive number');
  712. this._maxListeners = n;
  713. return this;
  714. };
  715. function $getMaxListeners(that) {
  716. if (that._maxListeners === undefined)
  717. return EventEmitter.defaultMaxListeners;
  718. return that._maxListeners;
  719. }
  720. EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
  721. return $getMaxListeners(this);
  722. };
  723. // These standalone emit* functions are used to optimize calling of event
  724. // handlers for fast cases because emit() itself often has a variable number of
  725. // arguments and can be deoptimized because of that. These functions always have
  726. // the same number of arguments and thus do not get deoptimized, so the code
  727. // inside them can execute faster.
  728. function emitNone(handler, isFn, self) {
  729. if (isFn)
  730. handler.call(self);
  731. else {
  732. var len = handler.length;
  733. var listeners = arrayClone(handler, len);
  734. for (var i = 0; i < len; ++i)
  735. listeners[i].call(self);
  736. }
  737. }
  738. function emitOne(handler, isFn, self, arg1) {
  739. if (isFn)
  740. handler.call(self, arg1);
  741. else {
  742. var len = handler.length;
  743. var listeners = arrayClone(handler, len);
  744. for (var i = 0; i < len; ++i)
  745. listeners[i].call(self, arg1);
  746. }
  747. }
  748. function emitTwo(handler, isFn, self, arg1, arg2) {
  749. if (isFn)
  750. handler.call(self, arg1, arg2);
  751. else {
  752. var len = handler.length;
  753. var listeners = arrayClone(handler, len);
  754. for (var i = 0; i < len; ++i)
  755. listeners[i].call(self, arg1, arg2);
  756. }
  757. }
  758. function emitThree(handler, isFn, self, arg1, arg2, arg3) {
  759. if (isFn)
  760. handler.call(self, arg1, arg2, arg3);
  761. else {
  762. var len = handler.length;
  763. var listeners = arrayClone(handler, len);
  764. for (var i = 0; i < len; ++i)
  765. listeners[i].call(self, arg1, arg2, arg3);
  766. }
  767. }
  768. function emitMany(handler, isFn, self, args) {
  769. if (isFn)
  770. handler.apply(self, args);
  771. else {
  772. var len = handler.length;
  773. var listeners = arrayClone(handler, len);
  774. for (var i = 0; i < len; ++i)
  775. listeners[i].apply(self, args);
  776. }
  777. }
  778. EventEmitter.prototype.emit = function emit(type) {
  779. var er, handler, len, args, i, events;
  780. var doError = (type === 'error');
  781. events = this._events;
  782. if (events)
  783. doError = (doError && events.error == null);
  784. else if (!doError)
  785. return false;
  786. // If there is no 'error' event listener then throw.
  787. if (doError) {
  788. if (arguments.length > 1)
  789. er = arguments[1];
  790. if (er instanceof Error) {
  791. throw er; // Unhandled 'error' event
  792. } else {
  793. // At least give some kind of context to the user
  794. var err = new Error('Unhandled "error" event. (' + er + ')');
  795. err.context = er;
  796. throw err;
  797. }
  798. return false;
  799. }
  800. handler = events[type];
  801. if (!handler)
  802. return false;
  803. var isFn = typeof handler === 'function';
  804. len = arguments.length;
  805. switch (len) {
  806. // fast cases
  807. case 1:
  808. emitNone(handler, isFn, this);
  809. break;
  810. case 2:
  811. emitOne(handler, isFn, this, arguments[1]);
  812. break;
  813. case 3:
  814. emitTwo(handler, isFn, this, arguments[1], arguments[2]);
  815. break;
  816. case 4:
  817. emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
  818. break;
  819. // slower
  820. default:
  821. args = new Array(len - 1);
  822. for (i = 1; i < len; i++)
  823. args[i - 1] = arguments[i];
  824. emitMany(handler, isFn, this, args);
  825. }
  826. return true;
  827. };
  828. function _addListener(target, type, listener, prepend) {
  829. var m;
  830. var events;
  831. var existing;
  832. if (typeof listener !== 'function')
  833. throw new TypeError('"listener" argument must be a function');
  834. events = target._events;
  835. if (!events) {
  836. events = target._events = objectCreate(null);
  837. target._eventsCount = 0;
  838. } else {
  839. // To avoid recursion in the case that type === "newListener"! Before
  840. // adding it to the listeners, first emit "newListener".
  841. if (events.newListener) {
  842. target.emit('newListener', type,
  843. listener.listener ? listener.listener : listener);
  844. // Re-assign `events` because a newListener handler could have caused the
  845. // this._events to be assigned to a new object
  846. events = target._events;
  847. }
  848. existing = events[type];
  849. }
  850. if (!existing) {
  851. // Optimize the case of one listener. Don't need the extra array object.
  852. existing = events[type] = listener;
  853. ++target._eventsCount;
  854. } else {
  855. if (typeof existing === 'function') {
  856. // Adding the second element, need to change to array.
  857. existing = events[type] =
  858. prepend ? [listener, existing] : [existing, listener];
  859. } else {
  860. // If we've already got an array, just append.
  861. if (prepend) {
  862. existing.unshift(listener);
  863. } else {
  864. existing.push(listener);
  865. }
  866. }
  867. // Check for listener leak
  868. if (!existing.warned) {
  869. m = $getMaxListeners(target);
  870. if (m && m > 0 && existing.length > m) {
  871. existing.warned = true;
  872. var w = new Error('Possible EventEmitter memory leak detected. ' +
  873. existing.length + ' "' + String(type) + '" listeners ' +
  874. 'added. Use emitter.setMaxListeners() to ' +
  875. 'increase limit.');
  876. w.name = 'MaxListenersExceededWarning';
  877. w.emitter = target;
  878. w.type = type;
  879. w.count = existing.length;
  880. if (typeof console === 'object' && console.warn) {
  881. console.warn('%s: %s', w.name, w.message);
  882. }
  883. }
  884. }
  885. }
  886. return target;
  887. }
  888. EventEmitter.prototype.addListener = function addListener(type, listener) {
  889. return _addListener(this, type, listener, false);
  890. };
  891. EventEmitter.prototype.on = EventEmitter.prototype.addListener;
  892. EventEmitter.prototype.prependListener =
  893. function prependListener(type, listener) {
  894. return _addListener(this, type, listener, true);
  895. };
  896. function onceWrapper() {
  897. if (!this.fired) {
  898. this.target.removeListener(this.type, this.wrapFn);
  899. this.fired = true;
  900. switch (arguments.length) {
  901. case 0:
  902. return this.listener.call(this.target);
  903. case 1:
  904. return this.listener.call(this.target, arguments[0]);
  905. case 2:
  906. return this.listener.call(this.target, arguments[0], arguments[1]);
  907. case 3:
  908. return this.listener.call(this.target, arguments[0], arguments[1],
  909. arguments[2]);
  910. default:
  911. var args = new Array(arguments.length);
  912. for (var i = 0; i < args.length; ++i)
  913. args[i] = arguments[i];
  914. this.listener.apply(this.target, args);
  915. }
  916. }
  917. }
  918. function _onceWrap(target, type, listener) {
  919. var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
  920. var wrapped = bind.call(onceWrapper, state);
  921. wrapped.listener = listener;
  922. state.wrapFn = wrapped;
  923. return wrapped;
  924. }
  925. EventEmitter.prototype.once = function once(type, listener) {
  926. if (typeof listener !== 'function')
  927. throw new TypeError('"listener" argument must be a function');
  928. this.on(type, _onceWrap(this, type, listener));
  929. return this;
  930. };
  931. EventEmitter.prototype.prependOnceListener =
  932. function prependOnceListener(type, listener) {
  933. if (typeof listener !== 'function')
  934. throw new TypeError('"listener" argument must be a function');
  935. this.prependListener(type, _onceWrap(this, type, listener));
  936. return this;
  937. };
  938. // Emits a 'removeListener' event if and only if the listener was removed.
  939. EventEmitter.prototype.removeListener =
  940. function removeListener(type, listener) {
  941. var list, events, position, i, originalListener;
  942. if (typeof listener !== 'function')
  943. throw new TypeError('"listener" argument must be a function');
  944. events = this._events;
  945. if (!events)
  946. return this;
  947. list = events[type];
  948. if (!list)
  949. return this;
  950. if (list === listener || list.listener === listener) {
  951. if (--this._eventsCount === 0)
  952. this._events = objectCreate(null);
  953. else {
  954. delete events[type];
  955. if (events.removeListener)
  956. this.emit('removeListener', type, list.listener || listener);
  957. }
  958. } else if (typeof list !== 'function') {
  959. position = -1;
  960. for (i = list.length - 1; i >= 0; i--) {
  961. if (list[i] === listener || list[i].listener === listener) {
  962. originalListener = list[i].listener;
  963. position = i;
  964. break;
  965. }
  966. }
  967. if (position < 0)
  968. return this;
  969. if (position === 0)
  970. list.shift();
  971. else
  972. spliceOne(list, position);
  973. if (list.length === 1)
  974. events[type] = list[0];
  975. if (events.removeListener)
  976. this.emit('removeListener', type, originalListener || listener);
  977. }
  978. return this;
  979. };
  980. EventEmitter.prototype.removeAllListeners =
  981. function removeAllListeners(type) {
  982. var listeners, events, i;
  983. events = this._events;
  984. if (!events)
  985. return this;
  986. // not listening for removeListener, no need to emit
  987. if (!events.removeListener) {
  988. if (arguments.length === 0) {
  989. this._events = objectCreate(null);
  990. this._eventsCount = 0;
  991. } else if (events[type]) {
  992. if (--this._eventsCount === 0)
  993. this._events = objectCreate(null);
  994. else
  995. delete events[type];
  996. }
  997. return this;
  998. }
  999. // emit removeListener for all listeners on all events
  1000. if (arguments.length === 0) {
  1001. var keys = objectKeys(events);
  1002. var key;
  1003. for (i = 0; i < keys.length; ++i) {
  1004. key = keys[i];
  1005. if (key === 'removeListener') continue;
  1006. this.removeAllListeners(key);
  1007. }
  1008. this.removeAllListeners('removeListener');
  1009. this._events = objectCreate(null);
  1010. this._eventsCount = 0;
  1011. return this;
  1012. }
  1013. listeners = events[type];
  1014. if (typeof listeners === 'function') {
  1015. this.removeListener(type, listeners);
  1016. } else if (listeners) {
  1017. // LIFO order
  1018. for (i = listeners.length - 1; i >= 0; i--) {
  1019. this.removeListener(type, listeners[i]);
  1020. }
  1021. }
  1022. return this;
  1023. };
  1024. function _listeners(target, type, unwrap) {
  1025. var events = target._events;
  1026. if (!events)
  1027. return [];
  1028. var evlistener = events[type];
  1029. if (!evlistener)
  1030. return [];
  1031. if (typeof evlistener === 'function')
  1032. return unwrap ? [evlistener.listener || evlistener] : [evlistener];
  1033. return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
  1034. }
  1035. EventEmitter.prototype.listeners = function listeners(type) {
  1036. return _listeners(this, type, true);
  1037. };
  1038. EventEmitter.prototype.rawListeners = function rawListeners(type) {
  1039. return _listeners(this, type, false);
  1040. };
  1041. EventEmitter.listenerCount = function(emitter, type) {
  1042. if (typeof emitter.listenerCount === 'function') {
  1043. return emitter.listenerCount(type);
  1044. } else {
  1045. return listenerCount.call(emitter, type);
  1046. }
  1047. };
  1048. EventEmitter.prototype.listenerCount = listenerCount;
  1049. function listenerCount(type) {
  1050. var events = this._events;
  1051. if (events) {
  1052. var evlistener = events[type];
  1053. if (typeof evlistener === 'function') {
  1054. return 1;
  1055. } else if (evlistener) {
  1056. return evlistener.length;
  1057. }
  1058. }
  1059. return 0;
  1060. }
  1061. EventEmitter.prototype.eventNames = function eventNames() {
  1062. return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
  1063. };
  1064. // About 1.5x faster than the two-arg version of Array#splice().
  1065. function spliceOne(list, index) {
  1066. for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
  1067. list[i] = list[k];
  1068. list.pop();
  1069. }
  1070. function arrayClone(arr, n) {
  1071. var copy = new Array(n);
  1072. for (var i = 0; i < n; ++i)
  1073. copy[i] = arr[i];
  1074. return copy;
  1075. }
  1076. function unwrapListeners(arr) {
  1077. var ret = new Array(arr.length);
  1078. for (var i = 0; i < ret.length; ++i) {
  1079. ret[i] = arr[i].listener || arr[i];
  1080. }
  1081. return ret;
  1082. }
  1083. function objectCreatePolyfill(proto) {
  1084. var F = function() {};
  1085. F.prototype = proto;
  1086. return new F;
  1087. }
  1088. function objectKeysPolyfill(obj) {
  1089. var keys = [];
  1090. for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) {
  1091. keys.push(k);
  1092. }
  1093. return k;
  1094. }
  1095. function functionBindPolyfill(context) {
  1096. var fn = this;
  1097. return function () {
  1098. return fn.apply(context, arguments);
  1099. };
  1100. }
  1101. },{}],5:[function(require,module,exports){
  1102. /* jshint node: true */
  1103. 'use strict';
  1104. var normalice = require('normalice');
  1105. /**
  1106. # freeice
  1107. The `freeice` module is a simple way of getting random STUN or TURN server
  1108. for your WebRTC application. The list of servers (just STUN at this stage)
  1109. were sourced from this [gist](https://gist.github.com/zziuni/3741933).
  1110. ## Example Use
  1111. The following demonstrates how you can use `freeice` with
  1112. [rtc-quickconnect](https://github.com/rtc-io/rtc-quickconnect):
  1113. <<< examples/quickconnect.js
  1114. As the `freeice` module generates ice servers in a list compliant with the
  1115. WebRTC spec you will be able to use it with raw `RTCPeerConnection`
  1116. constructors and other WebRTC libraries.
  1117. ## Hey, don't use my STUN/TURN server!
  1118. If for some reason your free STUN or TURN server ends up in the
  1119. list of servers ([stun](https://github.com/DamonOehlman/freeice/blob/master/stun.json) or
  1120. [turn](https://github.com/DamonOehlman/freeice/blob/master/turn.json))
  1121. that is used in this module, you can feel
  1122. free to open an issue on this repository and those servers will be removed
  1123. within 24 hours (or sooner). This is the quickest and probably the most
  1124. polite way to have something removed (and provides us some visibility
  1125. if someone opens a pull request requesting that a server is added).
  1126. ## Please add my server!
  1127. If you have a server that you wish to add to the list, that's awesome! I'm
  1128. sure I speak on behalf of a whole pile of WebRTC developers who say thanks.
  1129. To get it into the list, feel free to either open a pull request or if you
  1130. find that process a bit daunting then just create an issue requesting
  1131. the addition of the server (make sure you provide all the details, and if
  1132. you have a Terms of Service then including that in the PR/issue would be
  1133. awesome).
  1134. ## I know of a free server, can I add it?
  1135. Sure, if you do your homework and make sure it is ok to use (I'm currently
  1136. in the process of reviewing the terms of those STUN servers included from
  1137. the original list). If it's ok to go, then please see the previous entry
  1138. for how to add it.
  1139. ## Current List of Servers
  1140. * current as at the time of last `README.md` file generation
  1141. ### STUN
  1142. <<< stun.json
  1143. ### TURN
  1144. <<< turn.json
  1145. **/
  1146. var freeice = function(opts) {
  1147. // if a list of servers has been provided, then use it instead of defaults
  1148. var servers = {
  1149. stun: (opts || {}).stun || require('./stun.json'),
  1150. turn: (opts || {}).turn || require('./turn.json')
  1151. };
  1152. var stunCount = (opts || {}).stunCount || 2;
  1153. var turnCount = (opts || {}).turnCount || 0;
  1154. var selected;
  1155. function getServers(type, count) {
  1156. var out = [];
  1157. var input = [].concat(servers[type]);
  1158. var idx;
  1159. while (input.length && out.length < count) {
  1160. idx = (Math.random() * input.length) | 0;
  1161. out = out.concat(input.splice(idx, 1));
  1162. }
  1163. return out.map(function(url) {
  1164. //If it's a not a string, don't try to "normalice" it otherwise using type:url will screw it up
  1165. if ((typeof url !== 'string') && (! (url instanceof String))) {
  1166. return url;
  1167. } else {
  1168. return normalice(type + ':' + url);
  1169. }
  1170. });
  1171. }
  1172. // add stun servers
  1173. selected = [].concat(getServers('stun', stunCount));
  1174. if (turnCount) {
  1175. selected = selected.concat(getServers('turn', turnCount));
  1176. }
  1177. return selected;
  1178. };
  1179. module.exports = freeice;
  1180. },{"./stun.json":6,"./turn.json":7,"normalice":12}],6:[function(require,module,exports){
  1181. module.exports=[
  1182. "stun.l.google.com:19302",
  1183. "stun1.l.google.com:19302",
  1184. "stun2.l.google.com:19302",
  1185. "stun3.l.google.com:19302",
  1186. "stun4.l.google.com:19302",
  1187. "stun.ekiga.net",
  1188. "stun.ideasip.com",
  1189. "stun.schlund.de",
  1190. "stun.stunprotocol.org:3478",
  1191. "stun.voiparound.com",
  1192. "stun.voipbuster.com",
  1193. "stun.voipstunt.com",
  1194. "stun.voxgratia.org"
  1195. ]
  1196. },{}],7:[function(require,module,exports){
  1197. module.exports=[]
  1198. },{}],8:[function(require,module,exports){
  1199. var WildEmitter = require('wildemitter');
  1200. function getMaxVolume (analyser, fftBins) {
  1201. var maxVolume = -Infinity;
  1202. analyser.getFloatFrequencyData(fftBins);
  1203. for(var i=4, ii=fftBins.length; i < ii; i++) {
  1204. if (fftBins[i] > maxVolume && fftBins[i] < 0) {
  1205. maxVolume = fftBins[i];
  1206. }
  1207. };
  1208. return maxVolume;
  1209. }
  1210. var audioContextType;
  1211. if (typeof window !== 'undefined') {
  1212. audioContextType = window.AudioContext || window.webkitAudioContext;
  1213. }
  1214. // use a single audio context due to hardware limits
  1215. var audioContext = null;
  1216. module.exports = function(stream, options) {
  1217. var harker = new WildEmitter();
  1218. // make it not break in non-supported browsers
  1219. if (!audioContextType) return harker;
  1220. //Config
  1221. var options = options || {},
  1222. smoothing = (options.smoothing || 0.1),
  1223. interval = (options.interval || 50),
  1224. threshold = options.threshold,
  1225. play = options.play,
  1226. history = options.history || 10,
  1227. running = true;
  1228. // Ensure that just a single AudioContext is internally created
  1229. audioContext = options.audioContext || audioContext || new audioContextType();
  1230. var sourceNode, fftBins, analyser;
  1231. analyser = audioContext.createAnalyser();
  1232. analyser.fftSize = 512;
  1233. analyser.smoothingTimeConstant = smoothing;
  1234. fftBins = new Float32Array(analyser.frequencyBinCount);
  1235. if (stream.jquery) stream = stream[0];
  1236. if (stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement) {
  1237. //Audio Tag
  1238. sourceNode = audioContext.createMediaElementSource(stream);
  1239. if (typeof play === 'undefined') play = true;
  1240. threshold = threshold || -50;
  1241. } else {
  1242. //WebRTC Stream
  1243. sourceNode = audioContext.createMediaStreamSource(stream);
  1244. threshold = threshold || -50;
  1245. }
  1246. sourceNode.connect(analyser);
  1247. if (play) analyser.connect(audioContext.destination);
  1248. harker.speaking = false;
  1249. harker.suspend = function() {
  1250. return audioContext.suspend();
  1251. }
  1252. harker.resume = function() {
  1253. return audioContext.resume();
  1254. }
  1255. Object.defineProperty(harker, 'state', { get: function() {
  1256. return audioContext.state;
  1257. }});
  1258. audioContext.onstatechange = function() {
  1259. harker.emit('state_change', audioContext.state);
  1260. }
  1261. harker.setThreshold = function(t) {
  1262. threshold = t;
  1263. };
  1264. harker.setInterval = function(i) {
  1265. interval = i;
  1266. };
  1267. harker.stop = function() {
  1268. running = false;
  1269. harker.emit('volume_change', -100, threshold);
  1270. if (harker.speaking) {
  1271. harker.speaking = false;
  1272. harker.emit('stopped_speaking');
  1273. }
  1274. analyser.disconnect();
  1275. sourceNode.disconnect();
  1276. };
  1277. harker.speakingHistory = [];
  1278. for (var i = 0; i < history; i++) {
  1279. harker.speakingHistory.push(0);
  1280. }
  1281. // Poll the analyser node to determine if speaking
  1282. // and emit events if changed
  1283. var looper = function() {
  1284. setTimeout(function() {
  1285. //check if stop has been called
  1286. if(!running) {
  1287. return;
  1288. }
  1289. var currentVolume = getMaxVolume(analyser, fftBins);
  1290. harker.emit('volume_change', currentVolume, threshold);
  1291. var history = 0;
  1292. if (currentVolume > threshold && !harker.speaking) {
  1293. // trigger quickly, short history
  1294. for (var i = harker.speakingHistory.length - 3; i < harker.speakingHistory.length; i++) {
  1295. history += harker.speakingHistory[i];
  1296. }
  1297. if (history >= 2) {
  1298. harker.speaking = true;
  1299. harker.emit('speaking');
  1300. }
  1301. } else if (currentVolume < threshold && harker.speaking) {
  1302. for (var i = 0; i < harker.speakingHistory.length; i++) {
  1303. history += harker.speakingHistory[i];
  1304. }
  1305. if (history == 0) {
  1306. harker.speaking = false;
  1307. harker.emit('stopped_speaking');
  1308. }
  1309. }
  1310. harker.speakingHistory.shift();
  1311. harker.speakingHistory.push(0 + (currentVolume > threshold));
  1312. looper();
  1313. }, interval);
  1314. };
  1315. looper();
  1316. return harker;
  1317. }
  1318. },{"wildemitter":25}],9:[function(require,module,exports){
  1319. if (typeof Object.create === 'function') {
  1320. // implementation from standard node.js 'util' module
  1321. module.exports = function inherits(ctor, superCtor) {
  1322. if (superCtor) {
  1323. ctor.super_ = superCtor
  1324. ctor.prototype = Object.create(superCtor.prototype, {
  1325. constructor: {
  1326. value: ctor,
  1327. enumerable: false,
  1328. writable: true,
  1329. configurable: true
  1330. }
  1331. })
  1332. }
  1333. };
  1334. } else {
  1335. // old school shim for old browsers
  1336. module.exports = function inherits(ctor, superCtor) {
  1337. if (superCtor) {
  1338. ctor.super_ = superCtor
  1339. var TempCtor = function () {}
  1340. TempCtor.prototype = superCtor.prototype
  1341. ctor.prototype = new TempCtor()
  1342. ctor.prototype.constructor = ctor
  1343. }
  1344. }
  1345. }
  1346. },{}],10:[function(require,module,exports){
  1347. // Does nothing at all.
  1348. },{}],11:[function(require,module,exports){
  1349. /*!
  1350. * @name JavaScript/NodeJS Merge v1.2.1
  1351. * @author yeikos
  1352. * @repository https://github.com/yeikos/js.merge
  1353. * Copyright 2014 yeikos - MIT license
  1354. * https://raw.github.com/yeikos/js.merge/master/LICENSE
  1355. */
  1356. ;(function(isNode) {
  1357. /**
  1358. * Merge one or more objects
  1359. * @param bool? clone
  1360. * @param mixed,... arguments
  1361. * @return object
  1362. */
  1363. var Public = function(clone) {
  1364. return merge(clone === true, false, arguments);
  1365. }, publicName = 'merge';
  1366. /**
  1367. * Merge two or more objects recursively
  1368. * @param bool? clone
  1369. * @param mixed,... arguments
  1370. * @return object
  1371. */
  1372. Public.recursive = function(clone) {
  1373. return merge(clone === true, true, arguments);
  1374. };
  1375. /**
  1376. * Clone the input removing any reference
  1377. * @param mixed input
  1378. * @return mixed
  1379. */
  1380. Public.clone = function(input) {
  1381. var output = input,
  1382. type = typeOf(input),
  1383. index, size;
  1384. if (type === 'array') {
  1385. output = [];
  1386. size = input.length;
  1387. for (index=0;index<size;++index)
  1388. output[index] = Public.clone(input[index]);
  1389. } else if (type === 'object') {
  1390. output = {};
  1391. for (index in input)
  1392. output[index] = Public.clone(input[index]);
  1393. }
  1394. return output;
  1395. };
  1396. /**
  1397. * Merge two objects recursively
  1398. * @param mixed input
  1399. * @param mixed extend
  1400. * @return mixed
  1401. */
  1402. function merge_recursive(base, extend) {
  1403. if (typeOf(base) !== 'object')
  1404. return extend;
  1405. for (var key in extend) {
  1406. if (typeOf(base[key]) === 'object' && typeOf(extend[key]) === 'object') {
  1407. base[key] = merge_recursive(base[key], extend[key]);
  1408. } else {
  1409. base[key] = extend[key];
  1410. }
  1411. }
  1412. return base;
  1413. }
  1414. /**
  1415. * Merge two or more objects
  1416. * @param bool clone
  1417. * @param bool recursive
  1418. * @param array argv
  1419. * @return object
  1420. */
  1421. function merge(clone, recursive, argv) {
  1422. var result = argv[0],
  1423. size = argv.length;
  1424. if (clone || typeOf(result) !== 'object')
  1425. result = {};
  1426. for (var index=0;index<size;++index) {
  1427. var item = argv[index],
  1428. type = typeOf(item);
  1429. if (type !== 'object') continue;
  1430. for (var key in item) {
  1431. if (key === '__proto__') continue;
  1432. var sitem = clone ? Public.clone(item[key]) : item[key];
  1433. if (recursive) {
  1434. result[key] = merge_recursive(result[key], sitem);
  1435. } else {
  1436. result[key] = sitem;
  1437. }
  1438. }
  1439. }
  1440. return result;
  1441. }
  1442. /**
  1443. * Get type of variable
  1444. * @param mixed input
  1445. * @return string
  1446. *
  1447. * @see http://jsperf.com/typeofvar
  1448. */
  1449. function typeOf(input) {
  1450. return ({}).toString.call(input).slice(8, -1).toLowerCase();
  1451. }
  1452. if (isNode) {
  1453. module.exports = Public;
  1454. } else {
  1455. window[publicName] = Public;
  1456. }
  1457. })(typeof module === 'object' && module && typeof module.exports === 'object' && module.exports);
  1458. },{}],12:[function(require,module,exports){
  1459. /**
  1460. # normalice
  1461. Normalize an ice server configuration object (or plain old string) into a format
  1462. that is usable in all browsers supporting WebRTC. Primarily this module is designed
  1463. to help with the transition of the `url` attribute of the configuration object to
  1464. the `urls` attribute.
  1465. ## Example Usage
  1466. <<< examples/simple.js
  1467. **/
  1468. var protocols = [
  1469. 'stun:',
  1470. 'turn:'
  1471. ];
  1472. module.exports = function(input) {
  1473. var url = (input || {}).url || input;
  1474. var protocol;
  1475. var parts;
  1476. var output = {};
  1477. // if we don't have a string url, then allow the input to passthrough
  1478. if (typeof url != 'string' && (! (url instanceof String))) {
  1479. return input;
  1480. }
  1481. // trim the url string, and convert to an array
  1482. url = url.trim();
  1483. // if the protocol is not known, then passthrough
  1484. protocol = protocols[protocols.indexOf(url.slice(0, 5))];
  1485. if (! protocol) {
  1486. return input;
  1487. }
  1488. // now let's attack the remaining url parts
  1489. url = url.slice(5);
  1490. parts = url.split('@');
  1491. output.username = input.username;
  1492. output.credential = input.credential;
  1493. // if we have an authentication part, then set the credentials
  1494. if (parts.length > 1) {
  1495. url = parts[1];
  1496. parts = parts[0].split(':');
  1497. // add the output credential and username
  1498. output.username = parts[0];
  1499. output.credential = (input || {}).credential || parts[1] || '';
  1500. }
  1501. output.url = protocol + url;
  1502. output.urls = [ output.url ];
  1503. return output;
  1504. };
  1505. },{}],13:[function(require,module,exports){
  1506. var grammar = module.exports = {
  1507. v: [{
  1508. name: 'version',
  1509. reg: /^(\d*)$/
  1510. }],
  1511. o: [{ //o=- 20518 0 IN IP4 203.0.113.1
  1512. // NB: sessionId will be a String in most cases because it is huge
  1513. name: 'origin',
  1514. reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
  1515. names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
  1516. format: "%s %s %d %s IP%d %s"
  1517. }],
  1518. // default parsing of these only (though some of these feel outdated)
  1519. s: [{ name: 'name' }],
  1520. i: [{ name: 'description' }],
  1521. u: [{ name: 'uri' }],
  1522. e: [{ name: 'email' }],
  1523. p: [{ name: 'phone' }],
  1524. z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..
  1525. r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly
  1526. //k: [{}], // outdated thing ignored
  1527. t: [{ //t=0 0
  1528. name: 'timing',
  1529. reg: /^(\d*) (\d*)/,
  1530. names: ['start', 'stop'],
  1531. format: "%d %d"
  1532. }],
  1533. c: [{ //c=IN IP4 10.47.197.26
  1534. name: 'connection',
  1535. reg: /^IN IP(\d) (\S*)/,
  1536. names: ['version', 'ip'],
  1537. format: "IN IP%d %s"
  1538. }],
  1539. b: [{ //b=AS:4000
  1540. push: 'bandwidth',
  1541. reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
  1542. names: ['type', 'limit'],
  1543. format: "%s:%s"
  1544. }],
  1545. m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31
  1546. // NB: special - pushes to session
  1547. // TODO: rtp/fmtp should be filtered by the payloads found here?
  1548. reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,
  1549. names: ['type', 'port', 'protocol', 'payloads'],
  1550. format: "%s %d %s %s"
  1551. }],
  1552. a: [
  1553. { //a=rtpmap:110 opus/48000/2
  1554. push: 'rtp',
  1555. reg: /^rtpmap:(\d*) ([\w\-]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/,
  1556. names: ['payload', 'codec', 'rate', 'encoding'],
  1557. format: function (o) {
  1558. return (o.encoding) ?
  1559. "rtpmap:%d %s/%s/%s":
  1560. o.rate ?
  1561. "rtpmap:%d %s/%s":
  1562. "rtpmap:%d %s";
  1563. }
  1564. },
  1565. {
  1566. //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
  1567. //a=fmtp:111 minptime=10; useinbandfec=1
  1568. push: 'fmtp',
  1569. reg: /^fmtp:(\d*) ([\S| ]*)/,
  1570. names: ['payload', 'config'],
  1571. format: "fmtp:%d %s"
  1572. },
  1573. { //a=control:streamid=0
  1574. name: 'control',
  1575. reg: /^control:(.*)/,
  1576. format: "control:%s"
  1577. },
  1578. { //a=rtcp:65179 IN IP4 193.84.77.194
  1579. name: 'rtcp',
  1580. reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
  1581. names: ['port', 'netType', 'ipVer', 'address'],
  1582. format: function (o) {
  1583. return (o.address != null) ?
  1584. "rtcp:%d %s IP%d %s":
  1585. "rtcp:%d";
  1586. }
  1587. },
  1588. { //a=rtcp-fb:98 trr-int 100
  1589. push: 'rtcpFbTrrInt',
  1590. reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
  1591. names: ['payload', 'value'],
  1592. format: "rtcp-fb:%d trr-int %d"
  1593. },
  1594. { //a=rtcp-fb:98 nack rpsi
  1595. push: 'rtcpFb',
  1596. reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
  1597. names: ['payload', 'type', 'subtype'],
  1598. format: function (o) {
  1599. return (o.subtype != null) ?
  1600. "rtcp-fb:%s %s %s":
  1601. "rtcp-fb:%s %s";
  1602. }
  1603. },
  1604. { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
  1605. //a=extmap:1/recvonly URI-gps-string
  1606. push: 'ext',
  1607. reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/,
  1608. names: ['value', 'uri', 'config'], // value may include "/direction" suffix
  1609. format: function (o) {
  1610. return (o.config != null) ?
  1611. "extmap:%s %s %s":
  1612. "extmap:%s %s";
  1613. }
  1614. },
  1615. {
  1616. //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
  1617. push: 'crypto',
  1618. reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
  1619. names: ['id', 'suite', 'config', 'sessionConfig'],
  1620. format: function (o) {
  1621. return (o.sessionConfig != null) ?
  1622. "crypto:%d %s %s %s":
  1623. "crypto:%d %s %s";
  1624. }
  1625. },
  1626. { //a=setup:actpass
  1627. name: 'setup',
  1628. reg: /^setup:(\w*)/,
  1629. format: "setup:%s"
  1630. },
  1631. { //a=mid:1
  1632. name: 'mid',
  1633. reg: /^mid:([^\s]*)/,
  1634. format: "mid:%s"
  1635. },
  1636. { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
  1637. name: 'msid',
  1638. reg: /^msid:(.*)/,
  1639. format: "msid:%s"
  1640. },
  1641. { //a=ptime:20
  1642. name: 'ptime',
  1643. reg: /^ptime:(\d*)/,
  1644. format: "ptime:%d"
  1645. },
  1646. { //a=maxptime:60
  1647. name: 'maxptime',
  1648. reg: /^maxptime:(\d*)/,
  1649. format: "maxptime:%d"
  1650. },
  1651. { //a=sendrecv
  1652. name: 'direction',
  1653. reg: /^(sendrecv|recvonly|sendonly|inactive)/
  1654. },
  1655. { //a=ice-lite
  1656. name: 'icelite',
  1657. reg: /^(ice-lite)/
  1658. },
  1659. { //a=ice-ufrag:F7gI
  1660. name: 'iceUfrag',
  1661. reg: /^ice-ufrag:(\S*)/,
  1662. format: "ice-ufrag:%s"
  1663. },
  1664. { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g
  1665. name: 'icePwd',
  1666. reg: /^ice-pwd:(\S*)/,
  1667. format: "ice-pwd:%s"
  1668. },
  1669. { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
  1670. name: 'fingerprint',
  1671. reg: /^fingerprint:(\S*) (\S*)/,
  1672. names: ['type', 'hash'],
  1673. format: "fingerprint:%s %s"
  1674. },
  1675. {
  1676. //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
  1677. //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0
  1678. //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0
  1679. //a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0
  1680. //a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0
  1681. push:'candidates',
  1682. reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?/,
  1683. names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation'],
  1684. format: function (o) {
  1685. var str = "candidate:%s %d %s %d %s %d typ %s";
  1686. str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v";
  1687. // NB: candidate has three optional chunks, so %void middles one if it's missing
  1688. str += (o.tcptype != null) ? " tcptype %s" : "%v";
  1689. if (o.generation != null) {
  1690. str += " generation %d";
  1691. }
  1692. return str;
  1693. }
  1694. },
  1695. { //a=end-of-candidates (keep after the candidates line for readability)
  1696. name: 'endOfCandidates',
  1697. reg: /^(end-of-candidates)/
  1698. },
  1699. { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
  1700. name: 'remoteCandidates',
  1701. reg: /^remote-candidates:(.*)/,
  1702. format: "remote-candidates:%s"
  1703. },
  1704. { //a=ice-options:google-ice
  1705. name: 'iceOptions',
  1706. reg: /^ice-options:(\S*)/,
  1707. format: "ice-options:%s"
  1708. },
  1709. { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
  1710. push: "ssrcs",
  1711. reg: /^ssrc:(\d*) ([\w_]*):(.*)/,
  1712. names: ['id', 'attribute', 'value'],
  1713. format: "ssrc:%d %s:%s"
  1714. },
  1715. { //a=ssrc-group:FEC 1 2
  1716. push: "ssrcGroups",
  1717. reg: /^ssrc-group:(\w*) (.*)/,
  1718. names: ['semantics', 'ssrcs'],
  1719. format: "ssrc-group:%s %s"
  1720. },
  1721. { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
  1722. name: "msidSemantic",
  1723. reg: /^msid-semantic:\s?(\w*) (\S*)/,
  1724. names: ['semantic', 'token'],
  1725. format: "msid-semantic: %s %s" // space after ":" is not accidental
  1726. },
  1727. { //a=group:BUNDLE audio video
  1728. push: 'groups',
  1729. reg: /^group:(\w*) (.*)/,
  1730. names: ['type', 'mids'],
  1731. format: "group:%s %s"
  1732. },
  1733. { //a=rtcp-mux
  1734. name: 'rtcpMux',
  1735. reg: /^(rtcp-mux)/
  1736. },
  1737. { //a=rtcp-rsize
  1738. name: 'rtcpRsize',
  1739. reg: /^(rtcp-rsize)/
  1740. },
  1741. { // any a= that we don't understand is kepts verbatim on media.invalid
  1742. push: 'invalid',
  1743. names: ["value"]
  1744. }
  1745. ]
  1746. };
  1747. // set sensible defaults to avoid polluting the grammar with boring details
  1748. Object.keys(grammar).forEach(function (key) {
  1749. var objs = grammar[key];
  1750. objs.forEach(function (obj) {
  1751. if (!obj.reg) {
  1752. obj.reg = /(.*)/;
  1753. }
  1754. if (!obj.format) {
  1755. obj.format = "%s";
  1756. }
  1757. });
  1758. });
  1759. },{}],14:[function(require,module,exports){
  1760. var parser = require('./parser');
  1761. var writer = require('./writer');
  1762. exports.write = writer;
  1763. exports.parse = parser.parse;
  1764. exports.parseFmtpConfig = parser.parseFmtpConfig;
  1765. exports.parsePayloads = parser.parsePayloads;
  1766. exports.parseRemoteCandidates = parser.parseRemoteCandidates;
  1767. },{"./parser":15,"./writer":16}],15:[function(require,module,exports){
  1768. var toIntIfInt = function (v) {
  1769. return String(Number(v)) === v ? Number(v) : v;
  1770. };
  1771. var attachProperties = function (match, location, names, rawName) {
  1772. if (rawName && !names) {
  1773. location[rawName] = toIntIfInt(match[1]);
  1774. }
  1775. else {
  1776. for (var i = 0; i < names.length; i += 1) {
  1777. if (match[i+1] != null) {
  1778. location[names[i]] = toIntIfInt(match[i+1]);
  1779. }
  1780. }
  1781. }
  1782. };
  1783. var parseReg = function (obj, location, content) {
  1784. var needsBlank = obj.name && obj.names;
  1785. if (obj.push && !location[obj.push]) {
  1786. location[obj.push] = [];
  1787. }
  1788. else if (needsBlank && !location[obj.name]) {
  1789. location[obj.name] = {};
  1790. }
  1791. var keyLocation = obj.push ?
  1792. {} : // blank object that will be pushed
  1793. needsBlank ? location[obj.name] : location; // otherwise, named location or root
  1794. attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
  1795. if (obj.push) {
  1796. location[obj.push].push(keyLocation);
  1797. }
  1798. };
  1799. var grammar = require('./grammar');
  1800. var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
  1801. exports.parse = function (sdp) {
  1802. var session = {}
  1803. , media = []
  1804. , location = session; // points at where properties go under (one of the above)
  1805. // parse lines we understand
  1806. sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
  1807. var type = l[0];
  1808. var content = l.slice(2);
  1809. if (type === 'm') {
  1810. media.push({rtp: [], fmtp: []});
  1811. location = media[media.length-1]; // point at latest media line
  1812. }
  1813. for (var j = 0; j < (grammar[type] || []).length; j += 1) {
  1814. var obj = grammar[type][j];
  1815. if (obj.reg.test(content)) {
  1816. return parseReg(obj, location, content);
  1817. }
  1818. }
  1819. });
  1820. session.media = media; // link it up
  1821. return session;
  1822. };
  1823. var fmtpReducer = function (acc, expr) {
  1824. var s = expr.split('=');
  1825. if (s.length === 2) {
  1826. acc[s[0]] = toIntIfInt(s[1]);
  1827. }
  1828. return acc;
  1829. };
  1830. exports.parseFmtpConfig = function (str) {
  1831. return str.split(/\;\s?/).reduce(fmtpReducer, {});
  1832. };
  1833. exports.parsePayloads = function (str) {
  1834. return str.split(' ').map(Number);
  1835. };
  1836. exports.parseRemoteCandidates = function (str) {
  1837. var candidates = [];
  1838. var parts = str.split(' ').map(toIntIfInt);
  1839. for (var i = 0; i < parts.length; i += 3) {
  1840. candidates.push({
  1841. component: parts[i],
  1842. ip: parts[i + 1],
  1843. port: parts[i + 2]
  1844. });
  1845. }
  1846. return candidates;
  1847. };
  1848. },{"./grammar":13}],16:[function(require,module,exports){
  1849. var grammar = require('./grammar');
  1850. // customized util.format - discards excess arguments and can void middle ones
  1851. var formatRegExp = /%[sdv%]/g;
  1852. var format = function (formatStr) {
  1853. var i = 1;
  1854. var args = arguments;
  1855. var len = args.length;
  1856. return formatStr.replace(formatRegExp, function (x) {
  1857. if (i >= len) {
  1858. return x; // missing argument
  1859. }
  1860. var arg = args[i];
  1861. i += 1;
  1862. switch (x) {
  1863. case '%%':
  1864. return '%';
  1865. case '%s':
  1866. return String(arg);
  1867. case '%d':
  1868. return Number(arg);
  1869. case '%v':
  1870. return '';
  1871. }
  1872. });
  1873. // NB: we discard excess arguments - they are typically undefined from makeLine
  1874. };
  1875. var makeLine = function (type, obj, location) {
  1876. var str = obj.format instanceof Function ?
  1877. (obj.format(obj.push ? location : location[obj.name])) :
  1878. obj.format;
  1879. var args = [type + '=' + str];
  1880. if (obj.names) {
  1881. for (var i = 0; i < obj.names.length; i += 1) {
  1882. var n = obj.names[i];
  1883. if (obj.name) {
  1884. args.push(location[obj.name][n]);
  1885. }
  1886. else { // for mLine and push attributes
  1887. args.push(location[obj.names[i]]);
  1888. }
  1889. }
  1890. }
  1891. else {
  1892. args.push(location[obj.name]);
  1893. }
  1894. return format.apply(null, args);
  1895. };
  1896. // RFC specified order
  1897. // TODO: extend this with all the rest
  1898. var defaultOuterOrder = [
  1899. 'v', 'o', 's', 'i',
  1900. 'u', 'e', 'p', 'c',
  1901. 'b', 't', 'r', 'z', 'a'
  1902. ];
  1903. var defaultInnerOrder = ['i', 'c', 'b', 'a'];
  1904. module.exports = function (session, opts) {
  1905. opts = opts || {};
  1906. // ensure certain properties exist
  1907. if (session.version == null) {
  1908. session.version = 0; // "v=0" must be there (only defined version atm)
  1909. }
  1910. if (session.name == null) {
  1911. session.name = " "; // "s= " must be there if no meaningful name set
  1912. }
  1913. session.media.forEach(function (mLine) {
  1914. if (mLine.payloads == null) {
  1915. mLine.payloads = "";
  1916. }
  1917. });
  1918. var outerOrder = opts.outerOrder || defaultOuterOrder;
  1919. var innerOrder = opts.innerOrder || defaultInnerOrder;
  1920. var sdp = [];
  1921. // loop through outerOrder for matching properties on session
  1922. outerOrder.forEach(function (type) {
  1923. grammar[type].forEach(function (obj) {
  1924. if (obj.name in session && session[obj.name] != null) {
  1925. sdp.push(makeLine(type, obj, session));
  1926. }
  1927. else if (obj.push in session && session[obj.push] != null) {
  1928. session[obj.push].forEach(function (el) {
  1929. sdp.push(makeLine(type, obj, el));
  1930. });
  1931. }
  1932. });
  1933. });
  1934. // then for each media line, follow the innerOrder
  1935. session.media.forEach(function (mLine) {
  1936. sdp.push(makeLine('m', grammar.m[0], mLine));
  1937. innerOrder.forEach(function (type) {
  1938. grammar[type].forEach(function (obj) {
  1939. if (obj.name in mLine && mLine[obj.name] != null) {
  1940. sdp.push(makeLine(type, obj, mLine));
  1941. }
  1942. else if (obj.push in mLine && mLine[obj.push] != null) {
  1943. mLine[obj.push].forEach(function (el) {
  1944. sdp.push(makeLine(type, obj, el));
  1945. });
  1946. }
  1947. });
  1948. });
  1949. });
  1950. return sdp.join('\r\n') + '\r\n';
  1951. };
  1952. },{"./grammar":13}],17:[function(require,module,exports){
  1953. /* Copyright @ 2015 Atlassian Pty Ltd
  1954. *
  1955. * Licensed under the Apache License, Version 2.0 (the "License");
  1956. * you may not use this file except in compliance with the License.
  1957. * You may obtain a copy of the License at
  1958. *
  1959. * http://www.apache.org/licenses/LICENSE-2.0
  1960. *
  1961. * Unless required by applicable law or agreed to in writing, software
  1962. * distributed under the License is distributed on an "AS IS" BASIS,
  1963. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1964. * See the License for the specific language governing permissions and
  1965. * limitations under the License.
  1966. */
  1967. module.exports = function arrayEquals(array) {
  1968. // if the other array is a falsy value, return
  1969. if (!array)
  1970. return false;
  1971. // compare lengths - can save a lot of time
  1972. if (this.length != array.length)
  1973. return false;
  1974. for (var i = 0, l = this.length; i < l; i++) {
  1975. // Check if we have nested arrays
  1976. if (this[i] instanceof Array && array[i] instanceof Array) {
  1977. // recurse into the nested arrays
  1978. if (!arrayEquals.apply(this[i], [array[i]]))
  1979. return false;
  1980. } else if (this[i] != array[i]) {
  1981. // Warning - two different object instances will never be equal:
  1982. // {x:20} != {x:20}
  1983. return false;
  1984. }
  1985. }
  1986. return true;
  1987. };
  1988. },{}],18:[function(require,module,exports){
  1989. /* Copyright @ 2015 Atlassian Pty Ltd
  1990. *
  1991. * Licensed under the Apache License, Version 2.0 (the "License");
  1992. * you may not use this file except in compliance with the License.
  1993. * You may obtain a copy of the License at
  1994. *
  1995. * http://www.apache.org/licenses/LICENSE-2.0
  1996. *
  1997. * Unless required by applicable law or agreed to in writing, software
  1998. * distributed under the License is distributed on an "AS IS" BASIS,
  1999. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2000. * See the License for the specific language governing permissions and
  2001. * limitations under the License.
  2002. */
  2003. exports.Interop = require('./interop');
  2004. },{"./interop":19}],19:[function(require,module,exports){
  2005. /* Copyright @ 2015 Atlassian Pty Ltd
  2006. *
  2007. * Licensed under the Apache License, Version 2.0 (the "License");
  2008. * you may not use this file except in compliance with the License.
  2009. * You may obtain a copy of the License at
  2010. *
  2011. * http://www.apache.org/licenses/LICENSE-2.0
  2012. *
  2013. * Unless required by applicable law or agreed to in writing, software
  2014. * distributed under the License is distributed on an "AS IS" BASIS,
  2015. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2016. * See the License for the specific language governing permissions and
  2017. * limitations under the License.
  2018. */
  2019. /* global RTCSessionDescription */
  2020. /* global RTCIceCandidate */
  2021. /* jshint -W097 */
  2022. "use strict";
  2023. var transform = require('./transform');
  2024. var arrayEquals = require('./array-equals');
  2025. function Interop() {
  2026. /**
  2027. * This map holds the most recent Unified Plan offer/answer SDP that was
  2028. * converted to Plan B, with the SDP type ('offer' or 'answer') as keys and
  2029. * the SDP string as values.
  2030. *
  2031. * @type {{}}
  2032. */
  2033. this.cache = {
  2034. mlB2UMap : {},
  2035. mlU2BMap : {}
  2036. };
  2037. }
  2038. module.exports = Interop;
  2039. /**
  2040. * Changes the candidate args to match with the related Unified Plan
  2041. */
  2042. Interop.prototype.candidateToUnifiedPlan = function(candidate) {
  2043. var cand = new RTCIceCandidate(candidate);
  2044. cand.sdpMLineIndex = this.cache.mlB2UMap[cand.sdpMLineIndex];
  2045. /* TODO: change sdpMid to (audio|video)-SSRC */
  2046. return cand;
  2047. };
  2048. /**
  2049. * Changes the candidate args to match with the related Plan B
  2050. */
  2051. Interop.prototype.candidateToPlanB = function(candidate) {
  2052. var cand = new RTCIceCandidate(candidate);
  2053. if (cand.sdpMid.indexOf('audio') === 0) {
  2054. cand.sdpMid = 'audio';
  2055. } else if (cand.sdpMid.indexOf('video') === 0) {
  2056. cand.sdpMid = 'video';
  2057. } else {
  2058. throw new Error('candidate with ' + cand.sdpMid + ' not allowed');
  2059. }
  2060. cand.sdpMLineIndex = this.cache.mlU2BMap[cand.sdpMLineIndex];
  2061. return cand;
  2062. };
  2063. /**
  2064. * Returns the index of the first m-line with the given media type and with a
  2065. * direction which allows sending, in the last Unified Plan description with
  2066. * type "answer" converted to Plan B. Returns {null} if there is no saved
  2067. * answer, or if none of its m-lines with the given type allow sending.
  2068. * @param type the media type ("audio" or "video").
  2069. * @returns {*}
  2070. */
  2071. Interop.prototype.getFirstSendingIndexFromAnswer = function(type) {
  2072. if (!this.cache.answer) {
  2073. return null;
  2074. }
  2075. var session = transform.parse(this.cache.answer);
  2076. if (session && session.media && Array.isArray(session.media)){
  2077. for (var i = 0; i < session.media.length; i++) {
  2078. if (session.media[i].type == type &&
  2079. (!session.media[i].direction /* default to sendrecv */ ||
  2080. session.media[i].direction === 'sendrecv' ||
  2081. session.media[i].direction === 'sendonly')){
  2082. return i;
  2083. }
  2084. }
  2085. }
  2086. return null;
  2087. };
  2088. /**
  2089. * This method transforms a Unified Plan SDP to an equivalent Plan B SDP. A
  2090. * PeerConnection wrapper transforms the SDP to Plan B before passing it to the
  2091. * application.
  2092. *
  2093. * @param desc
  2094. * @returns {*}
  2095. */
  2096. Interop.prototype.toPlanB = function(desc) {
  2097. var self = this;
  2098. //#region Preliminary input validation.
  2099. if (typeof desc !== 'object' || desc === null ||
  2100. typeof desc.sdp !== 'string') {
  2101. console.warn('An empty description was passed as an argument.');
  2102. return desc;
  2103. }
  2104. // Objectify the SDP for easier manipulation.
  2105. var session = transform.parse(desc.sdp);
  2106. // If the SDP contains no media, there's nothing to transform.
  2107. if (typeof session.media === 'undefined' ||
  2108. !Array.isArray(session.media) || session.media.length === 0) {
  2109. console.warn('The description has no media.');
  2110. return desc;
  2111. }
  2112. // Try some heuristics to "make sure" this is a Unified Plan SDP. Plan B
  2113. // SDP has a video, an audio and a data "channel" at most.
  2114. if (session.media.length <= 3 && session.media.every(function(m) {
  2115. return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
  2116. })) {
  2117. console.warn('This description does not look like Unified Plan.');
  2118. return desc;
  2119. }
  2120. //#endregion
  2121. // HACK https://bugzilla.mozilla.org/show_bug.cgi?id=1113443
  2122. var sdp = desc.sdp;
  2123. var rewrite = false;
  2124. for (var i = 0; i < session.media.length; i++) {
  2125. var uLine = session.media[i];
  2126. uLine.rtp.forEach(function(rtp) {
  2127. if (rtp.codec === 'NULL')
  2128. {
  2129. rewrite = true;
  2130. var offer = transform.parse(self.cache.offer);
  2131. rtp.codec = offer.media[i].rtp[0].codec;
  2132. }
  2133. });
  2134. }
  2135. if (rewrite) {
  2136. sdp = transform.write(session);
  2137. }
  2138. // Unified Plan SDP is our "precious". Cache it for later use in the Plan B
  2139. // -> Unified Plan transformation.
  2140. this.cache[desc.type] = sdp;
  2141. //#region Convert from Unified Plan to Plan B.
  2142. // We rebuild the session.media array.
  2143. var media = session.media;
  2144. session.media = [];
  2145. // Associative array that maps channel types to channel objects for fast
  2146. // access to channel objects by their type, e.g. type2bl['audio']->channel
  2147. // obj.
  2148. var type2bl = {};
  2149. // Used to build the group:BUNDLE value after the channels construction
  2150. // loop.
  2151. var types = [];
  2152. media.forEach(function(uLine) {
  2153. // rtcp-mux is required in the Plan B SDP.
  2154. if ((typeof uLine.rtcpMux !== 'string' ||
  2155. uLine.rtcpMux !== 'rtcp-mux') &&
  2156. uLine.direction !== 'inactive') {
  2157. throw new Error('Cannot convert to Plan B because m-lines ' +
  2158. 'without the rtcp-mux attribute were found.');
  2159. }
  2160. // If we don't have a channel for this uLine.type OR the selected is
  2161. // inactive, then select this uLine as the channel basis.
  2162. if (typeof type2bl[uLine.type] === 'undefined' ||
  2163. type2bl[uLine.type].direction === 'inactive') {
  2164. type2bl[uLine.type] = uLine;
  2165. }
  2166. if (uLine.protocol != type2bl[uLine.type].protocol) {
  2167. throw new Error('Cannot convert to Plan B because m-lines ' +
  2168. 'have different protocols and this library does not have ' +
  2169. 'support for that');
  2170. }
  2171. if (uLine.payloads != type2bl[uLine.type].payloads) {
  2172. throw new Error('Cannot convert to Plan B because m-lines ' +
  2173. 'have different payloads and this library does not have ' +
  2174. 'support for that');
  2175. }
  2176. });
  2177. // Implode the Unified Plan m-lines/tracks into Plan B channels.
  2178. media.forEach(function(uLine) {
  2179. if (uLine.type === 'application') {
  2180. session.media.push(uLine);
  2181. types.push(uLine.mid);
  2182. return;
  2183. }
  2184. // Add sources to the channel and handle a=msid.
  2185. if (typeof uLine.sources === 'object') {
  2186. Object.keys(uLine.sources).forEach(function(ssrc) {
  2187. if (typeof type2bl[uLine.type].sources !== 'object')
  2188. type2bl[uLine.type].sources = {};
  2189. // Assign the sources to the channel.
  2190. type2bl[uLine.type].sources[ssrc] =
  2191. uLine.sources[ssrc];
  2192. if (typeof uLine.msid !== 'undefined') {
  2193. // In Plan B the msid is an SSRC attribute. Also, we don't
  2194. // care about the obsolete label and mslabel attributes.
  2195. //
  2196. // Note that it is not guaranteed that the uLine will
  2197. // have an msid. recvonly channels in particular don't have
  2198. // one.
  2199. type2bl[uLine.type].sources[ssrc].msid =
  2200. uLine.msid;
  2201. }
  2202. // NOTE ssrcs in ssrc groups will share msids, as
  2203. // draft-uberti-rtcweb-plan-00 mandates.
  2204. });
  2205. }
  2206. // Add ssrc groups to the channel.
  2207. if (typeof uLine.ssrcGroups !== 'undefined' &&
  2208. Array.isArray(uLine.ssrcGroups)) {
  2209. // Create the ssrcGroups array, if it's not defined.
  2210. if (typeof type2bl[uLine.type].ssrcGroups === 'undefined' ||
  2211. !Array.isArray(type2bl[uLine.type].ssrcGroups)) {
  2212. type2bl[uLine.type].ssrcGroups = [];
  2213. }
  2214. type2bl[uLine.type].ssrcGroups =
  2215. type2bl[uLine.type].ssrcGroups.concat(
  2216. uLine.ssrcGroups);
  2217. }
  2218. if (type2bl[uLine.type] === uLine) {
  2219. // Plan B mids are in ['audio', 'video', 'data']
  2220. uLine.mid = uLine.type;
  2221. // Plan B doesn't support/need the bundle-only attribute.
  2222. delete uLine.bundleOnly;
  2223. // In Plan B the msid is an SSRC attribute.
  2224. delete uLine.msid;
  2225. if (uLine.type == media[0].type) {
  2226. types.unshift(uLine.type);
  2227. // Add the channel to the new media array.
  2228. session.media.unshift(uLine);
  2229. } else {
  2230. types.push(uLine.type);
  2231. // Add the channel to the new media array.
  2232. session.media.push(uLine);
  2233. }
  2234. }
  2235. });
  2236. if (typeof session.groups !== 'undefined') {
  2237. // We regenerate the BUNDLE group with the new mids.
  2238. session.groups.some(function(group) {
  2239. if (group.type === 'BUNDLE') {
  2240. group.mids = types.join(' ');
  2241. return true;
  2242. }
  2243. });
  2244. }
  2245. // msid semantic
  2246. session.msidSemantic = {
  2247. semantic: 'WMS',
  2248. token: '*'
  2249. };
  2250. var resStr = transform.write(session);
  2251. return new RTCSessionDescription({
  2252. type: desc.type,
  2253. sdp: resStr
  2254. });
  2255. //#endregion
  2256. };
  2257. /* follow rules defined in RFC4145 */
  2258. function addSetupAttr(uLine) {
  2259. if (typeof uLine.setup === 'undefined') {
  2260. return;
  2261. }
  2262. if (uLine.setup === "active") {
  2263. uLine.setup = "passive";
  2264. } else if (uLine.setup === "passive") {
  2265. uLine.setup = "active";
  2266. }
  2267. }
  2268. /**
  2269. * This method transforms a Plan B SDP to an equivalent Unified Plan SDP. A
  2270. * PeerConnection wrapper transforms the SDP to Unified Plan before passing it
  2271. * to FF.
  2272. *
  2273. * @param desc
  2274. * @returns {*}
  2275. */
  2276. Interop.prototype.toUnifiedPlan = function(desc) {
  2277. var self = this;
  2278. //#region Preliminary input validation.
  2279. if (typeof desc !== 'object' || desc === null ||
  2280. typeof desc.sdp !== 'string') {
  2281. console.warn('An empty description was passed as an argument.');
  2282. return desc;
  2283. }
  2284. var session = transform.parse(desc.sdp);
  2285. // If the SDP contains no media, there's nothing to transform.
  2286. if (typeof session.media === 'undefined' ||
  2287. !Array.isArray(session.media) || session.media.length === 0) {
  2288. console.warn('The description has no media.');
  2289. return desc;
  2290. }
  2291. // Try some heuristics to "make sure" this is a Plan B SDP. Plan B SDP has
  2292. // a video, an audio and a data "channel" at most.
  2293. if (session.media.length > 3 || !session.media.every(function(m) {
  2294. return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
  2295. })) {
  2296. console.warn('This description does not look like Plan B.');
  2297. return desc;
  2298. }
  2299. // Make sure this Plan B SDP can be converted to a Unified Plan SDP.
  2300. var mids = [];
  2301. session.media.forEach(function(m) {
  2302. mids.push(m.mid);
  2303. });
  2304. var hasBundle = false;
  2305. if (typeof session.groups !== 'undefined' &&
  2306. Array.isArray(session.groups)) {
  2307. hasBundle = session.groups.every(function(g) {
  2308. return g.type !== 'BUNDLE' ||
  2309. arrayEquals.apply(g.mids.sort(), [mids.sort()]);
  2310. });
  2311. }
  2312. if (!hasBundle) {
  2313. var mustBeBundle = false;
  2314. session.media.forEach(function(m) {
  2315. if (m.direction !== 'inactive') {
  2316. mustBeBundle = true;
  2317. }
  2318. });
  2319. if (mustBeBundle) {
  2320. throw new Error("Cannot convert to Unified Plan because m-lines that" +
  2321. " are not bundled were found.");
  2322. }
  2323. }
  2324. //#endregion
  2325. //#region Convert from Plan B to Unified Plan.
  2326. // Unfortunately, a Plan B offer/answer doesn't have enough information to
  2327. // rebuild an equivalent Unified Plan offer/answer.
  2328. //
  2329. // For example, if this is a local answer (in Unified Plan style) that we
  2330. // convert to Plan B prior to handing it over to the application (the
  2331. // PeerConnection wrapper called us, for instance, after a successful
  2332. // createAnswer), we want to remember the m-line at which we've seen the
  2333. // (local) SSRC. That's because when the application wants to do call the
  2334. // SLD method, forcing us to do the inverse transformation (from Plan B to
  2335. // Unified Plan), we need to know to which m-line to assign the (local)
  2336. // SSRC. We also need to know all the other m-lines that the original
  2337. // answer had and include them in the transformed answer as well.
  2338. //
  2339. // Another example is if this is a remote offer that we convert to Plan B
  2340. // prior to giving it to the application, we want to remember the mid at
  2341. // which we've seen the (remote) SSRC.
  2342. //
  2343. // In the iteration that follows, we use the cached Unified Plan (if it
  2344. // exists) to assign mids to ssrcs.
  2345. var type;
  2346. if (desc.type === 'answer') {
  2347. type = 'offer';
  2348. } else if (desc.type === 'offer') {
  2349. type = 'answer';
  2350. } else {
  2351. throw new Error("Type '" + desc.type + "' not supported.");
  2352. }
  2353. var cached;
  2354. if (typeof this.cache[type] !== 'undefined') {
  2355. cached = transform.parse(this.cache[type]);
  2356. }
  2357. var recvonlySsrcs = {
  2358. audio: {},
  2359. video: {}
  2360. };
  2361. // A helper map that sends mids to m-line objects. We use it later to
  2362. // rebuild the Unified Plan style session.media array.
  2363. var mid2ul = {};
  2364. var bIdx = 0;
  2365. var uIdx = 0;
  2366. var sources2ul = {};
  2367. var candidates;
  2368. var iceUfrag;
  2369. var icePwd;
  2370. var fingerprint;
  2371. var payloads = {};
  2372. var rtcpFb = {};
  2373. var rtp = {};
  2374. session.media.forEach(function(bLine) {
  2375. if ((typeof bLine.rtcpMux !== 'string' ||
  2376. bLine.rtcpMux !== 'rtcp-mux') &&
  2377. bLine.direction !== 'inactive') {
  2378. throw new Error("Cannot convert to Unified Plan because m-lines " +
  2379. "without the rtcp-mux attribute were found.");
  2380. }
  2381. if (bLine.type === 'application') {
  2382. mid2ul[bLine.mid] = bLine;
  2383. return;
  2384. }
  2385. // With rtcp-mux and bundle all the channels should have the same ICE
  2386. // stuff.
  2387. var sources = bLine.sources;
  2388. var ssrcGroups = bLine.ssrcGroups;
  2389. var port = bLine.port;
  2390. /* Chrome adds different candidates even using bundle, so we concat the candidates list */
  2391. if (typeof bLine.candidates != 'undefined') {
  2392. if (typeof candidates != 'undefined') {
  2393. candidates = candidates.concat(bLine.candidates);
  2394. } else {
  2395. candidates = bLine.candidates;
  2396. }
  2397. }
  2398. if ((typeof iceUfrag != 'undefined') && (typeof bLine.iceUfrag != 'undefined') && (iceUfrag != bLine.iceUfrag)) {
  2399. throw new Error("Only BUNDLE supported, iceUfrag must be the same for all m-lines.\n" +
  2400. "\tLast iceUfrag: " + iceUfrag + "\n" +
  2401. "\tNew iceUfrag: " + bLine.iceUfrag
  2402. );
  2403. }
  2404. if (typeof bLine.iceUfrag != 'undefined') {
  2405. iceUfrag = bLine.iceUfrag;
  2406. }
  2407. if ((typeof icePwd != 'undefined') && (typeof bLine.icePwd != 'undefined') && (icePwd != bLine.icePwd)) {
  2408. throw new Error("Only BUNDLE supported, icePwd must be the same for all m-lines.\n" +
  2409. "\tLast icePwd: " + icePwd + "\n" +
  2410. "\tNew icePwd: " + bLine.icePwd
  2411. );
  2412. }
  2413. if (typeof bLine.icePwd != 'undefined') {
  2414. icePwd = bLine.icePwd;
  2415. }
  2416. if ((typeof fingerprint != 'undefined') && (typeof bLine.fingerprint != 'undefined') &&
  2417. (fingerprint.type != bLine.fingerprint.type || fingerprint.hash != bLine.fingerprint.hash)) {
  2418. throw new Error("Only BUNDLE supported, fingerprint must be the same for all m-lines.\n" +
  2419. "\tLast fingerprint: " + JSON.stringify(fingerprint) + "\n" +
  2420. "\tNew fingerprint: " + JSON.stringify(bLine.fingerprint)
  2421. );
  2422. }
  2423. if (typeof bLine.fingerprint != 'undefined') {
  2424. fingerprint = bLine.fingerprint;
  2425. }
  2426. payloads[bLine.type] = bLine.payloads;
  2427. rtcpFb[bLine.type] = bLine.rtcpFb;
  2428. rtp[bLine.type] = bLine.rtp;
  2429. // inverted ssrc group map
  2430. var ssrc2group = {};
  2431. if (typeof ssrcGroups !== 'undefined' && Array.isArray(ssrcGroups)) {
  2432. ssrcGroups.forEach(function (ssrcGroup) {
  2433. // XXX This might brake if an SSRC is in more than one group
  2434. // for some reason.
  2435. if (typeof ssrcGroup.ssrcs !== 'undefined' &&
  2436. Array.isArray(ssrcGroup.ssrcs)) {
  2437. ssrcGroup.ssrcs.forEach(function (ssrc) {
  2438. if (typeof ssrc2group[ssrc] === 'undefined') {
  2439. ssrc2group[ssrc] = [];
  2440. }
  2441. ssrc2group[ssrc].push(ssrcGroup);
  2442. });
  2443. }
  2444. });
  2445. }
  2446. // ssrc to m-line index.
  2447. var ssrc2ml = {};
  2448. if (typeof sources === 'object') {
  2449. // We'll use the "bLine" object as a prototype for each new "mLine"
  2450. // that we create, but first we need to clean it up a bit.
  2451. delete bLine.sources;
  2452. delete bLine.ssrcGroups;
  2453. delete bLine.candidates;
  2454. delete bLine.iceUfrag;
  2455. delete bLine.icePwd;
  2456. delete bLine.fingerprint;
  2457. delete bLine.port;
  2458. delete bLine.mid;
  2459. // Explode the Plan B channel sources with one m-line per source.
  2460. Object.keys(sources).forEach(function(ssrc) {
  2461. // The (unified) m-line for this SSRC. We either create it from
  2462. // scratch or, if it's a grouped SSRC, we re-use a related
  2463. // mline. In other words, if the source is grouped with another
  2464. // source, put the two together in the same m-line.
  2465. var uLine;
  2466. // We assume here that we are the answerer in the O/A, so any
  2467. // offers which we translate come from the remote side, while
  2468. // answers are local. So the check below is to make that we
  2469. // handle receive-only SSRCs in a special way only if they come
  2470. // from the remote side.
  2471. if (desc.type==='offer') {
  2472. // We want to detect SSRCs which are used by a remote peer
  2473. // in an m-line with direction=recvonly (i.e. they are
  2474. // being used for RTCP only).
  2475. // This information would have gotten lost if the remote
  2476. // peer used Unified Plan and their local description was
  2477. // translated to Plan B. So we use the lack of an MSID
  2478. // attribute to deduce a "receive only" SSRC.
  2479. if (!sources[ssrc].msid) {
  2480. recvonlySsrcs[bLine.type][ssrc] = sources[ssrc];
  2481. // Receive-only SSRCs must not create new m-lines. We
  2482. // will assign them to an existing m-line later.
  2483. return;
  2484. }
  2485. }
  2486. if (typeof ssrc2group[ssrc] !== 'undefined' &&
  2487. Array.isArray(ssrc2group[ssrc])) {
  2488. ssrc2group[ssrc].some(function (ssrcGroup) {
  2489. // ssrcGroup.ssrcs *is* an Array, no need to check
  2490. // again here.
  2491. return ssrcGroup.ssrcs.some(function (related) {
  2492. if (typeof ssrc2ml[related] === 'object') {
  2493. uLine = ssrc2ml[related];
  2494. return true;
  2495. }
  2496. });
  2497. });
  2498. }
  2499. if (typeof uLine === 'object') {
  2500. // the m-line already exists. Just add the source.
  2501. uLine.sources[ssrc] = sources[ssrc];
  2502. delete sources[ssrc].msid;
  2503. } else {
  2504. // Use the "bLine" as a prototype for the "uLine".
  2505. uLine = Object.create(bLine);
  2506. ssrc2ml[ssrc] = uLine;
  2507. if (typeof sources[ssrc].msid !== 'undefined') {
  2508. // Assign the msid of the source to the m-line. Note
  2509. // that it is not guaranteed that the source will have
  2510. // msid. In particular "recvonly" sources don't have an
  2511. // msid. Note that "recvonly" is a term only defined
  2512. // for m-lines.
  2513. uLine.msid = sources[ssrc].msid;
  2514. delete sources[ssrc].msid;
  2515. }
  2516. // We assign one SSRC per media line.
  2517. uLine.sources = {};
  2518. uLine.sources[ssrc] = sources[ssrc];
  2519. uLine.ssrcGroups = ssrc2group[ssrc];
  2520. // Use the cached Unified Plan SDP (if it exists) to assign
  2521. // SSRCs to mids.
  2522. if (typeof cached !== 'undefined' &&
  2523. typeof cached.media !== 'undefined' &&
  2524. Array.isArray(cached.media)) {
  2525. cached.media.forEach(function (m) {
  2526. if (typeof m.sources === 'object') {
  2527. Object.keys(m.sources).forEach(function (s) {
  2528. if (s === ssrc) {
  2529. uLine.mid = m.mid;
  2530. }
  2531. });
  2532. }
  2533. });
  2534. }
  2535. if (typeof uLine.mid === 'undefined') {
  2536. // If this is an SSRC that we see for the first time
  2537. // assign it a new mid. This is typically the case when
  2538. // this method is called to transform a remote
  2539. // description for the first time or when there is a
  2540. // new SSRC in the remote description because a new
  2541. // peer has joined the conference. Local SSRCs should
  2542. // have already been added to the map in the toPlanB
  2543. // method.
  2544. //
  2545. // Because FF generates answers in Unified Plan style,
  2546. // we MUST already have a cached answer with all the
  2547. // local SSRCs mapped to some m-line/mid.
  2548. uLine.mid = [bLine.type, '-', ssrc].join('');
  2549. }
  2550. // Include the candidates in the 1st media line.
  2551. uLine.candidates = candidates;
  2552. uLine.iceUfrag = iceUfrag;
  2553. uLine.icePwd = icePwd;
  2554. uLine.fingerprint = fingerprint;
  2555. uLine.port = port;
  2556. mid2ul[uLine.mid] = uLine;
  2557. sources2ul[uIdx] = uLine.sources;
  2558. self.cache.mlU2BMap[uIdx] = bIdx;
  2559. if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') {
  2560. self.cache.mlB2UMap[bIdx] = uIdx;
  2561. }
  2562. uIdx++;
  2563. }
  2564. });
  2565. } else {
  2566. var uLine = bLine;
  2567. uLine.candidates = candidates;
  2568. uLine.iceUfrag = iceUfrag;
  2569. uLine.icePwd = icePwd;
  2570. uLine.fingerprint = fingerprint;
  2571. uLine.port = port;
  2572. mid2ul[uLine.mid] = uLine;
  2573. self.cache.mlU2BMap[uIdx] = bIdx;
  2574. if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') {
  2575. self.cache.mlB2UMap[bIdx] = uIdx;
  2576. }
  2577. }
  2578. bIdx++;
  2579. });
  2580. // Rebuild the media array in the right order and add the missing mLines
  2581. // (missing from the Plan B SDP).
  2582. session.media = [];
  2583. mids = []; // reuse
  2584. if (desc.type === 'answer') {
  2585. // The media lines in the answer must match the media lines in the
  2586. // offer. The order is important too. Here we assume that Firefox is
  2587. // the answerer, so we merely have to use the reconstructed (unified)
  2588. // answer to update the cached (unified) answer accordingly.
  2589. //
  2590. // In the general case, one would have to use the cached (unified)
  2591. // offer to find the m-lines that are missing from the reconstructed
  2592. // answer, potentially grabbing them from the cached (unified) answer.
  2593. // One has to be careful with this approach because inactive m-lines do
  2594. // not always have an mid, making it tricky (impossible?) to find where
  2595. // exactly and which m-lines are missing from the reconstructed answer.
  2596. for (var i = 0; i < cached.media.length; i++) {
  2597. var uLine = cached.media[i];
  2598. delete uLine.msid;
  2599. delete uLine.sources;
  2600. delete uLine.ssrcGroups;
  2601. if (typeof sources2ul[i] === 'undefined') {
  2602. if (!uLine.direction
  2603. || uLine.direction === 'sendrecv')
  2604. uLine.direction = 'recvonly';
  2605. else if (uLine.direction === 'sendonly')
  2606. uLine.direction = 'inactive';
  2607. } else {
  2608. if (!uLine.direction
  2609. || uLine.direction === 'sendrecv')
  2610. uLine.direction = 'sendrecv';
  2611. else if (uLine.direction === 'recvonly')
  2612. uLine.direction = 'sendonly';
  2613. }
  2614. uLine.sources = sources2ul[i];
  2615. uLine.candidates = candidates;
  2616. uLine.iceUfrag = iceUfrag;
  2617. uLine.icePwd = icePwd;
  2618. uLine.fingerprint = fingerprint;
  2619. uLine.rtp = rtp[uLine.type];
  2620. uLine.payloads = payloads[uLine.type];
  2621. uLine.rtcpFb = rtcpFb[uLine.type];
  2622. session.media.push(uLine);
  2623. if (typeof uLine.mid === 'string') {
  2624. // inactive lines don't/may not have an mid.
  2625. mids.push(uLine.mid);
  2626. }
  2627. }
  2628. } else {
  2629. // SDP offer/answer (and the JSEP spec) forbids removing an m-section
  2630. // under any circumstances. If we are no longer interested in sending a
  2631. // track, we just remove the msid and ssrc attributes and set it to
  2632. // either a=recvonly (as the reofferer, we must use recvonly if the
  2633. // other side was previously sending on the m-section, but we can also
  2634. // leave the possibility open if it wasn't previously in use), or
  2635. // a=inactive.
  2636. if (typeof cached !== 'undefined' &&
  2637. typeof cached.media !== 'undefined' &&
  2638. Array.isArray(cached.media)) {
  2639. cached.media.forEach(function(uLine) {
  2640. mids.push(uLine.mid);
  2641. if (typeof mid2ul[uLine.mid] !== 'undefined') {
  2642. session.media.push(mid2ul[uLine.mid]);
  2643. } else {
  2644. delete uLine.msid;
  2645. delete uLine.sources;
  2646. delete uLine.ssrcGroups;
  2647. if (!uLine.direction
  2648. || uLine.direction === 'sendrecv') {
  2649. uLine.direction = 'sendonly';
  2650. }
  2651. if (!uLine.direction
  2652. || uLine.direction === 'recvonly') {
  2653. uLine.direction = 'inactive';
  2654. }
  2655. addSetupAttr (uLine);
  2656. session.media.push(uLine);
  2657. }
  2658. });
  2659. }
  2660. // Add all the remaining (new) m-lines of the transformed SDP.
  2661. Object.keys(mid2ul).forEach(function(mid) {
  2662. if (mids.indexOf(mid) === -1) {
  2663. mids.push(mid);
  2664. if (mid2ul[mid].direction === 'recvonly') {
  2665. // This is a remote recvonly channel. Add its SSRC to the
  2666. // appropriate sendrecv or sendonly channel.
  2667. // TODO(gp) what if we don't have sendrecv/sendonly
  2668. // channel?
  2669. var done = false;
  2670. session.media.some(function (uLine) {
  2671. if ((uLine.direction === 'sendrecv' ||
  2672. uLine.direction === 'sendonly') &&
  2673. uLine.type === mid2ul[mid].type) {
  2674. // mid2ul[mid] shouldn't have any ssrc-groups
  2675. Object.keys(mid2ul[mid].sources).forEach(
  2676. function (ssrc) {
  2677. uLine.sources[ssrc] =
  2678. mid2ul[mid].sources[ssrc];
  2679. });
  2680. done = true;
  2681. return true;
  2682. }
  2683. });
  2684. if (!done) {
  2685. session.media.push(mid2ul[mid]);
  2686. }
  2687. } else {
  2688. session.media.push(mid2ul[mid]);
  2689. }
  2690. }
  2691. });
  2692. }
  2693. // After we have constructed the Plan Unified m-lines we can figure out
  2694. // where (in which m-line) to place the 'recvonly SSRCs'.
  2695. // Note: we assume here that we are the answerer in the O/A, so any offers
  2696. // which we translate come from the remote side, while answers are local
  2697. // (and so our last local description is cached as an 'answer').
  2698. ["audio", "video"].forEach(function (type) {
  2699. if (!session || !session.media || !Array.isArray(session.media))
  2700. return;
  2701. var idx = null;
  2702. if (Object.keys(recvonlySsrcs[type]).length > 0) {
  2703. idx = self.getFirstSendingIndexFromAnswer(type);
  2704. if (idx === null){
  2705. // If this is the first offer we receive, we don't have a
  2706. // cached answer. Assume that we will be sending media using
  2707. // the first m-line for each media type.
  2708. for (var i = 0; i < session.media.length; i++) {
  2709. if (session.media[i].type === type) {
  2710. idx = i;
  2711. break;
  2712. }
  2713. }
  2714. }
  2715. }
  2716. if (idx && session.media.length > idx) {
  2717. var mLine = session.media[idx];
  2718. Object.keys(recvonlySsrcs[type]).forEach(function(ssrc) {
  2719. if (mLine.sources && mLine.sources[ssrc]) {
  2720. console.warn("Replacing an existing SSRC.");
  2721. }
  2722. if (!mLine.sources) {
  2723. mLine.sources = {};
  2724. }
  2725. mLine.sources[ssrc] = recvonlySsrcs[type][ssrc];
  2726. });
  2727. }
  2728. });
  2729. if (typeof session.groups !== 'undefined') {
  2730. // We regenerate the BUNDLE group (since we regenerated the mids)
  2731. session.groups.some(function(group) {
  2732. if (group.type === 'BUNDLE') {
  2733. group.mids = mids.join(' ');
  2734. return true;
  2735. }
  2736. });
  2737. }
  2738. // msid semantic
  2739. session.msidSemantic = {
  2740. semantic: 'WMS',
  2741. token: '*'
  2742. };
  2743. var resStr = transform.write(session);
  2744. // Cache the transformed SDP (Unified Plan) for later re-use in this
  2745. // function.
  2746. this.cache[desc.type] = resStr;
  2747. return new RTCSessionDescription({
  2748. type: desc.type,
  2749. sdp: resStr
  2750. });
  2751. //#endregion
  2752. };
  2753. },{"./array-equals":17,"./transform":20}],20:[function(require,module,exports){
  2754. /* Copyright @ 2015 Atlassian Pty Ltd
  2755. *
  2756. * Licensed under the Apache License, Version 2.0 (the "License");
  2757. * you may not use this file except in compliance with the License.
  2758. * You may obtain a copy of the License at
  2759. *
  2760. * http://www.apache.org/licenses/LICENSE-2.0
  2761. *
  2762. * Unless required by applicable law or agreed to in writing, software
  2763. * distributed under the License is distributed on an "AS IS" BASIS,
  2764. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2765. * See the License for the specific language governing permissions and
  2766. * limitations under the License.
  2767. */
  2768. var transform = require('sdp-transform');
  2769. exports.write = function(session, opts) {
  2770. if (typeof session !== 'undefined' &&
  2771. typeof session.media !== 'undefined' &&
  2772. Array.isArray(session.media)) {
  2773. session.media.forEach(function (mLine) {
  2774. // expand sources to ssrcs
  2775. if (typeof mLine.sources !== 'undefined' &&
  2776. Object.keys(mLine.sources).length !== 0) {
  2777. mLine.ssrcs = [];
  2778. Object.keys(mLine.sources).forEach(function (ssrc) {
  2779. var source = mLine.sources[ssrc];
  2780. Object.keys(source).forEach(function (attribute) {
  2781. mLine.ssrcs.push({
  2782. id: ssrc,
  2783. attribute: attribute,
  2784. value: source[attribute]
  2785. });
  2786. });
  2787. });
  2788. delete mLine.sources;
  2789. }
  2790. // join ssrcs in ssrc groups
  2791. if (typeof mLine.ssrcGroups !== 'undefined' &&
  2792. Array.isArray(mLine.ssrcGroups)) {
  2793. mLine.ssrcGroups.forEach(function (ssrcGroup) {
  2794. if (typeof ssrcGroup.ssrcs !== 'undefined' &&
  2795. Array.isArray(ssrcGroup.ssrcs)) {
  2796. ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' ');
  2797. }
  2798. });
  2799. }
  2800. });
  2801. }
  2802. // join group mids
  2803. if (typeof session !== 'undefined' &&
  2804. typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
  2805. session.groups.forEach(function (g) {
  2806. if (typeof g.mids !== 'undefined' && Array.isArray(g.mids)) {
  2807. g.mids = g.mids.join(' ');
  2808. }
  2809. });
  2810. }
  2811. return transform.write(session, opts);
  2812. };
  2813. exports.parse = function(sdp) {
  2814. var session = transform.parse(sdp);
  2815. if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
  2816. Array.isArray(session.media)) {
  2817. session.media.forEach(function (mLine) {
  2818. // group sources attributes by ssrc
  2819. if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
  2820. mLine.sources = {};
  2821. mLine.ssrcs.forEach(function (ssrc) {
  2822. if (!mLine.sources[ssrc.id])
  2823. mLine.sources[ssrc.id] = {};
  2824. mLine.sources[ssrc.id][ssrc.attribute] = ssrc.value;
  2825. });
  2826. delete mLine.ssrcs;
  2827. }
  2828. // split ssrcs in ssrc groups
  2829. if (typeof mLine.ssrcGroups !== 'undefined' &&
  2830. Array.isArray(mLine.ssrcGroups)) {
  2831. mLine.ssrcGroups.forEach(function (ssrcGroup) {
  2832. if (typeof ssrcGroup.ssrcs === 'string') {
  2833. ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' ');
  2834. }
  2835. });
  2836. }
  2837. });
  2838. }
  2839. // split group mids
  2840. if (typeof session !== 'undefined' &&
  2841. typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
  2842. session.groups.forEach(function (g) {
  2843. if (typeof g.mids === 'string') {
  2844. g.mids = g.mids.split(' ');
  2845. }
  2846. });
  2847. }
  2848. return session;
  2849. };
  2850. },{"sdp-transform":14}],21:[function(require,module,exports){
  2851. /*!
  2852. * UAParser.js v0.7.21
  2853. * Lightweight JavaScript-based User-Agent string parser
  2854. * https://github.com/faisalman/ua-parser-js
  2855. *
  2856. * Copyright © 2012-2019 Faisal Salman <[email protected]>
  2857. * Licensed under MIT License
  2858. */
  2859. (function (window, undefined) {
  2860. 'use strict';
  2861. //////////////
  2862. // Constants
  2863. /////////////
  2864. var LIBVERSION = '0.7.21',
  2865. EMPTY = '',
  2866. UNKNOWN = '?',
  2867. FUNC_TYPE = 'function',
  2868. UNDEF_TYPE = 'undefined',
  2869. OBJ_TYPE = 'object',
  2870. STR_TYPE = 'string',
  2871. MAJOR = 'major', // deprecated
  2872. MODEL = 'model',
  2873. NAME = 'name',
  2874. TYPE = 'type',
  2875. VENDOR = 'vendor',
  2876. VERSION = 'version',
  2877. ARCHITECTURE= 'architecture',
  2878. CONSOLE = 'console',
  2879. MOBILE = 'mobile',
  2880. TABLET = 'tablet',
  2881. SMARTTV = 'smarttv',
  2882. WEARABLE = 'wearable',
  2883. EMBEDDED = 'embedded';
  2884. ///////////
  2885. // Helper
  2886. //////////
  2887. var util = {
  2888. extend : function (regexes, extensions) {
  2889. var mergedRegexes = {};
  2890. for (var i in regexes) {
  2891. if (extensions[i] && extensions[i].length % 2 === 0) {
  2892. mergedRegexes[i] = extensions[i].concat(regexes[i]);
  2893. } else {
  2894. mergedRegexes[i] = regexes[i];
  2895. }
  2896. }
  2897. return mergedRegexes;
  2898. },
  2899. has : function (str1, str2) {
  2900. if (typeof str1 === "string") {
  2901. return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1;
  2902. } else {
  2903. return false;
  2904. }
  2905. },
  2906. lowerize : function (str) {
  2907. return str.toLowerCase();
  2908. },
  2909. major : function (version) {
  2910. return typeof(version) === STR_TYPE ? version.replace(/[^\d\.]/g,'').split(".")[0] : undefined;
  2911. },
  2912. trim : function (str) {
  2913. return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
  2914. }
  2915. };
  2916. ///////////////
  2917. // Map helper
  2918. //////////////
  2919. var mapper = {
  2920. rgx : function (ua, arrays) {
  2921. var i = 0, j, k, p, q, matches, match;
  2922. // loop through all regexes maps
  2923. while (i < arrays.length && !matches) {
  2924. var regex = arrays[i], // even sequence (0,2,4,..)
  2925. props = arrays[i + 1]; // odd sequence (1,3,5,..)
  2926. j = k = 0;
  2927. // try matching uastring with regexes
  2928. while (j < regex.length && !matches) {
  2929. matches = regex[j++].exec(ua);
  2930. if (!!matches) {
  2931. for (p = 0; p < props.length; p++) {
  2932. match = matches[++k];
  2933. q = props[p];
  2934. // check if given property is actually array
  2935. if (typeof q === OBJ_TYPE && q.length > 0) {
  2936. if (q.length == 2) {
  2937. if (typeof q[1] == FUNC_TYPE) {
  2938. // assign modified match
  2939. this[q[0]] = q[1].call(this, match);
  2940. } else {
  2941. // assign given value, ignore regex match
  2942. this[q[0]] = q[1];
  2943. }
  2944. } else if (q.length == 3) {
  2945. // check whether function or regex
  2946. if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) {
  2947. // call function (usually string mapper)
  2948. this[q[0]] = match ? q[1].call(this, match, q[2]) : undefined;
  2949. } else {
  2950. // sanitize match using given regex
  2951. this[q[0]] = match ? match.replace(q[1], q[2]) : undefined;
  2952. }
  2953. } else if (q.length == 4) {
  2954. this[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined;
  2955. }
  2956. } else {
  2957. this[q] = match ? match : undefined;
  2958. }
  2959. }
  2960. }
  2961. }
  2962. i += 2;
  2963. }
  2964. },
  2965. str : function (str, map) {
  2966. for (var i in map) {
  2967. // check if array
  2968. if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {
  2969. for (var j = 0; j < map[i].length; j++) {
  2970. if (util.has(map[i][j], str)) {
  2971. return (i === UNKNOWN) ? undefined : i;
  2972. }
  2973. }
  2974. } else if (util.has(map[i], str)) {
  2975. return (i === UNKNOWN) ? undefined : i;
  2976. }
  2977. }
  2978. return str;
  2979. }
  2980. };
  2981. ///////////////
  2982. // String map
  2983. //////////////
  2984. var maps = {
  2985. browser : {
  2986. oldsafari : {
  2987. version : {
  2988. '1.0' : '/8',
  2989. '1.2' : '/1',
  2990. '1.3' : '/3',
  2991. '2.0' : '/412',
  2992. '2.0.2' : '/416',
  2993. '2.0.3' : '/417',
  2994. '2.0.4' : '/419',
  2995. '?' : '/'
  2996. }
  2997. }
  2998. },
  2999. device : {
  3000. amazon : {
  3001. model : {
  3002. 'Fire Phone' : ['SD', 'KF']
  3003. }
  3004. },
  3005. sprint : {
  3006. model : {
  3007. 'Evo Shift 4G' : '7373KT'
  3008. },
  3009. vendor : {
  3010. 'HTC' : 'APA',
  3011. 'Sprint' : 'Sprint'
  3012. }
  3013. }
  3014. },
  3015. os : {
  3016. windows : {
  3017. version : {
  3018. 'ME' : '4.90',
  3019. 'NT 3.11' : 'NT3.51',
  3020. 'NT 4.0' : 'NT4.0',
  3021. '2000' : 'NT 5.0',
  3022. 'XP' : ['NT 5.1', 'NT 5.2'],
  3023. 'Vista' : 'NT 6.0',
  3024. '7' : 'NT 6.1',
  3025. '8' : 'NT 6.2',
  3026. '8.1' : 'NT 6.3',
  3027. '10' : ['NT 6.4', 'NT 10.0'],
  3028. 'RT' : 'ARM'
  3029. }
  3030. }
  3031. }
  3032. };
  3033. //////////////
  3034. // Regex map
  3035. /////////////
  3036. var regexes = {
  3037. browser : [[
  3038. // Presto based
  3039. /(opera\smini)\/([\w\.-]+)/i, // Opera Mini
  3040. /(opera\s[mobiletab]+).+version\/([\w\.-]+)/i, // Opera Mobi/Tablet
  3041. /(opera).+version\/([\w\.]+)/i, // Opera > 9.80
  3042. /(opera)[\/\s]+([\w\.]+)/i // Opera < 9.80
  3043. ], [NAME, VERSION], [
  3044. /(opios)[\/\s]+([\w\.]+)/i // Opera mini on iphone >= 8.0
  3045. ], [[NAME, 'Opera Mini'], VERSION], [
  3046. /\s(opr)\/([\w\.]+)/i // Opera Webkit
  3047. ], [[NAME, 'Opera'], VERSION], [
  3048. // Mixed
  3049. /(kindle)\/([\w\.]+)/i, // Kindle
  3050. /(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]*)/i,
  3051. // Lunascape/Maxthon/Netfront/Jasmine/Blazer
  3052. // Trident based
  3053. /(avant\s|iemobile|slim)(?:browser)?[\/\s]?([\w\.]*)/i,
  3054. // Avant/IEMobile/SlimBrowser
  3055. /(bidubrowser|baidubrowser)[\/\s]?([\w\.]+)/i, // Baidu Browser
  3056. /(?:ms|\()(ie)\s([\w\.]+)/i, // Internet Explorer
  3057. // Webkit/KHTML based
  3058. /(rekonq)\/([\w\.]*)/i, // Rekonq
  3059. /(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|quark|qupzilla|falkon)\/([\w\.-]+)/i
  3060. // Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon
  3061. ], [NAME, VERSION], [
  3062. /(konqueror)\/([\w\.]+)/i // Konqueror
  3063. ], [[NAME, 'Konqueror'], VERSION], [
  3064. /(trident).+rv[:\s]([\w\.]+).+like\sgecko/i // IE11
  3065. ], [[NAME, 'IE'], VERSION], [
  3066. /(edge|edgios|edga|edg)\/((\d+)?[\w\.]+)/i // Microsoft Edge
  3067. ], [[NAME, 'Edge'], VERSION], [
  3068. /(yabrowser)\/([\w\.]+)/i // Yandex
  3069. ], [[NAME, 'Yandex'], VERSION], [
  3070. /(Avast)\/([\w\.]+)/i // Avast Secure Browser
  3071. ], [[NAME, 'Avast Secure Browser'], VERSION], [
  3072. /(AVG)\/([\w\.]+)/i // AVG Secure Browser
  3073. ], [[NAME, 'AVG Secure Browser'], VERSION], [
  3074. /(puffin)\/([\w\.]+)/i // Puffin
  3075. ], [[NAME, 'Puffin'], VERSION], [
  3076. /(focus)\/([\w\.]+)/i // Firefox Focus
  3077. ], [[NAME, 'Firefox Focus'], VERSION], [
  3078. /(opt)\/([\w\.]+)/i // Opera Touch
  3079. ], [[NAME, 'Opera Touch'], VERSION], [
  3080. /((?:[\s\/])uc?\s?browser|(?:juc.+)ucweb)[\/\s]?([\w\.]+)/i // UCBrowser
  3081. ], [[NAME, 'UCBrowser'], VERSION], [
  3082. /(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon
  3083. ], [[NAME, /_/g, ' '], VERSION], [
  3084. /(windowswechat qbcore)\/([\w\.]+)/i // WeChat Desktop for Windows Built-in Browser
  3085. ], [[NAME, 'WeChat(Win) Desktop'], VERSION], [
  3086. /(micromessenger)\/([\w\.]+)/i // WeChat
  3087. ], [[NAME, 'WeChat'], VERSION], [
  3088. /(brave)\/([\w\.]+)/i // Brave browser
  3089. ], [[NAME, 'Brave'], VERSION], [
  3090. /(qqbrowserlite)\/([\w\.]+)/i // QQBrowserLite
  3091. ], [NAME, VERSION], [
  3092. /(QQ)\/([\d\.]+)/i // QQ, aka ShouQ
  3093. ], [NAME, VERSION], [
  3094. /m?(qqbrowser)[\/\s]?([\w\.]+)/i // QQBrowser
  3095. ], [NAME, VERSION], [
  3096. /(baiduboxapp)[\/\s]?([\w\.]+)/i // Baidu App
  3097. ], [NAME, VERSION], [
  3098. /(2345Explorer)[\/\s]?([\w\.]+)/i // 2345 Browser
  3099. ], [NAME, VERSION], [
  3100. /(MetaSr)[\/\s]?([\w\.]+)/i // SouGouBrowser
  3101. ], [NAME], [
  3102. /(LBBROWSER)/i // LieBao Browser
  3103. ], [NAME], [
  3104. /xiaomi\/miuibrowser\/([\w\.]+)/i // MIUI Browser
  3105. ], [VERSION, [NAME, 'MIUI Browser']], [
  3106. /;fbav\/([\w\.]+);/i // Facebook App for iOS & Android
  3107. ], [VERSION, [NAME, 'Facebook']], [
  3108. /safari\s(line)\/([\w\.]+)/i, // Line App for iOS
  3109. /android.+(line)\/([\w\.]+)\/iab/i // Line App for Android
  3110. ], [NAME, VERSION], [
  3111. /headlesschrome(?:\/([\w\.]+)|\s)/i // Chrome Headless
  3112. ], [VERSION, [NAME, 'Chrome Headless']], [
  3113. /\swv\).+(chrome)\/([\w\.]+)/i // Chrome WebView
  3114. ], [[NAME, /(.+)/, '$1 WebView'], VERSION], [
  3115. /((?:oculus|samsung)browser)\/([\w\.]+)/i
  3116. ], [[NAME, /(.+(?:g|us))(.+)/, '$1 $2'], VERSION], [ // Oculus / Samsung Browser
  3117. /android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)*/i // Android Browser
  3118. ], [VERSION, [NAME, 'Android Browser']], [
  3119. /(sailfishbrowser)\/([\w\.]+)/i // Sailfish Browser
  3120. ], [[NAME, 'Sailfish Browser'], VERSION], [
  3121. /(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i
  3122. // Chrome/OmniWeb/Arora/Tizen/Nokia
  3123. ], [NAME, VERSION], [
  3124. /(dolfin)\/([\w\.]+)/i // Dolphin
  3125. ], [[NAME, 'Dolphin'], VERSION], [
  3126. /(qihu|qhbrowser|qihoobrowser|360browser)/i // 360
  3127. ], [[NAME, '360 Browser']], [
  3128. /((?:android.+)crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS
  3129. ], [[NAME, 'Chrome'], VERSION], [
  3130. /(coast)\/([\w\.]+)/i // Opera Coast
  3131. ], [[NAME, 'Opera Coast'], VERSION], [
  3132. /fxios\/([\w\.-]+)/i // Firefox for iOS
  3133. ], [VERSION, [NAME, 'Firefox']], [
  3134. /version\/([\w\.]+).+?mobile\/\w+\s(safari)/i // Mobile Safari
  3135. ], [VERSION, [NAME, 'Mobile Safari']], [
  3136. /version\/([\w\.]+).+?(mobile\s?safari|safari)/i // Safari & Safari Mobile
  3137. ], [VERSION, NAME], [
  3138. /webkit.+?(gsa)\/([\w\.]+).+?(mobile\s?safari|safari)(\/[\w\.]+)/i // Google Search Appliance on iOS
  3139. ], [[NAME, 'GSA'], VERSION], [
  3140. /webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i // Safari < 3.0
  3141. ], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [
  3142. /(webkit|khtml)\/([\w\.]+)/i
  3143. ], [NAME, VERSION], [
  3144. // Gecko based
  3145. /(navigator|netscape)\/([\w\.-]+)/i // Netscape
  3146. ], [[NAME, 'Netscape'], VERSION], [
  3147. /(swiftfox)/i, // Swiftfox
  3148. /(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i,
  3149. // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
  3150. /(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix|palemoon|basilisk|waterfox)\/([\w\.-]+)$/i,
  3151. // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
  3152. /(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i, // Mozilla
  3153. // Other
  3154. /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir)[\/\s]?([\w\.]+)/i,
  3155. // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Sleipnir
  3156. /(links)\s\(([\w\.]+)/i, // Links
  3157. /(gobrowser)\/?([\w\.]*)/i, // GoBrowser
  3158. /(ice\s?browser)\/v?([\w\._]+)/i, // ICE Browser
  3159. /(mosaic)[\/\s]([\w\.]+)/i // Mosaic
  3160. ], [NAME, VERSION]
  3161. ],
  3162. cpu : [[
  3163. /(?:(amd|x(?:(?:86|64)[_-])?|wow|win)64)[;\)]/i // AMD64
  3164. ], [[ARCHITECTURE, 'amd64']], [
  3165. /(ia32(?=;))/i // IA32 (quicktime)
  3166. ], [[ARCHITECTURE, util.lowerize]], [
  3167. /((?:i[346]|x)86)[;\)]/i // IA32
  3168. ], [[ARCHITECTURE, 'ia32']], [
  3169. // PocketPC mistakenly identified as PowerPC
  3170. /windows\s(ce|mobile);\sppc;/i
  3171. ], [[ARCHITECTURE, 'arm']], [
  3172. /((?:ppc|powerpc)(?:64)?)(?:\smac|;|\))/i // PowerPC
  3173. ], [[ARCHITECTURE, /ower/, '', util.lowerize]], [
  3174. /(sun4\w)[;\)]/i // SPARC
  3175. ], [[ARCHITECTURE, 'sparc']], [
  3176. /((?:avr32|ia64(?=;))|68k(?=\))|arm(?:64|(?=v\d+[;l]))|(?=atmel\s)avr|(?:irix|mips|sparc)(?:64)?(?=;)|pa-risc)/i
  3177. // IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC
  3178. ], [[ARCHITECTURE, util.lowerize]]
  3179. ],
  3180. device : [[
  3181. /\((ipad|playbook);[\w\s\),;-]+(rim|apple)/i // iPad/PlayBook
  3182. ], [MODEL, VENDOR, [TYPE, TABLET]], [
  3183. /applecoremedia\/[\w\.]+ \((ipad)/ // iPad
  3184. ], [MODEL, [VENDOR, 'Apple'], [TYPE, TABLET]], [
  3185. /(apple\s{0,1}tv)/i // Apple TV
  3186. ], [[MODEL, 'Apple TV'], [VENDOR, 'Apple'], [TYPE, SMARTTV]], [
  3187. /(archos)\s(gamepad2?)/i, // Archos
  3188. /(hp).+(touchpad)/i, // HP TouchPad
  3189. /(hp).+(tablet)/i, // HP Tablet
  3190. /(kindle)\/([\w\.]+)/i, // Kindle
  3191. /\s(nook)[\w\s]+build\/(\w+)/i, // Nook
  3192. /(dell)\s(strea[kpr\s\d]*[\dko])/i // Dell Streak
  3193. ], [VENDOR, MODEL, [TYPE, TABLET]], [
  3194. /(kf[A-z]+)\sbuild\/.+silk\//i // Kindle Fire HD
  3195. ], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [
  3196. /(sd|kf)[0349hijorstuw]+\sbuild\/.+silk\//i // Fire Phone
  3197. ], [[MODEL, mapper.str, maps.device.amazon.model], [VENDOR, 'Amazon'], [TYPE, MOBILE]], [
  3198. /android.+aft([bms])\sbuild/i // Fire TV
  3199. ], [MODEL, [VENDOR, 'Amazon'], [TYPE, SMARTTV]], [
  3200. /\((ip[honed|\s\w*]+);.+(apple)/i // iPod/iPhone
  3201. ], [MODEL, VENDOR, [TYPE, MOBILE]], [
  3202. /\((ip[honed|\s\w*]+);/i // iPod/iPhone
  3203. ], [MODEL, [VENDOR, 'Apple'], [TYPE, MOBILE]], [
  3204. /(blackberry)[\s-]?(\w+)/i, // BlackBerry
  3205. /(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus|dell|meizu|motorola|polytron)[\s_-]?([\w-]*)/i,
  3206. // BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron
  3207. /(hp)\s([\w\s]+\w)/i, // HP iPAQ
  3208. /(asus)-?(\w+)/i // Asus
  3209. ], [VENDOR, MODEL, [TYPE, MOBILE]], [
  3210. /\(bb10;\s(\w+)/i // BlackBerry 10
  3211. ], [MODEL, [VENDOR, 'BlackBerry'], [TYPE, MOBILE]], [
  3212. // Asus Tablets
  3213. /android.+(transfo[prime\s]{4,10}\s\w+|eeepc|slider\s\w+|nexus 7|padfone|p00c)/i
  3214. ], [MODEL, [VENDOR, 'Asus'], [TYPE, TABLET]], [
  3215. /(sony)\s(tablet\s[ps])\sbuild\//i, // Sony
  3216. /(sony)?(?:sgp.+)\sbuild\//i
  3217. ], [[VENDOR, 'Sony'], [MODEL, 'Xperia Tablet'], [TYPE, TABLET]], [
  3218. /android.+\s([c-g]\d{4}|so[-l]\w+)(?=\sbuild\/|\).+chrome\/(?![1-6]{0,1}\d\.))/i
  3219. ], [MODEL, [VENDOR, 'Sony'], [TYPE, MOBILE]], [
  3220. /\s(ouya)\s/i, // Ouya
  3221. /(nintendo)\s([wids3u]+)/i // Nintendo
  3222. ], [VENDOR, MODEL, [TYPE, CONSOLE]], [
  3223. /android.+;\s(shield)\sbuild/i // Nvidia
  3224. ], [MODEL, [VENDOR, 'Nvidia'], [TYPE, CONSOLE]], [
  3225. /(playstation\s[34portablevi]+)/i // Playstation
  3226. ], [MODEL, [VENDOR, 'Sony'], [TYPE, CONSOLE]], [
  3227. /(sprint\s(\w+))/i // Sprint Phones
  3228. ], [[VENDOR, mapper.str, maps.device.sprint.vendor], [MODEL, mapper.str, maps.device.sprint.model], [TYPE, MOBILE]], [
  3229. /(htc)[;_\s-]+([\w\s]+(?=\)|\sbuild)|\w+)/i, // HTC
  3230. /(zte)-(\w*)/i, // ZTE
  3231. /(alcatel|geeksphone|nexian|panasonic|(?=;\s)sony)[_\s-]?([\w-]*)/i
  3232. // Alcatel/GeeksPhone/Nexian/Panasonic/Sony
  3233. ], [VENDOR, [MODEL, /_/g, ' '], [TYPE, MOBILE]], [
  3234. /(nexus\s9)/i // HTC Nexus 9
  3235. ], [MODEL, [VENDOR, 'HTC'], [TYPE, TABLET]], [
  3236. /d\/huawei([\w\s-]+)[;\)]/i,
  3237. /(nexus\s6p|vog-l29|ane-lx1|eml-l29)/i // Huawei
  3238. ], [MODEL, [VENDOR, 'Huawei'], [TYPE, MOBILE]], [
  3239. /android.+(bah2?-a?[lw]\d{2})/i // Huawei MediaPad
  3240. ], [MODEL, [VENDOR, 'Huawei'], [TYPE, TABLET]], [
  3241. /(microsoft);\s(lumia[\s\w]+)/i // Microsoft Lumia
  3242. ], [VENDOR, MODEL, [TYPE, MOBILE]], [
  3243. /[\s\(;](xbox(?:\sone)?)[\s\);]/i // Microsoft Xbox
  3244. ], [MODEL, [VENDOR, 'Microsoft'], [TYPE, CONSOLE]], [
  3245. /(kin\.[onetw]{3})/i // Microsoft Kin
  3246. ], [[MODEL, /\./g, ' '], [VENDOR, 'Microsoft'], [TYPE, MOBILE]], [
  3247. // Motorola
  3248. /\s(milestone|droid(?:[2-4x]|\s(?:bionic|x2|pro|razr))?:?(\s4g)?)[\w\s]+build\//i,
  3249. /mot[\s-]?(\w*)/i,
  3250. /(XT\d{3,4}) build\//i,
  3251. /(nexus\s6)/i
  3252. ], [MODEL, [VENDOR, 'Motorola'], [TYPE, MOBILE]], [
  3253. /android.+\s(mz60\d|xoom[\s2]{0,2})\sbuild\//i
  3254. ], [MODEL, [VENDOR, 'Motorola'], [TYPE, TABLET]], [
  3255. /hbbtv\/\d+\.\d+\.\d+\s+\([\w\s]*;\s*(\w[^;]*);([^;]*)/i // HbbTV devices
  3256. ], [[VENDOR, util.trim], [MODEL, util.trim], [TYPE, SMARTTV]], [
  3257. /hbbtv.+maple;(\d+)/i
  3258. ], [[MODEL, /^/, 'SmartTV'], [VENDOR, 'Samsung'], [TYPE, SMARTTV]], [
  3259. /\(dtv[\);].+(aquos)/i // Sharp
  3260. ], [MODEL, [VENDOR, 'Sharp'], [TYPE, SMARTTV]], [
  3261. /android.+((sch-i[89]0\d|shw-m380s|gt-p\d{4}|gt-n\d+|sgh-t8[56]9|nexus 10))/i,
  3262. /((SM-T\w+))/i
  3263. ], [[VENDOR, 'Samsung'], MODEL, [TYPE, TABLET]], [ // Samsung
  3264. /smart-tv.+(samsung)/i
  3265. ], [VENDOR, [TYPE, SMARTTV], MODEL], [
  3266. /((s[cgp]h-\w+|gt-\w+|galaxy\snexus|sm-\w[\w\d]+))/i,
  3267. /(sam[sung]*)[\s-]*(\w+-?[\w-]*)/i,
  3268. /sec-((sgh\w+))/i
  3269. ], [[VENDOR, 'Samsung'], MODEL, [TYPE, MOBILE]], [
  3270. /sie-(\w*)/i // Siemens
  3271. ], [MODEL, [VENDOR, 'Siemens'], [TYPE, MOBILE]], [
  3272. /(maemo|nokia).*(n900|lumia\s\d+)/i, // Nokia
  3273. /(nokia)[\s_-]?([\w-]*)/i
  3274. ], [[VENDOR, 'Nokia'], MODEL, [TYPE, MOBILE]], [
  3275. /android[x\d\.\s;]+\s([ab][1-7]\-?[0178a]\d\d?)/i // Acer
  3276. ], [MODEL, [VENDOR, 'Acer'], [TYPE, TABLET]], [
  3277. /android.+([vl]k\-?\d{3})\s+build/i // LG Tablet
  3278. ], [MODEL, [VENDOR, 'LG'], [TYPE, TABLET]], [
  3279. /android\s3\.[\s\w;-]{10}(lg?)-([06cv9]{3,4})/i // LG Tablet
  3280. ], [[VENDOR, 'LG'], MODEL, [TYPE, TABLET]], [
  3281. /(lg) netcast\.tv/i // LG SmartTV
  3282. ], [VENDOR, MODEL, [TYPE, SMARTTV]], [
  3283. /(nexus\s[45])/i, // LG
  3284. /lg[e;\s\/-]+(\w*)/i,
  3285. /android.+lg(\-?[\d\w]+)\s+build/i
  3286. ], [MODEL, [VENDOR, 'LG'], [TYPE, MOBILE]], [
  3287. /(lenovo)\s?(s(?:5000|6000)(?:[\w-]+)|tab(?:[\s\w]+))/i // Lenovo tablets
  3288. ], [VENDOR, MODEL, [TYPE, TABLET]], [
  3289. /android.+(ideatab[a-z0-9\-\s]+)/i // Lenovo
  3290. ], [MODEL, [VENDOR, 'Lenovo'], [TYPE, TABLET]], [
  3291. /(lenovo)[_\s-]?([\w-]+)/i
  3292. ], [VENDOR, MODEL, [TYPE, MOBILE]], [
  3293. /linux;.+((jolla));/i // Jolla
  3294. ], [VENDOR, MODEL, [TYPE, MOBILE]], [
  3295. /((pebble))app\/[\d\.]+\s/i // Pebble
  3296. ], [VENDOR, MODEL, [TYPE, WEARABLE]], [
  3297. /android.+;\s(oppo)\s?([\w\s]+)\sbuild/i // OPPO
  3298. ], [VENDOR, MODEL, [TYPE, MOBILE]], [
  3299. /crkey/i // Google Chromecast
  3300. ], [[MODEL, 'Chromecast'], [VENDOR, 'Google'], [TYPE, SMARTTV]], [
  3301. /android.+;\s(glass)\s\d/i // Google Glass
  3302. ], [MODEL, [VENDOR, 'Google'], [TYPE, WEARABLE]], [
  3303. /android.+;\s(pixel c)[\s)]/i // Google Pixel C
  3304. ], [MODEL, [VENDOR, 'Google'], [TYPE, TABLET]], [
  3305. /android.+;\s(pixel( [23])?( xl)?)[\s)]/i // Google Pixel
  3306. ], [MODEL, [VENDOR, 'Google'], [TYPE, MOBILE]], [
  3307. /android.+;\s(\w+)\s+build\/hm\1/i, // Xiaomi Hongmi 'numeric' models
  3308. /android.+(hm[\s\-_]*note?[\s_]*(?:\d\w)?)\s+build/i, // Xiaomi Hongmi
  3309. /android.+(mi[\s\-_]*(?:a\d|one|one[\s_]plus|note lte)?[\s_]*(?:\d?\w?)[\s_]*(?:plus)?)\s+build/i,
  3310. // Xiaomi Mi
  3311. /android.+(redmi[\s\-_]*(?:note)?(?:[\s_]*[\w\s]+))\s+build/i // Redmi Phones
  3312. ], [[MODEL, /_/g, ' '], [VENDOR, 'Xiaomi'], [TYPE, MOBILE]], [
  3313. /android.+(mi[\s\-_]*(?:pad)(?:[\s_]*[\w\s]+))\s+build/i // Mi Pad tablets
  3314. ],[[MODEL, /_/g, ' '], [VENDOR, 'Xiaomi'], [TYPE, TABLET]], [
  3315. /android.+;\s(m[1-5]\snote)\sbuild/i // Meizu
  3316. ], [MODEL, [VENDOR, 'Meizu'], [TYPE, MOBILE]], [
  3317. /(mz)-([\w-]{2,})/i
  3318. ], [[VENDOR, 'Meizu'], MODEL, [TYPE, MOBILE]], [
  3319. /android.+a000(1)\s+build/i, // OnePlus
  3320. /android.+oneplus\s(a\d{4})[\s)]/i
  3321. ], [MODEL, [VENDOR, 'OnePlus'], [TYPE, MOBILE]], [
  3322. /android.+[;\/]\s*(RCT[\d\w]+)\s+build/i // RCA Tablets
  3323. ], [MODEL, [VENDOR, 'RCA'], [TYPE, TABLET]], [
  3324. /android.+[;\/\s]+(Venue[\d\s]{2,7})\s+build/i // Dell Venue Tablets
  3325. ], [MODEL, [VENDOR, 'Dell'], [TYPE, TABLET]], [
  3326. /android.+[;\/]\s*(Q[T|M][\d\w]+)\s+build/i // Verizon Tablet
  3327. ], [MODEL, [VENDOR, 'Verizon'], [TYPE, TABLET]], [
  3328. /android.+[;\/]\s+(Barnes[&\s]+Noble\s+|BN[RT])(V?.*)\s+build/i // Barnes & Noble Tablet
  3329. ], [[VENDOR, 'Barnes & Noble'], MODEL, [TYPE, TABLET]], [
  3330. /android.+[;\/]\s+(TM\d{3}.*\b)\s+build/i // Barnes & Noble Tablet
  3331. ], [MODEL, [VENDOR, 'NuVision'], [TYPE, TABLET]], [
  3332. /android.+;\s(k88)\sbuild/i // ZTE K Series Tablet
  3333. ], [MODEL, [VENDOR, 'ZTE'], [TYPE, TABLET]], [
  3334. /android.+[;\/]\s*(gen\d{3})\s+build.*49h/i // Swiss GEN Mobile
  3335. ], [MODEL, [VENDOR, 'Swiss'], [TYPE, MOBILE]], [
  3336. /android.+[;\/]\s*(zur\d{3})\s+build/i // Swiss ZUR Tablet
  3337. ], [MODEL, [VENDOR, 'Swiss'], [TYPE, TABLET]], [
  3338. /android.+[;\/]\s*((Zeki)?TB.*\b)\s+build/i // Zeki Tablets
  3339. ], [MODEL, [VENDOR, 'Zeki'], [TYPE, TABLET]], [
  3340. /(android).+[;\/]\s+([YR]\d{2})\s+build/i,
  3341. /android.+[;\/]\s+(Dragon[\-\s]+Touch\s+|DT)(\w{5})\sbuild/i // Dragon Touch Tablet
  3342. ], [[VENDOR, 'Dragon Touch'], MODEL, [TYPE, TABLET]], [
  3343. /android.+[;\/]\s*(NS-?\w{0,9})\sbuild/i // Insignia Tablets
  3344. ], [MODEL, [VENDOR, 'Insignia'], [TYPE, TABLET]], [
  3345. /android.+[;\/]\s*((NX|Next)-?\w{0,9})\s+build/i // NextBook Tablets
  3346. ], [MODEL, [VENDOR, 'NextBook'], [TYPE, TABLET]], [
  3347. /android.+[;\/]\s*(Xtreme\_)?(V(1[045]|2[015]|30|40|60|7[05]|90))\s+build/i
  3348. ], [[VENDOR, 'Voice'], MODEL, [TYPE, MOBILE]], [ // Voice Xtreme Phones
  3349. /android.+[;\/]\s*(LVTEL\-)?(V1[12])\s+build/i // LvTel Phones
  3350. ], [[VENDOR, 'LvTel'], MODEL, [TYPE, MOBILE]], [
  3351. /android.+;\s(PH-1)\s/i
  3352. ], [MODEL, [VENDOR, 'Essential'], [TYPE, MOBILE]], [ // Essential PH-1
  3353. /android.+[;\/]\s*(V(100MD|700NA|7011|917G).*\b)\s+build/i // Envizen Tablets
  3354. ], [MODEL, [VENDOR, 'Envizen'], [TYPE, TABLET]], [
  3355. /android.+[;\/]\s*(Le[\s\-]+Pan)[\s\-]+(\w{1,9})\s+build/i // Le Pan Tablets
  3356. ], [VENDOR, MODEL, [TYPE, TABLET]], [
  3357. /android.+[;\/]\s*(Trio[\s\-]*.*)\s+build/i // MachSpeed Tablets
  3358. ], [MODEL, [VENDOR, 'MachSpeed'], [TYPE, TABLET]], [
  3359. /android.+[;\/]\s*(Trinity)[\-\s]*(T\d{3})\s+build/i // Trinity Tablets
  3360. ], [VENDOR, MODEL, [TYPE, TABLET]], [
  3361. /android.+[;\/]\s*TU_(1491)\s+build/i // Rotor Tablets
  3362. ], [MODEL, [VENDOR, 'Rotor'], [TYPE, TABLET]], [
  3363. /android.+(KS(.+))\s+build/i // Amazon Kindle Tablets
  3364. ], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [
  3365. /android.+(Gigaset)[\s\-]+(Q\w{1,9})\s+build/i // Gigaset Tablets
  3366. ], [VENDOR, MODEL, [TYPE, TABLET]], [
  3367. /\s(tablet|tab)[;\/]/i, // Unidentifiable Tablet
  3368. /\s(mobile)(?:[;\/]|\ssafari)/i // Unidentifiable Mobile
  3369. ], [[TYPE, util.lowerize], VENDOR, MODEL], [
  3370. /[\s\/\(](smart-?tv)[;\)]/i // SmartTV
  3371. ], [[TYPE, SMARTTV]], [
  3372. /(android[\w\.\s\-]{0,9});.+build/i // Generic Android Device
  3373. ], [MODEL, [VENDOR, 'Generic']]
  3374. ],
  3375. engine : [[
  3376. /windows.+\sedge\/([\w\.]+)/i // EdgeHTML
  3377. ], [VERSION, [NAME, 'EdgeHTML']], [
  3378. /webkit\/537\.36.+chrome\/(?!27)([\w\.]+)/i // Blink
  3379. ], [VERSION, [NAME, 'Blink']], [
  3380. /(presto)\/([\w\.]+)/i, // Presto
  3381. /(webkit|trident|netfront|netsurf|amaya|lynx|w3m|goanna)\/([\w\.]+)/i,
  3382. // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m/Goanna
  3383. /(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i, // KHTML/Tasman/Links
  3384. /(icab)[\/\s]([23]\.[\d\.]+)/i // iCab
  3385. ], [NAME, VERSION], [
  3386. /rv\:([\w\.]{1,9}).+(gecko)/i // Gecko
  3387. ], [VERSION, NAME]
  3388. ],
  3389. os : [[
  3390. // Windows based
  3391. /microsoft\s(windows)\s(vista|xp)/i // Windows (iTunes)
  3392. ], [NAME, VERSION], [
  3393. /(windows)\snt\s6\.2;\s(arm)/i, // Windows RT
  3394. /(windows\sphone(?:\sos)*)[\s\/]?([\d\.\s\w]*)/i, // Windows Phone
  3395. /(windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i
  3396. ], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [
  3397. /(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i
  3398. ], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [
  3399. // Mobile/Embedded OS
  3400. /\((bb)(10);/i // BlackBerry 10
  3401. ], [[NAME, 'BlackBerry'], VERSION], [
  3402. /(blackberry)\w*\/?([\w\.]*)/i, // Blackberry
  3403. /(tizen|kaios)[\/\s]([\w\.]+)/i, // Tizen/KaiOS
  3404. /(android|webos|palm\sos|qnx|bada|rim\stablet\sos|meego|sailfish|contiki)[\/\s-]?([\w\.]*)/i
  3405. // Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki/Sailfish OS
  3406. ], [NAME, VERSION], [
  3407. /(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]*)/i // Symbian
  3408. ], [[NAME, 'Symbian'], VERSION], [
  3409. /\((series40);/i // Series 40
  3410. ], [NAME], [
  3411. /mozilla.+\(mobile;.+gecko.+firefox/i // Firefox OS
  3412. ], [[NAME, 'Firefox OS'], VERSION], [
  3413. // Console
  3414. /(nintendo|playstation)\s([wids34portablevu]+)/i, // Nintendo/Playstation
  3415. // GNU/Linux based
  3416. /(mint)[\/\s\(]?(\w*)/i, // Mint
  3417. /(mageia|vectorlinux)[;\s]/i, // Mageia/VectorLinux
  3418. /(joli|[kxln]?ubuntu|debian|suse|opensuse|gentoo|(?=\s)arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?(?!chrom)([\w\.-]*)/i,
  3419. // Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware
  3420. // Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus
  3421. /(hurd|linux)\s?([\w\.]*)/i, // Hurd/Linux
  3422. /(gnu)\s?([\w\.]*)/i // GNU
  3423. ], [NAME, VERSION], [
  3424. /(cros)\s[\w]+\s([\w\.]+\w)/i // Chromium OS
  3425. ], [[NAME, 'Chromium OS'], VERSION],[
  3426. // Solaris
  3427. /(sunos)\s?([\w\.\d]*)/i // Solaris
  3428. ], [[NAME, 'Solaris'], VERSION], [
  3429. // BSD based
  3430. /\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]*)/i // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly
  3431. ], [NAME, VERSION],[
  3432. /(haiku)\s(\w+)/i // Haiku
  3433. ], [NAME, VERSION],[
  3434. /cfnetwork\/.+darwin/i,
  3435. /ip[honead]{2,4}(?:.*os\s([\w]+)\slike\smac|;\sopera)/i // iOS
  3436. ], [[VERSION, /_/g, '.'], [NAME, 'iOS']], [
  3437. /(mac\sos\sx)\s?([\w\s\.]*)/i,
  3438. /(macintosh|mac(?=_powerpc)\s)/i // Mac OS
  3439. ], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [
  3440. // Other
  3441. /((?:open)?solaris)[\/\s-]?([\w\.]*)/i, // Solaris
  3442. /(aix)\s((\d)(?=\.|\)|\s)[\w\.])*/i, // AIX
  3443. /(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms|fuchsia)/i,
  3444. // Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS/Fuchsia
  3445. /(unix)\s?([\w\.]*)/i // UNIX
  3446. ], [NAME, VERSION]
  3447. ]
  3448. };
  3449. /////////////////
  3450. // Constructor
  3451. ////////////////
  3452. var UAParser = function (uastring, extensions) {
  3453. if (typeof uastring === 'object') {
  3454. extensions = uastring;
  3455. uastring = undefined;
  3456. }
  3457. if (!(this instanceof UAParser)) {
  3458. return new UAParser(uastring, extensions).getResult();
  3459. }
  3460. var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY);
  3461. var rgxmap = extensions ? util.extend(regexes, extensions) : regexes;
  3462. this.getBrowser = function () {
  3463. var browser = { name: undefined, version: undefined };
  3464. mapper.rgx.call(browser, ua, rgxmap.browser);
  3465. browser.major = util.major(browser.version); // deprecated
  3466. return browser;
  3467. };
  3468. this.getCPU = function () {
  3469. var cpu = { architecture: undefined };
  3470. mapper.rgx.call(cpu, ua, rgxmap.cpu);
  3471. return cpu;
  3472. };
  3473. this.getDevice = function () {
  3474. var device = { vendor: undefined, model: undefined, type: undefined };
  3475. mapper.rgx.call(device, ua, rgxmap.device);
  3476. return device;
  3477. };
  3478. this.getEngine = function () {
  3479. var engine = { name: undefined, version: undefined };
  3480. mapper.rgx.call(engine, ua, rgxmap.engine);
  3481. return engine;
  3482. };
  3483. this.getOS = function () {
  3484. var os = { name: undefined, version: undefined };
  3485. mapper.rgx.call(os, ua, rgxmap.os);
  3486. return os;
  3487. };
  3488. this.getResult = function () {
  3489. return {
  3490. ua : this.getUA(),
  3491. browser : this.getBrowser(),
  3492. engine : this.getEngine(),
  3493. os : this.getOS(),
  3494. device : this.getDevice(),
  3495. cpu : this.getCPU()
  3496. };
  3497. };
  3498. this.getUA = function () {
  3499. return ua;
  3500. };
  3501. this.setUA = function (uastring) {
  3502. ua = uastring;
  3503. return this;
  3504. };
  3505. return this;
  3506. };
  3507. UAParser.VERSION = LIBVERSION;
  3508. UAParser.BROWSER = {
  3509. NAME : NAME,
  3510. MAJOR : MAJOR, // deprecated
  3511. VERSION : VERSION
  3512. };
  3513. UAParser.CPU = {
  3514. ARCHITECTURE : ARCHITECTURE
  3515. };
  3516. UAParser.DEVICE = {
  3517. MODEL : MODEL,
  3518. VENDOR : VENDOR,
  3519. TYPE : TYPE,
  3520. CONSOLE : CONSOLE,
  3521. MOBILE : MOBILE,
  3522. SMARTTV : SMARTTV,
  3523. TABLET : TABLET,
  3524. WEARABLE: WEARABLE,
  3525. EMBEDDED: EMBEDDED
  3526. };
  3527. UAParser.ENGINE = {
  3528. NAME : NAME,
  3529. VERSION : VERSION
  3530. };
  3531. UAParser.OS = {
  3532. NAME : NAME,
  3533. VERSION : VERSION
  3534. };
  3535. ///////////
  3536. // Export
  3537. //////////
  3538. // check js environment
  3539. if (typeof(exports) !== UNDEF_TYPE) {
  3540. // nodejs env
  3541. if (typeof module !== UNDEF_TYPE && module.exports) {
  3542. exports = module.exports = UAParser;
  3543. }
  3544. exports.UAParser = UAParser;
  3545. } else {
  3546. // requirejs env (optional)
  3547. if (typeof(define) === 'function' && define.amd) {
  3548. define(function () {
  3549. return UAParser;
  3550. });
  3551. } else if (window) {
  3552. // browser env
  3553. window.UAParser = UAParser;
  3554. }
  3555. }
  3556. // jQuery/Zepto specific (optional)
  3557. // Note:
  3558. // In AMD env the global scope should be kept clean, but jQuery is an exception.
  3559. // jQuery always exports to global scope, unless jQuery.noConflict(true) is used,
  3560. // and we should catch that.
  3561. var $ = window && (window.jQuery || window.Zepto);
  3562. if ($ && !$.ua) {
  3563. var parser = new UAParser();
  3564. $.ua = parser.getResult();
  3565. $.ua.get = function () {
  3566. return parser.getUA();
  3567. };
  3568. $.ua.set = function (uastring) {
  3569. parser.setUA(uastring);
  3570. var result = parser.getResult();
  3571. for (var prop in result) {
  3572. $.ua[prop] = result[prop];
  3573. }
  3574. };
  3575. }
  3576. })(typeof window === 'object' ? window : this);
  3577. },{}],22:[function(require,module,exports){
  3578. /**
  3579. * Convert array of 16 byte values to UUID string format of the form:
  3580. * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  3581. */
  3582. var byteToHex = [];
  3583. for (var i = 0; i < 256; ++i) {
  3584. byteToHex[i] = (i + 0x100).toString(16).substr(1);
  3585. }
  3586. function bytesToUuid(buf, offset) {
  3587. var i = offset || 0;
  3588. var bth = byteToHex;
  3589. // join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
  3590. return ([
  3591. bth[buf[i++]], bth[buf[i++]],
  3592. bth[buf[i++]], bth[buf[i++]], '-',
  3593. bth[buf[i++]], bth[buf[i++]], '-',
  3594. bth[buf[i++]], bth[buf[i++]], '-',
  3595. bth[buf[i++]], bth[buf[i++]], '-',
  3596. bth[buf[i++]], bth[buf[i++]],
  3597. bth[buf[i++]], bth[buf[i++]],
  3598. bth[buf[i++]], bth[buf[i++]]
  3599. ]).join('');
  3600. }
  3601. module.exports = bytesToUuid;
  3602. },{}],23:[function(require,module,exports){
  3603. // Unique ID creation requires a high quality random # generator. In the
  3604. // browser this is a little complicated due to unknown quality of Math.random()
  3605. // and inconsistent support for the `crypto` API. We do the best we can via
  3606. // feature-detection
  3607. // getRandomValues needs to be invoked in a context where "this" is a Crypto
  3608. // implementation. Also, find the complete implementation of crypto on IE11.
  3609. var getRandomValues = (typeof(crypto) != 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto)) ||
  3610. (typeof(msCrypto) != 'undefined' && typeof window.msCrypto.getRandomValues == 'function' && msCrypto.getRandomValues.bind(msCrypto));
  3611. if (getRandomValues) {
  3612. // WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto
  3613. var rnds8 = new Uint8Array(16); // eslint-disable-line no-undef
  3614. module.exports = function whatwgRNG() {
  3615. getRandomValues(rnds8);
  3616. return rnds8;
  3617. };
  3618. } else {
  3619. // Math.random()-based (RNG)
  3620. //
  3621. // If all else fails, use Math.random(). It's fast, but is of unspecified
  3622. // quality.
  3623. var rnds = new Array(16);
  3624. module.exports = function mathRNG() {
  3625. for (var i = 0, r; i < 16; i++) {
  3626. if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
  3627. rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
  3628. }
  3629. return rnds;
  3630. };
  3631. }
  3632. },{}],24:[function(require,module,exports){
  3633. var rng = require('./lib/rng');
  3634. var bytesToUuid = require('./lib/bytesToUuid');
  3635. function v4(options, buf, offset) {
  3636. var i = buf && offset || 0;
  3637. if (typeof(options) == 'string') {
  3638. buf = options === 'binary' ? new Array(16) : null;
  3639. options = null;
  3640. }
  3641. options = options || {};
  3642. var rnds = options.random || (options.rng || rng)();
  3643. // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
  3644. rnds[6] = (rnds[6] & 0x0f) | 0x40;
  3645. rnds[8] = (rnds[8] & 0x3f) | 0x80;
  3646. // Copy bytes to buffer, if provided
  3647. if (buf) {
  3648. for (var ii = 0; ii < 16; ++ii) {
  3649. buf[i + ii] = rnds[ii];
  3650. }
  3651. }
  3652. return buf || bytesToUuid(rnds);
  3653. }
  3654. module.exports = v4;
  3655. },{"./lib/bytesToUuid":22,"./lib/rng":23}],25:[function(require,module,exports){
  3656. /*
  3657. WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based
  3658. on @visionmedia's Emitter from UI Kit.
  3659. Why? I wanted it standalone.
  3660. I also wanted support for wildcard emitters like this:
  3661. emitter.on('*', function (eventName, other, event, payloads) {
  3662. });
  3663. emitter.on('somenamespace*', function (eventName, payloads) {
  3664. });
  3665. Please note that callbacks triggered by wildcard registered events also get
  3666. the event name as the first argument.
  3667. */
  3668. module.exports = WildEmitter;
  3669. function WildEmitter() { }
  3670. WildEmitter.mixin = function (constructor) {
  3671. var prototype = constructor.prototype || constructor;
  3672. prototype.isWildEmitter= true;
  3673. // Listen on the given `event` with `fn`. Store a group name if present.
  3674. prototype.on = function (event, groupName, fn) {
  3675. this.callbacks = this.callbacks || {};
  3676. var hasGroup = (arguments.length === 3),
  3677. group = hasGroup ? arguments[1] : undefined,
  3678. func = hasGroup ? arguments[2] : arguments[1];
  3679. func._groupName = group;
  3680. (this.callbacks[event] = this.callbacks[event] || []).push(func);
  3681. return this;
  3682. };
  3683. // Adds an `event` listener that will be invoked a single
  3684. // time then automatically removed.
  3685. prototype.once = function (event, groupName, fn) {
  3686. var self = this,
  3687. hasGroup = (arguments.length === 3),
  3688. group = hasGroup ? arguments[1] : undefined,
  3689. func = hasGroup ? arguments[2] : arguments[1];
  3690. function on() {
  3691. self.off(event, on);
  3692. func.apply(this, arguments);
  3693. }
  3694. this.on(event, group, on);
  3695. return this;
  3696. };
  3697. // Unbinds an entire group
  3698. prototype.releaseGroup = function (groupName) {
  3699. this.callbacks = this.callbacks || {};
  3700. var item, i, len, handlers;
  3701. for (item in this.callbacks) {
  3702. handlers = this.callbacks[item];
  3703. for (i = 0, len = handlers.length; i < len; i++) {
  3704. if (handlers[i]._groupName === groupName) {
  3705. //console.log('removing');
  3706. // remove it and shorten the array we're looping through
  3707. handlers.splice(i, 1);
  3708. i--;
  3709. len--;
  3710. }
  3711. }
  3712. }
  3713. return this;
  3714. };
  3715. // Remove the given callback for `event` or all
  3716. // registered callbacks.
  3717. prototype.off = function (event, fn) {
  3718. this.callbacks = this.callbacks || {};
  3719. var callbacks = this.callbacks[event],
  3720. i;
  3721. if (!callbacks) return this;
  3722. // remove all handlers
  3723. if (arguments.length === 1) {
  3724. delete this.callbacks[event];
  3725. return this;
  3726. }
  3727. // remove specific handler
  3728. i = callbacks.indexOf(fn);
  3729. if (i !== -1) {
  3730. callbacks.splice(i, 1);
  3731. if (callbacks.length === 0) {
  3732. delete this.callbacks[event];
  3733. }
  3734. }
  3735. return this;
  3736. };
  3737. /// Emit `event` with the given args.
  3738. // also calls any `*` handlers
  3739. prototype.emit = function (event) {
  3740. this.callbacks = this.callbacks || {};
  3741. var args = [].slice.call(arguments, 1),
  3742. callbacks = this.callbacks[event],
  3743. specialCallbacks = this.getWildcardCallbacks(event),
  3744. i,
  3745. len,
  3746. item,
  3747. listeners;
  3748. if (callbacks) {
  3749. listeners = callbacks.slice();
  3750. for (i = 0, len = listeners.length; i < len; ++i) {
  3751. if (!listeners[i]) {
  3752. break;
  3753. }
  3754. listeners[i].apply(this, args);
  3755. }
  3756. }
  3757. if (specialCallbacks) {
  3758. len = specialCallbacks.length;
  3759. listeners = specialCallbacks.slice();
  3760. for (i = 0, len = listeners.length; i < len; ++i) {
  3761. if (!listeners[i]) {
  3762. break;
  3763. }
  3764. listeners[i].apply(this, [event].concat(args));
  3765. }
  3766. }
  3767. return this;
  3768. };
  3769. // Helper for for finding special wildcard event handlers that match the event
  3770. prototype.getWildcardCallbacks = function (eventName) {
  3771. this.callbacks = this.callbacks || {};
  3772. var item,
  3773. split,
  3774. result = [];
  3775. for (item in this.callbacks) {
  3776. split = item.split('*');
  3777. if (item === '*' || (split.length === 2 && eventName.slice(0, split[0].length) === split[0])) {
  3778. result = result.concat(this.callbacks[item]);
  3779. }
  3780. }
  3781. return result;
  3782. };
  3783. };
  3784. WildEmitter.mixin(WildEmitter);
  3785. },{}]},{},[2])(2)
  3786. });