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

xmpp.bundle.js 189KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118
  1. !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.xmpp=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  2. /* jshint -W117 */
  3. var TraceablePeerConnection = require("./TraceablePeerConnection");
  4. var SDPDiffer = require("./SDPDiffer");
  5. var SDPUtil = require("./SDPUtil");
  6. var SDP = require("./SDP");
  7. // Jingle stuff
  8. function JingleSession(me, sid, connection, service) {
  9. this.me = me;
  10. this.sid = sid;
  11. this.connection = connection;
  12. this.initiator = null;
  13. this.responder = null;
  14. this.isInitiator = null;
  15. this.peerjid = null;
  16. this.state = null;
  17. this.localSDP = null;
  18. this.remoteSDP = null;
  19. this.relayedStreams = [];
  20. this.startTime = null;
  21. this.stopTime = null;
  22. this.media_constraints = null;
  23. this.pc_constraints = null;
  24. this.ice_config = {};
  25. this.drip_container = [];
  26. this.service = service;
  27. this.usetrickle = true;
  28. this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
  29. this.usedrip = false; // dripping is sending trickle candidates not one-by-one
  30. this.hadstuncandidate = false;
  31. this.hadturncandidate = false;
  32. this.lasticecandidate = false;
  33. this.statsinterval = null;
  34. this.reason = null;
  35. this.addssrc = [];
  36. this.removessrc = [];
  37. this.pendingop = null;
  38. this.switchstreams = false;
  39. this.wait = true;
  40. this.localStreamsSSRC = null;
  41. /**
  42. * The indicator which determines whether the (local) video has been muted
  43. * in response to a user command in contrast to an automatic decision made
  44. * by the application logic.
  45. */
  46. this.videoMuteByUser = false;
  47. }
  48. //TODO: this array must be removed when firefox implement multistream support
  49. JingleSession.notReceivedSSRCs = [];
  50. JingleSession.prototype.initiate = function (peerjid, isInitiator) {
  51. var self = this;
  52. if (this.state !== null) {
  53. console.error('attempt to initiate on session ' + this.sid +
  54. 'in state ' + this.state);
  55. return;
  56. }
  57. this.isInitiator = isInitiator;
  58. this.state = 'pending';
  59. this.initiator = isInitiator ? this.me : peerjid;
  60. this.responder = !isInitiator ? this.me : peerjid;
  61. this.peerjid = peerjid;
  62. this.hadstuncandidate = false;
  63. this.hadturncandidate = false;
  64. this.lasticecandidate = false;
  65. this.peerconnection
  66. = new TraceablePeerConnection(
  67. this.connection.jingle.ice_config,
  68. this.connection.jingle.pc_constraints );
  69. this.peerconnection.onicecandidate = function (event) {
  70. self.sendIceCandidate(event.candidate);
  71. };
  72. this.peerconnection.onaddstream = function (event) {
  73. console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
  74. self.remoteStreamAdded(event);
  75. };
  76. this.peerconnection.onremovestream = function (event) {
  77. // Remove the stream from remoteStreams
  78. // FIXME: remotestreamremoved.jingle not defined anywhere(unused)
  79. $(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
  80. };
  81. this.peerconnection.onsignalingstatechange = function (event) {
  82. if (!(self && self.peerconnection)) return;
  83. };
  84. this.peerconnection.oniceconnectionstatechange = function (event) {
  85. if (!(self && self.peerconnection)) return;
  86. switch (self.peerconnection.iceConnectionState) {
  87. case 'connected':
  88. this.startTime = new Date();
  89. break;
  90. case 'disconnected':
  91. this.stopTime = new Date();
  92. break;
  93. }
  94. onIceConnectionStateChange(self.sid, self);
  95. };
  96. // add any local and relayed stream
  97. RTC.localStreams.forEach(function(stream) {
  98. self.peerconnection.addStream(stream.getOriginalStream());
  99. });
  100. this.relayedStreams.forEach(function(stream) {
  101. self.peerconnection.addStream(stream);
  102. });
  103. };
  104. function onIceConnectionStateChange(sid, session) {
  105. switch (session.peerconnection.iceConnectionState) {
  106. case 'checking':
  107. session.timeChecking = (new Date()).getTime();
  108. session.firstconnect = true;
  109. break;
  110. case 'completed': // on caller side
  111. case 'connected':
  112. if (session.firstconnect) {
  113. session.firstconnect = false;
  114. var metadata = {};
  115. metadata.setupTime
  116. = (new Date()).getTime() - session.timeChecking;
  117. session.peerconnection.getStats(function (res) {
  118. if(res && res.result) {
  119. res.result().forEach(function (report) {
  120. if (report.type == 'googCandidatePair' &&
  121. report.stat('googActiveConnection') == 'true') {
  122. metadata.localCandidateType
  123. = report.stat('googLocalCandidateType');
  124. metadata.remoteCandidateType
  125. = report.stat('googRemoteCandidateType');
  126. // log pair as well so we can get nice pie
  127. // charts
  128. metadata.candidatePair
  129. = report.stat('googLocalCandidateType') +
  130. ';' +
  131. report.stat('googRemoteCandidateType');
  132. if (report.stat('googRemoteAddress').indexOf('[') === 0)
  133. {
  134. metadata.ipv6 = true;
  135. }
  136. }
  137. });
  138. }
  139. });
  140. }
  141. break;
  142. }
  143. }
  144. JingleSession.prototype.accept = function () {
  145. var self = this;
  146. this.state = 'active';
  147. var pranswer = this.peerconnection.localDescription;
  148. if (!pranswer || pranswer.type != 'pranswer') {
  149. return;
  150. }
  151. console.log('going from pranswer to answer');
  152. if (this.usetrickle) {
  153. // remove candidates already sent from session-accept
  154. var lines = SDPUtil.find_lines(pranswer.sdp, 'a=candidate:');
  155. for (var i = 0; i < lines.length; i++) {
  156. pranswer.sdp = pranswer.sdp.replace(lines[i] + '\r\n', '');
  157. }
  158. }
  159. while (SDPUtil.find_line(pranswer.sdp, 'a=inactive')) {
  160. // FIXME: change any inactive to sendrecv or whatever they were originally
  161. pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
  162. }
  163. pranswer = simulcast.reverseTransformLocalDescription(pranswer);
  164. var prsdp = new SDP(pranswer.sdp);
  165. var accept = $iq({to: this.peerjid,
  166. type: 'set'})
  167. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  168. action: 'session-accept',
  169. initiator: this.initiator,
  170. responder: this.responder,
  171. sid: this.sid });
  172. prsdp.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
  173. var sdp = this.peerconnection.localDescription.sdp;
  174. while (SDPUtil.find_line(sdp, 'a=inactive')) {
  175. // FIXME: change any inactive to sendrecv or whatever they were originally
  176. sdp = sdp.replace('a=inactive', 'a=sendrecv');
  177. }
  178. var self = this;
  179. this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
  180. function () {
  181. //console.log('setLocalDescription success');
  182. self.setLocalDescription();
  183. self.connection.sendIQ(accept,
  184. function () {
  185. var ack = {};
  186. ack.source = 'answer';
  187. $(document).trigger('ack.jingle', [self.sid, ack]);
  188. },
  189. function (stanza) {
  190. var error = ($(stanza).find('error').length) ? {
  191. code: $(stanza).find('error').attr('code'),
  192. reason: $(stanza).find('error :first')[0].tagName
  193. }:{};
  194. error.source = 'answer';
  195. JingleSession.onJingleError(self.sid, error);
  196. },
  197. 10000);
  198. },
  199. function (e) {
  200. console.error('setLocalDescription failed', e);
  201. }
  202. );
  203. };
  204. JingleSession.prototype.terminate = function (reason) {
  205. this.state = 'ended';
  206. this.reason = reason;
  207. this.peerconnection.close();
  208. if (this.statsinterval !== null) {
  209. window.clearInterval(this.statsinterval);
  210. this.statsinterval = null;
  211. }
  212. };
  213. JingleSession.prototype.active = function () {
  214. return this.state == 'active';
  215. };
  216. JingleSession.prototype.sendIceCandidate = function (candidate) {
  217. var self = this;
  218. if (candidate && !this.lasticecandidate) {
  219. var ice = SDPUtil.iceparams(this.localSDP.media[candidate.sdpMLineIndex], this.localSDP.session);
  220. var jcand = SDPUtil.candidateToJingle(candidate.candidate);
  221. if (!(ice && jcand)) {
  222. console.error('failed to get ice && jcand');
  223. return;
  224. }
  225. ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
  226. if (jcand.type === 'srflx') {
  227. this.hadstuncandidate = true;
  228. } else if (jcand.type === 'relay') {
  229. this.hadturncandidate = true;
  230. }
  231. if (this.usetrickle) {
  232. if (this.usedrip) {
  233. if (this.drip_container.length === 0) {
  234. // start 20ms callout
  235. window.setTimeout(function () {
  236. if (self.drip_container.length === 0) return;
  237. self.sendIceCandidates(self.drip_container);
  238. self.drip_container = [];
  239. }, 20);
  240. }
  241. this.drip_container.push(candidate);
  242. return;
  243. } else {
  244. self.sendIceCandidate([candidate]);
  245. }
  246. }
  247. } else {
  248. //console.log('sendIceCandidate: last candidate.');
  249. if (!this.usetrickle) {
  250. //console.log('should send full offer now...');
  251. var init = $iq({to: this.peerjid,
  252. type: 'set'})
  253. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  254. action: this.peerconnection.localDescription.type == 'offer' ? 'session-initiate' : 'session-accept',
  255. initiator: this.initiator,
  256. sid: this.sid});
  257. this.localSDP = new SDP(this.peerconnection.localDescription.sdp);
  258. var self = this;
  259. var sendJingle = function (ssrc) {
  260. if(!ssrc)
  261. ssrc = {};
  262. self.localSDP.toJingle(init, self.initiator == self.me ? 'initiator' : 'responder', ssrc);
  263. self.connection.sendIQ(init,
  264. function () {
  265. //console.log('session initiate ack');
  266. var ack = {};
  267. ack.source = 'offer';
  268. $(document).trigger('ack.jingle', [self.sid, ack]);
  269. },
  270. function (stanza) {
  271. self.state = 'error';
  272. self.peerconnection.close();
  273. var error = ($(stanza).find('error').length) ? {
  274. code: $(stanza).find('error').attr('code'),
  275. reason: $(stanza).find('error :first')[0].tagName,
  276. }:{};
  277. error.source = 'offer';
  278. JingleSession.onJingleError(self.sid, error);
  279. },
  280. 10000);
  281. }
  282. sendJingle();
  283. }
  284. this.lasticecandidate = true;
  285. console.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate);
  286. console.log('Have we encountered any relay candidates? ' + this.hadturncandidate);
  287. if (!(this.hadstuncandidate || this.hadturncandidate) && this.peerconnection.signalingState != 'closed') {
  288. $(document).trigger('nostuncandidates.jingle', [this.sid]);
  289. }
  290. }
  291. };
  292. JingleSession.prototype.sendIceCandidates = function (candidates) {
  293. console.log('sendIceCandidates', candidates);
  294. var cand = $iq({to: this.peerjid, type: 'set'})
  295. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  296. action: 'transport-info',
  297. initiator: this.initiator,
  298. sid: this.sid});
  299. for (var mid = 0; mid < this.localSDP.media.length; mid++) {
  300. var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; });
  301. var mline = SDPUtil.parse_mline(this.localSDP.media[mid].split('\r\n')[0]);
  302. if (cands.length > 0) {
  303. var ice = SDPUtil.iceparams(this.localSDP.media[mid], this.localSDP.session);
  304. ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
  305. cand.c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder',
  306. name: (cands[0].sdpMid? cands[0].sdpMid : mline.media)
  307. }).c('transport', ice);
  308. for (var i = 0; i < cands.length; i++) {
  309. cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
  310. }
  311. // add fingerprint
  312. if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
  313. var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
  314. tmp.required = true;
  315. cand.c(
  316. 'fingerprint',
  317. {xmlns: 'urn:xmpp:jingle:apps:dtls:0'})
  318. .t(tmp.fingerprint);
  319. delete tmp.fingerprint;
  320. cand.attrs(tmp);
  321. cand.up();
  322. }
  323. cand.up(); // transport
  324. cand.up(); // content
  325. }
  326. }
  327. // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
  328. //console.log('was this the last candidate', this.lasticecandidate);
  329. this.connection.sendIQ(cand,
  330. function () {
  331. var ack = {};
  332. ack.source = 'transportinfo';
  333. $(document).trigger('ack.jingle', [this.sid, ack]);
  334. },
  335. function (stanza) {
  336. var error = ($(stanza).find('error').length) ? {
  337. code: $(stanza).find('error').attr('code'),
  338. reason: $(stanza).find('error :first')[0].tagName,
  339. }:{};
  340. error.source = 'transportinfo';
  341. JingleSession.onJingleError(this.sid, error);
  342. },
  343. 10000);
  344. };
  345. JingleSession.prototype.sendOffer = function () {
  346. //console.log('sendOffer...');
  347. var self = this;
  348. this.peerconnection.createOffer(function (sdp) {
  349. self.createdOffer(sdp);
  350. },
  351. function (e) {
  352. console.error('createOffer failed', e);
  353. },
  354. this.media_constraints
  355. );
  356. };
  357. JingleSession.prototype.createdOffer = function (sdp) {
  358. //console.log('createdOffer', sdp);
  359. var self = this;
  360. this.localSDP = new SDP(sdp.sdp);
  361. //this.localSDP.mangle();
  362. var sendJingle = function () {
  363. var init = $iq({to: this.peerjid,
  364. type: 'set'})
  365. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  366. action: 'session-initiate',
  367. initiator: this.initiator,
  368. sid: this.sid});
  369. self.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
  370. self.connection.sendIQ(init,
  371. function () {
  372. var ack = {};
  373. ack.source = 'offer';
  374. $(document).trigger('ack.jingle', [self.sid, ack]);
  375. },
  376. function (stanza) {
  377. self.state = 'error';
  378. self.peerconnection.close();
  379. var error = ($(stanza).find('error').length) ? {
  380. code: $(stanza).find('error').attr('code'),
  381. reason: $(stanza).find('error :first')[0].tagName,
  382. }:{};
  383. error.source = 'offer';
  384. JingleSession.onJingleError(self.sid, error);
  385. },
  386. 10000);
  387. }
  388. sdp.sdp = this.localSDP.raw;
  389. this.peerconnection.setLocalDescription(sdp,
  390. function () {
  391. if(self.usetrickle)
  392. {
  393. sendJingle();
  394. }
  395. self.setLocalDescription();
  396. //console.log('setLocalDescription success');
  397. },
  398. function (e) {
  399. console.error('setLocalDescription failed', e);
  400. }
  401. );
  402. var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
  403. for (var i = 0; i < cands.length; i++) {
  404. var cand = SDPUtil.parse_icecandidate(cands[i]);
  405. if (cand.type == 'srflx') {
  406. this.hadstuncandidate = true;
  407. } else if (cand.type == 'relay') {
  408. this.hadturncandidate = true;
  409. }
  410. }
  411. };
  412. JingleSession.prototype.setRemoteDescription = function (elem, desctype) {
  413. //console.log('setting remote description... ', desctype);
  414. this.remoteSDP = new SDP('');
  415. this.remoteSDP.fromJingle(elem);
  416. if (this.peerconnection.remoteDescription !== null) {
  417. console.log('setRemoteDescription when remote description is not null, should be pranswer', this.peerconnection.remoteDescription);
  418. if (this.peerconnection.remoteDescription.type == 'pranswer') {
  419. var pranswer = new SDP(this.peerconnection.remoteDescription.sdp);
  420. for (var i = 0; i < pranswer.media.length; i++) {
  421. // make sure we have ice ufrag and pwd
  422. if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) {
  423. if (SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session)) {
  424. this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session) + '\r\n';
  425. } else {
  426. console.warn('no ice ufrag?');
  427. }
  428. if (SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session)) {
  429. this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session) + '\r\n';
  430. } else {
  431. console.warn('no ice pwd?');
  432. }
  433. }
  434. // copy over candidates
  435. var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:');
  436. for (var j = 0; j < lines.length; j++) {
  437. this.remoteSDP.media[i] += lines[j] + '\r\n';
  438. }
  439. }
  440. this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
  441. }
  442. }
  443. var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});
  444. this.peerconnection.setRemoteDescription(remotedesc,
  445. function () {
  446. //console.log('setRemoteDescription success');
  447. },
  448. function (e) {
  449. console.error('setRemoteDescription error', e);
  450. JingleSession.onJingleFatalError(self, e);
  451. }
  452. );
  453. };
  454. JingleSession.prototype.addIceCandidate = function (elem) {
  455. var self = this;
  456. if (this.peerconnection.signalingState == 'closed') {
  457. return;
  458. }
  459. if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') {
  460. console.log('trickle ice candidate arriving before session accept...');
  461. // create a PRANSWER for setRemoteDescription
  462. if (!this.remoteSDP) {
  463. var cobbled = 'v=0\r\n' +
  464. 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
  465. 's=-\r\n' +
  466. 't=0 0\r\n';
  467. // first, take some things from the local description
  468. for (var i = 0; i < this.localSDP.media.length; i++) {
  469. cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n';
  470. cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n';
  471. if (SDPUtil.find_line(this.localSDP.media[i], 'a=mid:')) {
  472. cobbled += SDPUtil.find_line(this.localSDP.media[i], 'a=mid:') + '\r\n';
  473. }
  474. cobbled += 'a=inactive\r\n';
  475. }
  476. this.remoteSDP = new SDP(cobbled);
  477. }
  478. // then add things like ice and dtls from remote candidate
  479. elem.each(function () {
  480. for (var i = 0; i < self.remoteSDP.media.length; i++) {
  481. if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
  482. self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
  483. if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) {
  484. var tmp = $(this).find('transport');
  485. self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
  486. self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
  487. tmp = $(this).find('transport>fingerprint');
  488. if (tmp.length) {
  489. self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n';
  490. } else {
  491. console.log('no dtls fingerprint (webrtc issue #1718?)');
  492. self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n';
  493. }
  494. break;
  495. }
  496. }
  497. }
  498. });
  499. this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
  500. // we need a complete SDP with ice-ufrag/ice-pwd in all parts
  501. // this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts
  502. // but it could be in the session part as well. since the code above constructs this sdp this can't happen however
  503. var iscomplete = this.remoteSDP.media.filter(function (mediapart) {
  504. return SDPUtil.find_line(mediapart, 'a=ice-ufrag:');
  505. }).length == this.remoteSDP.media.length;
  506. if (iscomplete) {
  507. console.log('setting pranswer');
  508. try {
  509. this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }),
  510. function() {
  511. },
  512. function(e) {
  513. console.log('setRemoteDescription pranswer failed', e.toString());
  514. });
  515. } catch (e) {
  516. console.error('setting pranswer failed', e);
  517. }
  518. } else {
  519. //console.log('not yet setting pranswer');
  520. }
  521. }
  522. // operate on each content element
  523. elem.each(function () {
  524. // would love to deactivate this, but firefox still requires it
  525. var idx = -1;
  526. var i;
  527. for (i = 0; i < self.remoteSDP.media.length; i++) {
  528. if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
  529. self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
  530. idx = i;
  531. break;
  532. }
  533. }
  534. if (idx == -1) { // fall back to localdescription
  535. for (i = 0; i < self.localSDP.media.length; i++) {
  536. if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
  537. self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
  538. idx = i;
  539. break;
  540. }
  541. }
  542. }
  543. var name = $(this).attr('name');
  544. // TODO: check ice-pwd and ice-ufrag?
  545. $(this).find('transport>candidate').each(function () {
  546. var line, candidate;
  547. line = SDPUtil.candidateFromJingle(this);
  548. candidate = new RTCIceCandidate({sdpMLineIndex: idx,
  549. sdpMid: name,
  550. candidate: line});
  551. try {
  552. self.peerconnection.addIceCandidate(candidate);
  553. } catch (e) {
  554. console.error('addIceCandidate failed', e.toString(), line);
  555. }
  556. });
  557. });
  558. };
  559. JingleSession.prototype.sendAnswer = function (provisional) {
  560. //console.log('createAnswer', provisional);
  561. var self = this;
  562. this.peerconnection.createAnswer(
  563. function (sdp) {
  564. self.createdAnswer(sdp, provisional);
  565. },
  566. function (e) {
  567. console.error('createAnswer failed', e);
  568. },
  569. this.media_constraints
  570. );
  571. };
  572. JingleSession.prototype.createdAnswer = function (sdp, provisional) {
  573. //console.log('createAnswer callback');
  574. var self = this;
  575. this.localSDP = new SDP(sdp.sdp);
  576. //this.localSDP.mangle();
  577. this.usepranswer = provisional === true;
  578. if (this.usetrickle) {
  579. if (this.usepranswer) {
  580. sdp.type = 'pranswer';
  581. for (var i = 0; i < this.localSDP.media.length; i++) {
  582. this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n');
  583. }
  584. this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join('');
  585. }
  586. }
  587. var self = this;
  588. var sendJingle = function (ssrcs) {
  589. var accept = $iq({to: self.peerjid,
  590. type: 'set'})
  591. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  592. action: 'session-accept',
  593. initiator: self.initiator,
  594. responder: self.responder,
  595. sid: self.sid });
  596. var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
  597. var publicLocalSDP = new SDP(publicLocalDesc.sdp);
  598. publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
  599. self.connection.sendIQ(accept,
  600. function () {
  601. var ack = {};
  602. ack.source = 'answer';
  603. $(document).trigger('ack.jingle', [self.sid, ack]);
  604. },
  605. function (stanza) {
  606. var error = ($(stanza).find('error').length) ? {
  607. code: $(stanza).find('error').attr('code'),
  608. reason: $(stanza).find('error :first')[0].tagName,
  609. }:{};
  610. error.source = 'answer';
  611. JingleSession.onJingleError(self.sid, error);
  612. },
  613. 10000);
  614. }
  615. sdp.sdp = this.localSDP.raw;
  616. this.peerconnection.setLocalDescription(sdp,
  617. function () {
  618. //console.log('setLocalDescription success');
  619. if (self.usetrickle && !self.usepranswer) {
  620. sendJingle();
  621. }
  622. self.setLocalDescription();
  623. },
  624. function (e) {
  625. console.error('setLocalDescription failed', e);
  626. }
  627. );
  628. var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
  629. for (var j = 0; j < cands.length; j++) {
  630. var cand = SDPUtil.parse_icecandidate(cands[j]);
  631. if (cand.type == 'srflx') {
  632. this.hadstuncandidate = true;
  633. } else if (cand.type == 'relay') {
  634. this.hadturncandidate = true;
  635. }
  636. }
  637. };
  638. JingleSession.prototype.sendTerminate = function (reason, text) {
  639. var self = this,
  640. term = $iq({to: this.peerjid,
  641. type: 'set'})
  642. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  643. action: 'session-terminate',
  644. initiator: this.initiator,
  645. sid: this.sid})
  646. .c('reason')
  647. .c(reason || 'success');
  648. if (text) {
  649. term.up().c('text').t(text);
  650. }
  651. this.connection.sendIQ(term,
  652. function () {
  653. self.peerconnection.close();
  654. self.peerconnection = null;
  655. self.terminate();
  656. var ack = {};
  657. ack.source = 'terminate';
  658. $(document).trigger('ack.jingle', [self.sid, ack]);
  659. },
  660. function (stanza) {
  661. var error = ($(stanza).find('error').length) ? {
  662. code: $(stanza).find('error').attr('code'),
  663. reason: $(stanza).find('error :first')[0].tagName,
  664. }:{};
  665. $(document).trigger('ack.jingle', [self.sid, error]);
  666. },
  667. 10000);
  668. if (this.statsinterval !== null) {
  669. window.clearInterval(this.statsinterval);
  670. this.statsinterval = null;
  671. }
  672. };
  673. JingleSession.prototype.addSource = function (elem, fromJid) {
  674. var self = this;
  675. // FIXME: dirty waiting
  676. if (!this.peerconnection.localDescription)
  677. {
  678. console.warn("addSource - localDescription not ready yet")
  679. setTimeout(function()
  680. {
  681. self.addSource(elem, fromJid);
  682. },
  683. 200
  684. );
  685. return;
  686. }
  687. console.log('addssrc', new Date().getTime());
  688. console.log('ice', this.peerconnection.iceConnectionState);
  689. var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
  690. var mySdp = new SDP(this.peerconnection.localDescription.sdp);
  691. $(elem).each(function (idx, content) {
  692. var name = $(content).attr('name');
  693. var lines = '';
  694. tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
  695. var semantics = this.getAttribute('semantics');
  696. var ssrcs = $(this).find('>source').map(function () {
  697. return this.getAttribute('ssrc');
  698. }).get();
  699. if (ssrcs.length != 0) {
  700. lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
  701. }
  702. });
  703. tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
  704. tmp.each(function () {
  705. var ssrc = $(this).attr('ssrc');
  706. if(mySdp.containsSSRC(ssrc)){
  707. /**
  708. * This happens when multiple participants change their streams at the same time and
  709. * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
  710. * addssrc are scheduled for update IQ. See
  711. */
  712. console.warn("Got add stream request for my own ssrc: "+ssrc);
  713. return;
  714. }
  715. $(this).find('>parameter').each(function () {
  716. lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
  717. if ($(this).attr('value') && $(this).attr('value').length)
  718. lines += ':' + $(this).attr('value');
  719. lines += '\r\n';
  720. });
  721. });
  722. sdp.media.forEach(function(media, idx) {
  723. if (!SDPUtil.find_line(media, 'a=mid:' + name))
  724. return;
  725. sdp.media[idx] += lines;
  726. if (!self.addssrc[idx]) self.addssrc[idx] = '';
  727. self.addssrc[idx] += lines;
  728. });
  729. sdp.raw = sdp.session + sdp.media.join('');
  730. });
  731. this.modifySources();
  732. };
  733. JingleSession.prototype.removeSource = function (elem, fromJid) {
  734. var self = this;
  735. // FIXME: dirty waiting
  736. if (!this.peerconnection.localDescription)
  737. {
  738. console.warn("removeSource - localDescription not ready yet")
  739. setTimeout(function()
  740. {
  741. self.removeSource(elem, fromJid);
  742. },
  743. 200
  744. );
  745. return;
  746. }
  747. console.log('removessrc', new Date().getTime());
  748. console.log('ice', this.peerconnection.iceConnectionState);
  749. var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
  750. var mySdp = new SDP(this.peerconnection.localDescription.sdp);
  751. $(elem).each(function (idx, content) {
  752. var name = $(content).attr('name');
  753. var lines = '';
  754. tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
  755. var semantics = this.getAttribute('semantics');
  756. var ssrcs = $(this).find('>source').map(function () {
  757. return this.getAttribute('ssrc');
  758. }).get();
  759. if (ssrcs.length != 0) {
  760. lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
  761. }
  762. });
  763. tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
  764. tmp.each(function () {
  765. var ssrc = $(this).attr('ssrc');
  766. // This should never happen, but can be useful for bug detection
  767. if(mySdp.containsSSRC(ssrc)){
  768. console.error("Got remove stream request for my own ssrc: "+ssrc);
  769. return;
  770. }
  771. $(this).find('>parameter').each(function () {
  772. lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
  773. if ($(this).attr('value') && $(this).attr('value').length)
  774. lines += ':' + $(this).attr('value');
  775. lines += '\r\n';
  776. });
  777. });
  778. sdp.media.forEach(function(media, idx) {
  779. if (!SDPUtil.find_line(media, 'a=mid:' + name))
  780. return;
  781. sdp.media[idx] += lines;
  782. if (!self.removessrc[idx]) self.removessrc[idx] = '';
  783. self.removessrc[idx] += lines;
  784. });
  785. sdp.raw = sdp.session + sdp.media.join('');
  786. });
  787. this.modifySources();
  788. };
  789. JingleSession.prototype.modifySources = function (successCallback) {
  790. var self = this;
  791. if (this.peerconnection.signalingState == 'closed') return;
  792. if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
  793. // There is nothing to do since scheduled job might have been executed by another succeeding call
  794. this.setLocalDescription();
  795. if(successCallback){
  796. successCallback();
  797. }
  798. return;
  799. }
  800. // FIXME: this is a big hack
  801. // https://code.google.com/p/webrtc/issues/detail?id=2688
  802. // ^ has been fixed.
  803. if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')) {
  804. console.warn('modifySources not yet', this.peerconnection.signalingState, this.peerconnection.iceConnectionState);
  805. this.wait = true;
  806. window.setTimeout(function() { self.modifySources(successCallback); }, 250);
  807. return;
  808. }
  809. if (this.wait) {
  810. window.setTimeout(function() { self.modifySources(successCallback); }, 2500);
  811. this.wait = false;
  812. return;
  813. }
  814. // Reset switch streams flag
  815. this.switchstreams = false;
  816. var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
  817. // add sources
  818. this.addssrc.forEach(function(lines, idx) {
  819. sdp.media[idx] += lines;
  820. });
  821. this.addssrc = [];
  822. // remove sources
  823. this.removessrc.forEach(function(lines, idx) {
  824. lines = lines.split('\r\n');
  825. lines.pop(); // remove empty last element;
  826. lines.forEach(function(line) {
  827. sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
  828. });
  829. });
  830. this.removessrc = [];
  831. // FIXME:
  832. // this was a hack for the situation when only one peer exists
  833. // in the conference.
  834. // check if still required and remove
  835. if (sdp.media[0])
  836. sdp.media[0] = sdp.media[0].replace('a=recvonly', 'a=sendrecv');
  837. if (sdp.media[1])
  838. sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
  839. sdp.raw = sdp.session + sdp.media.join('');
  840. this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
  841. function() {
  842. if(self.signalingState == 'closed') {
  843. console.error("createAnswer attempt on closed state");
  844. return;
  845. }
  846. self.peerconnection.createAnswer(
  847. function(modifiedAnswer) {
  848. // change video direction, see https://github.com/jitsi/jitmeet/issues/41
  849. if (self.pendingop !== null) {
  850. var sdp = new SDP(modifiedAnswer.sdp);
  851. if (sdp.media.length > 1) {
  852. switch(self.pendingop) {
  853. case 'mute':
  854. sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
  855. break;
  856. case 'unmute':
  857. sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
  858. break;
  859. }
  860. sdp.raw = sdp.session + sdp.media.join('');
  861. modifiedAnswer.sdp = sdp.raw;
  862. }
  863. self.pendingop = null;
  864. }
  865. // FIXME: pushing down an answer while ice connection state
  866. // is still checking is bad...
  867. //console.log(self.peerconnection.iceConnectionState);
  868. // trying to work around another chrome bug
  869. //modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
  870. self.peerconnection.setLocalDescription(modifiedAnswer,
  871. function() {
  872. //console.log('modified setLocalDescription ok');
  873. self.setLocalDescription();
  874. if(successCallback){
  875. successCallback();
  876. }
  877. },
  878. function(error) {
  879. console.error('modified setLocalDescription failed', error);
  880. }
  881. );
  882. },
  883. function(error) {
  884. console.error('modified answer failed', error);
  885. }
  886. );
  887. },
  888. function(error) {
  889. console.error('modify failed', error);
  890. }
  891. );
  892. };
  893. /**
  894. * Switches video streams.
  895. * @param new_stream new stream that will be used as video of this session.
  896. * @param oldStream old video stream of this session.
  897. * @param success_callback callback executed after successful stream switch.
  898. */
  899. JingleSession.prototype.switchStreams = function (new_stream, oldStream, success_callback) {
  900. var self = this;
  901. // Remember SDP to figure out added/removed SSRCs
  902. var oldSdp = null;
  903. if(self.peerconnection) {
  904. if(self.peerconnection.localDescription) {
  905. oldSdp = new SDP(self.peerconnection.localDescription.sdp);
  906. }
  907. self.peerconnection.removeStream(oldStream, true);
  908. self.peerconnection.addStream(new_stream);
  909. }
  910. RTC.switchVideoStreams(new_stream, oldStream);
  911. // Conference is not active
  912. if(!oldSdp || !self.peerconnection) {
  913. success_callback();
  914. return;
  915. }
  916. self.switchstreams = true;
  917. self.modifySources(function() {
  918. console.log('modify sources done');
  919. success_callback();
  920. var newSdp = new SDP(self.peerconnection.localDescription.sdp);
  921. console.log("SDPs", oldSdp, newSdp);
  922. self.notifyMySSRCUpdate(oldSdp, newSdp);
  923. });
  924. };
  925. /**
  926. * Figures out added/removed ssrcs and send update IQs.
  927. * @param old_sdp SDP object for old description.
  928. * @param new_sdp SDP object for new description.
  929. */
  930. JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
  931. if (!(this.peerconnection.signalingState == 'stable' &&
  932. this.peerconnection.iceConnectionState == 'connected')){
  933. console.log("Too early to send updates");
  934. return;
  935. }
  936. // send source-remove IQ.
  937. sdpDiffer = new SDPDiffer(new_sdp, old_sdp);
  938. var remove = $iq({to: this.peerjid, type: 'set'})
  939. .c('jingle', {
  940. xmlns: 'urn:xmpp:jingle:1',
  941. action: 'source-remove',
  942. initiator: this.initiator,
  943. sid: this.sid
  944. }
  945. );
  946. var removed = sdpDiffer.toJingle(remove);
  947. if (removed) {
  948. this.connection.sendIQ(remove,
  949. function (res) {
  950. console.info('got remove result', res);
  951. },
  952. function (err) {
  953. console.error('got remove error', err);
  954. }
  955. );
  956. } else {
  957. console.log('removal not necessary');
  958. }
  959. // send source-add IQ.
  960. var sdpDiffer = new SDPDiffer(old_sdp, new_sdp);
  961. var add = $iq({to: this.peerjid, type: 'set'})
  962. .c('jingle', {
  963. xmlns: 'urn:xmpp:jingle:1',
  964. action: 'source-add',
  965. initiator: this.initiator,
  966. sid: this.sid
  967. }
  968. );
  969. var added = sdpDiffer.toJingle(add);
  970. if (added) {
  971. this.connection.sendIQ(add,
  972. function (res) {
  973. console.info('got add result', res);
  974. },
  975. function (err) {
  976. console.error('got add error', err);
  977. }
  978. );
  979. } else {
  980. console.log('addition not necessary');
  981. }
  982. };
  983. /**
  984. * Determines whether the (local) video is mute i.e. all video tracks are
  985. * disabled.
  986. *
  987. * @return <tt>true</tt> if the (local) video is mute i.e. all video tracks are
  988. * disabled; otherwise, <tt>false</tt>
  989. */
  990. JingleSession.prototype.isVideoMute = function () {
  991. var tracks = RTC.localVideo.getVideoTracks();
  992. var mute = true;
  993. for (var i = 0; i < tracks.length; ++i) {
  994. if (tracks[i].enabled) {
  995. mute = false;
  996. break;
  997. }
  998. }
  999. return mute;
  1000. };
  1001. /**
  1002. * Mutes/unmutes the (local) video i.e. enables/disables all video tracks.
  1003. *
  1004. * @param mute <tt>true</tt> to mute the (local) video i.e. to disable all video
  1005. * tracks; otherwise, <tt>false</tt>
  1006. * @param callback a function to be invoked with <tt>mute</tt> after all video
  1007. * tracks have been enabled/disabled. The function may, optionally, return
  1008. * another function which is to be invoked after the whole mute/unmute operation
  1009. * has completed successfully.
  1010. * @param options an object which specifies optional arguments such as the
  1011. * <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
  1012. * specifies whether the method was initiated in response to a user command (in
  1013. * contrast to an automatic decision made by the application logic)
  1014. */
  1015. JingleSession.prototype.setVideoMute = function (mute, callback, options) {
  1016. var byUser;
  1017. if (options) {
  1018. byUser = options.byUser;
  1019. if (typeof byUser === 'undefined') {
  1020. byUser = true;
  1021. }
  1022. } else {
  1023. byUser = true;
  1024. }
  1025. // The user's command to mute the (local) video takes precedence over any
  1026. // automatic decision made by the application logic.
  1027. if (byUser) {
  1028. this.videoMuteByUser = mute;
  1029. } else if (this.videoMuteByUser) {
  1030. return;
  1031. }
  1032. var self = this;
  1033. var localCallback = function (mute) {
  1034. self.connection.emuc.addVideoInfoToPresence(mute);
  1035. self.connection.emuc.sendPresence();
  1036. return callback(mute)
  1037. };
  1038. if (mute == RTC.localVideo.isMuted())
  1039. {
  1040. // Even if no change occurs, the specified callback is to be executed.
  1041. // The specified callback may, optionally, return a successCallback
  1042. // which is to be executed as well.
  1043. var successCallback = localCallback(mute);
  1044. if (successCallback) {
  1045. successCallback();
  1046. }
  1047. } else {
  1048. RTC.localVideo.setMute(!mute);
  1049. this.hardMuteVideo(mute);
  1050. this.modifySources(localCallback(mute));
  1051. }
  1052. };
  1053. // SDP-based mute by going recvonly/sendrecv
  1054. // FIXME: should probably black out the screen as well
  1055. JingleSession.prototype.toggleVideoMute = function (callback) {
  1056. this.service.setVideoMute(RTC.localVideo.isMuted(), callback);
  1057. };
  1058. JingleSession.prototype.hardMuteVideo = function (muted) {
  1059. this.pendingop = muted ? 'mute' : 'unmute';
  1060. };
  1061. JingleSession.prototype.sendMute = function (muted, content) {
  1062. var info = $iq({to: this.peerjid,
  1063. type: 'set'})
  1064. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  1065. action: 'session-info',
  1066. initiator: this.initiator,
  1067. sid: this.sid });
  1068. info.c(muted ? 'mute' : 'unmute', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
  1069. info.attrs({'creator': this.me == this.initiator ? 'creator' : 'responder'});
  1070. if (content) {
  1071. info.attrs({'name': content});
  1072. }
  1073. this.connection.send(info);
  1074. };
  1075. JingleSession.prototype.sendRinging = function () {
  1076. var info = $iq({to: this.peerjid,
  1077. type: 'set'})
  1078. .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
  1079. action: 'session-info',
  1080. initiator: this.initiator,
  1081. sid: this.sid });
  1082. info.c('ringing', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
  1083. this.connection.send(info);
  1084. };
  1085. JingleSession.prototype.getStats = function (interval) {
  1086. var self = this;
  1087. var recv = {audio: 0, video: 0};
  1088. var lost = {audio: 0, video: 0};
  1089. var lastrecv = {audio: 0, video: 0};
  1090. var lastlost = {audio: 0, video: 0};
  1091. var loss = {audio: 0, video: 0};
  1092. var delta = {audio: 0, video: 0};
  1093. this.statsinterval = window.setInterval(function () {
  1094. if (self && self.peerconnection && self.peerconnection.getStats) {
  1095. self.peerconnection.getStats(function (stats) {
  1096. var results = stats.result();
  1097. // TODO: there are so much statistics you can get from this..
  1098. for (var i = 0; i < results.length; ++i) {
  1099. if (results[i].type == 'ssrc') {
  1100. var packetsrecv = results[i].stat('packetsReceived');
  1101. var packetslost = results[i].stat('packetsLost');
  1102. if (packetsrecv && packetslost) {
  1103. packetsrecv = parseInt(packetsrecv, 10);
  1104. packetslost = parseInt(packetslost, 10);
  1105. if (results[i].stat('googFrameRateReceived')) {
  1106. lastlost.video = lost.video;
  1107. lastrecv.video = recv.video;
  1108. recv.video = packetsrecv;
  1109. lost.video = packetslost;
  1110. } else {
  1111. lastlost.audio = lost.audio;
  1112. lastrecv.audio = recv.audio;
  1113. recv.audio = packetsrecv;
  1114. lost.audio = packetslost;
  1115. }
  1116. }
  1117. }
  1118. }
  1119. delta.audio = recv.audio - lastrecv.audio;
  1120. delta.video = recv.video - lastrecv.video;
  1121. loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0;
  1122. loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0;
  1123. $(document).trigger('packetloss.jingle', [self.sid, loss]);
  1124. });
  1125. }
  1126. }, interval || 3000);
  1127. return this.statsinterval;
  1128. };
  1129. JingleSession.onJingleError = function (session, error)
  1130. {
  1131. console.error("Jingle error", error);
  1132. }
  1133. JingleSession.onJingleFatalError = function (session, error)
  1134. {
  1135. this.service.sessionTerminated = true;
  1136. connection.emuc.doLeave();
  1137. UI.messageHandler.showError( "Sorry",
  1138. "Internal application error[setRemoteDescription]");
  1139. }
  1140. JingleSession.prototype.setLocalDescription = function () {
  1141. // put our ssrcs into presence so other clients can identify our stream
  1142. var newssrcs = [];
  1143. var media = simulcast.parseMedia(this.peerconnection.localDescription);
  1144. media.forEach(function (media) {
  1145. if(Object.keys(media.sources).length > 0) {
  1146. // TODO(gp) maybe exclude FID streams?
  1147. Object.keys(media.sources).forEach(function (ssrc) {
  1148. newssrcs.push({
  1149. 'ssrc': ssrc,
  1150. 'type': media.type,
  1151. 'direction': media.direction
  1152. });
  1153. });
  1154. }
  1155. else if(this.localStreamsSSRC && this.localStreamsSSRC[media.type])
  1156. {
  1157. newssrcs.push({
  1158. 'ssrc': this.localStreamsSSRC[media.type],
  1159. 'type': media.type,
  1160. 'direction': media.direction
  1161. });
  1162. }
  1163. });
  1164. console.log('new ssrcs', newssrcs);
  1165. // Have to clear presence map to get rid of removed streams
  1166. this.connection.emuc.clearPresenceMedia();
  1167. if (newssrcs.length > 0) {
  1168. for (var i = 1; i <= newssrcs.length; i ++) {
  1169. // Change video type to screen
  1170. if (newssrcs[i-1].type === 'video' && desktopsharing.isUsingScreenStream()) {
  1171. newssrcs[i-1].type = 'screen';
  1172. }
  1173. this.connection.emuc.addMediaToPresence(i,
  1174. newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
  1175. }
  1176. this.connection.emuc.sendPresence();
  1177. }
  1178. }
  1179. // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
  1180. function sendKeyframe(pc) {
  1181. console.log('sendkeyframe', pc.iceConnectionState);
  1182. if (pc.iceConnectionState !== 'connected') return; // safe...
  1183. pc.setRemoteDescription(
  1184. pc.remoteDescription,
  1185. function () {
  1186. pc.createAnswer(
  1187. function (modifiedAnswer) {
  1188. pc.setLocalDescription(
  1189. modifiedAnswer,
  1190. function () {
  1191. // noop
  1192. },
  1193. function (error) {
  1194. console.log('triggerKeyframe setLocalDescription failed', error);
  1195. UI.messageHandler.showError();
  1196. }
  1197. );
  1198. },
  1199. function (error) {
  1200. console.log('triggerKeyframe createAnswer failed', error);
  1201. UI.messageHandler.showError();
  1202. }
  1203. );
  1204. },
  1205. function (error) {
  1206. console.log('triggerKeyframe setRemoteDescription failed', error);
  1207. UI.messageHandler.showError();
  1208. }
  1209. );
  1210. }
  1211. JingleSession.prototype.remoteStreamAdded = function (data) {
  1212. var self = this;
  1213. var thessrc;
  1214. // look up an associated JID for a stream id
  1215. if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
  1216. // look only at a=ssrc: and _not_ at a=ssrc-group: lines
  1217. var ssrclines
  1218. = SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:');
  1219. ssrclines = ssrclines.filter(function (line) {
  1220. // NOTE(gp) previously we filtered on the mslabel, but that property
  1221. // is not always present.
  1222. // return line.indexOf('mslabel:' + data.stream.label) !== -1;
  1223. return ((line.indexOf('msid:' + data.stream.id) !== -1));
  1224. });
  1225. if (ssrclines.length) {
  1226. thessrc = ssrclines[0].substring(7).split(' ')[0];
  1227. // We signal our streams (through Jingle to the focus) before we set
  1228. // our presence (through which peers associate remote streams to
  1229. // jids). So, it might arrive that a remote stream is added but
  1230. // ssrc2jid is not yet updated and thus data.peerjid cannot be
  1231. // successfully set. Here we wait for up to a second for the
  1232. // presence to arrive.
  1233. if (!ssrc2jid[thessrc]) {
  1234. // TODO(gp) limit wait duration to 1 sec.
  1235. setTimeout(function(d) {
  1236. return function() {
  1237. self.remoteStreamAdded(d);
  1238. }
  1239. }(data), 250);
  1240. return;
  1241. }
  1242. // ok to overwrite the one from focus? might save work in colibri.js
  1243. console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
  1244. if (ssrc2jid[thessrc]) {
  1245. data.peerjid = ssrc2jid[thessrc];
  1246. }
  1247. }
  1248. }
  1249. //TODO: this code should be removed when firefox implement multistream support
  1250. if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX)
  1251. {
  1252. if((JingleSession.notReceivedSSRCs.length == 0) ||
  1253. !ssrc2jid[JingleSession.notReceivedSSRCs[JingleSession.notReceivedSSRCs.length - 1]])
  1254. {
  1255. // TODO(gp) limit wait duration to 1 sec.
  1256. setTimeout(function(d) {
  1257. return function() {
  1258. self.remoteStreamAdded(d);
  1259. }
  1260. }(data), 250);
  1261. return;
  1262. }
  1263. thessrc = JingleSession.notReceivedSSRCs.pop();
  1264. if (ssrc2jid[thessrc]) {
  1265. data.peerjid = ssrc2jid[thessrc];
  1266. }
  1267. }
  1268. RTC.createRemoteStream(data, this.sid, thessrc);
  1269. var isVideo = data.stream.getVideoTracks().length > 0;
  1270. // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
  1271. if (isVideo &&
  1272. data.peerjid && this.peerjid === data.peerjid &&
  1273. data.stream.getVideoTracks().length === 0 &&
  1274. RTC.localVideo.getTracks().length > 0) {
  1275. window.setTimeout(function () {
  1276. sendKeyframe(self.peerconnection);
  1277. }, 3000);
  1278. }
  1279. }
  1280. module.exports = JingleSession;
  1281. },{"./SDP":2,"./SDPDiffer":3,"./SDPUtil":4,"./TraceablePeerConnection":5}],2:[function(require,module,exports){
  1282. /* jshint -W117 */
  1283. var SDPUtil = require("./SDPUtil");
  1284. // SDP STUFF
  1285. function SDP(sdp) {
  1286. this.media = sdp.split('\r\nm=');
  1287. for (var i = 1; i < this.media.length; i++) {
  1288. this.media[i] = 'm=' + this.media[i];
  1289. if (i != this.media.length - 1) {
  1290. this.media[i] += '\r\n';
  1291. }
  1292. }
  1293. this.session = this.media.shift() + '\r\n';
  1294. this.raw = this.session + this.media.join('');
  1295. }
  1296. /**
  1297. * Returns map of MediaChannel mapped per channel idx.
  1298. */
  1299. SDP.prototype.getMediaSsrcMap = function() {
  1300. var self = this;
  1301. var media_ssrcs = {};
  1302. var tmp;
  1303. for (var mediaindex = 0; mediaindex < self.media.length; mediaindex++) {
  1304. tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc:');
  1305. var mid = SDPUtil.parse_mid(SDPUtil.find_line(self.media[mediaindex], 'a=mid:'));
  1306. var media = {
  1307. mediaindex: mediaindex,
  1308. mid: mid,
  1309. ssrcs: {},
  1310. ssrcGroups: []
  1311. };
  1312. media_ssrcs[mediaindex] = media;
  1313. tmp.forEach(function (line) {
  1314. var linessrc = line.substring(7).split(' ')[0];
  1315. // allocate new ChannelSsrc
  1316. if(!media.ssrcs[linessrc]) {
  1317. media.ssrcs[linessrc] = {
  1318. ssrc: linessrc,
  1319. lines: []
  1320. };
  1321. }
  1322. media.ssrcs[linessrc].lines.push(line);
  1323. });
  1324. tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:');
  1325. tmp.forEach(function(line){
  1326. var semantics = line.substr(0, idx).substr(13);
  1327. var ssrcs = line.substr(14 + semantics.length).split(' ');
  1328. if (ssrcs.length != 0) {
  1329. media.ssrcGroups.push({
  1330. semantics: semantics,
  1331. ssrcs: ssrcs
  1332. });
  1333. }
  1334. });
  1335. }
  1336. return media_ssrcs;
  1337. };
  1338. /**
  1339. * Returns <tt>true</tt> if this SDP contains given SSRC.
  1340. * @param ssrc the ssrc to check.
  1341. * @returns {boolean} <tt>true</tt> if this SDP contains given SSRC.
  1342. */
  1343. SDP.prototype.containsSSRC = function(ssrc) {
  1344. var medias = this.getMediaSsrcMap();
  1345. var contains = false;
  1346. Object.keys(medias).forEach(function(mediaindex){
  1347. var media = medias[mediaindex];
  1348. //console.log("Check", channel, ssrc);
  1349. if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){
  1350. contains = true;
  1351. }
  1352. });
  1353. return contains;
  1354. };
  1355. // remove iSAC and CN from SDP
  1356. SDP.prototype.mangle = function () {
  1357. var i, j, mline, lines, rtpmap, newdesc;
  1358. for (i = 0; i < this.media.length; i++) {
  1359. lines = this.media[i].split('\r\n');
  1360. lines.pop(); // remove empty last element
  1361. mline = SDPUtil.parse_mline(lines.shift());
  1362. if (mline.media != 'audio')
  1363. continue;
  1364. newdesc = '';
  1365. mline.fmt.length = 0;
  1366. for (j = 0; j < lines.length; j++) {
  1367. if (lines[j].substr(0, 9) == 'a=rtpmap:') {
  1368. rtpmap = SDPUtil.parse_rtpmap(lines[j]);
  1369. if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
  1370. continue;
  1371. mline.fmt.push(rtpmap.id);
  1372. newdesc += lines[j] + '\r\n';
  1373. } else {
  1374. newdesc += lines[j] + '\r\n';
  1375. }
  1376. }
  1377. this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
  1378. this.media[i] += newdesc;
  1379. }
  1380. this.raw = this.session + this.media.join('');
  1381. };
  1382. // remove lines matching prefix from session section
  1383. SDP.prototype.removeSessionLines = function(prefix) {
  1384. var self = this;
  1385. var lines = SDPUtil.find_lines(this.session, prefix);
  1386. lines.forEach(function(line) {
  1387. self.session = self.session.replace(line + '\r\n', '');
  1388. });
  1389. this.raw = this.session + this.media.join('');
  1390. return lines;
  1391. }
  1392. // remove lines matching prefix from a media section specified by mediaindex
  1393. // TODO: non-numeric mediaindex could match mid
  1394. SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
  1395. var self = this;
  1396. var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
  1397. lines.forEach(function(line) {
  1398. self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', '');
  1399. });
  1400. this.raw = this.session + this.media.join('');
  1401. return lines;
  1402. }
  1403. // add content's to a jingle element
  1404. SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
  1405. // console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]);
  1406. var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
  1407. var self = this;
  1408. // new bundle plan
  1409. if (SDPUtil.find_line(this.session, 'a=group:')) {
  1410. lines = SDPUtil.find_lines(this.session, 'a=group:');
  1411. for (i = 0; i < lines.length; i++) {
  1412. tmp = lines[i].split(' ');
  1413. var semantics = tmp.shift().substr(8);
  1414. elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics});
  1415. for (j = 0; j < tmp.length; j++) {
  1416. elem.c('content', {name: tmp[j]}).up();
  1417. }
  1418. elem.up();
  1419. }
  1420. }
  1421. for (i = 0; i < this.media.length; i++) {
  1422. mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
  1423. if (!(mline.media === 'audio' ||
  1424. mline.media === 'video' ||
  1425. mline.media === 'application'))
  1426. {
  1427. continue;
  1428. }
  1429. if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
  1430. ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
  1431. } else {
  1432. if(ssrcs && ssrcs[mline.media])
  1433. {
  1434. ssrc = ssrcs[mline.media];
  1435. }
  1436. else
  1437. ssrc = false;
  1438. }
  1439. elem.c('content', {creator: thecreator, name: mline.media});
  1440. if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
  1441. // prefer identifier from a=mid if present
  1442. var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
  1443. elem.attrs({ name: mid });
  1444. }
  1445. if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length)
  1446. {
  1447. elem.c('description',
  1448. {xmlns: 'urn:xmpp:jingle:apps:rtp:1',
  1449. media: mline.media });
  1450. if (ssrc) {
  1451. elem.attrs({ssrc: ssrc});
  1452. }
  1453. for (j = 0; j < mline.fmt.length; j++) {
  1454. rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
  1455. elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
  1456. // put any 'a=fmtp:' + mline.fmt[j] lines into <param name=foo value=bar/>
  1457. if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
  1458. tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
  1459. for (k = 0; k < tmp.length; k++) {
  1460. elem.c('parameter', tmp[k]).up();
  1461. }
  1462. }
  1463. this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb
  1464. elem.up();
  1465. }
  1466. if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
  1467. elem.c('encryption', {required: 1});
  1468. var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
  1469. crypto.forEach(function(line) {
  1470. elem.c('crypto', SDPUtil.parse_crypto(line)).up();
  1471. });
  1472. elem.up(); // end of encryption
  1473. }
  1474. if (ssrc) {
  1475. // new style mapping
  1476. elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
  1477. // FIXME: group by ssrc and support multiple different ssrcs
  1478. var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
  1479. if(ssrclines.length > 0) {
  1480. ssrclines.forEach(function (line) {
  1481. idx = line.indexOf(' ');
  1482. var linessrc = line.substr(0, idx).substr(7);
  1483. if (linessrc != ssrc) {
  1484. elem.up();
  1485. ssrc = linessrc;
  1486. elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
  1487. }
  1488. var kv = line.substr(idx + 1);
  1489. elem.c('parameter');
  1490. if (kv.indexOf(':') == -1) {
  1491. elem.attrs({ name: kv });
  1492. } else {
  1493. elem.attrs({ name: kv.split(':', 2)[0] });
  1494. elem.attrs({ value: kv.split(':', 2)[1] });
  1495. }
  1496. elem.up();
  1497. });
  1498. elem.up();
  1499. }
  1500. else
  1501. {
  1502. elem.up();
  1503. elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
  1504. elem.c('parameter');
  1505. elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)});
  1506. elem.up();
  1507. var msid = null;
  1508. if(mline.media == "audio")
  1509. {
  1510. msid = RTC.localAudio.getId();
  1511. }
  1512. else
  1513. {
  1514. msid = RTC.localVideo.getId();
  1515. }
  1516. if(msid != null)
  1517. {
  1518. msid = msid.replace(/[\{,\}]/g,"");
  1519. elem.c('parameter');
  1520. elem.attrs({name: "msid", value:msid});
  1521. elem.up();
  1522. elem.c('parameter');
  1523. elem.attrs({name: "mslabel", value:msid});
  1524. elem.up();
  1525. elem.c('parameter');
  1526. elem.attrs({name: "label", value:msid});
  1527. elem.up();
  1528. elem.up();
  1529. }
  1530. }
  1531. // XEP-0339 handle ssrc-group attributes
  1532. var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:');
  1533. ssrc_group_lines.forEach(function(line) {
  1534. idx = line.indexOf(' ');
  1535. var semantics = line.substr(0, idx).substr(13);
  1536. var ssrcs = line.substr(14 + semantics.length).split(' ');
  1537. if (ssrcs.length != 0) {
  1538. elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
  1539. ssrcs.forEach(function(ssrc) {
  1540. elem.c('source', { ssrc: ssrc })
  1541. .up();
  1542. });
  1543. elem.up();
  1544. }
  1545. });
  1546. }
  1547. if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
  1548. elem.c('rtcp-mux').up();
  1549. }
  1550. // XEP-0293 -- map a=rtcp-fb:*
  1551. this.RtcpFbToJingle(i, elem, '*');
  1552. // XEP-0294
  1553. if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
  1554. lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
  1555. for (j = 0; j < lines.length; j++) {
  1556. tmp = SDPUtil.parse_extmap(lines[j]);
  1557. elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
  1558. uri: tmp.uri,
  1559. id: tmp.value });
  1560. if (tmp.hasOwnProperty('direction')) {
  1561. switch (tmp.direction) {
  1562. case 'sendonly':
  1563. elem.attrs({senders: 'responder'});
  1564. break;
  1565. case 'recvonly':
  1566. elem.attrs({senders: 'initiator'});
  1567. break;
  1568. case 'sendrecv':
  1569. elem.attrs({senders: 'both'});
  1570. break;
  1571. case 'inactive':
  1572. elem.attrs({senders: 'none'});
  1573. break;
  1574. }
  1575. }
  1576. // TODO: handle params
  1577. elem.up();
  1578. }
  1579. }
  1580. elem.up(); // end of description
  1581. }
  1582. // map ice-ufrag/pwd, dtls fingerprint, candidates
  1583. this.TransportToJingle(i, elem);
  1584. if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
  1585. elem.attrs({senders: 'both'});
  1586. } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
  1587. elem.attrs({senders: 'initiator'});
  1588. } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
  1589. elem.attrs({senders: 'responder'});
  1590. } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
  1591. elem.attrs({senders: 'none'});
  1592. }
  1593. if (mline.port == '0') {
  1594. // estos hack to reject an m-line
  1595. elem.attrs({senders: 'rejected'});
  1596. }
  1597. elem.up(); // end of content
  1598. }
  1599. elem.up();
  1600. return elem;
  1601. };
  1602. SDP.prototype.TransportToJingle = function (mediaindex, elem) {
  1603. var i = mediaindex;
  1604. var tmp;
  1605. var self = this;
  1606. elem.c('transport');
  1607. // XEP-0343 DTLS/SCTP
  1608. if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length)
  1609. {
  1610. var sctpmap = SDPUtil.find_line(
  1611. this.media[i], 'a=sctpmap:', self.session);
  1612. if (sctpmap)
  1613. {
  1614. var sctpAttrs = SDPUtil.parse_sctpmap(sctpmap);
  1615. elem.c('sctpmap',
  1616. {
  1617. xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',
  1618. number: sctpAttrs[0], /* SCTP port */
  1619. protocol: sctpAttrs[1], /* protocol */
  1620. });
  1621. // Optional stream count attribute
  1622. if (sctpAttrs.length > 2)
  1623. elem.attrs({ streams: sctpAttrs[2]});
  1624. elem.up();
  1625. }
  1626. }
  1627. // XEP-0320
  1628. var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
  1629. fingerprints.forEach(function(line) {
  1630. tmp = SDPUtil.parse_fingerprint(line);
  1631. tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0';
  1632. elem.c('fingerprint').t(tmp.fingerprint);
  1633. delete tmp.fingerprint;
  1634. line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session);
  1635. if (line) {
  1636. tmp.setup = line.substr(8);
  1637. }
  1638. elem.attrs(tmp);
  1639. elem.up(); // end of fingerprint
  1640. });
  1641. tmp = SDPUtil.iceparams(this.media[mediaindex], this.session);
  1642. if (tmp) {
  1643. tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
  1644. elem.attrs(tmp);
  1645. // XEP-0176
  1646. if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines
  1647. var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session);
  1648. lines.forEach(function (line) {
  1649. elem.c('candidate', SDPUtil.candidateToJingle(line)).up();
  1650. });
  1651. }
  1652. }
  1653. elem.up(); // end of transport
  1654. }
  1655. SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293
  1656. var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype);
  1657. lines.forEach(function (line) {
  1658. var tmp = SDPUtil.parse_rtcpfb(line);
  1659. if (tmp.type == 'trr-int') {
  1660. elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
  1661. elem.up();
  1662. } else {
  1663. elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
  1664. if (tmp.params.length > 0) {
  1665. elem.attrs({'subtype': tmp.params[0]});
  1666. }
  1667. elem.up();
  1668. }
  1669. });
  1670. };
  1671. SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
  1672. var media = '';
  1673. var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
  1674. if (tmp.length) {
  1675. media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
  1676. if (tmp.attr('value')) {
  1677. media += tmp.attr('value');
  1678. } else {
  1679. media += '0';
  1680. }
  1681. media += '\r\n';
  1682. }
  1683. tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
  1684. tmp.each(function () {
  1685. media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
  1686. if ($(this).attr('subtype')) {
  1687. media += ' ' + $(this).attr('subtype');
  1688. }
  1689. media += '\r\n';
  1690. });
  1691. return media;
  1692. };
  1693. // construct an SDP from a jingle stanza
  1694. SDP.prototype.fromJingle = function (jingle) {
  1695. var self = this;
  1696. this.raw = 'v=0\r\n' +
  1697. 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
  1698. 's=-\r\n' +
  1699. 't=0 0\r\n';
  1700. // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
  1701. if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
  1702. $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
  1703. var contents = $(group).find('>content').map(function (idx, content) {
  1704. return content.getAttribute('name');
  1705. }).get();
  1706. if (contents.length > 0) {
  1707. self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n';
  1708. }
  1709. });
  1710. }
  1711. this.session = this.raw;
  1712. jingle.find('>content').each(function () {
  1713. var m = self.jingle2media($(this));
  1714. self.media.push(m);
  1715. });
  1716. // reconstruct msid-semantic -- apparently not necessary
  1717. /*
  1718. var msid = SDPUtil.parse_ssrc(this.raw);
  1719. if (msid.hasOwnProperty('mslabel')) {
  1720. this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
  1721. }
  1722. */
  1723. this.raw = this.session + this.media.join('');
  1724. };
  1725. // translate a jingle content element into an an SDP media part
  1726. SDP.prototype.jingle2media = function (content) {
  1727. var media = '',
  1728. desc = content.find('description'),
  1729. ssrc = desc.attr('ssrc'),
  1730. self = this,
  1731. tmp;
  1732. var sctp = content.find(
  1733. '>transport>sctpmap[xmlns="urn:xmpp:jingle:transports:dtls-sctp:1"]');
  1734. tmp = { media: desc.attr('media') };
  1735. tmp.port = '1';
  1736. if (content.attr('senders') == 'rejected') {
  1737. // estos hack to reject an m-line.
  1738. tmp.port = '0';
  1739. }
  1740. if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
  1741. if (sctp.length)
  1742. tmp.proto = 'DTLS/SCTP';
  1743. else
  1744. tmp.proto = 'RTP/SAVPF';
  1745. } else {
  1746. tmp.proto = 'RTP/AVPF';
  1747. }
  1748. if (!sctp.length)
  1749. {
  1750. tmp.fmt = desc.find('payload-type').map(
  1751. function () { return this.getAttribute('id'); }).get();
  1752. media += SDPUtil.build_mline(tmp) + '\r\n';
  1753. }
  1754. else
  1755. {
  1756. media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n';
  1757. media += 'a=sctpmap:' + sctp.attr('number') +
  1758. ' ' + sctp.attr('protocol');
  1759. var streamCount = sctp.attr('streams');
  1760. if (streamCount)
  1761. media += ' ' + streamCount + '\r\n';
  1762. else
  1763. media += '\r\n';
  1764. }
  1765. media += 'c=IN IP4 0.0.0.0\r\n';
  1766. if (!sctp.length)
  1767. media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
  1768. tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
  1769. if (tmp.length) {
  1770. if (tmp.attr('ufrag')) {
  1771. media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
  1772. }
  1773. if (tmp.attr('pwd')) {
  1774. media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
  1775. }
  1776. tmp.find('>fingerprint').each(function () {
  1777. // FIXME: check namespace at some point
  1778. media += 'a=fingerprint:' + this.getAttribute('hash');
  1779. media += ' ' + $(this).text();
  1780. media += '\r\n';
  1781. if (this.getAttribute('setup')) {
  1782. media += 'a=setup:' + this.getAttribute('setup') + '\r\n';
  1783. }
  1784. });
  1785. }
  1786. switch (content.attr('senders')) {
  1787. case 'initiator':
  1788. media += 'a=sendonly\r\n';
  1789. break;
  1790. case 'responder':
  1791. media += 'a=recvonly\r\n';
  1792. break;
  1793. case 'none':
  1794. media += 'a=inactive\r\n';
  1795. break;
  1796. case 'both':
  1797. media += 'a=sendrecv\r\n';
  1798. break;
  1799. }
  1800. media += 'a=mid:' + content.attr('name') + '\r\n';
  1801. // <description><rtcp-mux/></description>
  1802. // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
  1803. // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
  1804. if (desc.find('rtcp-mux').length) {
  1805. media += 'a=rtcp-mux\r\n';
  1806. }
  1807. if (desc.find('encryption').length) {
  1808. desc.find('encryption>crypto').each(function () {
  1809. media += 'a=crypto:' + this.getAttribute('tag');
  1810. media += ' ' + this.getAttribute('crypto-suite');
  1811. media += ' ' + this.getAttribute('key-params');
  1812. if (this.getAttribute('session-params')) {
  1813. media += ' ' + this.getAttribute('session-params');
  1814. }
  1815. media += '\r\n';
  1816. });
  1817. }
  1818. desc.find('payload-type').each(function () {
  1819. media += SDPUtil.build_rtpmap(this) + '\r\n';
  1820. if ($(this).find('>parameter').length) {
  1821. media += 'a=fmtp:' + this.getAttribute('id') + ' ';
  1822. media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join('; ');
  1823. media += '\r\n';
  1824. }
  1825. // xep-0293
  1826. media += self.RtcpFbFromJingle($(this), this.getAttribute('id'));
  1827. });
  1828. // xep-0293
  1829. media += self.RtcpFbFromJingle(desc, '*');
  1830. // xep-0294
  1831. tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
  1832. tmp.each(function () {
  1833. media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n';
  1834. });
  1835. content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
  1836. media += SDPUtil.candidateFromJingle(this);
  1837. });
  1838. // XEP-0339 handle ssrc-group attributes
  1839. tmp = content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
  1840. var semantics = this.getAttribute('semantics');
  1841. var ssrcs = $(this).find('>source').map(function() {
  1842. return this.getAttribute('ssrc');
  1843. }).get();
  1844. if (ssrcs.length != 0) {
  1845. media += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
  1846. }
  1847. });
  1848. tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
  1849. tmp.each(function () {
  1850. var ssrc = this.getAttribute('ssrc');
  1851. $(this).find('>parameter').each(function () {
  1852. media += 'a=ssrc:' + ssrc + ' ' + this.getAttribute('name');
  1853. if (this.getAttribute('value') && this.getAttribute('value').length)
  1854. media += ':' + this.getAttribute('value');
  1855. media += '\r\n';
  1856. });
  1857. });
  1858. return media;
  1859. };
  1860. module.exports = SDP;
  1861. },{"./SDPUtil":4}],3:[function(require,module,exports){
  1862. function SDPDiffer(mySDP, otherSDP) {
  1863. this.mySDP = mySDP;
  1864. this.otherSDP = otherSDP;
  1865. }
  1866. /**
  1867. * Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
  1868. * @param otherSdp the other SDP to check ssrc with.
  1869. */
  1870. SDPDiffer.prototype.getNewMedia = function() {
  1871. // this could be useful in Array.prototype.
  1872. function arrayEquals(array) {
  1873. // if the other array is a falsy value, return
  1874. if (!array)
  1875. return false;
  1876. // compare lengths - can save a lot of time
  1877. if (this.length != array.length)
  1878. return false;
  1879. for (var i = 0, l=this.length; i < l; i++) {
  1880. // Check if we have nested arrays
  1881. if (this[i] instanceof Array && array[i] instanceof Array) {
  1882. // recurse into the nested arrays
  1883. if (!this[i].equals(array[i]))
  1884. return false;
  1885. }
  1886. else if (this[i] != array[i]) {
  1887. // Warning - two different object instances will never be equal: {x:20} != {x:20}
  1888. return false;
  1889. }
  1890. }
  1891. return true;
  1892. }
  1893. var myMedias = this.mySDP.getMediaSsrcMap();
  1894. var othersMedias = this.otherSDP.getMediaSsrcMap();
  1895. var newMedia = {};
  1896. Object.keys(othersMedias).forEach(function(othersMediaIdx) {
  1897. var myMedia = myMedias[othersMediaIdx];
  1898. var othersMedia = othersMedias[othersMediaIdx];
  1899. if(!myMedia && othersMedia) {
  1900. // Add whole channel
  1901. newMedia[othersMediaIdx] = othersMedia;
  1902. return;
  1903. }
  1904. // Look for new ssrcs accross the channel
  1905. Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
  1906. if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
  1907. // Allocate channel if we've found ssrc that doesn't exist in our channel
  1908. if(!newMedia[othersMediaIdx]){
  1909. newMedia[othersMediaIdx] = {
  1910. mediaindex: othersMedia.mediaindex,
  1911. mid: othersMedia.mid,
  1912. ssrcs: {},
  1913. ssrcGroups: []
  1914. };
  1915. }
  1916. newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
  1917. }
  1918. });
  1919. // Look for new ssrc groups across the channels
  1920. othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
  1921. // try to match the other ssrc-group with an ssrc-group of ours
  1922. var matched = false;
  1923. for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
  1924. var mySsrcGroup = myMedia.ssrcGroups[i];
  1925. if (otherSsrcGroup.semantics == mySsrcGroup.semantics
  1926. && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
  1927. matched = true;
  1928. break;
  1929. }
  1930. }
  1931. if (!matched) {
  1932. // Allocate channel if we've found an ssrc-group that doesn't
  1933. // exist in our channel
  1934. if(!newMedia[othersMediaIdx]){
  1935. newMedia[othersMediaIdx] = {
  1936. mediaindex: othersMedia.mediaindex,
  1937. mid: othersMedia.mid,
  1938. ssrcs: {},
  1939. ssrcGroups: []
  1940. };
  1941. }
  1942. newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
  1943. }
  1944. });
  1945. });
  1946. return newMedia;
  1947. };
  1948. /**
  1949. * Sends SSRC update IQ.
  1950. * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
  1951. * @param sid session identifier that will be put into the IQ.
  1952. * @param initiator initiator identifier.
  1953. * @param toJid destination Jid
  1954. * @param isAdd indicates if this is remove or add operation.
  1955. */
  1956. SDPDiffer.prototype.toJingle = function(modify) {
  1957. var sdpMediaSsrcs = this.getNewMedia();
  1958. var self = this;
  1959. // FIXME: only announce video ssrcs since we mix audio and dont need
  1960. // the audio ssrcs therefore
  1961. var modified = false;
  1962. Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
  1963. modified = true;
  1964. var media = sdpMediaSsrcs[mediaindex];
  1965. modify.c('content', {name: media.mid});
  1966. modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
  1967. // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
  1968. // generate sources from lines
  1969. Object.keys(media.ssrcs).forEach(function(ssrcNum) {
  1970. var mediaSsrc = media.ssrcs[ssrcNum];
  1971. modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
  1972. modify.attrs({ssrc: mediaSsrc.ssrc});
  1973. // iterate over ssrc lines
  1974. mediaSsrc.lines.forEach(function (line) {
  1975. var idx = line.indexOf(' ');
  1976. var kv = line.substr(idx + 1);
  1977. modify.c('parameter');
  1978. if (kv.indexOf(':') == -1) {
  1979. modify.attrs({ name: kv });
  1980. } else {
  1981. modify.attrs({ name: kv.split(':', 2)[0] });
  1982. modify.attrs({ value: kv.split(':', 2)[1] });
  1983. }
  1984. modify.up(); // end of parameter
  1985. });
  1986. modify.up(); // end of source
  1987. });
  1988. // generate source groups from lines
  1989. media.ssrcGroups.forEach(function(ssrcGroup) {
  1990. if (ssrcGroup.ssrcs.length != 0) {
  1991. modify.c('ssrc-group', {
  1992. semantics: ssrcGroup.semantics,
  1993. xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
  1994. });
  1995. ssrcGroup.ssrcs.forEach(function (ssrc) {
  1996. modify.c('source', { ssrc: ssrc })
  1997. .up(); // end of source
  1998. });
  1999. modify.up(); // end of ssrc-group
  2000. }
  2001. });
  2002. modify.up(); // end of description
  2003. modify.up(); // end of content
  2004. });
  2005. return modified;
  2006. };
  2007. module.exports = SDPDiffer;
  2008. },{}],4:[function(require,module,exports){
  2009. SDPUtil = {
  2010. iceparams: function (mediadesc, sessiondesc) {
  2011. var data = null;
  2012. if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
  2013. SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
  2014. data = {
  2015. ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
  2016. pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
  2017. };
  2018. }
  2019. return data;
  2020. },
  2021. parse_iceufrag: function (line) {
  2022. return line.substring(12);
  2023. },
  2024. build_iceufrag: function (frag) {
  2025. return 'a=ice-ufrag:' + frag;
  2026. },
  2027. parse_icepwd: function (line) {
  2028. return line.substring(10);
  2029. },
  2030. build_icepwd: function (pwd) {
  2031. return 'a=ice-pwd:' + pwd;
  2032. },
  2033. parse_mid: function (line) {
  2034. return line.substring(6);
  2035. },
  2036. parse_mline: function (line) {
  2037. var parts = line.substring(2).split(' '),
  2038. data = {};
  2039. data.media = parts.shift();
  2040. data.port = parts.shift();
  2041. data.proto = parts.shift();
  2042. if (parts[parts.length - 1] === '') { // trailing whitespace
  2043. parts.pop();
  2044. }
  2045. data.fmt = parts;
  2046. return data;
  2047. },
  2048. build_mline: function (mline) {
  2049. return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
  2050. },
  2051. parse_rtpmap: function (line) {
  2052. var parts = line.substring(9).split(' '),
  2053. data = {};
  2054. data.id = parts.shift();
  2055. parts = parts[0].split('/');
  2056. data.name = parts.shift();
  2057. data.clockrate = parts.shift();
  2058. data.channels = parts.length ? parts.shift() : '1';
  2059. return data;
  2060. },
  2061. /**
  2062. * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
  2063. * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
  2064. * @returns [SCTP port number, protocol, streams]
  2065. */
  2066. parse_sctpmap: function (line)
  2067. {
  2068. var parts = line.substring(10).split(' ');
  2069. var sctpPort = parts[0];
  2070. var protocol = parts[1];
  2071. // Stream count is optional
  2072. var streamCount = parts.length > 2 ? parts[2] : null;
  2073. return [sctpPort, protocol, streamCount];// SCTP port
  2074. },
  2075. build_rtpmap: function (el) {
  2076. var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
  2077. if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
  2078. line += '/' + el.getAttribute('channels');
  2079. }
  2080. return line;
  2081. },
  2082. parse_crypto: function (line) {
  2083. var parts = line.substring(9).split(' '),
  2084. data = {};
  2085. data.tag = parts.shift();
  2086. data['crypto-suite'] = parts.shift();
  2087. data['key-params'] = parts.shift();
  2088. if (parts.length) {
  2089. data['session-params'] = parts.join(' ');
  2090. }
  2091. return data;
  2092. },
  2093. parse_fingerprint: function (line) { // RFC 4572
  2094. var parts = line.substring(14).split(' '),
  2095. data = {};
  2096. data.hash = parts.shift();
  2097. data.fingerprint = parts.shift();
  2098. // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
  2099. return data;
  2100. },
  2101. parse_fmtp: function (line) {
  2102. var parts = line.split(' '),
  2103. i, key, value,
  2104. data = [];
  2105. parts.shift();
  2106. parts = parts.join(' ').split(';');
  2107. for (i = 0; i < parts.length; i++) {
  2108. key = parts[i].split('=')[0];
  2109. while (key.length && key[0] == ' ') {
  2110. key = key.substring(1);
  2111. }
  2112. value = parts[i].split('=')[1];
  2113. if (key && value) {
  2114. data.push({name: key, value: value});
  2115. } else if (key) {
  2116. // rfc 4733 (DTMF) style stuff
  2117. data.push({name: '', value: key});
  2118. }
  2119. }
  2120. return data;
  2121. },
  2122. parse_icecandidate: function (line) {
  2123. var candidate = {},
  2124. elems = line.split(' ');
  2125. candidate.foundation = elems[0].substring(12);
  2126. candidate.component = elems[1];
  2127. candidate.protocol = elems[2].toLowerCase();
  2128. candidate.priority = elems[3];
  2129. candidate.ip = elems[4];
  2130. candidate.port = elems[5];
  2131. // elems[6] => "typ"
  2132. candidate.type = elems[7];
  2133. candidate.generation = 0; // default value, may be overwritten below
  2134. for (var i = 8; i < elems.length; i += 2) {
  2135. switch (elems[i]) {
  2136. case 'raddr':
  2137. candidate['rel-addr'] = elems[i + 1];
  2138. break;
  2139. case 'rport':
  2140. candidate['rel-port'] = elems[i + 1];
  2141. break;
  2142. case 'generation':
  2143. candidate.generation = elems[i + 1];
  2144. break;
  2145. case 'tcptype':
  2146. candidate.tcptype = elems[i + 1];
  2147. break;
  2148. default: // TODO
  2149. console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
  2150. }
  2151. }
  2152. candidate.network = '1';
  2153. candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
  2154. return candidate;
  2155. },
  2156. build_icecandidate: function (cand) {
  2157. var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
  2158. line += ' ';
  2159. switch (cand.type) {
  2160. case 'srflx':
  2161. case 'prflx':
  2162. case 'relay':
  2163. if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
  2164. line += 'raddr';
  2165. line += ' ';
  2166. line += cand['rel-addr'];
  2167. line += ' ';
  2168. line += 'rport';
  2169. line += ' ';
  2170. line += cand['rel-port'];
  2171. line += ' ';
  2172. }
  2173. break;
  2174. }
  2175. if (cand.hasOwnAttribute('tcptype')) {
  2176. line += 'tcptype';
  2177. line += ' ';
  2178. line += cand.tcptype;
  2179. line += ' ';
  2180. }
  2181. line += 'generation';
  2182. line += ' ';
  2183. line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
  2184. return line;
  2185. },
  2186. parse_ssrc: function (desc) {
  2187. // proprietary mapping of a=ssrc lines
  2188. // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
  2189. // and parse according to that
  2190. var lines = desc.split('\r\n'),
  2191. data = {};
  2192. for (var i = 0; i < lines.length; i++) {
  2193. if (lines[i].substring(0, 7) == 'a=ssrc:') {
  2194. var idx = lines[i].indexOf(' ');
  2195. data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
  2196. }
  2197. }
  2198. return data;
  2199. },
  2200. parse_rtcpfb: function (line) {
  2201. var parts = line.substr(10).split(' ');
  2202. var data = {};
  2203. data.pt = parts.shift();
  2204. data.type = parts.shift();
  2205. data.params = parts;
  2206. return data;
  2207. },
  2208. parse_extmap: function (line) {
  2209. var parts = line.substr(9).split(' ');
  2210. var data = {};
  2211. data.value = parts.shift();
  2212. if (data.value.indexOf('/') != -1) {
  2213. data.direction = data.value.substr(data.value.indexOf('/') + 1);
  2214. data.value = data.value.substr(0, data.value.indexOf('/'));
  2215. } else {
  2216. data.direction = 'both';
  2217. }
  2218. data.uri = parts.shift();
  2219. data.params = parts;
  2220. return data;
  2221. },
  2222. find_line: function (haystack, needle, sessionpart) {
  2223. var lines = haystack.split('\r\n');
  2224. for (var i = 0; i < lines.length; i++) {
  2225. if (lines[i].substring(0, needle.length) == needle) {
  2226. return lines[i];
  2227. }
  2228. }
  2229. if (!sessionpart) {
  2230. return false;
  2231. }
  2232. // search session part
  2233. lines = sessionpart.split('\r\n');
  2234. for (var j = 0; j < lines.length; j++) {
  2235. if (lines[j].substring(0, needle.length) == needle) {
  2236. return lines[j];
  2237. }
  2238. }
  2239. return false;
  2240. },
  2241. find_lines: function (haystack, needle, sessionpart) {
  2242. var lines = haystack.split('\r\n'),
  2243. needles = [];
  2244. for (var i = 0; i < lines.length; i++) {
  2245. if (lines[i].substring(0, needle.length) == needle)
  2246. needles.push(lines[i]);
  2247. }
  2248. if (needles.length || !sessionpart) {
  2249. return needles;
  2250. }
  2251. // search session part
  2252. lines = sessionpart.split('\r\n');
  2253. for (var j = 0; j < lines.length; j++) {
  2254. if (lines[j].substring(0, needle.length) == needle) {
  2255. needles.push(lines[j]);
  2256. }
  2257. }
  2258. return needles;
  2259. },
  2260. candidateToJingle: function (line) {
  2261. // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
  2262. // <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
  2263. if (line.indexOf('candidate:') === 0) {
  2264. line = 'a=' + line;
  2265. } else if (line.substring(0, 12) != 'a=candidate:') {
  2266. console.log('parseCandidate called with a line that is not a candidate line');
  2267. console.log(line);
  2268. return null;
  2269. }
  2270. if (line.substring(line.length - 2) == '\r\n') // chomp it
  2271. line = line.substring(0, line.length - 2);
  2272. var candidate = {},
  2273. elems = line.split(' '),
  2274. i;
  2275. if (elems[6] != 'typ') {
  2276. console.log('did not find typ in the right place');
  2277. console.log(line);
  2278. return null;
  2279. }
  2280. candidate.foundation = elems[0].substring(12);
  2281. candidate.component = elems[1];
  2282. candidate.protocol = elems[2].toLowerCase();
  2283. candidate.priority = elems[3];
  2284. candidate.ip = elems[4];
  2285. candidate.port = elems[5];
  2286. // elems[6] => "typ"
  2287. candidate.type = elems[7];
  2288. candidate.generation = '0'; // default, may be overwritten below
  2289. for (i = 8; i < elems.length; i += 2) {
  2290. switch (elems[i]) {
  2291. case 'raddr':
  2292. candidate['rel-addr'] = elems[i + 1];
  2293. break;
  2294. case 'rport':
  2295. candidate['rel-port'] = elems[i + 1];
  2296. break;
  2297. case 'generation':
  2298. candidate.generation = elems[i + 1];
  2299. break;
  2300. case 'tcptype':
  2301. candidate.tcptype = elems[i + 1];
  2302. break;
  2303. default: // TODO
  2304. console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
  2305. }
  2306. }
  2307. candidate.network = '1';
  2308. candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
  2309. return candidate;
  2310. },
  2311. candidateFromJingle: function (cand) {
  2312. var line = 'a=candidate:';
  2313. line += cand.getAttribute('foundation');
  2314. line += ' ';
  2315. line += cand.getAttribute('component');
  2316. line += ' ';
  2317. line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
  2318. line += ' ';
  2319. line += cand.getAttribute('priority');
  2320. line += ' ';
  2321. line += cand.getAttribute('ip');
  2322. line += ' ';
  2323. line += cand.getAttribute('port');
  2324. line += ' ';
  2325. line += 'typ';
  2326. line += ' ' + cand.getAttribute('type');
  2327. line += ' ';
  2328. switch (cand.getAttribute('type')) {
  2329. case 'srflx':
  2330. case 'prflx':
  2331. case 'relay':
  2332. if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
  2333. line += 'raddr';
  2334. line += ' ';
  2335. line += cand.getAttribute('rel-addr');
  2336. line += ' ';
  2337. line += 'rport';
  2338. line += ' ';
  2339. line += cand.getAttribute('rel-port');
  2340. line += ' ';
  2341. }
  2342. break;
  2343. }
  2344. if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
  2345. line += 'tcptype';
  2346. line += ' ';
  2347. line += cand.getAttribute('tcptype');
  2348. line += ' ';
  2349. }
  2350. line += 'generation';
  2351. line += ' ';
  2352. line += cand.getAttribute('generation') || '0';
  2353. return line + '\r\n';
  2354. }
  2355. };
  2356. module.exports = SDPUtil;
  2357. },{}],5:[function(require,module,exports){
  2358. function TraceablePeerConnection(ice_config, constraints) {
  2359. var self = this;
  2360. var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
  2361. this.peerconnection = new RTCPeerconnection(ice_config, constraints);
  2362. this.updateLog = [];
  2363. this.stats = {};
  2364. this.statsinterval = null;
  2365. this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
  2366. // override as desired
  2367. this.trace = function (what, info) {
  2368. //console.warn('WTRACE', what, info);
  2369. self.updateLog.push({
  2370. time: new Date(),
  2371. type: what,
  2372. value: info || ""
  2373. });
  2374. };
  2375. this.onicecandidate = null;
  2376. this.peerconnection.onicecandidate = function (event) {
  2377. self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
  2378. if (self.onicecandidate !== null) {
  2379. self.onicecandidate(event);
  2380. }
  2381. };
  2382. this.onaddstream = null;
  2383. this.peerconnection.onaddstream = function (event) {
  2384. self.trace('onaddstream', event.stream.id);
  2385. if (self.onaddstream !== null) {
  2386. self.onaddstream(event);
  2387. }
  2388. };
  2389. this.onremovestream = null;
  2390. this.peerconnection.onremovestream = function (event) {
  2391. self.trace('onremovestream', event.stream.id);
  2392. if (self.onremovestream !== null) {
  2393. self.onremovestream(event);
  2394. }
  2395. };
  2396. this.onsignalingstatechange = null;
  2397. this.peerconnection.onsignalingstatechange = function (event) {
  2398. self.trace('onsignalingstatechange', self.signalingState);
  2399. if (self.onsignalingstatechange !== null) {
  2400. self.onsignalingstatechange(event);
  2401. }
  2402. };
  2403. this.oniceconnectionstatechange = null;
  2404. this.peerconnection.oniceconnectionstatechange = function (event) {
  2405. self.trace('oniceconnectionstatechange', self.iceConnectionState);
  2406. if (self.oniceconnectionstatechange !== null) {
  2407. self.oniceconnectionstatechange(event);
  2408. }
  2409. };
  2410. this.onnegotiationneeded = null;
  2411. this.peerconnection.onnegotiationneeded = function (event) {
  2412. self.trace('onnegotiationneeded');
  2413. if (self.onnegotiationneeded !== null) {
  2414. self.onnegotiationneeded(event);
  2415. }
  2416. };
  2417. self.ondatachannel = null;
  2418. this.peerconnection.ondatachannel = function (event) {
  2419. self.trace('ondatachannel', event);
  2420. if (self.ondatachannel !== null) {
  2421. self.ondatachannel(event);
  2422. }
  2423. };
  2424. if (!navigator.mozGetUserMedia && this.maxstats) {
  2425. this.statsinterval = window.setInterval(function() {
  2426. self.peerconnection.getStats(function(stats) {
  2427. var results = stats.result();
  2428. for (var i = 0; i < results.length; ++i) {
  2429. //console.log(results[i].type, results[i].id, results[i].names())
  2430. var now = new Date();
  2431. results[i].names().forEach(function (name) {
  2432. var id = results[i].id + '-' + name;
  2433. if (!self.stats[id]) {
  2434. self.stats[id] = {
  2435. startTime: now,
  2436. endTime: now,
  2437. values: [],
  2438. times: []
  2439. };
  2440. }
  2441. self.stats[id].values.push(results[i].stat(name));
  2442. self.stats[id].times.push(now.getTime());
  2443. if (self.stats[id].values.length > self.maxstats) {
  2444. self.stats[id].values.shift();
  2445. self.stats[id].times.shift();
  2446. }
  2447. self.stats[id].endTime = now;
  2448. });
  2449. }
  2450. });
  2451. }, 1000);
  2452. }
  2453. };
  2454. dumpSDP = function(description) {
  2455. return 'type: ' + description.type + '\r\n' + description.sdp;
  2456. }
  2457. if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
  2458. TraceablePeerConnection.prototype.__defineGetter__('signalingState', function() { return this.peerconnection.signalingState; });
  2459. TraceablePeerConnection.prototype.__defineGetter__('iceConnectionState', function() { return this.peerconnection.iceConnectionState; });
  2460. TraceablePeerConnection.prototype.__defineGetter__('localDescription', function() {
  2461. var publicLocalDescription = simulcast.reverseTransformLocalDescription(this.peerconnection.localDescription);
  2462. return publicLocalDescription;
  2463. });
  2464. TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() {
  2465. var publicRemoteDescription = simulcast.reverseTransformRemoteDescription(this.peerconnection.remoteDescription);
  2466. return publicRemoteDescription;
  2467. });
  2468. }
  2469. TraceablePeerConnection.prototype.addStream = function (stream) {
  2470. this.trace('addStream', stream.id);
  2471. simulcast.resetSender();
  2472. try
  2473. {
  2474. this.peerconnection.addStream(stream);
  2475. }
  2476. catch (e)
  2477. {
  2478. console.error(e);
  2479. return;
  2480. }
  2481. };
  2482. TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) {
  2483. this.trace('removeStream', stream.id);
  2484. simulcast.resetSender();
  2485. if(stopStreams) {
  2486. stream.getAudioTracks().forEach(function (track) {
  2487. track.stop();
  2488. });
  2489. stream.getVideoTracks().forEach(function (track) {
  2490. track.stop();
  2491. });
  2492. }
  2493. this.peerconnection.removeStream(stream);
  2494. };
  2495. TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
  2496. this.trace('createDataChannel', label, opts);
  2497. return this.peerconnection.createDataChannel(label, opts);
  2498. };
  2499. TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
  2500. var self = this;
  2501. description = simulcast.transformLocalDescription(description);
  2502. this.trace('setLocalDescription', dumpSDP(description));
  2503. this.peerconnection.setLocalDescription(description,
  2504. function () {
  2505. self.trace('setLocalDescriptionOnSuccess');
  2506. successCallback();
  2507. },
  2508. function (err) {
  2509. self.trace('setLocalDescriptionOnFailure', err);
  2510. failureCallback(err);
  2511. }
  2512. );
  2513. /*
  2514. if (this.statsinterval === null && this.maxstats > 0) {
  2515. // start gathering stats
  2516. }
  2517. */
  2518. };
  2519. TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
  2520. var self = this;
  2521. description = simulcast.transformRemoteDescription(description);
  2522. this.trace('setRemoteDescription', dumpSDP(description));
  2523. this.peerconnection.setRemoteDescription(description,
  2524. function () {
  2525. self.trace('setRemoteDescriptionOnSuccess');
  2526. successCallback();
  2527. },
  2528. function (err) {
  2529. self.trace('setRemoteDescriptionOnFailure', err);
  2530. failureCallback(err);
  2531. }
  2532. );
  2533. /*
  2534. if (this.statsinterval === null && this.maxstats > 0) {
  2535. // start gathering stats
  2536. }
  2537. */
  2538. };
  2539. TraceablePeerConnection.prototype.close = function () {
  2540. this.trace('stop');
  2541. if (this.statsinterval !== null) {
  2542. window.clearInterval(this.statsinterval);
  2543. this.statsinterval = null;
  2544. }
  2545. this.peerconnection.close();
  2546. };
  2547. TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
  2548. var self = this;
  2549. this.trace('createOffer', JSON.stringify(constraints, null, ' '));
  2550. this.peerconnection.createOffer(
  2551. function (offer) {
  2552. self.trace('createOfferOnSuccess', dumpSDP(offer));
  2553. successCallback(offer);
  2554. },
  2555. function(err) {
  2556. self.trace('createOfferOnFailure', err);
  2557. failureCallback(err);
  2558. },
  2559. constraints
  2560. );
  2561. };
  2562. TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
  2563. var self = this;
  2564. this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
  2565. this.peerconnection.createAnswer(
  2566. function (answer) {
  2567. answer = simulcast.transformAnswer(answer);
  2568. self.trace('createAnswerOnSuccess', dumpSDP(answer));
  2569. successCallback(answer);
  2570. },
  2571. function(err) {
  2572. self.trace('createAnswerOnFailure', err);
  2573. failureCallback(err);
  2574. },
  2575. constraints
  2576. );
  2577. };
  2578. TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
  2579. var self = this;
  2580. this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
  2581. this.peerconnection.addIceCandidate(candidate);
  2582. /* maybe later
  2583. this.peerconnection.addIceCandidate(candidate,
  2584. function () {
  2585. self.trace('addIceCandidateOnSuccess');
  2586. successCallback();
  2587. },
  2588. function (err) {
  2589. self.trace('addIceCandidateOnFailure', err);
  2590. failureCallback(err);
  2591. }
  2592. );
  2593. */
  2594. };
  2595. TraceablePeerConnection.prototype.getStats = function(callback, errback) {
  2596. if (navigator.mozGetUserMedia) {
  2597. // ignore for now...
  2598. if(!errback)
  2599. errback = function () {
  2600. }
  2601. this.peerconnection.getStats(null,callback,errback);
  2602. } else {
  2603. this.peerconnection.getStats(callback);
  2604. }
  2605. };
  2606. module.exports = TraceablePeerConnection;
  2607. },{}],6:[function(require,module,exports){
  2608. /* global $, $iq, config, connection, UI, messageHandler,
  2609. roomName, sessionTerminated, Strophe, Util */
  2610. /**
  2611. * Contains logic responsible for enabling/disabling functionality available
  2612. * only to moderator users.
  2613. */
  2614. var connection = null;
  2615. var focusUserJid;
  2616. var getNextTimeout = Util.createExpBackoffTimer(1000);
  2617. var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
  2618. // External authentication stuff
  2619. var externalAuthEnabled = false;
  2620. // Sip gateway can be enabled by configuring Jigasi host in config.js or
  2621. // it will be enabled automatically if focus detects the component through
  2622. // service discovery.
  2623. var sipGatewayEnabled = config.hosts.call_control !== undefined;
  2624. var Moderator = {
  2625. isModerator: function () {
  2626. return connection && connection.emuc.isModerator();
  2627. },
  2628. isPeerModerator: function (peerJid) {
  2629. return connection &&
  2630. connection.emuc.getMemberRole(peerJid) === 'moderator';
  2631. },
  2632. isExternalAuthEnabled: function () {
  2633. return externalAuthEnabled;
  2634. },
  2635. isSipGatewayEnabled: function () {
  2636. return sipGatewayEnabled;
  2637. },
  2638. setConnection: function (con) {
  2639. connection = con;
  2640. },
  2641. init: function (xmpp) {
  2642. this.xmppService = xmpp;
  2643. this.onLocalRoleChange = function (from, member, pres) {
  2644. UI.onModeratorStatusChanged(Moderator.isModerator());
  2645. };
  2646. },
  2647. onMucLeft: function (jid) {
  2648. console.info("Someone left is it focus ? " + jid);
  2649. var resource = Strophe.getResourceFromJid(jid);
  2650. if (resource === 'focus' && !this.xmppService.sessionTerminated) {
  2651. console.info(
  2652. "Focus has left the room - leaving conference");
  2653. //hangUp();
  2654. // We'd rather reload to have everything re-initialized
  2655. // FIXME: show some message before reload
  2656. location.reload();
  2657. }
  2658. },
  2659. setFocusUserJid: function (focusJid) {
  2660. if (!focusUserJid) {
  2661. focusUserJid = focusJid;
  2662. console.info("Focus jid set to: " + focusUserJid);
  2663. }
  2664. },
  2665. getFocusUserJid: function () {
  2666. return focusUserJid;
  2667. },
  2668. getFocusComponent: function () {
  2669. // Get focus component address
  2670. var focusComponent = config.hosts.focus;
  2671. // If not specified use default: 'focus.domain'
  2672. if (!focusComponent) {
  2673. focusComponent = 'focus.' + config.hosts.domain;
  2674. }
  2675. return focusComponent;
  2676. },
  2677. createConferenceIq: function (roomName) {
  2678. // Generate create conference IQ
  2679. var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
  2680. elem.c('conference', {
  2681. xmlns: 'http://jitsi.org/protocol/focus',
  2682. room: roomName
  2683. });
  2684. if (config.hosts.bridge !== undefined) {
  2685. elem.c(
  2686. 'property',
  2687. { name: 'bridge', value: config.hosts.bridge})
  2688. .up();
  2689. }
  2690. // Tell the focus we have Jigasi configured
  2691. if (config.hosts.call_control !== undefined) {
  2692. elem.c(
  2693. 'property',
  2694. { name: 'call_control', value: config.hosts.call_control})
  2695. .up();
  2696. }
  2697. if (config.channelLastN !== undefined) {
  2698. elem.c(
  2699. 'property',
  2700. { name: 'channelLastN', value: config.channelLastN})
  2701. .up();
  2702. }
  2703. if (config.adaptiveLastN !== undefined) {
  2704. elem.c(
  2705. 'property',
  2706. { name: 'adaptiveLastN', value: config.adaptiveLastN})
  2707. .up();
  2708. }
  2709. if (config.adaptiveSimulcast !== undefined) {
  2710. elem.c(
  2711. 'property',
  2712. { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
  2713. .up();
  2714. }
  2715. if (config.openSctp !== undefined) {
  2716. elem.c(
  2717. 'property',
  2718. { name: 'openSctp', value: config.openSctp})
  2719. .up();
  2720. }
  2721. if (config.enableFirefoxSupport !== undefined) {
  2722. elem.c(
  2723. 'property',
  2724. { name: 'enableFirefoxHacks',
  2725. value: config.enableFirefoxSupport})
  2726. .up();
  2727. }
  2728. elem.up();
  2729. return elem;
  2730. },
  2731. parseConfigOptions: function (resultIq) {
  2732. Moderator.setFocusUserJid(
  2733. $(resultIq).find('conference').attr('focusjid'));
  2734. var extAuthParam
  2735. = $(resultIq).find('>conference>property[name=\'externalAuth\']');
  2736. if (extAuthParam.length) {
  2737. externalAuthEnabled = extAuthParam.attr('value') === 'true';
  2738. }
  2739. console.info("External authentication enabled: " + externalAuthEnabled);
  2740. // Check if focus has auto-detected Jigasi component(this will be also
  2741. // included if we have passed our host from the config)
  2742. if ($(resultIq).find(
  2743. '>conference>property[name=\'sipGatewayEnabled\']').length) {
  2744. sipGatewayEnabled = true;
  2745. }
  2746. console.info("Sip gateway enabled: " + sipGatewayEnabled);
  2747. },
  2748. // FIXME: we need to show the fact that we're waiting for the focus
  2749. // to the user(or that focus is not available)
  2750. allocateConferenceFocus: function (roomName, callback) {
  2751. // Try to use focus user JID from the config
  2752. Moderator.setFocusUserJid(config.focusUserJid);
  2753. // Send create conference IQ
  2754. var iq = Moderator.createConferenceIq(roomName);
  2755. var self = this;
  2756. connection.sendIQ(
  2757. iq,
  2758. function (result) {
  2759. if ('true' === $(result).find('conference').attr('ready')) {
  2760. // Reset both timers
  2761. getNextTimeout(true);
  2762. getNextErrorTimeout(true);
  2763. // Setup config options
  2764. Moderator.parseConfigOptions(result);
  2765. // Exec callback
  2766. callback();
  2767. } else {
  2768. var waitMs = getNextTimeout();
  2769. console.info("Waiting for the focus... " + waitMs);
  2770. // Reset error timeout
  2771. getNextErrorTimeout(true);
  2772. window.setTimeout(
  2773. function () {
  2774. Moderator.allocateConferenceFocus(
  2775. roomName, callback);
  2776. }, waitMs);
  2777. }
  2778. },
  2779. function (error) {
  2780. // Not authorized to create new room
  2781. if ($(error).find('>error>not-authorized').length) {
  2782. console.warn("Unauthorized to start the conference");
  2783. var toDomain
  2784. = Strophe.getDomainFromJid(error.getAttribute('to'));
  2785. if (toDomain === config.hosts.anonymousdomain) {
  2786. // we are connected with anonymous domain and
  2787. // only non anonymous users can create rooms
  2788. // we must authorize the user
  2789. self.xmppService.promptLogin();
  2790. } else {
  2791. // External authentication mode
  2792. UI.onAuthenticationRequired(function () {
  2793. Moderator.allocateConferenceFocus(
  2794. roomName, callback);
  2795. });
  2796. }
  2797. return;
  2798. }
  2799. var waitMs = getNextErrorTimeout();
  2800. console.error("Focus error, retry after " + waitMs, error);
  2801. // Show message
  2802. UI.messageHandler.notify(
  2803. 'Conference focus', 'disconnected',
  2804. Moderator.getFocusComponent() +
  2805. ' not available - retry in ' +
  2806. (waitMs / 1000) + ' sec');
  2807. // Reset response timeout
  2808. getNextTimeout(true);
  2809. window.setTimeout(
  2810. function () {
  2811. Moderator.allocateConferenceFocus(roomName, callback);
  2812. }, waitMs);
  2813. }
  2814. );
  2815. },
  2816. getAuthUrl: function (roomName, urlCallback) {
  2817. var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
  2818. iq.c('auth-url', {
  2819. xmlns: 'http://jitsi.org/protocol/focus',
  2820. room: roomName
  2821. });
  2822. connection.sendIQ(
  2823. iq,
  2824. function (result) {
  2825. var url = $(result).find('auth-url').attr('url');
  2826. if (url) {
  2827. console.info("Got auth url: " + url);
  2828. urlCallback(url);
  2829. } else {
  2830. console.error(
  2831. "Failed to get auth url fro mthe focus", result);
  2832. }
  2833. },
  2834. function (error) {
  2835. console.error("Get auth url error", error);
  2836. }
  2837. );
  2838. }
  2839. };
  2840. module.exports = Moderator;
  2841. },{}],7:[function(require,module,exports){
  2842. /* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
  2843. Toolbar, Util */
  2844. var Moderator = require("./moderator");
  2845. var recordingToken = null;
  2846. var recordingEnabled;
  2847. /**
  2848. * Whether to use a jirecon component for recording, or use the videobridge
  2849. * through COLIBRI.
  2850. */
  2851. var useJirecon = (typeof config.hosts.jirecon != "undefined");
  2852. /**
  2853. * The ID of the jirecon recording session. Jirecon generates it when we
  2854. * initially start recording, and it needs to be used in subsequent requests
  2855. * to jirecon.
  2856. */
  2857. var jireconRid = null;
  2858. function setRecordingToken(token) {
  2859. recordingToken = token;
  2860. }
  2861. function setRecording(state, token, callback, connection) {
  2862. if (useJirecon){
  2863. this.setRecordingJirecon(state, token, callback, connection);
  2864. } else {
  2865. this.setRecordingColibri(state, token, callback, connection);
  2866. }
  2867. }
  2868. function setRecordingJirecon(state, token, callback, connection) {
  2869. if (state == recordingEnabled){
  2870. return;
  2871. }
  2872. var iq = $iq({to: config.hosts.jirecon, type: 'set'})
  2873. .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
  2874. action: state ? 'start' : 'stop',
  2875. mucjid: connection.emuc.roomjid});
  2876. if (!state){
  2877. iq.attrs({rid: jireconRid});
  2878. }
  2879. console.log('Start recording');
  2880. connection.sendIQ(
  2881. iq,
  2882. function (result) {
  2883. // TODO wait for an IQ with the real status, since this is
  2884. // provisional?
  2885. jireconRid = $(result).find('recording').attr('rid');
  2886. console.log('Recording ' + (state ? 'started' : 'stopped') +
  2887. '(jirecon)' + result);
  2888. recordingEnabled = state;
  2889. if (!state){
  2890. jireconRid = null;
  2891. }
  2892. callback(state);
  2893. },
  2894. function (error) {
  2895. console.log('Failed to start recording, error: ', error);
  2896. callback(recordingEnabled);
  2897. });
  2898. }
  2899. // Sends a COLIBRI message which enables or disables (according to 'state')
  2900. // the recording on the bridge. Waits for the result IQ and calls 'callback'
  2901. // with the new recording state, according to the IQ.
  2902. function setRecordingColibri(state, token, callback, connection) {
  2903. var elem = $iq({to: connection.emuc.focusMucJid, type: 'set'});
  2904. elem.c('conference', {
  2905. xmlns: 'http://jitsi.org/protocol/colibri'
  2906. });
  2907. elem.c('recording', {state: state, token: token});
  2908. connection.sendIQ(elem,
  2909. function (result) {
  2910. console.log('Set recording "', state, '". Result:', result);
  2911. var recordingElem = $(result).find('>conference>recording');
  2912. var newState = ('true' === recordingElem.attr('state'));
  2913. recordingEnabled = newState;
  2914. callback(newState);
  2915. },
  2916. function (error) {
  2917. console.warn(error);
  2918. callback(recordingEnabled);
  2919. }
  2920. );
  2921. }
  2922. var Recording = {
  2923. toggleRecording: function (tokenEmptyCallback,
  2924. startingCallback, startedCallback, connection) {
  2925. if (!Moderator.isModerator()) {
  2926. console.log(
  2927. 'non-focus, or conference not yet organized:' +
  2928. ' not enabling recording');
  2929. return;
  2930. }
  2931. // Jirecon does not (currently) support a token.
  2932. if (!recordingToken && !useJirecon) {
  2933. tokenEmptyCallback(function (value) {
  2934. setRecordingToken(value);
  2935. this.toggleRecording();
  2936. });
  2937. return;
  2938. }
  2939. var oldState = recordingEnabled;
  2940. startingCallback(!oldState);
  2941. setRecording(!oldState,
  2942. recordingToken,
  2943. function (state) {
  2944. console.log("New recording state: ", state);
  2945. if (state === oldState) {
  2946. // FIXME: new focus:
  2947. // this will not work when moderator changes
  2948. // during active session. Then it will assume that
  2949. // recording status has changed to true, but it might have
  2950. // been already true(and we only received actual status from
  2951. // the focus).
  2952. //
  2953. // SO we start with status null, so that it is initialized
  2954. // here and will fail only after second click, so if invalid
  2955. // token was used we have to press the button twice before
  2956. // current status will be fetched and token will be reset.
  2957. //
  2958. // Reliable way would be to return authentication error.
  2959. // Or status update when moderator connects.
  2960. // Or we have to stop recording session when current
  2961. // moderator leaves the room.
  2962. // Failed to change, reset the token because it might
  2963. // have been wrong
  2964. setRecordingToken(null);
  2965. }
  2966. startedCallback(state);
  2967. },
  2968. connection
  2969. );
  2970. }
  2971. }
  2972. module.exports = Recording;
  2973. },{"./moderator":6}],8:[function(require,module,exports){
  2974. /* jshint -W117 */
  2975. /* a simple MUC connection plugin
  2976. * can only handle a single MUC room
  2977. */
  2978. var bridgeIsDown = false;
  2979. var Moderator = require("./moderator");
  2980. var JingleSession = require("./JingleSession");
  2981. module.exports = function(XMPP, eventEmitter) {
  2982. Strophe.addConnectionPlugin('emuc', {
  2983. connection: null,
  2984. roomjid: null,
  2985. myroomjid: null,
  2986. members: {},
  2987. list_members: [], // so we can elect a new focus
  2988. presMap: {},
  2989. preziMap: {},
  2990. joined: false,
  2991. isOwner: false,
  2992. role: null,
  2993. focusMucJid: null,
  2994. init: function (conn) {
  2995. this.connection = conn;
  2996. },
  2997. initPresenceMap: function (myroomjid) {
  2998. this.presMap['to'] = myroomjid;
  2999. this.presMap['xns'] = 'http://jabber.org/protocol/muc';
  3000. },
  3001. doJoin: function (jid, password) {
  3002. this.myroomjid = jid;
  3003. console.info("Joined MUC as " + this.myroomjid);
  3004. this.initPresenceMap(this.myroomjid);
  3005. if (!this.roomjid) {
  3006. this.roomjid = Strophe.getBareJidFromJid(jid);
  3007. // add handlers (just once)
  3008. this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
  3009. this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
  3010. this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
  3011. this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
  3012. }
  3013. if (password !== undefined) {
  3014. this.presMap['password'] = password;
  3015. }
  3016. this.sendPresence();
  3017. },
  3018. doLeave: function () {
  3019. console.log("do leave", this.myroomjid);
  3020. var pres = $pres({to: this.myroomjid, type: 'unavailable' });
  3021. this.presMap.length = 0;
  3022. this.connection.send(pres);
  3023. },
  3024. createNonAnonymousRoom: function () {
  3025. // http://xmpp.org/extensions/xep-0045.html#createroom-reserved
  3026. var getForm = $iq({type: 'get', to: this.roomjid})
  3027. .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
  3028. .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
  3029. this.connection.sendIQ(getForm, function (form) {
  3030. if (!$(form).find(
  3031. '>query>x[xmlns="jabber:x:data"]' +
  3032. '>field[var="muc#roomconfig_whois"]').length) {
  3033. console.error('non-anonymous rooms not supported');
  3034. return;
  3035. }
  3036. var formSubmit = $iq({to: this.roomjid, type: 'set'})
  3037. .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
  3038. formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
  3039. formSubmit.c('field', {'var': 'FORM_TYPE'})
  3040. .c('value')
  3041. .t('http://jabber.org/protocol/muc#roomconfig').up().up();
  3042. formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
  3043. .c('value').t('anyone').up().up();
  3044. this.connection.sendIQ(formSubmit);
  3045. }, function (error) {
  3046. console.error("Error getting room configuration form");
  3047. });
  3048. },
  3049. onPresence: function (pres) {
  3050. var from = pres.getAttribute('from');
  3051. // What is this for? A workaround for something?
  3052. if (pres.getAttribute('type')) {
  3053. return true;
  3054. }
  3055. // Parse etherpad tag.
  3056. var etherpad = $(pres).find('>etherpad');
  3057. if (etherpad.length) {
  3058. if (config.etherpad_base && !Moderator.isModerator()) {
  3059. UI.initEtherpad(etherpad.text());
  3060. }
  3061. }
  3062. // Parse prezi tag.
  3063. var presentation = $(pres).find('>prezi');
  3064. if (presentation.length) {
  3065. var url = presentation.attr('url');
  3066. var current = presentation.find('>current').text();
  3067. console.log('presentation info received from', from, url);
  3068. if (this.preziMap[from] == null) {
  3069. this.preziMap[from] = url;
  3070. $(document).trigger('presentationadded.muc', [from, url, current]);
  3071. }
  3072. else {
  3073. $(document).trigger('gotoslide.muc', [from, url, current]);
  3074. }
  3075. }
  3076. else if (this.preziMap[from] != null) {
  3077. var url = this.preziMap[from];
  3078. delete this.preziMap[from];
  3079. $(document).trigger('presentationremoved.muc', [from, url]);
  3080. }
  3081. // Parse audio info tag.
  3082. var audioMuted = $(pres).find('>audiomuted');
  3083. if (audioMuted.length) {
  3084. $(document).trigger('audiomuted.muc', [from, audioMuted.text()]);
  3085. }
  3086. // Parse video info tag.
  3087. var videoMuted = $(pres).find('>videomuted');
  3088. if (videoMuted.length) {
  3089. $(document).trigger('videomuted.muc', [from, videoMuted.text()]);
  3090. }
  3091. var stats = $(pres).find('>stats');
  3092. if (stats.length) {
  3093. var statsObj = {};
  3094. Strophe.forEachChild(stats[0], "stat", function (el) {
  3095. statsObj[el.getAttribute("name")] = el.getAttribute("value");
  3096. });
  3097. connectionquality.updateRemoteStats(from, statsObj);
  3098. }
  3099. // Parse status.
  3100. if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
  3101. this.isOwner = true;
  3102. this.createNonAnonymousRoom();
  3103. }
  3104. // Parse roles.
  3105. var member = {};
  3106. member.show = $(pres).find('>show').text();
  3107. member.status = $(pres).find('>status').text();
  3108. var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
  3109. member.affiliation = tmp.attr('affiliation');
  3110. member.role = tmp.attr('role');
  3111. // Focus recognition
  3112. member.jid = tmp.attr('jid');
  3113. member.isFocus = false;
  3114. if (member.jid
  3115. && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
  3116. member.isFocus = true;
  3117. }
  3118. var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
  3119. member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
  3120. if (from == this.myroomjid) {
  3121. if (member.affiliation == 'owner') this.isOwner = true;
  3122. if (this.role !== member.role) {
  3123. this.role = member.role;
  3124. if (Moderator.onLocalRoleChange)
  3125. Moderator.onLocalRoleChange(from, member, pres);
  3126. UI.onLocalRoleChange(from, member, pres);
  3127. }
  3128. if (!this.joined) {
  3129. this.joined = true;
  3130. eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
  3131. this.list_members.push(from);
  3132. }
  3133. } else if (this.members[from] === undefined) {
  3134. // new participant
  3135. this.members[from] = member;
  3136. this.list_members.push(from);
  3137. console.log('entered', from, member);
  3138. if (member.isFocus) {
  3139. this.focusMucJid = from;
  3140. console.info("Ignore focus: " + from + ", real JID: " + member.jid);
  3141. }
  3142. else {
  3143. var id = $(pres).find('>userID').text();
  3144. var email = $(pres).find('>email');
  3145. if (email.length > 0) {
  3146. id = email.text();
  3147. }
  3148. UI.onMucEntered(from, id, member.displayName);
  3149. API.triggerEvent("participantJoined", {jid: from});
  3150. }
  3151. } else {
  3152. // Presence update for existing participant
  3153. // Watch role change:
  3154. if (this.members[from].role != member.role) {
  3155. this.members[from].role = member.role;
  3156. UI.onMucRoleChanged(member.role, member.displayName);
  3157. }
  3158. }
  3159. // Always trigger presence to update bindings
  3160. $(document).trigger('presence.muc', [from, member, pres]);
  3161. this.parsePresence(from, member, pres);
  3162. // Trigger status message update
  3163. if (member.status) {
  3164. UI.onMucPresenceStatus(from, member);
  3165. }
  3166. return true;
  3167. },
  3168. onPresenceUnavailable: function (pres) {
  3169. var from = pres.getAttribute('from');
  3170. // Status code 110 indicates that this notification is "self-presence".
  3171. if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
  3172. delete this.members[from];
  3173. this.list_members.splice(this.list_members.indexOf(from), 1);
  3174. this.onParticipantLeft(from);
  3175. }
  3176. // If the status code is 110 this means we're leaving and we would like
  3177. // to remove everyone else from our view, so we trigger the event.
  3178. else if (this.list_members.length > 1) {
  3179. for (var i = 0; i < this.list_members.length; i++) {
  3180. var member = this.list_members[i];
  3181. delete this.members[i];
  3182. this.list_members.splice(i, 1);
  3183. this.onParticipantLeft(member);
  3184. }
  3185. }
  3186. if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
  3187. $(document).trigger('kicked.muc', [from]);
  3188. if (this.myroomjid === from) {
  3189. XMPP.disposeConference(false);
  3190. eventEmitter.emit(XMPPEvents.KICKED);
  3191. }
  3192. }
  3193. return true;
  3194. },
  3195. onPresenceError: function (pres) {
  3196. var from = pres.getAttribute('from');
  3197. if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
  3198. console.log('on password required', from);
  3199. var self = this;
  3200. UI.onPasswordReqiured(function (value) {
  3201. self.doJoin(from, value);
  3202. });
  3203. } else if ($(pres).find(
  3204. '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
  3205. var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
  3206. if (toDomain === config.hosts.anonymousdomain) {
  3207. // enter the room by replying with 'not-authorized'. This would
  3208. // result in reconnection from authorized domain.
  3209. // We're either missing Jicofo/Prosody config for anonymous
  3210. // domains or something is wrong.
  3211. // XMPP.promptLogin();
  3212. UI.messageHandler.openReportDialog(null,
  3213. 'Oops ! We couldn`t join the conference.' +
  3214. ' There might be some problem with security' +
  3215. ' configuration. Please contact service' +
  3216. ' administrator.', pres);
  3217. } else {
  3218. console.warn('onPresError ', pres);
  3219. UI.messageHandler.openReportDialog(null,
  3220. 'Oops! Something went wrong and we couldn`t connect to the conference.',
  3221. pres);
  3222. }
  3223. } else {
  3224. console.warn('onPresError ', pres);
  3225. UI.messageHandler.openReportDialog(null,
  3226. 'Oops! Something went wrong and we couldn`t connect to the conference.',
  3227. pres);
  3228. }
  3229. return true;
  3230. },
  3231. sendMessage: function (body, nickname) {
  3232. var msg = $msg({to: this.roomjid, type: 'groupchat'});
  3233. msg.c('body', body).up();
  3234. if (nickname) {
  3235. msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
  3236. }
  3237. this.connection.send(msg);
  3238. API.triggerEvent("outgoingMessage", {"message": body});
  3239. },
  3240. setSubject: function (subject) {
  3241. var msg = $msg({to: this.roomjid, type: 'groupchat'});
  3242. msg.c('subject', subject);
  3243. this.connection.send(msg);
  3244. console.log("topic changed to " + subject);
  3245. },
  3246. onMessage: function (msg) {
  3247. // FIXME: this is a hack. but jingle on muc makes nickchanges hard
  3248. var from = msg.getAttribute('from');
  3249. var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from);
  3250. var txt = $(msg).find('>body').text();
  3251. var type = msg.getAttribute("type");
  3252. if (type == "error") {
  3253. UI.chatAddError($(msg).find('>text').text(), txt);
  3254. return true;
  3255. }
  3256. var subject = $(msg).find('>subject');
  3257. if (subject.length) {
  3258. var subjectText = subject.text();
  3259. if (subjectText || subjectText == "") {
  3260. UI.chatSetSubject(subjectText);
  3261. console.log("Subject is changed to " + subjectText);
  3262. }
  3263. }
  3264. if (txt) {
  3265. console.log('chat', nick, txt);
  3266. UI.updateChatConversation(from, nick, txt);
  3267. if (from != this.myroomjid)
  3268. API.triggerEvent("incomingMessage",
  3269. {"from": from, "nick": nick, "message": txt});
  3270. }
  3271. return true;
  3272. },
  3273. lockRoom: function (key, onSuccess, onError, onNotSupported) {
  3274. //http://xmpp.org/extensions/xep-0045.html#roomconfig
  3275. var ob = this;
  3276. this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
  3277. function (res) {
  3278. if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
  3279. var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
  3280. formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
  3281. formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
  3282. formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
  3283. // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
  3284. formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
  3285. // FIXME: is muc#roomconfig_passwordprotectedroom required?
  3286. this.connection.sendIQ(formsubmit,
  3287. onSuccess,
  3288. onError);
  3289. } else {
  3290. onNotSupported();
  3291. }
  3292. }, onError);
  3293. },
  3294. kick: function (jid) {
  3295. var kickIQ = $iq({to: this.roomjid, type: 'set'})
  3296. .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
  3297. .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
  3298. .c('reason').t('You have been kicked.').up().up().up();
  3299. this.connection.sendIQ(
  3300. kickIQ,
  3301. function (result) {
  3302. console.log('Kick participant with jid: ', jid, result);
  3303. },
  3304. function (error) {
  3305. console.log('Kick participant error: ', error);
  3306. });
  3307. },
  3308. sendPresence: function () {
  3309. var pres = $pres({to: this.presMap['to'] });
  3310. pres.c('x', {xmlns: this.presMap['xns']});
  3311. if (this.presMap['password']) {
  3312. pres.c('password').t(this.presMap['password']).up();
  3313. }
  3314. pres.up();
  3315. // Send XEP-0115 'c' stanza that contains our capabilities info
  3316. if (this.connection.caps) {
  3317. this.connection.caps.node = config.clientNode;
  3318. pres.c('c', this.connection.caps.generateCapsAttrs()).up();
  3319. }
  3320. pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
  3321. .t(navigator.userAgent).up();
  3322. if (this.presMap['bridgeIsDown']) {
  3323. pres.c('bridgeIsDown').up();
  3324. }
  3325. if (this.presMap['email']) {
  3326. pres.c('email').t(this.presMap['email']).up();
  3327. }
  3328. if (this.presMap['userId']) {
  3329. pres.c('userId').t(this.presMap['userId']).up();
  3330. }
  3331. if (this.presMap['displayName']) {
  3332. // XEP-0172
  3333. pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
  3334. .t(this.presMap['displayName']).up();
  3335. }
  3336. if (this.presMap['audions']) {
  3337. pres.c('audiomuted', {xmlns: this.presMap['audions']})
  3338. .t(this.presMap['audiomuted']).up();
  3339. }
  3340. if (this.presMap['videons']) {
  3341. pres.c('videomuted', {xmlns: this.presMap['videons']})
  3342. .t(this.presMap['videomuted']).up();
  3343. }
  3344. if (this.presMap['statsns']) {
  3345. var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
  3346. for (var stat in this.presMap["stats"])
  3347. if (this.presMap["stats"][stat] != null)
  3348. stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up();
  3349. pres.up();
  3350. }
  3351. if (this.presMap['prezins']) {
  3352. pres.c('prezi',
  3353. {xmlns: this.presMap['prezins'],
  3354. 'url': this.presMap['preziurl']})
  3355. .c('current').t(this.presMap['prezicurrent']).up().up();
  3356. }
  3357. if (this.presMap['etherpadns']) {
  3358. pres.c('etherpad', {xmlns: this.presMap['etherpadns']})
  3359. .t(this.presMap['etherpadname']).up();
  3360. }
  3361. if (this.presMap['medians']) {
  3362. pres.c('media', {xmlns: this.presMap['medians']});
  3363. var sourceNumber = 0;
  3364. Object.keys(this.presMap).forEach(function (key) {
  3365. if (key.indexOf('source') >= 0) {
  3366. sourceNumber++;
  3367. }
  3368. });
  3369. if (sourceNumber > 0)
  3370. for (var i = 1; i <= sourceNumber / 3; i++) {
  3371. pres.c('source',
  3372. {type: this.presMap['source' + i + '_type'],
  3373. ssrc: this.presMap['source' + i + '_ssrc'],
  3374. direction: this.presMap['source' + i + '_direction']
  3375. || 'sendrecv' }
  3376. ).up();
  3377. }
  3378. }
  3379. pres.up();
  3380. // console.debug(pres.toString());
  3381. this.connection.send(pres);
  3382. },
  3383. addDisplayNameToPresence: function (displayName) {
  3384. this.presMap['displayName'] = displayName;
  3385. },
  3386. addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
  3387. if (!this.presMap['medians'])
  3388. this.presMap['medians'] = 'http://estos.de/ns/mjs';
  3389. this.presMap['source' + sourceNumber + '_type'] = mtype;
  3390. this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
  3391. this.presMap['source' + sourceNumber + '_direction'] = direction;
  3392. },
  3393. clearPresenceMedia: function () {
  3394. var self = this;
  3395. Object.keys(this.presMap).forEach(function (key) {
  3396. if (key.indexOf('source') != -1) {
  3397. delete self.presMap[key];
  3398. }
  3399. });
  3400. },
  3401. addPreziToPresence: function (url, currentSlide) {
  3402. this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
  3403. this.presMap['preziurl'] = url;
  3404. this.presMap['prezicurrent'] = currentSlide;
  3405. },
  3406. removePreziFromPresence: function () {
  3407. delete this.presMap['prezins'];
  3408. delete this.presMap['preziurl'];
  3409. delete this.presMap['prezicurrent'];
  3410. },
  3411. addCurrentSlideToPresence: function (currentSlide) {
  3412. this.presMap['prezicurrent'] = currentSlide;
  3413. },
  3414. getPrezi: function (roomjid) {
  3415. return this.preziMap[roomjid];
  3416. },
  3417. addEtherpadToPresence: function (etherpadName) {
  3418. this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad';
  3419. this.presMap['etherpadname'] = etherpadName;
  3420. },
  3421. addAudioInfoToPresence: function (isMuted) {
  3422. this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
  3423. this.presMap['audiomuted'] = isMuted.toString();
  3424. },
  3425. addVideoInfoToPresence: function (isMuted) {
  3426. this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
  3427. this.presMap['videomuted'] = isMuted.toString();
  3428. },
  3429. addConnectionInfoToPresence: function (stats) {
  3430. this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
  3431. this.presMap['stats'] = stats;
  3432. },
  3433. findJidFromResource: function (resourceJid) {
  3434. if (resourceJid &&
  3435. resourceJid === Strophe.getResourceFromJid(this.myroomjid)) {
  3436. return this.myroomjid;
  3437. }
  3438. var peerJid = null;
  3439. Object.keys(this.members).some(function (jid) {
  3440. peerJid = jid;
  3441. return Strophe.getResourceFromJid(jid) === resourceJid;
  3442. });
  3443. return peerJid;
  3444. },
  3445. addBridgeIsDownToPresence: function () {
  3446. this.presMap['bridgeIsDown'] = true;
  3447. },
  3448. addEmailToPresence: function (email) {
  3449. this.presMap['email'] = email;
  3450. },
  3451. addUserIdToPresence: function (userId) {
  3452. this.presMap['userId'] = userId;
  3453. },
  3454. isModerator: function () {
  3455. return this.role === 'moderator';
  3456. },
  3457. getMemberRole: function (peerJid) {
  3458. if (this.members[peerJid]) {
  3459. return this.members[peerJid].role;
  3460. }
  3461. return null;
  3462. },
  3463. onParticipantLeft: function (jid) {
  3464. UI.onMucLeft(jid);
  3465. API.triggerEvent("participantLeft", {jid: jid});
  3466. this.connection.jingle.terminateByJid(jid);
  3467. if (this.getPrezi(jid)) {
  3468. $(document).trigger('presentationremoved.muc',
  3469. [jid, this.getPrezi(jid)]);
  3470. }
  3471. Moderator.onMucLeft(jid);
  3472. },
  3473. parsePresence: function (from, memeber, pres) {
  3474. if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
  3475. bridgeIsDown = true;
  3476. eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
  3477. }
  3478. if(memeber.isFocus)
  3479. return;
  3480. // Remove old ssrcs coming from the jid
  3481. Object.keys(ssrc2jid).forEach(function (ssrc) {
  3482. if (ssrc2jid[ssrc] == jid) {
  3483. delete ssrc2jid[ssrc];
  3484. }
  3485. });
  3486. var changedStreams = [];
  3487. $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
  3488. //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
  3489. var ssrcV = ssrc.getAttribute('ssrc');
  3490. ssrc2jid[ssrcV] = from;
  3491. JingleSession.notReceivedSSRCs.push(ssrcV);
  3492. var type = ssrc.getAttribute('type');
  3493. var direction = ssrc.getAttribute('direction');
  3494. changedStreams.push({type: type, direction: direction});
  3495. });
  3496. eventEmitter.emit(XMPPEvents.CHANGED_STREAMS, from, changedStreams);
  3497. var displayName = !config.displayJids
  3498. ? memeber.displayName : Strophe.getResourceFromJid(from);
  3499. if (displayName && displayName.length > 0)
  3500. {
  3501. // $(document).trigger('displaynamechanged',
  3502. // [jid, displayName]);
  3503. eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
  3504. }
  3505. var id = $(pres).find('>userID').text();
  3506. var email = $(pres).find('>email');
  3507. if(email.length > 0) {
  3508. id = email.text();
  3509. }
  3510. eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id);
  3511. }
  3512. });
  3513. };
  3514. },{"./JingleSession":1,"./moderator":6}],9:[function(require,module,exports){
  3515. /* jshint -W117 */
  3516. var JingleSession = require("./JingleSession");
  3517. module.exports = function(XMPP)
  3518. {
  3519. function CallIncomingJingle(sid, connection) {
  3520. var sess = connection.jingle.sessions[sid];
  3521. // TODO: do we check activecall == null?
  3522. connection.jingle.activecall = sess;
  3523. statistics.onConferenceCreated(sess);
  3524. RTC.onConferenceCreated(sess);
  3525. // TODO: check affiliation and/or role
  3526. console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
  3527. sess.usedrip = true; // not-so-naive trickle ice
  3528. sess.sendAnswer();
  3529. sess.accept();
  3530. };
  3531. Strophe.addConnectionPlugin('jingle', {
  3532. connection: null,
  3533. sessions: {},
  3534. jid2session: {},
  3535. ice_config: {iceServers: []},
  3536. pc_constraints: {},
  3537. activecall: null,
  3538. media_constraints: {
  3539. mandatory: {
  3540. 'OfferToReceiveAudio': true,
  3541. 'OfferToReceiveVideo': true
  3542. }
  3543. // MozDontOfferDataChannel: true when this is firefox
  3544. },
  3545. init: function (conn) {
  3546. this.connection = conn;
  3547. if (this.connection.disco) {
  3548. // http://xmpp.org/extensions/xep-0167.html#support
  3549. // http://xmpp.org/extensions/xep-0176.html#support
  3550. this.connection.disco.addFeature('urn:xmpp:jingle:1');
  3551. this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
  3552. this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
  3553. this.connection.disco.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
  3554. this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
  3555. this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
  3556. // this is dealt with by SDP O/A so we don't need to annouce this
  3557. //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
  3558. //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
  3559. if (config.useRtcpMux) {
  3560. this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
  3561. }
  3562. if (config.useBundle) {
  3563. this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
  3564. }
  3565. //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
  3566. }
  3567. this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
  3568. },
  3569. onJingle: function (iq) {
  3570. var sid = $(iq).find('jingle').attr('sid');
  3571. var action = $(iq).find('jingle').attr('action');
  3572. var fromJid = iq.getAttribute('from');
  3573. // send ack first
  3574. var ack = $iq({type: 'result',
  3575. to: fromJid,
  3576. id: iq.getAttribute('id')
  3577. });
  3578. console.log('on jingle ' + action + ' from ' + fromJid, iq);
  3579. var sess = this.sessions[sid];
  3580. if ('session-initiate' != action) {
  3581. if (sess === null) {
  3582. ack.type = 'error';
  3583. ack.c('error', {type: 'cancel'})
  3584. .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
  3585. .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
  3586. this.connection.send(ack);
  3587. return true;
  3588. }
  3589. // compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
  3590. // local jid is not checked
  3591. if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
  3592. console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
  3593. ack.type = 'error';
  3594. ack.c('error', {type: 'cancel'})
  3595. .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
  3596. .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
  3597. this.connection.send(ack);
  3598. return true;
  3599. }
  3600. } else if (sess !== undefined) {
  3601. // existing session with same session id
  3602. // this might be out-of-order if the sess.peerjid is the same as from
  3603. ack.type = 'error';
  3604. ack.c('error', {type: 'cancel'})
  3605. .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
  3606. console.warn('duplicate session id', sid);
  3607. this.connection.send(ack);
  3608. return true;
  3609. }
  3610. // FIXME: check for a defined action
  3611. this.connection.send(ack);
  3612. // see http://xmpp.org/extensions/xep-0166.html#concepts-session
  3613. switch (action) {
  3614. case 'session-initiate':
  3615. sess = new JingleSession(
  3616. $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
  3617. this.connection, XMPP);
  3618. // configure session
  3619. sess.media_constraints = this.media_constraints;
  3620. sess.pc_constraints = this.pc_constraints;
  3621. sess.ice_config = this.ice_config;
  3622. sess.initiate(fromJid, false);
  3623. // FIXME: setRemoteDescription should only be done when this call is to be accepted
  3624. sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
  3625. this.sessions[sess.sid] = sess;
  3626. this.jid2session[sess.peerjid] = sess;
  3627. // the callback should either
  3628. // .sendAnswer and .accept
  3629. // or .sendTerminate -- not necessarily synchronus
  3630. CallIncomingJingle(sess.sid, this.connection);
  3631. break;
  3632. case 'session-accept':
  3633. sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
  3634. sess.accept();
  3635. $(document).trigger('callaccepted.jingle', [sess.sid]);
  3636. break;
  3637. case 'session-terminate':
  3638. // If this is not the focus sending the terminate, we have
  3639. // nothing more to do here.
  3640. if (Object.keys(this.sessions).length < 1
  3641. || !(this.sessions[Object.keys(this.sessions)[0]]
  3642. instanceof JingleSession))
  3643. {
  3644. break;
  3645. }
  3646. console.log('terminating...', sess.sid);
  3647. sess.terminate();
  3648. this.terminate(sess.sid);
  3649. if ($(iq).find('>jingle>reason').length) {
  3650. $(document).trigger('callterminated.jingle', [
  3651. sess.sid,
  3652. sess.peerjid,
  3653. $(iq).find('>jingle>reason>:first')[0].tagName,
  3654. $(iq).find('>jingle>reason>text').text()
  3655. ]);
  3656. } else {
  3657. $(document).trigger('callterminated.jingle',
  3658. [sess.sid, sess.peerjid]);
  3659. }
  3660. break;
  3661. case 'transport-info':
  3662. sess.addIceCandidate($(iq).find('>jingle>content'));
  3663. break;
  3664. case 'session-info':
  3665. var affected;
  3666. if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
  3667. $(document).trigger('ringing.jingle', [sess.sid]);
  3668. } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
  3669. affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
  3670. $(document).trigger('mute.jingle', [sess.sid, affected]);
  3671. } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
  3672. affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
  3673. $(document).trigger('unmute.jingle', [sess.sid, affected]);
  3674. }
  3675. break;
  3676. case 'addsource': // FIXME: proprietary, un-jingleish
  3677. case 'source-add': // FIXME: proprietary
  3678. sess.addSource($(iq).find('>jingle>content'), fromJid);
  3679. break;
  3680. case 'removesource': // FIXME: proprietary, un-jingleish
  3681. case 'source-remove': // FIXME: proprietary
  3682. sess.removeSource($(iq).find('>jingle>content'), fromJid);
  3683. break;
  3684. default:
  3685. console.warn('jingle action not implemented', action);
  3686. break;
  3687. }
  3688. return true;
  3689. },
  3690. initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
  3691. var sess = new JingleSession(myjid || this.connection.jid,
  3692. Math.random().toString(36).substr(2, 12), // random string
  3693. this.connection, XMPP);
  3694. // configure session
  3695. sess.media_constraints = this.media_constraints;
  3696. sess.pc_constraints = this.pc_constraints;
  3697. sess.ice_config = this.ice_config;
  3698. sess.initiate(peerjid, true);
  3699. this.sessions[sess.sid] = sess;
  3700. this.jid2session[sess.peerjid] = sess;
  3701. sess.sendOffer();
  3702. return sess;
  3703. },
  3704. terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
  3705. if (sid === null || sid === undefined) {
  3706. for (sid in this.sessions) {
  3707. if (this.sessions[sid].state != 'ended') {
  3708. this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
  3709. this.sessions[sid].terminate();
  3710. }
  3711. delete this.jid2session[this.sessions[sid].peerjid];
  3712. delete this.sessions[sid];
  3713. }
  3714. } else if (this.sessions.hasOwnProperty(sid)) {
  3715. if (this.sessions[sid].state != 'ended') {
  3716. this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
  3717. this.sessions[sid].terminate();
  3718. }
  3719. delete this.jid2session[this.sessions[sid].peerjid];
  3720. delete this.sessions[sid];
  3721. }
  3722. },
  3723. // Used to terminate a session when an unavailable presence is received.
  3724. terminateByJid: function (jid) {
  3725. if (this.jid2session.hasOwnProperty(jid)) {
  3726. var sess = this.jid2session[jid];
  3727. if (sess) {
  3728. sess.terminate();
  3729. console.log('peer went away silently', jid);
  3730. delete this.sessions[sess.sid];
  3731. delete this.jid2session[jid];
  3732. $(document).trigger('callterminated.jingle',
  3733. [sess.sid, jid], 'gone');
  3734. }
  3735. }
  3736. },
  3737. terminateRemoteByJid: function (jid, reason) {
  3738. if (this.jid2session.hasOwnProperty(jid)) {
  3739. var sess = this.jid2session[jid];
  3740. if (sess) {
  3741. sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
  3742. sess.terminate();
  3743. console.log('terminate peer with jid', sess.sid, jid);
  3744. delete this.sessions[sess.sid];
  3745. delete this.jid2session[jid];
  3746. $(document).trigger('callterminated.jingle',
  3747. [sess.sid, jid, 'kicked']);
  3748. }
  3749. }
  3750. },
  3751. getStunAndTurnCredentials: function () {
  3752. // get stun and turn configuration from server via xep-0215
  3753. // uses time-limited credentials as described in
  3754. // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
  3755. //
  3756. // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
  3757. // for a prosody module which implements this
  3758. //
  3759. // currently, this doesn't work with updateIce and therefore credentials with a long
  3760. // validity have to be fetched before creating the peerconnection
  3761. // TODO: implement refresh via updateIce as described in
  3762. // https://code.google.com/p/webrtc/issues/detail?id=1650
  3763. var self = this;
  3764. this.connection.sendIQ(
  3765. $iq({type: 'get', to: this.connection.domain})
  3766. .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
  3767. function (res) {
  3768. var iceservers = [];
  3769. $(res).find('>services>service').each(function (idx, el) {
  3770. el = $(el);
  3771. var dict = {};
  3772. var type = el.attr('type');
  3773. switch (type) {
  3774. case 'stun':
  3775. dict.url = 'stun:' + el.attr('host');
  3776. if (el.attr('port')) {
  3777. dict.url += ':' + el.attr('port');
  3778. }
  3779. iceservers.push(dict);
  3780. break;
  3781. case 'turn':
  3782. case 'turns':
  3783. dict.url = type + ':';
  3784. if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
  3785. if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
  3786. dict.url += el.attr('username') + '@';
  3787. } else {
  3788. dict.username = el.attr('username'); // only works in M28
  3789. }
  3790. }
  3791. dict.url += el.attr('host');
  3792. if (el.attr('port') && el.attr('port') != '3478') {
  3793. dict.url += ':' + el.attr('port');
  3794. }
  3795. if (el.attr('transport') && el.attr('transport') != 'udp') {
  3796. dict.url += '?transport=' + el.attr('transport');
  3797. }
  3798. if (el.attr('password')) {
  3799. dict.credential = el.attr('password');
  3800. }
  3801. iceservers.push(dict);
  3802. break;
  3803. }
  3804. });
  3805. self.ice_config.iceServers = iceservers;
  3806. },
  3807. function (err) {
  3808. console.warn('getting turn credentials failed', err);
  3809. console.warn('is mod_turncredentials or similar installed?');
  3810. }
  3811. );
  3812. // implement push?
  3813. },
  3814. /**
  3815. * Populates the log data
  3816. */
  3817. populateData: function () {
  3818. var data = {};
  3819. Object.keys(this.sessions).forEach(function (sid) {
  3820. var session = this.sessions[sid];
  3821. if (session.peerconnection && session.peerconnection.updateLog) {
  3822. // FIXME: should probably be a .dump call
  3823. data["jingle_" + session.sid] = {
  3824. updateLog: session.peerconnection.updateLog,
  3825. stats: session.peerconnection.stats,
  3826. url: window.location.href
  3827. };
  3828. }
  3829. });
  3830. return data;
  3831. }
  3832. });
  3833. };
  3834. },{"./JingleSession":1}],10:[function(require,module,exports){
  3835. /* global Strophe */
  3836. module.exports = function () {
  3837. Strophe.addConnectionPlugin('logger', {
  3838. // logs raw stanzas and makes them available for download as JSON
  3839. connection: null,
  3840. log: [],
  3841. init: function (conn) {
  3842. this.connection = conn;
  3843. this.connection.rawInput = this.log_incoming.bind(this);
  3844. this.connection.rawOutput = this.log_outgoing.bind(this);
  3845. },
  3846. log_incoming: function (stanza) {
  3847. this.log.push([new Date().getTime(), 'incoming', stanza]);
  3848. },
  3849. log_outgoing: function (stanza) {
  3850. this.log.push([new Date().getTime(), 'outgoing', stanza]);
  3851. }
  3852. });
  3853. };
  3854. },{}],11:[function(require,module,exports){
  3855. /* global $, $iq, config, connection, focusMucJid, forceMuted,
  3856. setAudioMuted, Strophe */
  3857. /**
  3858. * Moderate connection plugin.
  3859. */
  3860. module.exports = function (XMPP) {
  3861. Strophe.addConnectionPlugin('moderate', {
  3862. connection: null,
  3863. init: function (conn) {
  3864. this.connection = conn;
  3865. this.connection.addHandler(this.onMute.bind(this),
  3866. 'http://jitsi.org/jitmeet/audio',
  3867. 'iq',
  3868. 'set',
  3869. null,
  3870. null);
  3871. },
  3872. setMute: function (jid, mute) {
  3873. console.info("set mute", mute);
  3874. var iqToFocus = $iq({to: this.connection.emuc.focusMucJid, type: 'set'})
  3875. .c('mute', {
  3876. xmlns: 'http://jitsi.org/jitmeet/audio',
  3877. jid: jid
  3878. })
  3879. .t(mute.toString())
  3880. .up();
  3881. this.connection.sendIQ(
  3882. iqToFocus,
  3883. function (result) {
  3884. console.log('set mute', result);
  3885. },
  3886. function (error) {
  3887. console.log('set mute error', error);
  3888. });
  3889. },
  3890. onMute: function (iq) {
  3891. var from = iq.getAttribute('from');
  3892. if (from !== this.connection.emuc.focusMucJid) {
  3893. console.warn("Ignored mute from non focus peer");
  3894. return false;
  3895. }
  3896. var mute = $(iq).find('mute');
  3897. if (mute.length) {
  3898. var doMuteAudio = mute.text() === "true";
  3899. UI.setAudioMuted(doMuteAudio);
  3900. XMPP.forceMuted = doMuteAudio;
  3901. }
  3902. return true;
  3903. },
  3904. eject: function (jid) {
  3905. // We're not the focus, so can't terminate
  3906. //connection.jingle.terminateRemoteByJid(jid, 'kick');
  3907. this.connection.emuc.kick(jid);
  3908. }
  3909. });
  3910. }
  3911. },{}],12:[function(require,module,exports){
  3912. /* jshint -W117 */
  3913. module.exports = function() {
  3914. Strophe.addConnectionPlugin('rayo',
  3915. {
  3916. RAYO_XMLNS: 'urn:xmpp:rayo:1',
  3917. connection: null,
  3918. init: function (conn) {
  3919. this.connection = conn;
  3920. if (this.connection.disco) {
  3921. this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
  3922. }
  3923. this.connection.addHandler(
  3924. this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
  3925. },
  3926. onRayo: function (iq) {
  3927. console.info("Rayo IQ", iq);
  3928. },
  3929. dial: function (to, from, roomName, roomPass) {
  3930. var self = this;
  3931. var req = $iq(
  3932. {
  3933. type: 'set',
  3934. to: this.connection.emuc.focusMucJid
  3935. }
  3936. );
  3937. req.c('dial',
  3938. {
  3939. xmlns: this.RAYO_XMLNS,
  3940. to: to,
  3941. from: from
  3942. });
  3943. req.c('header',
  3944. {
  3945. name: 'JvbRoomName',
  3946. value: roomName
  3947. }).up();
  3948. if (roomPass !== null && roomPass.length) {
  3949. req.c('header',
  3950. {
  3951. name: 'JvbRoomPassword',
  3952. value: roomPass
  3953. }).up();
  3954. }
  3955. this.connection.sendIQ(
  3956. req,
  3957. function (result) {
  3958. console.info('Dial result ', result);
  3959. var resource = $(result).find('ref').attr('uri');
  3960. this.call_resource = resource.substr('xmpp:'.length);
  3961. console.info(
  3962. "Received call resource: " + this.call_resource);
  3963. },
  3964. function (error) {
  3965. console.info('Dial error ', error);
  3966. }
  3967. );
  3968. },
  3969. hang_up: function () {
  3970. if (!this.call_resource) {
  3971. console.warn("No call in progress");
  3972. return;
  3973. }
  3974. var self = this;
  3975. var req = $iq(
  3976. {
  3977. type: 'set',
  3978. to: this.call_resource
  3979. }
  3980. );
  3981. req.c('hangup',
  3982. {
  3983. xmlns: this.RAYO_XMLNS
  3984. });
  3985. this.connection.sendIQ(
  3986. req,
  3987. function (result) {
  3988. console.info('Hangup result ', result);
  3989. self.call_resource = null;
  3990. },
  3991. function (error) {
  3992. console.info('Hangup error ', error);
  3993. self.call_resource = null;
  3994. }
  3995. );
  3996. }
  3997. }
  3998. );
  3999. };
  4000. },{}],13:[function(require,module,exports){
  4001. /**
  4002. * Strophe logger implementation. Logs from level WARN and above.
  4003. */
  4004. module.exports = function () {
  4005. Strophe.log = function (level, msg) {
  4006. switch (level) {
  4007. case Strophe.LogLevel.WARN:
  4008. console.warn("Strophe: " + msg);
  4009. break;
  4010. case Strophe.LogLevel.ERROR:
  4011. case Strophe.LogLevel.FATAL:
  4012. console.error("Strophe: " + msg);
  4013. break;
  4014. }
  4015. };
  4016. Strophe.getStatusString = function (status) {
  4017. switch (status) {
  4018. case Strophe.Status.ERROR:
  4019. return "ERROR";
  4020. case Strophe.Status.CONNECTING:
  4021. return "CONNECTING";
  4022. case Strophe.Status.CONNFAIL:
  4023. return "CONNFAIL";
  4024. case Strophe.Status.AUTHENTICATING:
  4025. return "AUTHENTICATING";
  4026. case Strophe.Status.AUTHFAIL:
  4027. return "AUTHFAIL";
  4028. case Strophe.Status.CONNECTED:
  4029. return "CONNECTED";
  4030. case Strophe.Status.DISCONNECTED:
  4031. return "DISCONNECTED";
  4032. case Strophe.Status.DISCONNECTING:
  4033. return "DISCONNECTING";
  4034. case Strophe.Status.ATTACHED:
  4035. return "ATTACHED";
  4036. default:
  4037. return "unknown";
  4038. }
  4039. };
  4040. };
  4041. },{}],14:[function(require,module,exports){
  4042. var Moderator = require("./moderator");
  4043. var EventEmitter = require("events");
  4044. var Recording = require("./recording");
  4045. var SDP = require("./SDP");
  4046. var eventEmitter = new EventEmitter();
  4047. var connection = null;
  4048. var authenticatedUser = false;
  4049. function connect(jid, password, uiCredentials) {
  4050. var bosh
  4051. = uiCredentials.bosh || config.bosh || '/http-bind';
  4052. connection = new Strophe.Connection(bosh);
  4053. Moderator.setConnection(connection);
  4054. var settings = UI.getSettings();
  4055. var email = settings.email;
  4056. var displayName = settings.displayName;
  4057. if(email) {
  4058. connection.emuc.addEmailToPresence(email);
  4059. } else {
  4060. connection.emuc.addUserIdToPresence(settings.uid);
  4061. }
  4062. if(displayName) {
  4063. connection.emuc.addDisplayNameToPresence(displayName);
  4064. }
  4065. if (connection.disco) {
  4066. // for chrome, add multistream cap
  4067. }
  4068. connection.jingle.pc_constraints = RTC.getPCConstraints();
  4069. if (config.useIPv6) {
  4070. // https://code.google.com/p/webrtc/issues/detail?id=2828
  4071. if (!connection.jingle.pc_constraints.optional)
  4072. connection.jingle.pc_constraints.optional = [];
  4073. connection.jingle.pc_constraints.optional.push({googIPv6: true});
  4074. }
  4075. if(!password)
  4076. password = uiCredentials.password;
  4077. var anonymousConnectionFailed = false;
  4078. connection.connect(jid, password, function (status, msg) {
  4079. console.log('Strophe status changed to',
  4080. Strophe.getStatusString(status));
  4081. if (status === Strophe.Status.CONNECTED) {
  4082. if (config.useStunTurn) {
  4083. connection.jingle.getStunAndTurnCredentials();
  4084. }
  4085. UI.disableConnect();
  4086. console.info("My Jabber ID: " + connection.jid);
  4087. if(password)
  4088. authenticatedUser = true;
  4089. maybeDoJoin();
  4090. } else if (status === Strophe.Status.CONNFAIL) {
  4091. if(msg === 'x-strophe-bad-non-anon-jid') {
  4092. anonymousConnectionFailed = true;
  4093. }
  4094. } else if (status === Strophe.Status.DISCONNECTED) {
  4095. if(anonymousConnectionFailed) {
  4096. // prompt user for username and password
  4097. XMPP.promptLogin();
  4098. }
  4099. } else if (status === Strophe.Status.AUTHFAIL) {
  4100. // wrong password or username, prompt user
  4101. XMPP.promptLogin();
  4102. }
  4103. });
  4104. }
  4105. function maybeDoJoin() {
  4106. if (connection && connection.connected &&
  4107. Strophe.getResourceFromJid(connection.jid)
  4108. && (RTC.localAudio || RTC.localVideo)) {
  4109. // .connected is true while connecting?
  4110. doJoin();
  4111. }
  4112. }
  4113. function doJoin() {
  4114. var roomName = UI.generateRoomName();
  4115. Moderator.allocateConferenceFocus(
  4116. roomName, UI.checkForNicknameAndJoin);
  4117. }
  4118. function initStrophePlugins()
  4119. {
  4120. require("./strophe.emuc")(XMPP, eventEmitter);
  4121. require("./strophe.jingle")();
  4122. require("./strophe.moderate")(XMPP);
  4123. require("./strophe.util")();
  4124. require("./strophe.rayo")();
  4125. require("./strophe.logger")();
  4126. }
  4127. function registerListeners() {
  4128. RTC.addStreamListener(maybeDoJoin,
  4129. StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
  4130. }
  4131. function setupEvents() {
  4132. $(window).bind('beforeunload', function () {
  4133. if (connection && connection.connected) {
  4134. // ensure signout
  4135. $.ajax({
  4136. type: 'POST',
  4137. url: config.bosh,
  4138. async: false,
  4139. cache: false,
  4140. contentType: 'application/xml',
  4141. data: "<body rid='" + (connection.rid || connection._proto.rid)
  4142. + "' xmlns='http://jabber.org/protocol/httpbind' sid='"
  4143. + (connection.sid || connection._proto.sid)
  4144. + "' type='terminate'>" +
  4145. "<presence xmlns='jabber:client' type='unavailable'/>" +
  4146. "</body>",
  4147. success: function (data) {
  4148. console.log('signed out');
  4149. console.log(data);
  4150. },
  4151. error: function (XMLHttpRequest, textStatus, errorThrown) {
  4152. console.log('signout error',
  4153. textStatus + ' (' + errorThrown + ')');
  4154. }
  4155. });
  4156. }
  4157. XMPP.disposeConference(true);
  4158. });
  4159. }
  4160. var XMPP = {
  4161. sessionTerminated: false,
  4162. /**
  4163. * Remembers if we were muted by the focus.
  4164. * @type {boolean}
  4165. */
  4166. forceMuted: false,
  4167. start: function (uiCredentials) {
  4168. setupEvents();
  4169. initStrophePlugins();
  4170. registerListeners();
  4171. Moderator.init();
  4172. var configDomain = config.hosts.anonymousdomain || config.hosts.domain;
  4173. // Force authenticated domain if room is appended with '?login=true'
  4174. if (config.hosts.anonymousdomain &&
  4175. window.location.search.indexOf("login=true") !== -1) {
  4176. configDomain = config.hosts.domain;
  4177. }
  4178. var jid = uiCredentials.jid || configDomain || window.location.hostname;
  4179. connect(jid, null, uiCredentials);
  4180. },
  4181. promptLogin: function () {
  4182. UI.showLoginPopup(connect);
  4183. },
  4184. joinRooom: function(roomName, useNicks, nick)
  4185. {
  4186. var roomjid;
  4187. roomjid = roomName;
  4188. if (useNicks) {
  4189. if (nick) {
  4190. roomjid += '/' + nick;
  4191. } else {
  4192. roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
  4193. }
  4194. } else {
  4195. var tmpJid = Strophe.getNodeFromJid(connection.jid);
  4196. if(!authenticatedUser)
  4197. tmpJid = tmpJid.substr(0, 8);
  4198. roomjid += '/' + tmpJid;
  4199. }
  4200. connection.emuc.doJoin(roomjid);
  4201. },
  4202. myJid: function () {
  4203. if(!connection)
  4204. return null;
  4205. return connection.emuc.myroomjid;
  4206. },
  4207. myResource: function () {
  4208. if(!connection || ! connection.emuc.myroomjid)
  4209. return null;
  4210. return Strophe.getResourceFromJid(connection.emuc.myroomjid);
  4211. },
  4212. disposeConference: function (onUnload) {
  4213. eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
  4214. var handler = connection.jingle.activecall;
  4215. if (handler && handler.peerconnection) {
  4216. // FIXME: probably removing streams is not required and close() should
  4217. // be enough
  4218. if (RTC.localAudio) {
  4219. handler.peerconnection.removeStream(RTC.localAudio.getOriginalStream(), onUnload);
  4220. }
  4221. if (RTC.localVideo) {
  4222. handler.peerconnection.removeStream(RTC.localVideo.getOriginalStream(), onUnload);
  4223. }
  4224. handler.peerconnection.close();
  4225. }
  4226. connection.jingle.activecall = null;
  4227. if(!onUnload)
  4228. {
  4229. this.sessionTerminated = true;
  4230. connection.emuc.doLeave();
  4231. }
  4232. },
  4233. addListener: function(type, listener)
  4234. {
  4235. eventEmitter.on(type, listener);
  4236. },
  4237. removeListener: function (type, listener) {
  4238. eventEmitter.removeListener(type, listener);
  4239. },
  4240. allocateConferenceFocus: function(roomName, callback) {
  4241. Moderator.allocateConferenceFocus(roomName, callback);
  4242. },
  4243. isModerator: function () {
  4244. return Moderator.isModerator();
  4245. },
  4246. isSipGatewayEnabled: function () {
  4247. return Moderator.isSipGatewayEnabled();
  4248. },
  4249. isExternalAuthEnabled: function () {
  4250. return Moderator.isExternalAuthEnabled();
  4251. },
  4252. switchStreams: function (stream, oldStream, callback) {
  4253. if (connection && connection.jingle.activecall) {
  4254. // FIXME: will block switchInProgress on true value in case of exception
  4255. connection.jingle.activecall.switchStreams(stream, oldStream, callback);
  4256. } else {
  4257. // We are done immediately
  4258. console.error("No conference handler");
  4259. UI.messageHandler.showError('Error',
  4260. 'Unable to switch video stream.');
  4261. callback();
  4262. }
  4263. },
  4264. setVideoMute: function (mute, callback, options) {
  4265. if(connection && RTC.localVideo && connection.jingle.activecall)
  4266. {
  4267. connection.jingle.activecall.setVideoMute(mute, callback, options);
  4268. }
  4269. },
  4270. setAudioMute: function (mute, callback) {
  4271. if (!(connection && RTC.localAudio)) {
  4272. return false;
  4273. }
  4274. if (this.forceMuted && !mute) {
  4275. console.info("Asking focus for unmute");
  4276. connection.moderate.setMute(connection.emuc.myroomjid, mute);
  4277. // FIXME: wait for result before resetting muted status
  4278. this.forceMuted = false;
  4279. }
  4280. if (mute == RTC.localAudio.isMuted()) {
  4281. // Nothing to do
  4282. return true;
  4283. }
  4284. // It is not clear what is the right way to handle multiple tracks.
  4285. // So at least make sure that they are all muted or all unmuted and
  4286. // that we send presence just once.
  4287. RTC.localAudio.mute();
  4288. // isMuted is the opposite of audioEnabled
  4289. connection.emuc.addAudioInfoToPresence(mute);
  4290. connection.emuc.sendPresence();
  4291. callback();
  4292. return true;
  4293. },
  4294. // Really mute video, i.e. dont even send black frames
  4295. muteVideo: function (pc, unmute) {
  4296. // FIXME: this probably needs another of those lovely state safeguards...
  4297. // which checks for iceconn == connected and sigstate == stable
  4298. pc.setRemoteDescription(pc.remoteDescription,
  4299. function () {
  4300. pc.createAnswer(
  4301. function (answer) {
  4302. var sdp = new SDP(answer.sdp);
  4303. if (sdp.media.length > 1) {
  4304. if (unmute)
  4305. sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
  4306. else
  4307. sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
  4308. sdp.raw = sdp.session + sdp.media.join('');
  4309. answer.sdp = sdp.raw;
  4310. }
  4311. pc.setLocalDescription(answer,
  4312. function () {
  4313. console.log('mute SLD ok');
  4314. },
  4315. function (error) {
  4316. console.log('mute SLD error');
  4317. UI.messageHandler.showError('Error',
  4318. 'Oops! Something went wrong and we failed to ' +
  4319. 'mute! (SLD Failure)');
  4320. }
  4321. );
  4322. },
  4323. function (error) {
  4324. console.log(error);
  4325. UI.messageHandler.showError();
  4326. }
  4327. );
  4328. },
  4329. function (error) {
  4330. console.log('muteVideo SRD error');
  4331. UI.messageHandler.showError('Error',
  4332. 'Oops! Something went wrong and we failed to stop video!' +
  4333. '(SRD Failure)');
  4334. }
  4335. );
  4336. },
  4337. toggleRecording: function (tokenEmptyCallback,
  4338. startingCallback, startedCallback) {
  4339. Recording.toggleRecording(tokenEmptyCallback,
  4340. startingCallback, startedCallback, connection);
  4341. },
  4342. addToPresence: function (name, value, dontSend) {
  4343. switch (name)
  4344. {
  4345. case "displayName":
  4346. connection.emuc.addDisplayNameToPresence(value);
  4347. break;
  4348. case "etherpad":
  4349. connection.emuc.addEtherpadToPresence(value);
  4350. break;
  4351. case "prezi":
  4352. connection.emuc.addPreziToPresence(value, 0);
  4353. break;
  4354. case "preziSlide":
  4355. connection.emuc.addCurrentSlideToPresence(value);
  4356. break;
  4357. case "connectionQuality":
  4358. connection.emuc.addConnectionInfoToPresence(value);
  4359. break;
  4360. case "email":
  4361. connection.emuc.addEmailToPresence(value);
  4362. default :
  4363. console.log("Unknown tag for presence.");
  4364. return;
  4365. }
  4366. if(!dontSend)
  4367. connection.emuc.sendPresence();
  4368. },
  4369. sendLogs: function (data) {
  4370. if(!connection.emuc.focusMucJid)
  4371. return;
  4372. var deflate = true;
  4373. var content = JSON.stringify(data);
  4374. if (deflate) {
  4375. content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
  4376. }
  4377. content = Base64.encode(content);
  4378. // XEP-0337-ish
  4379. var message = $msg({to: connection.emuc.focusMucJid, type: 'normal'});
  4380. message.c('log', { xmlns: 'urn:xmpp:eventlog',
  4381. id: 'PeerConnectionStats'});
  4382. message.c('message').t(content).up();
  4383. if (deflate) {
  4384. message.c('tag', {name: "deflated", value: "true"}).up();
  4385. }
  4386. message.up();
  4387. connection.send(message);
  4388. },
  4389. populateData: function () {
  4390. var data = {};
  4391. if (connection.jingle) {
  4392. data = connection.jingle.populateData();
  4393. }
  4394. return data;
  4395. },
  4396. getLogger: function () {
  4397. if(connection.logger)
  4398. return connection.logger.log;
  4399. return null;
  4400. },
  4401. getPrezi: function () {
  4402. return connection.emuc.getPrezi(this.myJid());
  4403. },
  4404. removePreziFromPresence: function () {
  4405. connection.emuc.removePreziFromPresence();
  4406. connection.emuc.sendPresence();
  4407. },
  4408. sendChatMessage: function (message, nickname) {
  4409. connection.emuc.sendMessage(message, nickname);
  4410. },
  4411. setSubject: function (topic) {
  4412. connection.emuc.setSubject(topic);
  4413. },
  4414. lockRoom: function (key, onSuccess, onError, onNotSupported) {
  4415. connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
  4416. },
  4417. dial: function (to, from, roomName,roomPass) {
  4418. connection.rayo.dial(to, from, roomName,roomPass);
  4419. },
  4420. setMute: function (jid, mute) {
  4421. connection.moderate.setMute(jid, mute);
  4422. },
  4423. eject: function (jid) {
  4424. connection.moderate.eject(jid);
  4425. },
  4426. findJidFromResource: function (resource) {
  4427. return connection.emuc.findJidFromResource(resource);
  4428. },
  4429. getMembers: function () {
  4430. return connection.emuc.members;
  4431. }
  4432. };
  4433. module.exports = XMPP;
  4434. },{"./SDP":2,"./moderator":6,"./recording":7,"./strophe.emuc":8,"./strophe.jingle":9,"./strophe.logger":10,"./strophe.moderate":11,"./strophe.rayo":12,"./strophe.util":13,"events":15}],15:[function(require,module,exports){
  4435. // Copyright Joyent, Inc. and other Node contributors.
  4436. //
  4437. // Permission is hereby granted, free of charge, to any person obtaining a
  4438. // copy of this software and associated documentation files (the
  4439. // "Software"), to deal in the Software without restriction, including
  4440. // without limitation the rights to use, copy, modify, merge, publish,
  4441. // distribute, sublicense, and/or sell copies of the Software, and to permit
  4442. // persons to whom the Software is furnished to do so, subject to the
  4443. // following conditions:
  4444. //
  4445. // The above copyright notice and this permission notice shall be included
  4446. // in all copies or substantial portions of the Software.
  4447. //
  4448. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  4449. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  4450. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  4451. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  4452. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  4453. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  4454. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  4455. function EventEmitter() {
  4456. this._events = this._events || {};
  4457. this._maxListeners = this._maxListeners || undefined;
  4458. }
  4459. module.exports = EventEmitter;
  4460. // Backwards-compat with node 0.10.x
  4461. EventEmitter.EventEmitter = EventEmitter;
  4462. EventEmitter.prototype._events = undefined;
  4463. EventEmitter.prototype._maxListeners = undefined;
  4464. // By default EventEmitters will print a warning if more than 10 listeners are
  4465. // added to it. This is a useful default which helps finding memory leaks.
  4466. EventEmitter.defaultMaxListeners = 10;
  4467. // Obviously not all Emitters should be limited to 10. This function allows
  4468. // that to be increased. Set to zero for unlimited.
  4469. EventEmitter.prototype.setMaxListeners = function(n) {
  4470. if (!isNumber(n) || n < 0 || isNaN(n))
  4471. throw TypeError('n must be a positive number');
  4472. this._maxListeners = n;
  4473. return this;
  4474. };
  4475. EventEmitter.prototype.emit = function(type) {
  4476. var er, handler, len, args, i, listeners;
  4477. if (!this._events)
  4478. this._events = {};
  4479. // If there is no 'error' event listener then throw.
  4480. if (type === 'error') {
  4481. if (!this._events.error ||
  4482. (isObject(this._events.error) && !this._events.error.length)) {
  4483. er = arguments[1];
  4484. if (er instanceof Error) {
  4485. throw er; // Unhandled 'error' event
  4486. } else {
  4487. throw TypeError('Uncaught, unspecified "error" event.');
  4488. }
  4489. return false;
  4490. }
  4491. }
  4492. handler = this._events[type];
  4493. if (isUndefined(handler))
  4494. return false;
  4495. if (isFunction(handler)) {
  4496. switch (arguments.length) {
  4497. // fast cases
  4498. case 1:
  4499. handler.call(this);
  4500. break;
  4501. case 2:
  4502. handler.call(this, arguments[1]);
  4503. break;
  4504. case 3:
  4505. handler.call(this, arguments[1], arguments[2]);
  4506. break;
  4507. // slower
  4508. default:
  4509. len = arguments.length;
  4510. args = new Array(len - 1);
  4511. for (i = 1; i < len; i++)
  4512. args[i - 1] = arguments[i];
  4513. handler.apply(this, args);
  4514. }
  4515. } else if (isObject(handler)) {
  4516. len = arguments.length;
  4517. args = new Array(len - 1);
  4518. for (i = 1; i < len; i++)
  4519. args[i - 1] = arguments[i];
  4520. listeners = handler.slice();
  4521. len = listeners.length;
  4522. for (i = 0; i < len; i++)
  4523. listeners[i].apply(this, args);
  4524. }
  4525. return true;
  4526. };
  4527. EventEmitter.prototype.addListener = function(type, listener) {
  4528. var m;
  4529. if (!isFunction(listener))
  4530. throw TypeError('listener must be a function');
  4531. if (!this._events)
  4532. this._events = {};
  4533. // To avoid recursion in the case that type === "newListener"! Before
  4534. // adding it to the listeners, first emit "newListener".
  4535. if (this._events.newListener)
  4536. this.emit('newListener', type,
  4537. isFunction(listener.listener) ?
  4538. listener.listener : listener);
  4539. if (!this._events[type])
  4540. // Optimize the case of one listener. Don't need the extra array object.
  4541. this._events[type] = listener;
  4542. else if (isObject(this._events[type]))
  4543. // If we've already got an array, just append.
  4544. this._events[type].push(listener);
  4545. else
  4546. // Adding the second element, need to change to array.
  4547. this._events[type] = [this._events[type], listener];
  4548. // Check for listener leak
  4549. if (isObject(this._events[type]) && !this._events[type].warned) {
  4550. var m;
  4551. if (!isUndefined(this._maxListeners)) {
  4552. m = this._maxListeners;
  4553. } else {
  4554. m = EventEmitter.defaultMaxListeners;
  4555. }
  4556. if (m && m > 0 && this._events[type].length > m) {
  4557. this._events[type].warned = true;
  4558. console.error('(node) warning: possible EventEmitter memory ' +
  4559. 'leak detected. %d listeners added. ' +
  4560. 'Use emitter.setMaxListeners() to increase limit.',
  4561. this._events[type].length);
  4562. if (typeof console.trace === 'function') {
  4563. // not supported in IE 10
  4564. console.trace();
  4565. }
  4566. }
  4567. }
  4568. return this;
  4569. };
  4570. EventEmitter.prototype.on = EventEmitter.prototype.addListener;
  4571. EventEmitter.prototype.once = function(type, listener) {
  4572. if (!isFunction(listener))
  4573. throw TypeError('listener must be a function');
  4574. var fired = false;
  4575. function g() {
  4576. this.removeListener(type, g);
  4577. if (!fired) {
  4578. fired = true;
  4579. listener.apply(this, arguments);
  4580. }
  4581. }
  4582. g.listener = listener;
  4583. this.on(type, g);
  4584. return this;
  4585. };
  4586. // emits a 'removeListener' event iff the listener was removed
  4587. EventEmitter.prototype.removeListener = function(type, listener) {
  4588. var list, position, length, i;
  4589. if (!isFunction(listener))
  4590. throw TypeError('listener must be a function');
  4591. if (!this._events || !this._events[type])
  4592. return this;
  4593. list = this._events[type];
  4594. length = list.length;
  4595. position = -1;
  4596. if (list === listener ||
  4597. (isFunction(list.listener) && list.listener === listener)) {
  4598. delete this._events[type];
  4599. if (this._events.removeListener)
  4600. this.emit('removeListener', type, listener);
  4601. } else if (isObject(list)) {
  4602. for (i = length; i-- > 0;) {
  4603. if (list[i] === listener ||
  4604. (list[i].listener && list[i].listener === listener)) {
  4605. position = i;
  4606. break;
  4607. }
  4608. }
  4609. if (position < 0)
  4610. return this;
  4611. if (list.length === 1) {
  4612. list.length = 0;
  4613. delete this._events[type];
  4614. } else {
  4615. list.splice(position, 1);
  4616. }
  4617. if (this._events.removeListener)
  4618. this.emit('removeListener', type, listener);
  4619. }
  4620. return this;
  4621. };
  4622. EventEmitter.prototype.removeAllListeners = function(type) {
  4623. var key, listeners;
  4624. if (!this._events)
  4625. return this;
  4626. // not listening for removeListener, no need to emit
  4627. if (!this._events.removeListener) {
  4628. if (arguments.length === 0)
  4629. this._events = {};
  4630. else if (this._events[type])
  4631. delete this._events[type];
  4632. return this;
  4633. }
  4634. // emit removeListener for all listeners on all events
  4635. if (arguments.length === 0) {
  4636. for (key in this._events) {
  4637. if (key === 'removeListener') continue;
  4638. this.removeAllListeners(key);
  4639. }
  4640. this.removeAllListeners('removeListener');
  4641. this._events = {};
  4642. return this;
  4643. }
  4644. listeners = this._events[type];
  4645. if (isFunction(listeners)) {
  4646. this.removeListener(type, listeners);
  4647. } else {
  4648. // LIFO order
  4649. while (listeners.length)
  4650. this.removeListener(type, listeners[listeners.length - 1]);
  4651. }
  4652. delete this._events[type];
  4653. return this;
  4654. };
  4655. EventEmitter.prototype.listeners = function(type) {
  4656. var ret;
  4657. if (!this._events || !this._events[type])
  4658. ret = [];
  4659. else if (isFunction(this._events[type]))
  4660. ret = [this._events[type]];
  4661. else
  4662. ret = this._events[type].slice();
  4663. return ret;
  4664. };
  4665. EventEmitter.listenerCount = function(emitter, type) {
  4666. var ret;
  4667. if (!emitter._events || !emitter._events[type])
  4668. ret = 0;
  4669. else if (isFunction(emitter._events[type]))
  4670. ret = 1;
  4671. else
  4672. ret = emitter._events[type].length;
  4673. return ret;
  4674. };
  4675. function isFunction(arg) {
  4676. return typeof arg === 'function';
  4677. }
  4678. function isNumber(arg) {
  4679. return typeof arg === 'number';
  4680. }
  4681. function isObject(arg) {
  4682. return typeof arg === 'object' && arg !== null;
  4683. }
  4684. function isUndefined(arg) {
  4685. return arg === void 0;
  4686. }
  4687. },{}]},{},[14])(14)
  4688. });