您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

xmpp.bundle.js 189KB

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