123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606 |
- /*
- The MIT License (MIT)
- Copyright (c) 2016 Meetecho
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the "Software"),
- to deal in the Software without restriction, including without limitation
- the rights to use, copy, modify, merge, publish, distribute, sublicense,
- and/or sell copies of the Software, and to permit persons to whom the
- Software is furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- OTHER DEALINGS IN THE SOFTWARE.
- */
- // List of sessions
- Janus.sessions = {};
- Janus.isExtensionEnabled = function() {
- if(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
- // No need for the extension, getDisplayMedia is supported
- return true;
- }
- if(window.navigator.userAgent.match('Chrome')) {
- var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10);
- var maxver = 33;
- if(window.navigator.userAgent.match('Linux'))
- maxver = 35; // "known" crash in chrome 34 and 35 on linux
- if(chromever >= 26 && chromever <= maxver) {
- // Older versions of Chrome don't support this extension-based approach, so lie
- return true;
- }
- return Janus.extension.isInstalled();
- } else {
- // Firefox of others, no need for the extension (but this doesn't mean it will work)
- return true;
- }
- };
- var defaultExtension = {
- // Screensharing Chrome Extension ID
- extensionId: 'hapfgfdkleiggjjpfpenajgdnfckjpaj',
- isInstalled: function() { return document.querySelector('#janus-extension-installed') !== null; },
- getScreen: function (callback) {
- var pending = window.setTimeout(function () {
- var error = new Error('NavigatorUserMediaError');
- error.name = 'The required Chrome extension is not installed: click <a href="#">here</a> to install it. (NOTE: this will need you to refresh the page)';
- return callback(error);
- }, 1000);
- this.cache[pending] = callback;
- window.postMessage({ type: 'janusGetScreen', id: pending }, '*');
- },
- init: function () {
- var cache = {};
- this.cache = cache;
- // Wait for events from the Chrome Extension
- window.addEventListener('message', function (event) {
- if(event.origin != window.location.origin)
- return;
- if(event.data.type == 'janusGotScreen' && cache[event.data.id]) {
- var callback = cache[event.data.id];
- delete cache[event.data.id];
- if (event.data.sourceId === '') {
- // user canceled
- var error = new Error('NavigatorUserMediaError');
- error.name = 'You cancelled the request for permission, giving up...';
- callback(error);
- } else {
- callback(null, event.data.sourceId);
- }
- } else if (event.data.type == 'janusGetScreenPending') {
- console.log('clearing ', event.data.id);
- window.clearTimeout(event.data.id);
- }
- });
- }
- };
- Janus.useDefaultDependencies = function (deps) {
- var f = (deps && deps.fetch) || fetch;
- var p = (deps && deps.Promise) || Promise;
- var socketCls = (deps && deps.WebSocket) || WebSocket;
- return {
- newWebSocket: function(server, proto) { return new socketCls(server, proto); },
- extension: (deps && deps.extension) || defaultExtension,
- isArray: function(arr) { return Array.isArray(arr); },
- webRTCAdapter: (deps && deps.adapter) || adapter,
- httpAPICall: function(url, options) {
- var fetchOptions = {
- method: options.verb,
- headers: {
- 'Accept': 'application/json, text/plain, */*'
- },
- cache: 'no-cache'
- };
- if(options.verb === "POST") {
- fetchOptions.headers['Content-Type'] = 'application/json';
- }
- if(options.withCredentials !== undefined) {
- fetchOptions.credentials = options.withCredentials === true ? 'include' : (options.withCredentials ? options.withCredentials : 'omit');
- }
- if(options.body) {
- fetchOptions.body = JSON.stringify(options.body);
- }
- var fetching = f(url, fetchOptions).catch(function(error) {
- return p.reject({message: 'Probably a network error, is the server down?', error: error});
- });
- /*
- * fetch() does not natively support timeouts.
- * Work around this by starting a timeout manually, and racing it agains the fetch() to see which thing resolves first.
- */
- if(options.timeout) {
- var timeout = new p(function(resolve, reject) {
- var timerId = setTimeout(function() {
- clearTimeout(timerId);
- return reject({message: 'Request timed out', timeout: options.timeout});
- }, options.timeout);
- });
- fetching = p.race([fetching, timeout]);
- }
- fetching.then(function(response) {
- if(response.ok) {
- if(typeof(options.success) === typeof(Janus.noop)) {
- return response.json().then(function(parsed) {
- options.success(parsed);
- }).catch(function(error) {
- return p.reject({message: 'Failed to parse response body', error: error, response: response});
- });
- }
- }
- else {
- return p.reject({message: 'API call failed', response: response});
- }
- }).catch(function(error) {
- if(typeof(options.error) === typeof(Janus.noop)) {
- options.error(error.message || '<< internal error >>', error);
- }
- });
- return fetching;
- }
- }
- };
- Janus.useOldDependencies = function (deps) {
- var jq = (deps && deps.jQuery) || jQuery;
- var socketCls = (deps && deps.WebSocket) || WebSocket;
- return {
- newWebSocket: function(server, proto) { return new socketCls(server, proto); },
- isArray: function(arr) { return jq.isArray(arr); },
- extension: (deps && deps.extension) || defaultExtension,
- webRTCAdapter: (deps && deps.adapter) || adapter,
- httpAPICall: function(url, options) {
- var payload = options.body !== undefined ? {
- contentType: 'application/json',
- data: JSON.stringify(options.body)
- } : {};
- var credentials = options.withCredentials !== undefined ? {xhrFields: {withCredentials: options.withCredentials}} : {};
- return jq.ajax(jq.extend(payload, credentials, {
- url: url,
- type: options.verb,
- cache: false,
- dataType: 'json',
- async: options.async,
- timeout: options.timeout,
- success: function(result) {
- if(typeof(options.success) === typeof(Janus.noop)) {
- options.success(result);
- }
- },
- error: function(xhr, status, err) {
- if(typeof(options.error) === typeof(Janus.noop)) {
- options.error(status, err);
- }
- }
- }));
- }
- };
- };
- Janus.noop = function() {};
- Janus.dataChanDefaultLabel = "JanusDataChannel";
- // Note: in the future we may want to change this, e.g., as was
- // attempted in https://github.com/meetecho/janus-gateway/issues/1670
- Janus.endOfCandidates = null;
- // Stop all tracks from a given stream
- Janus.stopAllTracks = function(stream) {
- try {
- // Try a MediaStreamTrack.stop() for each track
- var tracks = stream.getTracks();
- for(var mst of tracks) {
- Janus.log(mst);
- if(mst) {
- mst.stop();
- }
- }
- } catch(e) {
- // Do nothing if this fails
- }
- }
- // Initialization
- Janus.init = function(options) {
- options = options || {};
- options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop;
- if(Janus.initDone) {
- // Already initialized
- options.callback();
- } else {
- if(typeof console == "undefined" || typeof console.log == "undefined") {
- console = { log: function() {} };
- }
- // Console logging (all debugging disabled by default)
- Janus.trace = Janus.noop;
- Janus.debug = Janus.noop;
- Janus.vdebug = Janus.noop;
- Janus.log = Janus.noop;
- Janus.warn = Janus.noop;
- Janus.error = Janus.noop;
- if(options.debug === true || options.debug === "all") {
- // Enable all debugging levels
- Janus.trace = console.trace.bind(console);
- Janus.debug = console.debug.bind(console);
- Janus.vdebug = console.debug.bind(console);
- Janus.log = console.log.bind(console);
- Janus.warn = console.warn.bind(console);
- Janus.error = console.error.bind(console);
- } else if(Array.isArray(options.debug)) {
- for(var d of options.debug) {
- switch(d) {
- case "trace":
- Janus.trace = console.trace.bind(console);
- break;
- case "debug":
- Janus.debug = console.debug.bind(console);
- break;
- case "vdebug":
- Janus.vdebug = console.debug.bind(console);
- break;
- case "log":
- Janus.log = console.log.bind(console);
- break;
- case "warn":
- Janus.warn = console.warn.bind(console);
- break;
- case "error":
- Janus.error = console.error.bind(console);
- break;
- default:
- console.error("Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'vdebug', 'log', warn', 'error')");
- break;
- }
- }
- }
- Janus.log("Initializing library");
- var usedDependencies = options.dependencies || Janus.useDefaultDependencies();
- Janus.isArray = usedDependencies.isArray;
- Janus.webRTCAdapter = usedDependencies.webRTCAdapter;
- Janus.httpAPICall = usedDependencies.httpAPICall;
- Janus.newWebSocket = usedDependencies.newWebSocket;
- Janus.extension = usedDependencies.extension;
- Janus.extension.init();
- // Helper method to enumerate devices
- Janus.listDevices = function(callback, config) {
- callback = (typeof callback == "function") ? callback : Janus.noop;
- if (config == null) config = { audio: true, video: true };
- if(Janus.isGetUserMediaAvailable()) {
- navigator.mediaDevices.getUserMedia(config)
- .then(function(stream) {
- navigator.mediaDevices.enumerateDevices().then(function(devices) {
- Janus.debug(devices);
- callback(devices);
- // Get rid of the now useless stream
- Janus.stopAllTracks(stream)
- });
- })
- .catch(function(err) {
- Janus.error(err);
- callback([]);
- });
- } else {
- Janus.warn("navigator.mediaDevices unavailable");
- callback([]);
- }
- };
- // Helper methods to attach/reattach a stream to a video element (previously part of adapter.js)
- Janus.attachMediaStream = function(element, stream) {
- try {
- element.srcObject = stream;
- } catch (e) {
- try {
- element.src = URL.createObjectURL(stream);
- } catch (e) {
- Janus.error("Error attaching stream to element");
- }
- }
- };
- Janus.reattachMediaStream = function(to, from) {
- try {
- to.srcObject = from.srcObject;
- } catch (e) {
- try {
- to.src = from.src;
- } catch (e) {
- Janus.error("Error reattaching stream to element");
- }
- }
- };
- // Detect tab close: make sure we don't loose existing onbeforeunload handlers
- // (note: for iOS we need to subscribe to a different event, 'pagehide', see
- // https://gist.github.com/thehunmonkgroup/6bee8941a49b86be31a787fe8f4b8cfe)
- var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
- var eventName = iOS ? 'pagehide' : 'beforeunload';
- var oldOBF = window["on" + eventName];
- window.addEventListener(eventName, function(event) {
- Janus.log("Closing window");
- for(var s in Janus.sessions) {
- if(Janus.sessions[s] && Janus.sessions[s].destroyOnUnload) {
- Janus.log("Destroying session " + s);
- Janus.sessions[s].destroy({unload: true, notifyDestroyed: false});
- }
- }
- if(oldOBF && typeof oldOBF == "function") {
- oldOBF();
- }
- });
- // If this is a Safari Technology Preview, check if VP8 is supported
- Janus.safariVp8 = false;
- if(Janus.webRTCAdapter.browserDetails.browser === 'safari' &&
- Janus.webRTCAdapter.browserDetails.version >= 605) {
- // Let's see if RTCRtpSender.getCapabilities() is there
- if(RTCRtpSender && RTCRtpSender.getCapabilities && RTCRtpSender.getCapabilities("video") &&
- RTCRtpSender.getCapabilities("video").codecs && RTCRtpSender.getCapabilities("video").codecs.length) {
- for(var codec of RTCRtpSender.getCapabilities("video").codecs) {
- if(codec && codec.mimeType && codec.mimeType.toLowerCase() === "video/vp8") {
- Janus.safariVp8 = true;
- break;
- }
- }
- if(Janus.safariVp8) {
- Janus.log("This version of Safari supports VP8");
- } else {
- Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " +
- "try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu");
- }
- } else {
- // We do it in a very ugly way, as there's no alternative...
- // We create a PeerConnection to see if VP8 is in an offer
- var testpc = new RTCPeerConnection({});
- testpc.createOffer({offerToReceiveVideo: true}).then(function(offer) {
- Janus.safariVp8 = offer.sdp.indexOf("VP8") !== -1;
- if(Janus.safariVp8) {
- Janus.log("This version of Safari supports VP8");
- } else {
- Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " +
- "try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu");
- }
- testpc.close();
- testpc = null;
- });
- }
- }
- // Check if this browser supports Unified Plan and transceivers
- // Based on https://codepen.io/anon/pen/ZqLwWV?editors=0010
- Janus.unifiedPlan = false;
- if(Janus.webRTCAdapter.browserDetails.browser === 'firefox' &&
- Janus.webRTCAdapter.browserDetails.version >= 59) {
- // Firefox definitely does, starting from version 59
- Janus.unifiedPlan = true;
- } else if(Janus.webRTCAdapter.browserDetails.browser === 'chrome' &&
- Janus.webRTCAdapter.browserDetails.version < 72) {
- // Chrome does, but it's only usable from version 72 on
- Janus.unifiedPlan = false;
- } else if(!window.RTCRtpTransceiver || !('currentDirection' in RTCRtpTransceiver.prototype)) {
- // Safari supports addTransceiver() but not Unified Plan when
- // currentDirection is not defined (see codepen above).
- Janus.unifiedPlan = false;
- } else {
- // Check if addTransceiver() throws an exception
- var tempPc = new RTCPeerConnection();
- try {
- tempPc.addTransceiver('audio');
- Janus.unifiedPlan = true;
- } catch (e) {}
- tempPc.close();
- }
- Janus.initDone = true;
- options.callback();
- }
- };
- // Helper method to check whether WebRTC is supported by this browser
- Janus.isWebrtcSupported = function() {
- return !!window.RTCPeerConnection;
- };
- // Helper method to check whether devices can be accessed by this browser (e.g., not possible via plain HTTP)
- Janus.isGetUserMediaAvailable = function() {
- return navigator.mediaDevices && navigator.mediaDevices.getUserMedia;
- };
- // Helper method to create random identifiers (e.g., transaction)
- Janus.randomString = function(len) {
- var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- var randomString = '';
- for (var i = 0; i < len; i++) {
- var randomPoz = Math.floor(Math.random() * charSet.length);
- randomString += charSet.substring(randomPoz,randomPoz+1);
- }
- return randomString;
- };
- function Janus(gatewayCallbacks) {
- gatewayCallbacks = gatewayCallbacks || {};
- gatewayCallbacks.success = (typeof gatewayCallbacks.success == "function") ? gatewayCallbacks.success : Janus.noop;
- gatewayCallbacks.error = (typeof gatewayCallbacks.error == "function") ? gatewayCallbacks.error : Janus.noop;
- gatewayCallbacks.destroyed = (typeof gatewayCallbacks.destroyed == "function") ? gatewayCallbacks.destroyed : Janus.noop;
- if(!Janus.initDone) {
- gatewayCallbacks.error("Library not initialized");
- return {};
- }
- if(!Janus.isWebrtcSupported()) {
- gatewayCallbacks.error("WebRTC not supported by this browser");
- return {};
- }
- Janus.log("Library initialized: " + Janus.initDone);
- if(!gatewayCallbacks.server) {
- gatewayCallbacks.error("Invalid server url");
- return {};
- }
- var websockets = false;
- var ws = null;
- var wsHandlers = {};
- var wsKeepaliveTimeoutId = null;
- var servers = null;
- var serversIndex = 0;
- var server = gatewayCallbacks.server;
- if(Janus.isArray(server)) {
- Janus.log("Multiple servers provided (" + server.length + "), will use the first that works");
- server = null;
- servers = gatewayCallbacks.server;
- Janus.debug(servers);
- } else {
- if(server.indexOf("ws") === 0) {
- websockets = true;
- Janus.log("Using WebSockets to contact Janus: " + server);
- } else {
- websockets = false;
- Janus.log("Using REST API to contact Janus: " + server);
- }
- }
- var iceServers = gatewayCallbacks.iceServers || [{urls: "stun:stun.l.google.com:19302"}];
- var iceTransportPolicy = gatewayCallbacks.iceTransportPolicy;
- var bundlePolicy = gatewayCallbacks.bundlePolicy;
- // Whether IPv6 candidates should be gathered
- var ipv6Support = (gatewayCallbacks.ipv6 === true);
- // Whether we should enable the withCredentials flag for XHR requests
- var withCredentials = false;
- if(gatewayCallbacks.withCredentials !== undefined && gatewayCallbacks.withCredentials !== null)
- withCredentials = gatewayCallbacks.withCredentials === true;
- // Optional max events
- var maxev = 10;
- if(gatewayCallbacks.max_poll_events !== undefined && gatewayCallbacks.max_poll_events !== null)
- maxev = gatewayCallbacks.max_poll_events;
- if(maxev < 1)
- maxev = 1;
- // Token to use (only if the token based authentication mechanism is enabled)
- var token = null;
- if(gatewayCallbacks.token !== undefined && gatewayCallbacks.token !== null)
- token = gatewayCallbacks.token;
- // API secret to use (only if the shared API secret is enabled)
- var apisecret = null;
- if(gatewayCallbacks.apisecret !== undefined && gatewayCallbacks.apisecret !== null)
- apisecret = gatewayCallbacks.apisecret;
- // Whether we should destroy this session when onbeforeunload is called
- this.destroyOnUnload = true;
- if(gatewayCallbacks.destroyOnUnload !== undefined && gatewayCallbacks.destroyOnUnload !== null)
- this.destroyOnUnload = (gatewayCallbacks.destroyOnUnload === true);
- // Some timeout-related values
- var keepAlivePeriod = 25000;
- if(gatewayCallbacks.keepAlivePeriod !== undefined && gatewayCallbacks.keepAlivePeriod !== null)
- keepAlivePeriod = gatewayCallbacks.keepAlivePeriod;
- if(isNaN(keepAlivePeriod))
- keepAlivePeriod = 25000;
- var longPollTimeout = 60000;
- if(gatewayCallbacks.longPollTimeout !== undefined && gatewayCallbacks.longPollTimeout !== null)
- longPollTimeout = gatewayCallbacks.longPollTimeout;
- if(isNaN(longPollTimeout))
- longPollTimeout = 60000;
- // overrides for default maxBitrate values for simulcasting
- function getMaxBitrates(simulcastMaxBitrates) {
- var maxBitrates = {
- high: 900000,
- medium: 300000,
- low: 100000,
- };
- if (simulcastMaxBitrates !== undefined && simulcastMaxBitrates !== null) {
- if (simulcastMaxBitrates.high)
- maxBitrates.high = simulcastMaxBitrates.high;
- if (simulcastMaxBitrates.medium)
- maxBitrates.medium = simulcastMaxBitrates.medium;
- if (simulcastMaxBitrates.low)
- maxBitrates.low = simulcastMaxBitrates.low;
- }
- return maxBitrates;
- }
- var connected = false;
- var sessionId = null;
- var pluginHandles = {};
- var that = this;
- var retries = 0;
- var transactions = {};
- createSession(gatewayCallbacks);
- // Public methods
- this.getServer = function() { return server; };
- this.isConnected = function() { return connected; };
- this.reconnect = function(callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- callbacks["reconnect"] = true;
- createSession(callbacks);
- };
- this.getSessionId = function() { return sessionId; };
- this.getInfo = function(callbacks) { getInfo(callbacks); };
- this.destroy = function(callbacks) { destroySession(callbacks); };
- this.attach = function(callbacks) { createHandle(callbacks); };
- function eventHandler() {
- if(sessionId == null)
- return;
- Janus.debug('Long poll...');
- if(!connected) {
- Janus.warn("Is the server down? (connected=false)");
- return;
- }
- var longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime();
- if(maxev)
- longpoll = longpoll + "&maxev=" + maxev;
- if(token)
- longpoll = longpoll + "&token=" + encodeURIComponent(token);
- if(apisecret)
- longpoll = longpoll + "&apisecret=" + encodeURIComponent(apisecret);
- Janus.httpAPICall(longpoll, {
- verb: 'GET',
- withCredentials: withCredentials,
- success: handleEvent,
- timeout: longPollTimeout,
- error: function(textStatus, errorThrown) {
- Janus.error(textStatus + ":", errorThrown);
- retries++;
- if(retries > 3) {
- // Did we just lose the server? :-(
- connected = false;
- gatewayCallbacks.error("Lost connection to the server (is it down?)");
- return;
- }
- eventHandler();
- }
- });
- }
- // Private event handler: this will trigger plugin callbacks, if set
- function handleEvent(json, skipTimeout) {
- retries = 0;
- if(!websockets && sessionId !== undefined && sessionId !== null && skipTimeout !== true)
- eventHandler();
- if(!websockets && Janus.isArray(json)) {
- // We got an array: it means we passed a maxev > 1, iterate on all objects
- for(var i=0; i<json.length; i++) {
- handleEvent(json[i], true);
- }
- return;
- }
- if(json["janus"] === "keepalive") {
- // Nothing happened
- Janus.vdebug("Got a keepalive on session " + sessionId);
- return;
- } else if(json["janus"] === "server_info") {
- // Just info on the Janus instance
- Janus.debug("Got info on the Janus instance");
- Janus.debug(json);
- var transaction = json["transaction"];
- if(transaction) {
- var reportSuccess = transactions[transaction];
- if(reportSuccess)
- reportSuccess(json);
- delete transactions[transaction];
- }
- return;
- } else if(json["janus"] === "ack") {
- // Just an ack, we can probably ignore
- Janus.debug("Got an ack on session " + sessionId);
- Janus.debug(json);
- var transaction = json["transaction"];
- if(transaction) {
- var reportSuccess = transactions[transaction];
- if(reportSuccess)
- reportSuccess(json);
- delete transactions[transaction];
- }
- return;
- } else if(json["janus"] === "success") {
- // Success!
- Janus.debug("Got a success on session " + sessionId);
- Janus.debug(json);
- var transaction = json["transaction"];
- if(transaction) {
- var reportSuccess = transactions[transaction];
- if(reportSuccess)
- reportSuccess(json);
- delete transactions[transaction];
- }
- return;
- } else if(json["janus"] === "trickle") {
- // We got a trickle candidate from Janus
- var sender = json["sender"];
- if(!sender) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(!pluginHandle) {
- Janus.debug("This handle is not attached to this session");
- return;
- }
- var candidate = json["candidate"];
- Janus.debug("Got a trickled candidate on session " + sessionId);
- Janus.debug(candidate);
- var config = pluginHandle.webrtcStuff;
- if(config.pc && config.remoteSdp) {
- // Add candidate right now
- Janus.debug("Adding remote candidate:", candidate);
- if(!candidate || candidate.completed === true) {
- // end-of-candidates
- config.pc.addIceCandidate(Janus.endOfCandidates);
- } else {
- // New candidate
- config.pc.addIceCandidate(candidate);
- }
- } else {
- // We didn't do setRemoteDescription (trickle got here before the offer?)
- Janus.debug("We didn't do setRemoteDescription (trickle got here before the offer?), caching candidate");
- if(!config.candidates)
- config.candidates = [];
- config.candidates.push(candidate);
- Janus.debug(config.candidates);
- }
- } else if(json["janus"] === "webrtcup") {
- // The PeerConnection with the server is up! Notify this
- Janus.debug("Got a webrtcup event on session " + sessionId);
- Janus.debug(json);
- var sender = json["sender"];
- if(!sender) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(!pluginHandle) {
- Janus.debug("This handle is not attached to this session");
- return;
- }
- pluginHandle.webrtcState(true);
- return;
- } else if(json["janus"] === "hangup") {
- // A plugin asked the core to hangup a PeerConnection on one of our handles
- Janus.debug("Got a hangup event on session " + sessionId);
- Janus.debug(json);
- var sender = json["sender"];
- if(!sender) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(!pluginHandle) {
- Janus.debug("This handle is not attached to this session");
- return;
- }
- pluginHandle.webrtcState(false, json["reason"]);
- pluginHandle.hangup();
- } else if(json["janus"] === "detached") {
- // A plugin asked the core to detach one of our handles
- Janus.debug("Got a detached event on session " + sessionId);
- Janus.debug(json);
- var sender = json["sender"];
- if(!sender) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(!pluginHandle) {
- // Don't warn here because destroyHandle causes this situation.
- return;
- }
- pluginHandle.detached = true;
- pluginHandle.ondetached();
- pluginHandle.detach();
- } else if(json["janus"] === "media") {
- // Media started/stopped flowing
- Janus.debug("Got a media event on session " + sessionId);
- Janus.debug(json);
- var sender = json["sender"];
- if(!sender) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(!pluginHandle) {
- Janus.debug("This handle is not attached to this session");
- return;
- }
- pluginHandle.mediaState(json["type"], json["receiving"]);
- } else if(json["janus"] === "slowlink") {
- Janus.debug("Got a slowlink event on session " + sessionId);
- Janus.debug(json);
- // Trouble uplink or downlink
- var sender = json["sender"];
- if(!sender) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(!pluginHandle) {
- Janus.debug("This handle is not attached to this session");
- return;
- }
- pluginHandle.slowLink(json["uplink"], json["lost"]);
- } else if(json["janus"] === "error") {
- // Oops, something wrong happened
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- Janus.debug(json);
- var transaction = json["transaction"];
- if(transaction) {
- var reportSuccess = transactions[transaction];
- if(reportSuccess) {
- reportSuccess(json);
- }
- delete transactions[transaction];
- }
- return;
- } else if(json["janus"] === "event") {
- Janus.debug("Got a plugin event on session " + sessionId);
- Janus.debug(json);
- var sender = json["sender"];
- if(!sender) {
- Janus.warn("Missing sender...");
- return;
- }
- var plugindata = json["plugindata"];
- if(!plugindata) {
- Janus.warn("Missing plugindata...");
- return;
- }
- Janus.debug(" -- Event is coming from " + sender + " (" + plugindata["plugin"] + ")");
- var data = plugindata["data"];
- Janus.debug(data);
- var pluginHandle = pluginHandles[sender];
- if(!pluginHandle) {
- Janus.warn("This handle is not attached to this session");
- return;
- }
- var jsep = json["jsep"];
- if(jsep) {
- Janus.debug("Handling SDP as well...");
- Janus.debug(jsep);
- }
- var callback = pluginHandle.onmessage;
- if(callback) {
- Janus.debug("Notifying application...");
- // Send to callback specified when attaching plugin handle
- callback(data, jsep);
- } else {
- // Send to generic callback (?)
- Janus.debug("No provided notification callback");
- }
- } else if(json["janus"] === "timeout") {
- Janus.error("Timeout on session " + sessionId);
- Janus.debug(json);
- if (websockets) {
- ws.close(3504, "Gateway timeout");
- }
- return;
- } else {
- Janus.warn("Unknown message/event '" + json["janus"] + "' on session " + sessionId);
- Janus.debug(json);
- }
- }
- // Private helper to send keep-alive messages on WebSockets
- function keepAlive() {
- if(!server || !websockets || !connected)
- return;
- wsKeepaliveTimeoutId = setTimeout(keepAlive, keepAlivePeriod);
- var request = { "janus": "keepalive", "session_id": sessionId, "transaction": Janus.randomString(12) };
- if(token)
- request["token"] = token;
- if(apisecret)
- request["apisecret"] = apisecret;
- ws.send(JSON.stringify(request));
- }
- // Private method to create a session
- function createSession(callbacks) {
- var transaction = Janus.randomString(12);
- var request = { "janus": "create", "transaction": transaction };
- if(callbacks["reconnect"]) {
- // We're reconnecting, claim the session
- connected = false;
- request["janus"] = "claim";
- request["session_id"] = sessionId;
- // If we were using websockets, ignore the old connection
- if(ws) {
- ws.onopen = null;
- ws.onerror = null;
- ws.onclose = null;
- if(wsKeepaliveTimeoutId) {
- clearTimeout(wsKeepaliveTimeoutId);
- wsKeepaliveTimeoutId = null;
- }
- }
- }
- if(token)
- request["token"] = token;
- if(apisecret)
- request["apisecret"] = apisecret;
- if(!server && Janus.isArray(servers)) {
- // We still need to find a working server from the list we were given
- server = servers[serversIndex];
- if(server.indexOf("ws") === 0) {
- websockets = true;
- Janus.log("Server #" + (serversIndex+1) + ": trying WebSockets to contact Janus (" + server + ")");
- } else {
- websockets = false;
- Janus.log("Server #" + (serversIndex+1) + ": trying REST API to contact Janus (" + server + ")");
- }
- }
- if(websockets) {
- ws = Janus.newWebSocket(server, 'janus-protocol');
- wsHandlers = {
- 'error': function() {
- Janus.error("Error connecting to the Janus WebSockets server... " + server);
- if (Janus.isArray(servers) && !callbacks["reconnect"]) {
- serversIndex++;
- if (serversIndex === servers.length) {
- // We tried all the servers the user gave us and they all failed
- callbacks.error("Error connecting to any of the provided Janus servers: Is the server down?");
- return;
- }
- // Let's try the next server
- server = null;
- setTimeout(function() {
- createSession(callbacks);
- }, 200);
- return;
- }
- callbacks.error("Error connecting to the Janus WebSockets server: Is the server down?");
- },
- 'open': function() {
- // We need to be notified about the success
- transactions[transaction] = function(json) {
- Janus.debug(json);
- if (json["janus"] !== "success") {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- callbacks.error(json["error"].reason);
- return;
- }
- wsKeepaliveTimeoutId = setTimeout(keepAlive, keepAlivePeriod);
- connected = true;
- sessionId = json["session_id"] ? json["session_id"] : json.data["id"];
- if(callbacks["reconnect"]) {
- Janus.log("Claimed session: " + sessionId);
- } else {
- Janus.log("Created session: " + sessionId);
- }
- Janus.sessions[sessionId] = that;
- callbacks.success();
- };
- ws.send(JSON.stringify(request));
- },
- 'message': function(event) {
- handleEvent(JSON.parse(event.data));
- },
- 'close': function() {
- if (!server || !connected) {
- return;
- }
- connected = false;
- // FIXME What if this is called when the page is closed?
- gatewayCallbacks.error("Lost connection to the server (is it down?)");
- }
- };
- for(var eventName in wsHandlers) {
- ws.addEventListener(eventName, wsHandlers[eventName]);
- }
- return;
- }
- Janus.httpAPICall(server, {
- verb: 'POST',
- withCredentials: withCredentials,
- body: request,
- success: function(json) {
- Janus.debug(json);
- if(json["janus"] !== "success") {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- callbacks.error(json["error"].reason);
- return;
- }
- connected = true;
- sessionId = json["session_id"] ? json["session_id"] : json.data["id"];
- if(callbacks["reconnect"]) {
- Janus.log("Claimed session: " + sessionId);
- } else {
- Janus.log("Created session: " + sessionId);
- }
- Janus.sessions[sessionId] = that;
- eventHandler();
- callbacks.success();
- },
- error: function(textStatus, errorThrown) {
- Janus.error(textStatus + ":", errorThrown); // FIXME
- if(Janus.isArray(servers) && !callbacks["reconnect"]) {
- serversIndex++;
- if(serversIndex === servers.length) {
- // We tried all the servers the user gave us and they all failed
- callbacks.error("Error connecting to any of the provided Janus servers: Is the server down?");
- return;
- }
- // Let's try the next server
- server = null;
- setTimeout(function() { createSession(callbacks); }, 200);
- return;
- }
- if(errorThrown === "")
- callbacks.error(textStatus + ": Is the server down?");
- else
- callbacks.error(textStatus + ": " + errorThrown);
- }
- });
- }
- // Private method to get info on the server
- function getInfo(callbacks) {
- callbacks = callbacks || {};
- // FIXME This method triggers a success even when we fail
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- Janus.log("Getting info on Janus instance");
- if(!connected) {
- Janus.warn("Is the server down? (connected=false)");
- callbacks.error("Is the server down? (connected=false)");
- return;
- }
- // We just need to send an "info" request
- var transaction = Janus.randomString(12);
- var request = { "janus": "info", "transaction": transaction };
- if(token)
- request["token"] = token;
- if(apisecret)
- request["apisecret"] = apisecret;
- if(websockets) {
- transactions[transaction] = function(json) {
- Janus.log("Server info:");
- Janus.debug(json);
- if(json["janus"] !== "server_info") {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- }
- callbacks.success(json);
- }
- ws.send(JSON.stringify(request));
- return;
- }
- Janus.httpAPICall(server, {
- verb: 'POST',
- withCredentials: withCredentials,
- body: request,
- success: function(json) {
- Janus.log("Server info:");
- Janus.debug(json);
- if(json["janus"] !== "server_info") {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- }
- callbacks.success(json);
- },
- error: function(textStatus, errorThrown) {
- Janus.error(textStatus + ":", errorThrown); // FIXME
- if(errorThrown === "")
- callbacks.error(textStatus + ": Is the server down?");
- else
- callbacks.error(textStatus + ": " + errorThrown);
- }
- });
- }
- // Private method to destroy a session
- function destroySession(callbacks) {
- callbacks = callbacks || {};
- // FIXME This method triggers a success even when we fail
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- var unload = (callbacks.unload === true);
- var notifyDestroyed = true;
- if(callbacks.notifyDestroyed !== undefined && callbacks.notifyDestroyed !== null)
- notifyDestroyed = (callbacks.notifyDestroyed === true);
- var cleanupHandles = (callbacks.cleanupHandles === true);
- Janus.log("Destroying session " + sessionId + " (unload=" + unload + ")");
- if(!sessionId) {
- Janus.warn("No session to destroy");
- callbacks.success();
- if(notifyDestroyed)
- gatewayCallbacks.destroyed();
- return;
- }
- if(cleanupHandles) {
- for(var handleId in pluginHandles)
- destroyHandle(handleId, { noRequest: true });
- }
- if(!connected) {
- Janus.warn("Is the server down? (connected=false)");
- sessionId = null;
- callbacks.success();
- return;
- }
- // No need to destroy all handles first, Janus will do that itself
- var request = { "janus": "destroy", "transaction": Janus.randomString(12) };
- if(token)
- request["token"] = token;
- if(apisecret)
- request["apisecret"] = apisecret;
- if(unload) {
- // We're unloading the page: use sendBeacon for HTTP instead,
- // or just close the WebSocket connection if we're using that
- if(websockets) {
- ws.onclose = null;
- ws.close();
- ws = null;
- } else {
- navigator.sendBeacon(server + "/" + sessionId, JSON.stringify(request));
- }
- Janus.log("Destroyed session:");
- sessionId = null;
- connected = false;
- callbacks.success();
- if(notifyDestroyed)
- gatewayCallbacks.destroyed();
- return;
- }
- if(websockets) {
- request["session_id"] = sessionId;
- var unbindWebSocket = function() {
- for(var eventName in wsHandlers) {
- ws.removeEventListener(eventName, wsHandlers[eventName]);
- }
- ws.removeEventListener('message', onUnbindMessage);
- ws.removeEventListener('error', onUnbindError);
- if(wsKeepaliveTimeoutId) {
- clearTimeout(wsKeepaliveTimeoutId);
- }
- ws.close();
- };
- var onUnbindMessage = function(event){
- var data = JSON.parse(event.data);
- if(data.session_id == request.session_id && data.transaction == request.transaction) {
- unbindWebSocket();
- callbacks.success();
- if(notifyDestroyed)
- gatewayCallbacks.destroyed();
- }
- };
- var onUnbindError = function(event) {
- unbindWebSocket();
- callbacks.error("Failed to destroy the server: Is the server down?");
- if(notifyDestroyed)
- gatewayCallbacks.destroyed();
- };
- ws.addEventListener('message', onUnbindMessage);
- ws.addEventListener('error', onUnbindError);
- if (ws.readyState === 1) {
- ws.send(JSON.stringify(request));
- } else {
- onUnbindError();
- }
- return;
- }
- Janus.httpAPICall(server + "/" + sessionId, {
- verb: 'POST',
- withCredentials: withCredentials,
- body: request,
- success: function(json) {
- Janus.log("Destroyed session:");
- Janus.debug(json);
- sessionId = null;
- connected = false;
- if(json["janus"] !== "success") {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- }
- callbacks.success();
- if(notifyDestroyed)
- gatewayCallbacks.destroyed();
- },
- error: function(textStatus, errorThrown) {
- Janus.error(textStatus + ":", errorThrown); // FIXME
- // Reset everything anyway
- sessionId = null;
- connected = false;
- callbacks.success();
- if(notifyDestroyed)
- gatewayCallbacks.destroyed();
- }
- });
- }
- // Private method to create a plugin handle
- function createHandle(callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- callbacks.consentDialog = (typeof callbacks.consentDialog == "function") ? callbacks.consentDialog : Janus.noop;
- callbacks.iceState = (typeof callbacks.iceState == "function") ? callbacks.iceState : Janus.noop;
- callbacks.mediaState = (typeof callbacks.mediaState == "function") ? callbacks.mediaState : Janus.noop;
- callbacks.webrtcState = (typeof callbacks.webrtcState == "function") ? callbacks.webrtcState : Janus.noop;
- callbacks.slowLink = (typeof callbacks.slowLink == "function") ? callbacks.slowLink : Janus.noop;
- callbacks.onmessage = (typeof callbacks.onmessage == "function") ? callbacks.onmessage : Janus.noop;
- callbacks.onlocalstream = (typeof callbacks.onlocalstream == "function") ? callbacks.onlocalstream : Janus.noop;
- callbacks.onremotestream = (typeof callbacks.onremotestream == "function") ? callbacks.onremotestream : Janus.noop;
- callbacks.ondata = (typeof callbacks.ondata == "function") ? callbacks.ondata : Janus.noop;
- callbacks.ondataopen = (typeof callbacks.ondataopen == "function") ? callbacks.ondataopen : Janus.noop;
- callbacks.oncleanup = (typeof callbacks.oncleanup == "function") ? callbacks.oncleanup : Janus.noop;
- callbacks.ondetached = (typeof callbacks.ondetached == "function") ? callbacks.ondetached : Janus.noop;
- if(!connected) {
- Janus.warn("Is the server down? (connected=false)");
- callbacks.error("Is the server down? (connected=false)");
- return;
- }
- var plugin = callbacks.plugin;
- if(!plugin) {
- Janus.error("Invalid plugin");
- callbacks.error("Invalid plugin");
- return;
- }
- var opaqueId = callbacks.opaqueId;
- var handleToken = callbacks.token ? callbacks.token : token;
- var transaction = Janus.randomString(12);
- var request = { "janus": "attach", "plugin": plugin, "opaque_id": opaqueId, "transaction": transaction };
- if(handleToken)
- request["token"] = handleToken;
- if(apisecret)
- request["apisecret"] = apisecret;
- if(websockets) {
- transactions[transaction] = function(json) {
- Janus.debug(json);
- if(json["janus"] !== "success") {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason);
- return;
- }
- var handleId = json.data["id"];
- Janus.log("Created handle: " + handleId);
- var pluginHandle =
- {
- session : that,
- plugin : plugin,
- id : handleId,
- token : handleToken,
- detached : false,
- webrtcStuff : {
- started : false,
- myStream : null,
- streamExternal : false,
- remoteStream : null,
- mySdp : null,
- mediaConstraints : null,
- pc : null,
- dataChannel : {},
- dtmfSender : null,
- trickle : true,
- iceDone : false,
- volume : {
- value : null,
- timer : null
- },
- bitrate : {
- value : null,
- bsnow : null,
- bsbefore : null,
- tsnow : null,
- tsbefore : null,
- timer : null
- }
- },
- getId : function() { return handleId; },
- getPlugin : function() { return plugin; },
- getVolume : function() { return getVolume(handleId, true); },
- getRemoteVolume : function() { return getVolume(handleId, true); },
- getLocalVolume : function() { return getVolume(handleId, false); },
- isAudioMuted : function() { return isMuted(handleId, false); },
- muteAudio : function() { return mute(handleId, false, true); },
- unmuteAudio : function() { return mute(handleId, false, false); },
- isVideoMuted : function() { return isMuted(handleId, true); },
- muteVideo : function() { return mute(handleId, true, true); },
- unmuteVideo : function() { return mute(handleId, true, false); },
- getBitrate : function() { return getBitrate(handleId); },
- send : function(callbacks) { sendMessage(handleId, callbacks); },
- data : function(callbacks) { sendData(handleId, callbacks); },
- dtmf : function(callbacks) { sendDtmf(handleId, callbacks); },
- consentDialog : callbacks.consentDialog,
- iceState : callbacks.iceState,
- mediaState : callbacks.mediaState,
- webrtcState : callbacks.webrtcState,
- slowLink : callbacks.slowLink,
- onmessage : callbacks.onmessage,
- createOffer : function(callbacks) { prepareWebrtc(handleId, true, callbacks); },
- createAnswer : function(callbacks) { prepareWebrtc(handleId, false, callbacks); },
- handleRemoteJsep : function(callbacks) { prepareWebrtcPeer(handleId, callbacks); },
- onlocalstream : callbacks.onlocalstream,
- onremotestream : callbacks.onremotestream,
- ondata : callbacks.ondata,
- ondataopen : callbacks.ondataopen,
- oncleanup : callbacks.oncleanup,
- ondetached : callbacks.ondetached,
- hangup : function(sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },
- detach : function(callbacks) { destroyHandle(handleId, callbacks); }
- };
- pluginHandles[handleId] = pluginHandle;
- callbacks.success(pluginHandle);
- };
- request["session_id"] = sessionId;
- ws.send(JSON.stringify(request));
- return;
- }
- Janus.httpAPICall(server + "/" + sessionId, {
- verb: 'POST',
- withCredentials: withCredentials,
- body: request,
- success: function(json) {
- Janus.debug(json);
- if(json["janus"] !== "success") {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason);
- return;
- }
- var handleId = json.data["id"];
- Janus.log("Created handle: " + handleId);
- var pluginHandle =
- {
- session : that,
- plugin : plugin,
- id : handleId,
- token : handleToken,
- detached : false,
- webrtcStuff : {
- started : false,
- myStream : null,
- streamExternal : false,
- remoteStream : null,
- mySdp : null,
- mediaConstraints : null,
- pc : null,
- dataChannel : {},
- dtmfSender : null,
- trickle : true,
- iceDone : false,
- volume : {
- value : null,
- timer : null
- },
- bitrate : {
- value : null,
- bsnow : null,
- bsbefore : null,
- tsnow : null,
- tsbefore : null,
- timer : null
- }
- },
- getId : function() { return handleId; },
- getPlugin : function() { return plugin; },
- getVolume : function() { return getVolume(handleId, true); },
- getRemoteVolume : function() { return getVolume(handleId, true); },
- getLocalVolume : function() { return getVolume(handleId, false); },
- isAudioMuted : function() { return isMuted(handleId, false); },
- muteAudio : function() { return mute(handleId, false, true); },
- unmuteAudio : function() { return mute(handleId, false, false); },
- isVideoMuted : function() { return isMuted(handleId, true); },
- muteVideo : function() { return mute(handleId, true, true); },
- unmuteVideo : function() { return mute(handleId, true, false); },
- getBitrate : function() { return getBitrate(handleId); },
- send : function(callbacks) { sendMessage(handleId, callbacks); },
- data : function(callbacks) { sendData(handleId, callbacks); },
- dtmf : function(callbacks) { sendDtmf(handleId, callbacks); },
- consentDialog : callbacks.consentDialog,
- iceState : callbacks.iceState,
- mediaState : callbacks.mediaState,
- webrtcState : callbacks.webrtcState,
- slowLink : callbacks.slowLink,
- onmessage : callbacks.onmessage,
- createOffer : function(callbacks) { prepareWebrtc(handleId, true, callbacks); },
- createAnswer : function(callbacks) { prepareWebrtc(handleId, false, callbacks); },
- handleRemoteJsep : function(callbacks) { prepareWebrtcPeer(handleId, callbacks); },
- onlocalstream : callbacks.onlocalstream,
- onremotestream : callbacks.onremotestream,
- ondata : callbacks.ondata,
- ondataopen : callbacks.ondataopen,
- oncleanup : callbacks.oncleanup,
- ondetached : callbacks.ondetached,
- hangup : function(sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },
- detach : function(callbacks) { destroyHandle(handleId, callbacks); }
- }
- pluginHandles[handleId] = pluginHandle;
- callbacks.success(pluginHandle);
- },
- error: function(textStatus, errorThrown) {
- Janus.error(textStatus + ":", errorThrown); // FIXME
- if(errorThrown === "")
- callbacks.error(textStatus + ": Is the server down?");
- else
- callbacks.error(textStatus + ": " + errorThrown);
- }
- });
- }
- // Private method to send a message
- function sendMessage(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- if(!connected) {
- Janus.warn("Is the server down? (connected=false)");
- callbacks.error("Is the server down? (connected=false)");
- return;
- }
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var message = callbacks.message;
- var jsep = callbacks.jsep;
- var transaction = Janus.randomString(12);
- var request = { "janus": "message", "body": message, "transaction": transaction };
- if(pluginHandle.token)
- request["token"] = pluginHandle.token;
- if(apisecret)
- request["apisecret"] = apisecret;
- if(jsep) {
- request.jsep = {
- type: jsep.type,
- sdp: jsep.sdp
- };
- if(jsep.e2ee)
- request.jsep.e2ee = true;
- }
- Janus.debug("Sending message to plugin (handle=" + handleId + "):");
- Janus.debug(request);
- if(websockets) {
- request["session_id"] = sessionId;
- request["handle_id"] = handleId;
- transactions[transaction] = function(json) {
- Janus.debug("Message sent!");
- Janus.debug(json);
- if(json["janus"] === "success") {
- // We got a success, must have been a synchronous transaction
- var plugindata = json["plugindata"];
- if(!plugindata) {
- Janus.warn("Request succeeded, but missing plugindata...");
- callbacks.success();
- return;
- }
- Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")");
- var data = plugindata["data"];
- Janus.debug(data);
- callbacks.success(data);
- return;
- } else if(json["janus"] !== "ack") {
- // Not a success and not an ack, must be an error
- if(json["error"]) {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- callbacks.error(json["error"].code + " " + json["error"].reason);
- } else {
- Janus.error("Unknown error"); // FIXME
- callbacks.error("Unknown error");
- }
- return;
- }
- // If we got here, the plugin decided to handle the request asynchronously
- callbacks.success();
- };
- ws.send(JSON.stringify(request));
- return;
- }
- Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
- verb: 'POST',
- withCredentials: withCredentials,
- body: request,
- success: function(json) {
- Janus.debug("Message sent!");
- Janus.debug(json);
- if(json["janus"] === "success") {
- // We got a success, must have been a synchronous transaction
- var plugindata = json["plugindata"];
- if(!plugindata) {
- Janus.warn("Request succeeded, but missing plugindata...");
- callbacks.success();
- return;
- }
- Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")");
- var data = plugindata["data"];
- Janus.debug(data);
- callbacks.success(data);
- return;
- } else if(json["janus"] !== "ack") {
- // Not a success and not an ack, must be an error
- if(json["error"]) {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- callbacks.error(json["error"].code + " " + json["error"].reason);
- } else {
- Janus.error("Unknown error"); // FIXME
- callbacks.error("Unknown error");
- }
- return;
- }
- // If we got here, the plugin decided to handle the request asynchronously
- callbacks.success();
- },
- error: function(textStatus, errorThrown) {
- Janus.error(textStatus + ":", errorThrown); // FIXME
- callbacks.error(textStatus + ": " + errorThrown);
- }
- });
- }
- // Private method to send a trickle candidate
- function sendTrickleCandidate(handleId, candidate) {
- if(!connected) {
- Janus.warn("Is the server down? (connected=false)");
- return;
- }
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- return;
- }
- var request = { "janus": "trickle", "candidate": candidate, "transaction": Janus.randomString(12) };
- if(pluginHandle.token)
- request["token"] = pluginHandle.token;
- if(apisecret)
- request["apisecret"] = apisecret;
- Janus.vdebug("Sending trickle candidate (handle=" + handleId + "):");
- Janus.vdebug(request);
- if(websockets) {
- request["session_id"] = sessionId;
- request["handle_id"] = handleId;
- ws.send(JSON.stringify(request));
- return;
- }
- Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
- verb: 'POST',
- withCredentials: withCredentials,
- body: request,
- success: function(json) {
- Janus.vdebug("Candidate sent!");
- Janus.vdebug(json);
- if(json["janus"] !== "ack") {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- return;
- }
- },
- error: function(textStatus, errorThrown) {
- Janus.error(textStatus + ":", errorThrown); // FIXME
- }
- });
- }
- // Private method to create a data channel
- function createDataChannel(handleId, dclabel, dcprotocol, incoming, pendingData) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- var onDataChannelMessage = function(event) {
- Janus.log('Received message on data channel:', event);
- var label = event.target.label;
- pluginHandle.ondata(event.data, label);
- };
- var onDataChannelStateChange = function(event) {
- Janus.log('Received state change on data channel:', event);
- var label = event.target.label;
- var protocol = event.target.protocol;
- var dcState = config.dataChannel[label] ? config.dataChannel[label].readyState : "null";
- Janus.log('State change on <' + label + '> data channel: ' + dcState);
- if(dcState === 'open') {
- // Any pending messages to send?
- if(config.dataChannel[label].pending && config.dataChannel[label].pending.length > 0) {
- Janus.log("Sending pending messages on <" + label + ">:", config.dataChannel[label].pending.length);
- for(var data of config.dataChannel[label].pending) {
- Janus.log("Sending data on data channel <" + label + ">");
- Janus.debug(data);
- config.dataChannel[label].send(data);
- }
- config.dataChannel[label].pending = [];
- }
- // Notify the open data channel
- pluginHandle.ondataopen(label, protocol);
- }
- };
- var onDataChannelError = function(error) {
- Janus.error('Got error on data channel:', error);
- // TODO
- };
- if(!incoming) {
- // FIXME Add options (ordered, maxRetransmits, etc.)
- var dcoptions = { ordered: true };
- if(dcprotocol)
- dcoptions.protocol = dcprotocol;
- config.dataChannel[dclabel] = config.pc.createDataChannel(dclabel, dcoptions);
- } else {
- // The channel was created by Janus
- config.dataChannel[dclabel] = incoming;
- }
- config.dataChannel[dclabel].onmessage = onDataChannelMessage;
- config.dataChannel[dclabel].onopen = onDataChannelStateChange;
- config.dataChannel[dclabel].onclose = onDataChannelStateChange;
- config.dataChannel[dclabel].onerror = onDataChannelError;
- config.dataChannel[dclabel].pending = [];
- if(pendingData)
- config.dataChannel[dclabel].pending.push(pendingData);
- }
- // Private method to send a data channel message
- function sendData(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- var data = callbacks.text || callbacks.data;
- if(!data) {
- Janus.warn("Invalid data");
- callbacks.error("Invalid data");
- return;
- }
- var label = callbacks.label ? callbacks.label : Janus.dataChanDefaultLabel;
- if(!config.dataChannel[label]) {
- // Create new data channel and wait for it to open
- createDataChannel(handleId, label, callbacks.protocol, false, data, callbacks.protocol);
- callbacks.success();
- return;
- }
- if(config.dataChannel[label].readyState !== "open") {
- config.dataChannel[label].pending.push(data);
- callbacks.success();
- return;
- }
- Janus.log("Sending data on data channel <" + label + ">");
- Janus.debug(data);
- config.dataChannel[label].send(data);
- callbacks.success();
- }
- // Private method to send a DTMF tone
- function sendDtmf(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- if(!config.dtmfSender) {
- // Create the DTMF sender the proper way, if possible
- if(config.pc) {
- var senders = config.pc.getSenders();
- var audioSender = senders.find(function(sender) {
- return sender.track && sender.track.kind === 'audio';
- });
- if(!audioSender) {
- Janus.warn("Invalid DTMF configuration (no audio track)");
- callbacks.error("Invalid DTMF configuration (no audio track)");
- return;
- }
- config.dtmfSender = audioSender.dtmf;
- if(config.dtmfSender) {
- Janus.log("Created DTMF Sender");
- config.dtmfSender.ontonechange = function(tone) { Janus.debug("Sent DTMF tone: " + tone.tone); };
- }
- }
- if(!config.dtmfSender) {
- Janus.warn("Invalid DTMF configuration");
- callbacks.error("Invalid DTMF configuration");
- return;
- }
- }
- var dtmf = callbacks.dtmf;
- if(!dtmf) {
- Janus.warn("Invalid DTMF parameters");
- callbacks.error("Invalid DTMF parameters");
- return;
- }
- var tones = dtmf.tones;
- if(!tones) {
- Janus.warn("Invalid DTMF string");
- callbacks.error("Invalid DTMF string");
- return;
- }
- var duration = (typeof dtmf.duration === 'number') ? dtmf.duration : 500; // We choose 500ms as the default duration for a tone
- var gap = (typeof dtmf.gap === 'number') ? dtmf.gap : 50; // We choose 50ms as the default gap between tones
- Janus.debug("Sending DTMF string " + tones + " (duration " + duration + "ms, gap " + gap + "ms)");
- config.dtmfSender.insertDTMF(tones, duration, gap);
- callbacks.success();
- }
- // Private method to destroy a plugin handle
- function destroyHandle(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- var noRequest = (callbacks.noRequest === true);
- Janus.log("Destroying handle " + handleId + " (only-locally=" + noRequest + ")");
- cleanupWebrtc(handleId);
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || pluginHandle.detached) {
- // Plugin was already detached by Janus, calling detach again will return a handle not found error, so just exit here
- delete pluginHandles[handleId];
- callbacks.success();
- return;
- }
- if(noRequest) {
- // We're only removing the handle locally
- delete pluginHandles[handleId];
- callbacks.success();
- return;
- }
- if(!connected) {
- Janus.warn("Is the server down? (connected=false)");
- callbacks.error("Is the server down? (connected=false)");
- return;
- }
- var request = { "janus": "detach", "transaction": Janus.randomString(12) };
- if(pluginHandle.token)
- request["token"] = pluginHandle.token;
- if(apisecret)
- request["apisecret"] = apisecret;
- if(websockets) {
- request["session_id"] = sessionId;
- request["handle_id"] = handleId;
- ws.send(JSON.stringify(request));
- delete pluginHandles[handleId];
- callbacks.success();
- return;
- }
- Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
- verb: 'POST',
- withCredentials: withCredentials,
- body: request,
- success: function(json) {
- Janus.log("Destroyed handle:");
- Janus.debug(json);
- if(json["janus"] !== "success") {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- }
- delete pluginHandles[handleId];
- callbacks.success();
- },
- error: function(textStatus, errorThrown) {
- Janus.error(textStatus + ":", errorThrown); // FIXME
- // We cleanup anyway
- delete pluginHandles[handleId];
- callbacks.success();
- }
- });
- }
- // WebRTC stuff
- function streamsDone(handleId, jsep, media, callbacks, stream) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- // Close all tracks if the given stream has been created internally
- if(!callbacks.stream) {
- Janus.stopAllTracks(stream);
- }
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- Janus.debug("streamsDone:", stream);
- if(stream) {
- Janus.debug(" -- Audio tracks:", stream.getAudioTracks());
- Janus.debug(" -- Video tracks:", stream.getVideoTracks());
- }
- // We're now capturing the new stream: check if we're updating or if it's a new thing
- var addTracks = false;
- if(!config.myStream || !media.update || config.streamExternal) {
- config.myStream = stream;
- addTracks = true;
- } else {
- // We only need to update the existing stream
- if(((!media.update && isAudioSendEnabled(media)) || (media.update && (media.addAudio || media.replaceAudio))) &&
- stream.getAudioTracks() && stream.getAudioTracks().length) {
- config.myStream.addTrack(stream.getAudioTracks()[0]);
- if(Janus.unifiedPlan) {
- // Use Transceivers
- Janus.log((media.replaceAudio ? "Replacing" : "Adding") + " audio track:", stream.getAudioTracks()[0]);
- var audioTransceiver = null;
- var transceivers = config.pc.getTransceivers();
- if(transceivers && transceivers.length > 0) {
- for(var t of transceivers) {
- if((t.sender && t.sender.track && t.sender.track.kind === "audio") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) {
- audioTransceiver = t;
- break;
- }
- }
- }
- if(audioTransceiver && audioTransceiver.sender) {
- audioTransceiver.sender.replaceTrack(stream.getAudioTracks()[0]);
- } else {
- config.pc.addTrack(stream.getAudioTracks()[0], stream);
- }
- } else {
- Janus.log((media.replaceAudio ? "Replacing" : "Adding") + " audio track:", stream.getAudioTracks()[0]);
- config.pc.addTrack(stream.getAudioTracks()[0], stream);
- }
- }
- if(((!media.update && isVideoSendEnabled(media)) || (media.update && (media.addVideo || media.replaceVideo))) &&
- stream.getVideoTracks() && stream.getVideoTracks().length) {
- config.myStream.addTrack(stream.getVideoTracks()[0]);
- if(Janus.unifiedPlan) {
- // Use Transceivers
- Janus.log((media.replaceVideo ? "Replacing" : "Adding") + " video track:", stream.getVideoTracks()[0]);
- var videoTransceiver = null;
- var transceivers = config.pc.getTransceivers();
- if(transceivers && transceivers.length > 0) {
- for(var t of transceivers) {
- if((t.sender && t.sender.track && t.sender.track.kind === "video") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) {
- videoTransceiver = t;
- break;
- }
- }
- }
- if(videoTransceiver && videoTransceiver.sender) {
- videoTransceiver.sender.replaceTrack(stream.getVideoTracks()[0]);
- } else {
- config.pc.addTrack(stream.getVideoTracks()[0], stream);
- }
- } else {
- Janus.log((media.replaceVideo ? "Replacing" : "Adding") + " video track:", stream.getVideoTracks()[0]);
- config.pc.addTrack(stream.getVideoTracks()[0], stream);
- }
- }
- }
- // If we still need to create a PeerConnection, let's do that
- if(!config.pc) {
- var pc_config = {"iceServers": iceServers, "iceTransportPolicy": iceTransportPolicy, "bundlePolicy": bundlePolicy};
- if(Janus.webRTCAdapter.browserDetails.browser === "chrome") {
- // For Chrome versions before 72, we force a plan-b semantic, and unified-plan otherwise
- pc_config["sdpSemantics"] = (Janus.webRTCAdapter.browserDetails.version < 72) ? "plan-b" : "unified-plan";
- }
- var pc_constraints = {
- "optional": [{"DtlsSrtpKeyAgreement": true}]
- };
- if(ipv6Support) {
- pc_constraints.optional.push({"googIPv6":true});
- }
- // Any custom constraint to add?
- if(callbacks.rtcConstraints && typeof callbacks.rtcConstraints === 'object') {
- Janus.debug("Adding custom PeerConnection constraints:", callbacks.rtcConstraints);
- for(var i in callbacks.rtcConstraints) {
- pc_constraints.optional.push(callbacks.rtcConstraints[i]);
- }
- }
- if(Janus.webRTCAdapter.browserDetails.browser === "edge") {
- // This is Edge, enable BUNDLE explicitly
- pc_config.bundlePolicy = "max-bundle";
- }
- // Check if a sender or receiver transform has been provided
- if(RTCRtpSender && RTCRtpSender.prototype.createEncodedAudioStreams &&
- RTCRtpSender.prototype.createEncodedVideoStreams &&
- (callbacks.senderTransforms || callbacks.receiverTransforms)) {
- config.senderTransforms = callbacks.senderTransforms;
- config.receiverTransforms = callbacks.receiverTransforms;
- pc_config["forceEncodedAudioInsertableStreams"] = true;
- pc_config["forceEncodedVideoInsertableStreams"] = true;
- pc_config["encodedInsertableStreams"] = true;
- }
- Janus.log("Creating PeerConnection");
- Janus.debug(pc_constraints);
- config.pc = new RTCPeerConnection(pc_config, pc_constraints);
- Janus.debug(config.pc);
- if(config.pc.getStats) { // FIXME
- config.volume = {};
- config.bitrate.value = "0 kbits/sec";
- }
- Janus.log("Preparing local SDP and gathering candidates (trickle=" + config.trickle + ")");
- config.pc.oniceconnectionstatechange = function(e) {
- if(config.pc)
- pluginHandle.iceState(config.pc.iceConnectionState);
- };
- config.pc.onicecandidate = function(event) {
- if (!event.candidate ||
- (Janus.webRTCAdapter.browserDetails.browser === 'edge' && event.candidate.candidate.indexOf('endOfCandidates') > 0)) {
- Janus.log("End of candidates.");
- config.iceDone = true;
- if(config.trickle === true) {
- // Notify end of candidates
- sendTrickleCandidate(handleId, {"completed": true});
- } else {
- // No trickle, time to send the complete SDP (including all candidates)
- sendSDP(handleId, callbacks);
- }
- } else {
- // JSON.stringify doesn't work on some WebRTC objects anymore
- // See https://code.google.com/p/chromium/issues/detail?id=467366
- var candidate = {
- "candidate": event.candidate.candidate,
- "sdpMid": event.candidate.sdpMid,
- "sdpMLineIndex": event.candidate.sdpMLineIndex
- };
- if(config.trickle === true) {
- // Send candidate
- sendTrickleCandidate(handleId, candidate);
- }
- }
- };
- config.pc.ontrack = function(event) {
- Janus.log("Handling Remote Track");
- Janus.debug(event);
- if(!event.streams)
- return;
- config.remoteStream = event.streams[0];
- pluginHandle.onremotestream(config.remoteStream);
- if(event.track.onended)
- return;
- if(config.receiverTransforms) {
- var receiverStreams = null;
- if(event.track.kind === "audio" && config.receiverTransforms["audio"]) {
- receiverStreams = event.receiver.createEncodedAudioStreams();
- } else if(event.track.kind === "video" && config.receiverTransforms["video"]) {
- receiverStreams = event.receiver.createEncodedVideoStreams();
- }
- if(receiverStreams) {
- receiverStreams.readableStream
- .pipeThrough(config.receiverTransforms[event.track.kind])
- .pipeTo(receiverStreams.writableStream);
- }
- }
- var trackMutedTimeoutId = null;
- Janus.log("Adding onended callback to track:", event.track);
- event.track.onended = function(ev) {
- Janus.log("Remote track removed:", ev);
- if(config.remoteStream) {
- clearTimeout(trackMutedTimeoutId);
- config.remoteStream.removeTrack(ev.target);
- pluginHandle.onremotestream(config.remoteStream);
- }
- };
- event.track.onmute = function(ev) {
- Janus.log("Remote track muted:", ev);
- if(config.remoteStream && trackMutedTimeoutId == null) {
- trackMutedTimeoutId = setTimeout(function() {
- Janus.log("Removing remote track");
- if (config.remoteStream) {
- config.remoteStream.removeTrack(ev.target);
- pluginHandle.onremotestream(config.remoteStream);
- }
- trackMutedTimeoutId = null;
- // Chrome seems to raise mute events only at multiples of 834ms;
- // we set the timeout to three times this value (rounded to 840ms)
- }, 3 * 840);
- }
- };
- event.track.onunmute = function(ev) {
- Janus.log("Remote track flowing again:", ev);
- if(trackMutedTimeoutId != null) {
- clearTimeout(trackMutedTimeoutId);
- trackMutedTimeoutId = null;
- } else {
- try {
- config.remoteStream.addTrack(ev.target);
- pluginHandle.onremotestream(config.remoteStream);
- } catch(e) {
- Janus.error(e);
- };
- }
- };
- };
- }
- if(addTracks && stream) {
- Janus.log('Adding local stream');
- var simulcast2 = (callbacks.simulcast2 === true);
- stream.getTracks().forEach(function(track) {
- Janus.log('Adding local track:', track);
- if(!simulcast2) {
- var sender = config.pc.addTrack(track, stream);
- // Check if insertable streams are involved
- if(sender && config.senderTransforms) {
- var senderStreams = null;
- if(sender.track.kind === "audio" && config.senderTransforms["audio"]) {
- senderStreams = sender.createEncodedAudioStreams();
- } else if(sender.track.kind === "video" && config.senderTransforms["video"]) {
- senderStreams = sender.createEncodedVideoStreams();
- }
- if(senderStreams) {
- senderStreams.readableStream
- .pipeThrough(config.senderTransforms[sender.track.kind])
- .pipeTo(senderStreams.writableStream);
- }
- }
- } else {
- if(track.kind === "audio") {
- config.pc.addTrack(track, stream);
- } else {
- Janus.log('Enabling rid-based simulcasting:', track);
- var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);
- config.pc.addTransceiver(track, {
- direction: "sendrecv",
- streams: [stream],
- sendEncodings: [
- { rid: "h", active: true, maxBitrate: maxBitrates.high },
- { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2 },
- { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4 }
- ]
- });
- }
- }
- });
- }
- // Any data channel to create?
- if(isDataEnabled(media) && !config.dataChannel[Janus.dataChanDefaultLabel]) {
- Janus.log("Creating default data channel");
- createDataChannel(handleId, Janus.dataChanDefaultLabel, null, false);
- config.pc.ondatachannel = function(event) {
- Janus.log("Data channel created by Janus:", event);
- createDataChannel(handleId, event.channel.label, event.channel.protocol, event.channel);
- };
- }
- // If there's a new local stream, let's notify the application
- if(config.myStream) {
- pluginHandle.onlocalstream(config.myStream);
- }
- // Create offer/answer now
- if(!jsep) {
- createOffer(handleId, media, callbacks);
- } else {
- config.pc.setRemoteDescription(jsep)
- .then(function() {
- Janus.log("Remote description accepted!");
- config.remoteSdp = jsep.sdp;
- // Any trickle candidate we cached?
- if(config.candidates && config.candidates.length > 0) {
- for(var i = 0; i< config.candidates.length; i++) {
- var candidate = config.candidates[i];
- Janus.debug("Adding remote candidate:", candidate);
- if(!candidate || candidate.completed === true) {
- // end-of-candidates
- config.pc.addIceCandidate(Janus.endOfCandidates);
- } else {
- // New candidate
- config.pc.addIceCandidate(candidate);
- }
- }
- config.candidates = [];
- }
- // Create the answer now
- createAnswer(handleId, media, callbacks);
- }, callbacks.error);
- }
- }
- function prepareWebrtc(handleId, offer, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError;
- var jsep = callbacks.jsep;
- if(offer && jsep) {
- Janus.error("Provided a JSEP to a createOffer");
- callbacks.error("Provided a JSEP to a createOffer");
- return;
- } else if(!offer && (!jsep || !jsep.type || !jsep.sdp)) {
- Janus.error("A valid JSEP is required for createAnswer");
- callbacks.error("A valid JSEP is required for createAnswer");
- return;
- }
- /* Check that callbacks.media is a (not null) Object */
- callbacks.media = (typeof callbacks.media === 'object' && callbacks.media) ? callbacks.media : { audio: true, video: true };
- var media = callbacks.media;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- config.trickle = isTrickleEnabled(callbacks.trickle);
- // Are we updating a session?
- if(!config.pc) {
- // Nope, new PeerConnection
- media.update = false;
- media.keepAudio = false;
- media.keepVideo = false;
- } else {
- Janus.log("Updating existing media session");
- media.update = true;
- // Check if there's anything to add/remove/replace, or if we
- // can go directly to preparing the new SDP offer or answer
- if(callbacks.stream) {
- // External stream: is this the same as the one we were using before?
- if(callbacks.stream !== config.myStream) {
- Janus.log("Renegotiation involves a new external stream");
- }
- } else {
- // Check if there are changes on audio
- if(media.addAudio) {
- media.keepAudio = false;
- media.replaceAudio = false;
- media.removeAudio = false;
- media.audioSend = true;
- if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) {
- Janus.error("Can't add audio stream, there already is one");
- callbacks.error("Can't add audio stream, there already is one");
- return;
- }
- } else if(media.removeAudio) {
- media.keepAudio = false;
- media.replaceAudio = false;
- media.addAudio = false;
- media.audioSend = false;
- } else if(media.replaceAudio) {
- media.keepAudio = false;
- media.addAudio = false;
- media.removeAudio = false;
- media.audioSend = true;
- }
- if(!config.myStream) {
- // No media stream: if we were asked to replace, it's actually an "add"
- if(media.replaceAudio) {
- media.keepAudio = false;
- media.replaceAudio = false;
- media.addAudio = true;
- media.audioSend = true;
- }
- if(isAudioSendEnabled(media)) {
- media.keepAudio = false;
- media.addAudio = true;
- }
- } else {
- if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {
- // No audio track: if we were asked to replace, it's actually an "add"
- if(media.replaceAudio) {
- media.keepAudio = false;
- media.replaceAudio = false;
- media.addAudio = true;
- media.audioSend = true;
- }
- if(isAudioSendEnabled(media)) {
- media.keepAudio = false;
- media.addAudio = true;
- }
- } else {
- // We have an audio track: should we keep it as it is?
- if(isAudioSendEnabled(media) &&
- !media.removeAudio && !media.replaceAudio) {
- media.keepAudio = true;
- }
- }
- }
- // Check if there are changes on video
- if(media.addVideo) {
- media.keepVideo = false;
- media.replaceVideo = false;
- media.removeVideo = false;
- media.videoSend = true;
- if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) {
- Janus.error("Can't add video stream, there already is one");
- callbacks.error("Can't add video stream, there already is one");
- return;
- }
- } else if(media.removeVideo) {
- media.keepVideo = false;
- media.replaceVideo = false;
- media.addVideo = false;
- media.videoSend = false;
- } else if(media.replaceVideo) {
- media.keepVideo = false;
- media.addVideo = false;
- media.removeVideo = false;
- media.videoSend = true;
- }
- if(!config.myStream) {
- // No media stream: if we were asked to replace, it's actually an "add"
- if(media.replaceVideo) {
- media.keepVideo = false;
- media.replaceVideo = false;
- media.addVideo = true;
- media.videoSend = true;
- }
- if(isVideoSendEnabled(media)) {
- media.keepVideo = false;
- media.addVideo = true;
- }
- } else {
- if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {
- // No video track: if we were asked to replace, it's actually an "add"
- if(media.replaceVideo) {
- media.keepVideo = false;
- media.replaceVideo = false;
- media.addVideo = true;
- media.videoSend = true;
- }
- if(isVideoSendEnabled(media)) {
- media.keepVideo = false;
- media.addVideo = true;
- }
- } else {
- // We have a video track: should we keep it as it is?
- if(isVideoSendEnabled(media) && !media.removeVideo && !media.replaceVideo) {
- media.keepVideo = true;
- }
- }
- }
- // Data channels can only be added
- if(media.addData) {
- media.data = true;
- }
- }
- // If we're updating and keeping all tracks, let's skip the getUserMedia part
- if((isAudioSendEnabled(media) && media.keepAudio) &&
- (isVideoSendEnabled(media) && media.keepVideo)) {
- pluginHandle.consentDialog(false);
- streamsDone(handleId, jsep, media, callbacks, config.myStream);
- return;
- }
- }
- // If we're updating, check if we need to remove/replace one of the tracks
- if(media.update && !config.streamExternal) {
- if(media.removeAudio || media.replaceAudio) {
- if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) {
- var at = config.myStream.getAudioTracks()[0];
- Janus.log("Removing audio track:", at);
- config.myStream.removeTrack(at);
- try {
- at.stop();
- } catch(e) {}
- }
- if(config.pc.getSenders() && config.pc.getSenders().length) {
- var ra = true;
- if(media.replaceAudio && Janus.unifiedPlan) {
- // We can use replaceTrack
- ra = false;
- }
- if(ra) {
- for(var asnd of config.pc.getSenders()) {
- if(asnd && asnd.track && asnd.track.kind === "audio") {
- Janus.log("Removing audio sender:", asnd);
- config.pc.removeTrack(asnd);
- }
- }
- }
- }
- }
- if(media.removeVideo || media.replaceVideo) {
- if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) {
- var vt = config.myStream.getVideoTracks()[0];
- Janus.log("Removing video track:", vt);
- config.myStream.removeTrack(vt);
- try {
- vt.stop();
- } catch(e) {}
- }
- if(config.pc.getSenders() && config.pc.getSenders().length) {
- var rv = true;
- if(media.replaceVideo && Janus.unifiedPlan) {
- // We can use replaceTrack
- rv = false;
- }
- if(rv) {
- for(var vsnd of config.pc.getSenders()) {
- if(vsnd && vsnd.track && vsnd.track.kind === "video") {
- Janus.log("Removing video sender:", vsnd);
- config.pc.removeTrack(vsnd);
- }
- }
- }
- }
- }
- }
- // Was a MediaStream object passed, or do we need to take care of that?
- if(callbacks.stream) {
- var stream = callbacks.stream;
- Janus.log("MediaStream provided by the application");
- Janus.debug(stream);
- // If this is an update, let's check if we need to release the previous stream
- if(media.update) {
- if(config.myStream && config.myStream !== callbacks.stream && !config.streamExternal) {
- // We're replacing a stream we captured ourselves with an external one
- Janus.stopAllTracks(config.myStream);
- config.myStream = null;
- }
- }
- // Skip the getUserMedia part
- config.streamExternal = true;
- pluginHandle.consentDialog(false);
- streamsDone(handleId, jsep, media, callbacks, stream);
- return;
- }
- if(isAudioSendEnabled(media) || isVideoSendEnabled(media)) {
- if(!Janus.isGetUserMediaAvailable()) {
- callbacks.error("getUserMedia not available");
- return;
- }
- var constraints = { mandatory: {}, optional: []};
- pluginHandle.consentDialog(true);
- var audioSupport = isAudioSendEnabled(media);
- if(audioSupport && media && typeof media.audio === 'object')
- audioSupport = media.audio;
- var videoSupport = isVideoSendEnabled(media);
- if(videoSupport && media) {
- var simulcast = (callbacks.simulcast === true);
- var simulcast2 = (callbacks.simulcast2 === true);
- if((simulcast || simulcast2) && !jsep && !media.video)
- media.video = "hires";
- if(media.video && media.video != 'screen' && media.video != 'window') {
- if(typeof media.video === 'object') {
- videoSupport = media.video;
- } else {
- var width = 0;
- var height = 0, maxHeight = 0;
- if(media.video === 'lowres') {
- // Small resolution, 4:3
- height = 240;
- maxHeight = 240;
- width = 320;
- } else if(media.video === 'lowres-16:9') {
- // Small resolution, 16:9
- height = 180;
- maxHeight = 180;
- width = 320;
- } else if(media.video === 'hires' || media.video === 'hires-16:9' || media.video === 'hdres') {
- // High(HD) resolution is only 16:9
- height = 720;
- maxHeight = 720;
- width = 1280;
- } else if(media.video === 'fhdres') {
- // Full HD resolution is only 16:9
- height = 1080;
- maxHeight = 1080;
- width = 1920;
- } else if(media.video === '4kres') {
- // 4K resolution is only 16:9
- height = 2160;
- maxHeight = 2160;
- width = 3840;
- } else if(media.video === 'stdres') {
- // Normal resolution, 4:3
- height = 480;
- maxHeight = 480;
- width = 640;
- } else if(media.video === 'stdres-16:9') {
- // Normal resolution, 16:9
- height = 360;
- maxHeight = 360;
- width = 640;
- } else {
- Janus.log("Default video setting is stdres 4:3");
- height = 480;
- maxHeight = 480;
- width = 640;
- }
- Janus.log("Adding media constraint:", media.video);
- videoSupport = {
- 'height': {'ideal': height},
- 'width': {'ideal': width}
- };
- Janus.log("Adding video constraint:", videoSupport);
- }
- } else if(media.video === 'screen' || media.video === 'window') {
- if(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
- // The new experimental getDisplayMedia API is available, let's use that
- // https://groups.google.com/forum/#!topic/discuss-webrtc/Uf0SrR4uxzk
- // https://webrtchacks.com/chrome-screensharing-getdisplaymedia/
- constraints.video = {};
- if(media.screenshareFrameRate) {
- constraints.video.frameRate = media.screenshareFrameRate;
- }
- if(media.screenshareHeight) {
- constraints.video.height = media.screenshareHeight;
- }
- if(media.screenshareWidth) {
- constraints.video.width = media.screenshareWidth;
- }
- constraints.audio = media.captureDesktopAudio;
- navigator.mediaDevices.getDisplayMedia(constraints)
- .then(function(stream) {
- pluginHandle.consentDialog(false);
- if(isAudioSendEnabled(media) && !media.keepAudio) {
- navigator.mediaDevices.getUserMedia({ audio: true, video: false })
- .then(function (audioStream) {
- stream.addTrack(audioStream.getAudioTracks()[0]);
- streamsDone(handleId, jsep, media, callbacks, stream);
- })
- } else {
- streamsDone(handleId, jsep, media, callbacks, stream);
- }
- }, function (error) {
- pluginHandle.consentDialog(false);
- callbacks.error(error);
- });
- return;
- }
- // We're going to try and use the extension for Chrome 34+, the old approach
- // for older versions of Chrome, or the experimental support in Firefox 33+
- function callbackUserMedia (error, stream) {
- pluginHandle.consentDialog(false);
- if(error) {
- callbacks.error(error);
- } else {
- streamsDone(handleId, jsep, media, callbacks, stream);
- }
- }
- function getScreenMedia(constraints, gsmCallback, useAudio) {
- Janus.log("Adding media constraint (screen capture)");
- Janus.debug(constraints);
- navigator.mediaDevices.getUserMedia(constraints)
- .then(function(stream) {
- if(useAudio) {
- navigator.mediaDevices.getUserMedia({ audio: true, video: false })
- .then(function (audioStream) {
- stream.addTrack(audioStream.getAudioTracks()[0]);
- gsmCallback(null, stream);
- })
- } else {
- gsmCallback(null, stream);
- }
- })
- .catch(function(error) { pluginHandle.consentDialog(false); gsmCallback(error); });
- }
- if(Janus.webRTCAdapter.browserDetails.browser === 'chrome') {
- var chromever = Janus.webRTCAdapter.browserDetails.version;
- var maxver = 33;
- if(window.navigator.userAgent.match('Linux'))
- maxver = 35; // "known" crash in chrome 34 and 35 on linux
- if(chromever >= 26 && chromever <= maxver) {
- // Chrome 26->33 requires some awkward chrome://flags manipulation
- constraints = {
- video: {
- mandatory: {
- googLeakyBucket: true,
- maxWidth: window.screen.width,
- maxHeight: window.screen.height,
- minFrameRate: media.screenshareFrameRate,
- maxFrameRate: media.screenshareFrameRate,
- chromeMediaSource: 'screen'
- }
- },
- audio: isAudioSendEnabled(media) && !media.keepAudio
- };
- getScreenMedia(constraints, callbackUserMedia);
- } else {
- // Chrome 34+ requires an extension
- Janus.extension.getScreen(function (error, sourceId) {
- if (error) {
- pluginHandle.consentDialog(false);
- return callbacks.error(error);
- }
- constraints = {
- audio: false,
- video: {
- mandatory: {
- chromeMediaSource: 'desktop',
- maxWidth: window.screen.width,
- maxHeight: window.screen.height,
- minFrameRate: media.screenshareFrameRate,
- maxFrameRate: media.screenshareFrameRate,
- },
- optional: [
- {googLeakyBucket: true},
- {googTemporalLayeredScreencast: true}
- ]
- }
- };
- constraints.video.mandatory.chromeMediaSourceId = sourceId;
- getScreenMedia(constraints, callbackUserMedia,
- isAudioSendEnabled(media) && !media.keepAudio);
- });
- }
- } else if(Janus.webRTCAdapter.browserDetails.browser === 'firefox') {
- if(Janus.webRTCAdapter.browserDetails.version >= 33) {
- // Firefox 33+ has experimental support for screen sharing
- constraints = {
- video: {
- mozMediaSource: media.video,
- mediaSource: media.video
- },
- audio: isAudioSendEnabled(media) && !media.keepAudio
- };
- getScreenMedia(constraints, function (err, stream) {
- callbackUserMedia(err, stream);
- // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810
- if (!err) {
- var lastTime = stream.currentTime;
- var polly = window.setInterval(function () {
- if(!stream)
- window.clearInterval(polly);
- if(stream.currentTime == lastTime) {
- window.clearInterval(polly);
- if(stream.onended) {
- stream.onended();
- }
- }
- lastTime = stream.currentTime;
- }, 500);
- }
- });
- } else {
- var error = new Error('NavigatorUserMediaError');
- error.name = 'Your version of Firefox does not support screen sharing, please install Firefox 33 (or more recent versions)';
- pluginHandle.consentDialog(false);
- callbacks.error(error);
- return;
- }
- }
- return;
- }
- }
- // If we got here, we're not screensharing
- if(!media || media.video !== 'screen') {
- // Check whether all media sources are actually available or not
- navigator.mediaDevices.enumerateDevices().then(function(devices) {
- var audioExist = devices.some(function(device) {
- return device.kind === 'audioinput';
- }),
- videoExist = isScreenSendEnabled(media) || devices.some(function(device) {
- return device.kind === 'videoinput';
- });
- // Check whether a missing device is really a problem
- var audioSend = isAudioSendEnabled(media);
- var videoSend = isVideoSendEnabled(media);
- var needAudioDevice = isAudioSendRequired(media);
- var needVideoDevice = isVideoSendRequired(media);
- if(audioSend || videoSend || needAudioDevice || needVideoDevice) {
- // We need to send either audio or video
- var haveAudioDevice = audioSend ? audioExist : false;
- var haveVideoDevice = videoSend ? videoExist : false;
- if(!haveAudioDevice && !haveVideoDevice) {
- // FIXME Should we really give up, or just assume recvonly for both?
- pluginHandle.consentDialog(false);
- callbacks.error('No capture device found');
- return false;
- } else if(!haveAudioDevice && needAudioDevice) {
- pluginHandle.consentDialog(false);
- callbacks.error('Audio capture is required, but no capture device found');
- return false;
- } else if(!haveVideoDevice && needVideoDevice) {
- pluginHandle.consentDialog(false);
- callbacks.error('Video capture is required, but no capture device found');
- return false;
- }
- }
- var gumConstraints = {
- audio: (audioExist && !media.keepAudio) ? audioSupport : false,
- video: (videoExist && !media.keepVideo) ? videoSupport : false
- };
- Janus.debug("getUserMedia constraints", gumConstraints);
- if (!gumConstraints.audio && !gumConstraints.video) {
- pluginHandle.consentDialog(false);
- streamsDone(handleId, jsep, media, callbacks, stream);
- } else {
- navigator.mediaDevices.getUserMedia(gumConstraints)
- .then(function(stream) {
- pluginHandle.consentDialog(false);
- streamsDone(handleId, jsep, media, callbacks, stream);
- }).catch(function(error) {
- pluginHandle.consentDialog(false);
- callbacks.error({code: error.code, name: error.name, message: error.message});
- });
- }
- })
- .catch(function(error) {
- pluginHandle.consentDialog(false);
- callbacks.error(error);
- });
- }
- } else {
- // No need to do a getUserMedia, create offer/answer right away
- streamsDone(handleId, jsep, media, callbacks);
- }
- }
- function prepareWebrtcPeer(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError;
- var jsep = callbacks.jsep;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- if(jsep) {
- if(!config.pc) {
- Janus.warn("Wait, no PeerConnection?? if this is an answer, use createAnswer and not handleRemoteJsep");
- callbacks.error("No PeerConnection: if this is an answer, use createAnswer and not handleRemoteJsep");
- return;
- }
- config.pc.setRemoteDescription(jsep)
- .then(function() {
- Janus.log("Remote description accepted!");
- config.remoteSdp = jsep.sdp;
- // Any trickle candidate we cached?
- if(config.candidates && config.candidates.length > 0) {
- for(var i = 0; i< config.candidates.length; i++) {
- var candidate = config.candidates[i];
- Janus.debug("Adding remote candidate:", candidate);
- if(!candidate || candidate.completed === true) {
- // end-of-candidates
- config.pc.addIceCandidate(Janus.endOfCandidates);
- } else {
- // New candidate
- config.pc.addIceCandidate(candidate);
- }
- }
- config.candidates = [];
- }
- // Done
- callbacks.success();
- }, callbacks.error);
- } else {
- callbacks.error("Invalid JSEP");
- }
- }
- function createOffer(handleId, media, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- var simulcast = (callbacks.simulcast === true);
- if(!simulcast) {
- Janus.log("Creating offer (iceDone=" + config.iceDone + ")");
- } else {
- Janus.log("Creating offer (iceDone=" + config.iceDone + ", simulcast=" + simulcast + ")");
- }
- // https://code.google.com/p/webrtc/issues/detail?id=3508
- var mediaConstraints = {};
- if(Janus.unifiedPlan) {
- // We can use Transceivers
- var audioTransceiver = null, videoTransceiver = null;
- var transceivers = config.pc.getTransceivers();
- if(transceivers && transceivers.length > 0) {
- for(var t of transceivers) {
- if((t.sender && t.sender.track && t.sender.track.kind === "audio") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) {
- if(!audioTransceiver) {
- audioTransceiver = t;
- }
- continue;
- }
- if((t.sender && t.sender.track && t.sender.track.kind === "video") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) {
- if(!videoTransceiver) {
- videoTransceiver = t;
- }
- continue;
- }
- }
- }
- // Handle audio (and related changes, if any)
- var audioSend = isAudioSendEnabled(media);
- var audioRecv = isAudioRecvEnabled(media);
- if(!audioSend && !audioRecv) {
- // Audio disabled: have we removed it?
- if(media.removeAudio && audioTransceiver) {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("inactive");
- } else {
- audioTransceiver.direction = "inactive";
- }
- Janus.log("Setting audio transceiver to inactive:", audioTransceiver);
- }
- } else {
- // Take care of audio m-line
- if(audioSend && audioRecv) {
- if(audioTransceiver) {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("sendrecv");
- } else {
- audioTransceiver.direction = "sendrecv";
- }
- Janus.log("Setting audio transceiver to sendrecv:", audioTransceiver);
- }
- } else if(audioSend && !audioRecv) {
- if(audioTransceiver) {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("sendonly");
- } else {
- audioTransceiver.direction = "sendonly";
- }
- Janus.log("Setting audio transceiver to sendonly:", audioTransceiver);
- }
- } else if(!audioSend && audioRecv) {
- if(audioTransceiver) {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("recvonly");
- } else {
- audioTransceiver.direction = "recvonly";
- }
- Janus.log("Setting audio transceiver to recvonly:", audioTransceiver);
- } else {
- // In theory, this is the only case where we might not have a transceiver yet
- audioTransceiver = config.pc.addTransceiver("audio", { direction: "recvonly" });
- Janus.log("Adding recvonly audio transceiver:", audioTransceiver);
- }
- }
- }
- // Handle video (and related changes, if any)
- var videoSend = isVideoSendEnabled(media);
- var videoRecv = isVideoRecvEnabled(media);
- if(!videoSend && !videoRecv) {
- // Video disabled: have we removed it?
- if(media.removeVideo && videoTransceiver) {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("inactive");
- } else {
- videoTransceiver.direction = "inactive";
- }
- Janus.log("Setting video transceiver to inactive:", videoTransceiver);
- }
- } else {
- // Take care of video m-line
- if(videoSend && videoRecv) {
- if(videoTransceiver) {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("sendrecv");
- } else {
- videoTransceiver.direction = "sendrecv";
- }
- Janus.log("Setting video transceiver to sendrecv:", videoTransceiver);
- }
- } else if(videoSend && !videoRecv) {
- if(videoTransceiver) {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("sendonly");
- } else {
- videoTransceiver.direction = "sendonly";
- }
- Janus.log("Setting video transceiver to sendonly:", videoTransceiver);
- }
- } else if(!videoSend && videoRecv) {
- if(videoTransceiver) {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("recvonly");
- } else {
- videoTransceiver.direction = "recvonly";
- }
- Janus.log("Setting video transceiver to recvonly:", videoTransceiver);
- } else {
- // In theory, this is the only case where we might not have a transceiver yet
- videoTransceiver = config.pc.addTransceiver("video", { direction: "recvonly" });
- Janus.log("Adding recvonly video transceiver:", videoTransceiver);
- }
- }
- }
- } else {
- mediaConstraints["offerToReceiveAudio"] = isAudioRecvEnabled(media);
- mediaConstraints["offerToReceiveVideo"] = isVideoRecvEnabled(media);
- }
- var iceRestart = (callbacks.iceRestart === true);
- if(iceRestart) {
- mediaConstraints["iceRestart"] = true;
- }
- Janus.debug(mediaConstraints);
- // Check if this is Firefox and we've been asked to do simulcasting
- var sendVideo = isVideoSendEnabled(media);
- if(sendVideo && simulcast && Janus.webRTCAdapter.browserDetails.browser === "firefox") {
- // FIXME Based on https://gist.github.com/voluntas/088bc3cc62094730647b
- Janus.log("Enabling Simulcasting for Firefox (RID)");
- var sender = config.pc.getSenders().find(function(s) {return s.track.kind === "video"});
- if(sender) {
- var parameters = sender.getParameters();
- if(!parameters) {
- parameters = {};
- }
- var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);
- parameters.encodings = [
- { rid: "h", active: true, maxBitrate: maxBitrates.high },
- { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2 },
- { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4 }
- ];
- sender.setParameters(parameters);
- }
- }
- config.pc.createOffer(mediaConstraints)
- .then(function(offer) {
- Janus.debug(offer);
- // JSON.stringify doesn't work on some WebRTC objects anymore
- // See https://code.google.com/p/chromium/issues/detail?id=467366
- var jsep = {
- "type": offer.type,
- "sdp": offer.sdp
- };
- callbacks.customizeSdp(jsep);
- offer.sdp = jsep.sdp;
- Janus.log("Setting local description");
- if(sendVideo && simulcast) {
- // This SDP munging only works with Chrome (Safari STP may support it too)
- if(Janus.webRTCAdapter.browserDetails.browser === "chrome" ||
- Janus.webRTCAdapter.browserDetails.browser === "safari") {
- Janus.log("Enabling Simulcasting for Chrome (SDP munging)");
- offer.sdp = mungeSdpForSimulcasting(offer.sdp);
- } else if(Janus.webRTCAdapter.browserDetails.browser !== "firefox") {
- Janus.warn("simulcast=true, but this is not Chrome nor Firefox, ignoring");
- }
- }
- config.mySdp = offer.sdp;
- config.pc.setLocalDescription(offer)
- .catch(callbacks.error);
- config.mediaConstraints = mediaConstraints;
- if(!config.iceDone && !config.trickle) {
- // Don't do anything until we have all candidates
- Janus.log("Waiting for all candidates...");
- return;
- }
- // If transforms are present, notify Janus that the media is end-to-end encrypted
- if(config.senderTransforms || config.receiverTransforms) {
- offer["e2ee"] = true;
- }
- callbacks.success(offer);
- }, callbacks.error);
- }
- function createAnswer(handleId, media, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- var simulcast = (callbacks.simulcast === true);
- if(!simulcast) {
- Janus.log("Creating answer (iceDone=" + config.iceDone + ")");
- } else {
- Janus.log("Creating answer (iceDone=" + config.iceDone + ", simulcast=" + simulcast + ")");
- }
- var mediaConstraints = null;
- if(Janus.unifiedPlan) {
- // We can use Transceivers
- mediaConstraints = {};
- var audioTransceiver = null, videoTransceiver = null;
- var transceivers = config.pc.getTransceivers();
- if(transceivers && transceivers.length > 0) {
- for(var t of transceivers) {
- if((t.sender && t.sender.track && t.sender.track.kind === "audio") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) {
- if(!audioTransceiver)
- audioTransceiver = t;
- continue;
- }
- if((t.sender && t.sender.track && t.sender.track.kind === "video") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) {
- if(!videoTransceiver)
- videoTransceiver = t;
- continue;
- }
- }
- }
- // Handle audio (and related changes, if any)
- var audioSend = isAudioSendEnabled(media);
- var audioRecv = isAudioRecvEnabled(media);
- if(!audioSend && !audioRecv) {
- // Audio disabled: have we removed it?
- if(media.removeAudio && audioTransceiver) {
- try {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("inactive");
- } else {
- audioTransceiver.direction = "inactive";
- }
- Janus.log("Setting audio transceiver to inactive:", audioTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- }
- } else {
- // Take care of audio m-line
- if(audioSend && audioRecv) {
- if(audioTransceiver) {
- try {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("sendrecv");
- } else {
- audioTransceiver.direction = "sendrecv";
- }
- Janus.log("Setting audio transceiver to sendrecv:", audioTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- }
- } else if(audioSend && !audioRecv) {
- try {
- if(audioTransceiver) {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("sendonly");
- } else {
- audioTransceiver.direction = "sendonly";
- }
- Janus.log("Setting audio transceiver to sendonly:", audioTransceiver);
- }
- } catch(e) {
- Janus.error(e);
- }
- } else if(!audioSend && audioRecv) {
- if(audioTransceiver) {
- try {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("recvonly");
- } else {
- audioTransceiver.direction = "recvonly";
- }
- Janus.log("Setting audio transceiver to recvonly:", audioTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- } else {
- // In theory, this is the only case where we might not have a transceiver yet
- audioTransceiver = config.pc.addTransceiver("audio", { direction: "recvonly" });
- Janus.log("Adding recvonly audio transceiver:", audioTransceiver);
- }
- }
- }
- // Handle video (and related changes, if any)
- var videoSend = isVideoSendEnabled(media);
- var videoRecv = isVideoRecvEnabled(media);
- if(!videoSend && !videoRecv) {
- // Video disabled: have we removed it?
- if(media.removeVideo && videoTransceiver) {
- try {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("inactive");
- } else {
- videoTransceiver.direction = "inactive";
- }
- Janus.log("Setting video transceiver to inactive:", videoTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- }
- } else {
- // Take care of video m-line
- if(videoSend && videoRecv) {
- if(videoTransceiver) {
- try {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("sendrecv");
- } else {
- videoTransceiver.direction = "sendrecv";
- }
- Janus.log("Setting video transceiver to sendrecv:", videoTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- }
- } else if(videoSend && !videoRecv) {
- if(videoTransceiver) {
- try {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("sendonly");
- } else {
- videoTransceiver.direction = "sendonly";
- }
- Janus.log("Setting video transceiver to sendonly:", videoTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- }
- } else if(!videoSend && videoRecv) {
- if(videoTransceiver) {
- try {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("recvonly");
- } else {
- videoTransceiver.direction = "recvonly";
- }
- Janus.log("Setting video transceiver to recvonly:", videoTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- } else {
- // In theory, this is the only case where we might not have a transceiver yet
- videoTransceiver = config.pc.addTransceiver("video", { direction: "recvonly" });
- Janus.log("Adding recvonly video transceiver:", videoTransceiver);
- }
- }
- }
- } else {
- if(Janus.webRTCAdapter.browserDetails.browser === "firefox" || Janus.webRTCAdapter.browserDetails.browser === "edge") {
- mediaConstraints = {
- offerToReceiveAudio: isAudioRecvEnabled(media),
- offerToReceiveVideo: isVideoRecvEnabled(media)
- };
- } else {
- mediaConstraints = {
- mandatory: {
- OfferToReceiveAudio: isAudioRecvEnabled(media),
- OfferToReceiveVideo: isVideoRecvEnabled(media)
- }
- };
- }
- }
- Janus.debug(mediaConstraints);
- // Check if this is Firefox and we've been asked to do simulcasting
- var sendVideo = isVideoSendEnabled(media);
- if(sendVideo && simulcast && Janus.webRTCAdapter.browserDetails.browser === "firefox") {
- // FIXME Based on https://gist.github.com/voluntas/088bc3cc62094730647b
- Janus.log("Enabling Simulcasting for Firefox (RID)");
- var sender = config.pc.getSenders()[1];
- Janus.log(sender);
- var parameters = sender.getParameters();
- Janus.log(parameters);
- var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);
- sender.setParameters({encodings: [
- { rid: "high", active: true, priority: "high", maxBitrate: maxBitrates.high },
- { rid: "medium", active: true, priority: "medium", maxBitrate: maxBitrates.medium },
- { rid: "low", active: true, priority: "low", maxBitrate: maxBitrates.low }
- ]});
- }
- config.pc.createAnswer(mediaConstraints)
- .then(function(answer) {
- Janus.debug(answer);
- // JSON.stringify doesn't work on some WebRTC objects anymore
- // See https://code.google.com/p/chromium/issues/detail?id=467366
- var jsep = {
- "type": answer.type,
- "sdp": answer.sdp
- };
- callbacks.customizeSdp(jsep);
- answer.sdp = jsep.sdp;
- Janus.log("Setting local description");
- if(sendVideo && simulcast) {
- // This SDP munging only works with Chrome
- if(Janus.webRTCAdapter.browserDetails.browser === "chrome") {
- // FIXME Apparently trying to simulcast when answering breaks video in Chrome...
- //~ Janus.log("Enabling Simulcasting for Chrome (SDP munging)");
- //~ answer.sdp = mungeSdpForSimulcasting(answer.sdp);
- Janus.warn("simulcast=true, but this is an answer, and video breaks in Chrome if we enable it");
- } else if(Janus.webRTCAdapter.browserDetails.browser !== "firefox") {
- Janus.warn("simulcast=true, but this is not Chrome nor Firefox, ignoring");
- }
- }
- config.mySdp = answer.sdp;
- config.pc.setLocalDescription(answer)
- .catch(callbacks.error);
- config.mediaConstraints = mediaConstraints;
- if(!config.iceDone && !config.trickle) {
- // Don't do anything until we have all candidates
- Janus.log("Waiting for all candidates...");
- return;
- }
- // If transforms are present, notify Janus that the media is end-to-end encrypted
- if(config.senderTransforms || config.receiverTransforms) {
- answer["e2ee"] = true;
- }
- callbacks.success(answer);
- }, callbacks.error);
- }
- function sendSDP(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle, not sending anything");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- Janus.log("Sending offer/answer SDP...");
- if(!config.mySdp) {
- Janus.warn("Local SDP instance is invalid, not sending anything...");
- return;
- }
- config.mySdp = {
- "type": config.pc.localDescription.type,
- "sdp": config.pc.localDescription.sdp
- };
- if(config.trickle === false)
- config.mySdp["trickle"] = false;
- Janus.debug(callbacks);
- config.sdpSent = true;
- callbacks.success(config.mySdp);
- }
- function getVolume(handleId, remote) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- return 0;
- }
- var stream = remote ? "remote" : "local";
- var config = pluginHandle.webrtcStuff;
- if(!config.volume[stream])
- config.volume[stream] = { value: 0 };
- // Start getting the volume, if audioLevel in getStats is supported (apparently
- // they're only available in Chrome/Safari right now: https://webrtc-stats.callstats.io/)
- if(config.pc.getStats && (Janus.webRTCAdapter.browserDetails.browser === "chrome" ||
- Janus.webRTCAdapter.browserDetails.browser === "safari")) {
- if(remote && !config.remoteStream) {
- Janus.warn("Remote stream unavailable");
- return 0;
- } else if(!remote && !config.myStream) {
- Janus.warn("Local stream unavailable");
- return 0;
- }
- if(!config.volume[stream].timer) {
- Janus.log("Starting " + stream + " volume monitor");
- config.volume[stream].timer = setInterval(function() {
- config.pc.getStats()
- .then(function(stats) {
- stats.forEach(function (res) {
- if(!res || res.kind !== "audio")
- return;
- if((remote && !res.remoteSource) || (!remote && res.type !== "media-source"))
- return;
- config.volume[stream].value = (res.audioLevel ? res.audioLevel : 0);
- });
- });
- }, 200);
- return 0; // We don't have a volume to return yet
- }
- return config.volume[stream].value;
- } else {
- // audioInputLevel and audioOutputLevel seem only available in Chrome? audioLevel
- // seems to be available on Chrome and Firefox, but they don't seem to work
- Janus.warn("Getting the " + stream + " volume unsupported by browser");
- return 0;
- }
- }
- function isMuted(handleId, video) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- return true;
- }
- var config = pluginHandle.webrtcStuff;
- if(!config.pc) {
- Janus.warn("Invalid PeerConnection");
- return true;
- }
- if(!config.myStream) {
- Janus.warn("Invalid local MediaStream");
- return true;
- }
- if(video) {
- // Check video track
- if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {
- Janus.warn("No video track");
- return true;
- }
- return !config.myStream.getVideoTracks()[0].enabled;
- } else {
- // Check audio track
- if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {
- Janus.warn("No audio track");
- return true;
- }
- return !config.myStream.getAudioTracks()[0].enabled;
- }
- }
- function mute(handleId, video, mute) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- return false;
- }
- var config = pluginHandle.webrtcStuff;
- if(!config.pc) {
- Janus.warn("Invalid PeerConnection");
- return false;
- }
- if(!config.myStream) {
- Janus.warn("Invalid local MediaStream");
- return false;
- }
- if(video) {
- // Mute/unmute video track
- if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {
- Janus.warn("No video track");
- return false;
- }
- config.myStream.getVideoTracks()[0].enabled = !mute;
- return true;
- } else {
- // Mute/unmute audio track
- if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {
- Janus.warn("No audio track");
- return false;
- }
- config.myStream.getAudioTracks()[0].enabled = !mute;
- return true;
- }
- }
- function getBitrate(handleId) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- return "Invalid handle";
- }
- var config = pluginHandle.webrtcStuff;
- if(!config.pc)
- return "Invalid PeerConnection";
- // Start getting the bitrate, if getStats is supported
- if(config.pc.getStats) {
- if(!config.bitrate.timer) {
- Janus.log("Starting bitrate timer (via getStats)");
- config.bitrate.timer = setInterval(function() {
- config.pc.getStats()
- .then(function(stats) {
- stats.forEach(function (res) {
- if(!res)
- return;
- var inStats = false;
- // Check if these are statistics on incoming media
- if((res.mediaType === "video" || res.id.toLowerCase().indexOf("video") > -1) &&
- res.type === "inbound-rtp" && res.id.indexOf("rtcp") < 0) {
- // New stats
- inStats = true;
- } else if(res.type == 'ssrc' && res.bytesReceived &&
- (res.googCodecName === "VP8" || res.googCodecName === "")) {
- // Older Chromer versions
- inStats = true;
- }
- // Parse stats now
- if(inStats) {
- config.bitrate.bsnow = res.bytesReceived;
- config.bitrate.tsnow = res.timestamp;
- if(config.bitrate.bsbefore === null || config.bitrate.tsbefore === null) {
- // Skip this round
- config.bitrate.bsbefore = config.bitrate.bsnow;
- config.bitrate.tsbefore = config.bitrate.tsnow;
- } else {
- // Calculate bitrate
- var timePassed = config.bitrate.tsnow - config.bitrate.tsbefore;
- if(Janus.webRTCAdapter.browserDetails.browser === "safari")
- timePassed = timePassed/1000; // Apparently the timestamp is in microseconds, in Safari
- var bitRate = Math.round((config.bitrate.bsnow - config.bitrate.bsbefore) * 8 / timePassed);
- if(Janus.webRTCAdapter.browserDetails.browser === "safari")
- bitRate = parseInt(bitRate/1000);
- config.bitrate.value = bitRate + ' kbits/sec';
- //~ Janus.log("Estimated bitrate is " + config.bitrate.value);
- config.bitrate.bsbefore = config.bitrate.bsnow;
- config.bitrate.tsbefore = config.bitrate.tsnow;
- }
- }
- });
- });
- }, 1000);
- return "0 kbits/sec"; // We don't have a bitrate value yet
- }
- return config.bitrate.value;
- } else {
- Janus.warn("Getting the video bitrate unsupported by browser");
- return "Feature unsupported by browser";
- }
- }
- function webrtcError(error) {
- Janus.error("WebRTC error:", error);
- }
- function cleanupWebrtc(handleId, hangupRequest) {
- Janus.log("Cleaning WebRTC stuff");
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle) {
- // Nothing to clean
- return;
- }
- var config = pluginHandle.webrtcStuff;
- if(config) {
- if(hangupRequest === true) {
- // Send a hangup request (we don't really care about the response)
- var request = { "janus": "hangup", "transaction": Janus.randomString(12) };
- if(pluginHandle.token)
- request["token"] = pluginHandle.token;
- if(apisecret)
- request["apisecret"] = apisecret;
- Janus.debug("Sending hangup request (handle=" + handleId + "):");
- Janus.debug(request);
- if(websockets) {
- request["session_id"] = sessionId;
- request["handle_id"] = handleId;
- ws.send(JSON.stringify(request));
- } else {
- Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
- verb: 'POST',
- withCredentials: withCredentials,
- body: request
- });
- }
- }
- // Cleanup stack
- config.remoteStream = null;
- if(config.volume) {
- if(config.volume["local"] && config.volume["local"].timer)
- clearInterval(config.volume["local"].timer);
- if(config.volume["remote"] && config.volume["remote"].timer)
- clearInterval(config.volume["remote"].timer);
- }
- config.volume = {};
- if(config.bitrate.timer)
- clearInterval(config.bitrate.timer);
- config.bitrate.timer = null;
- config.bitrate.bsnow = null;
- config.bitrate.bsbefore = null;
- config.bitrate.tsnow = null;
- config.bitrate.tsbefore = null;
- config.bitrate.value = null;
- if(!config.streamExternal && config.myStream) {
- Janus.log("Stopping local stream tracks");
- Janus.stopAllTracks(config.myStream);
- }
- config.streamExternal = false;
- config.myStream = null;
- // Close PeerConnection
- try {
- config.pc.close();
- } catch(e) {
- // Do nothing
- }
- config.pc = null;
- config.candidates = null;
- config.mySdp = null;
- config.remoteSdp = null;
- config.iceDone = false;
- config.dataChannel = {};
- config.dtmfSender = null;
- config.senderTransforms = null;
- config.receiverTransforms = null;
- }
- pluginHandle.oncleanup();
- }
- // Helper method to munge an SDP to enable simulcasting (Chrome only)
- function mungeSdpForSimulcasting(sdp) {
- // Let's munge the SDP to add the attributes for enabling simulcasting
- // (based on https://gist.github.com/ggarber/a19b4c33510028b9c657)
- var lines = sdp.split("\r\n");
- var video = false;
- var ssrc = [ -1 ], ssrc_fid = [ -1 ];
- var cname = null, msid = null, mslabel = null, label = null;
- var insertAt = -1;
- for(var i=0; i<lines.length; i++) {
- var mline = lines[i].match(/m=(\w+) */);
- if(mline) {
- var medium = mline[1];
- if(medium === "video") {
- // New video m-line: make sure it's the first one
- if(ssrc[0] < 0) {
- video = true;
- } else {
- // We're done, let's add the new attributes here
- insertAt = i;
- break;
- }
- } else {
- // New non-video m-line: do we have what we were looking for?
- if(ssrc[0] > -1) {
- // We're done, let's add the new attributes here
- insertAt = i;
- break;
- }
- }
- continue;
- }
- if(!video)
- continue;
- var fid = lines[i].match(/a=ssrc-group:FID (\d+) (\d+)/);
- if(fid) {
- ssrc[0] = fid[1];
- ssrc_fid[0] = fid[2];
- lines.splice(i, 1); i--;
- continue;
- }
- if(ssrc[0]) {
- var match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)')
- if(match) {
- cname = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)')
- if(match) {
- msid = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)')
- if(match) {
- mslabel = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)')
- if(match) {
- label = match[1];
- }
- if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- }
- if(lines[i].length == 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- }
- if(ssrc[0] < 0) {
- // Couldn't find a FID attribute, let's just take the first video SSRC we find
- insertAt = -1;
- video = false;
- for(var i=0; i<lines.length; i++) {
- var mline = lines[i].match(/m=(\w+) */);
- if(mline) {
- var medium = mline[1];
- if(medium === "video") {
- // New video m-line: make sure it's the first one
- if(ssrc[0] < 0) {
- video = true;
- } else {
- // We're done, let's add the new attributes here
- insertAt = i;
- break;
- }
- } else {
- // New non-video m-line: do we have what we were looking for?
- if(ssrc[0] > -1) {
- // We're done, let's add the new attributes here
- insertAt = i;
- break;
- }
- }
- continue;
- }
- if(!video)
- continue;
- if(ssrc[0] < 0) {
- var value = lines[i].match(/a=ssrc:(\d+)/);
- if(value) {
- ssrc[0] = value[1];
- lines.splice(i, 1); i--;
- continue;
- }
- } else {
- var match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)')
- if(match) {
- cname = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)')
- if(match) {
- msid = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)')
- if(match) {
- mslabel = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)')
- if(match) {
- label = match[1];
- }
- if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- }
- if(lines[i].length === 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- }
- }
- if(ssrc[0] < 0) {
- // Still nothing, let's just return the SDP we were asked to munge
- Janus.warn("Couldn't find the video SSRC, simulcasting NOT enabled");
- return sdp;
- }
- if(insertAt < 0) {
- // Append at the end
- insertAt = lines.length;
- }
- // Generate a couple of SSRCs (for retransmissions too)
- // Note: should we check if there are conflicts, here?
- ssrc[1] = Math.floor(Math.random()*0xFFFFFFFF);
- ssrc[2] = Math.floor(Math.random()*0xFFFFFFFF);
- ssrc_fid[1] = Math.floor(Math.random()*0xFFFFFFFF);
- ssrc_fid[2] = Math.floor(Math.random()*0xFFFFFFFF);
- // Add attributes to the SDP
- for(var i=0; i<ssrc.length; i++) {
- if(cname) {
- lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' cname:' + cname);
- insertAt++;
- }
- if(msid) {
- lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' msid:' + msid);
- insertAt++;
- }
- if(mslabel) {
- lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' mslabel:' + mslabel);
- insertAt++;
- }
- if(label) {
- lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' label:' + label);
- insertAt++;
- }
- // Add the same info for the retransmission SSRC
- if(cname) {
- lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' cname:' + cname);
- insertAt++;
- }
- if(msid) {
- lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' msid:' + msid);
- insertAt++;
- }
- if(mslabel) {
- lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' mslabel:' + mslabel);
- insertAt++;
- }
- if(label) {
- lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' label:' + label);
- insertAt++;
- }
- }
- lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[2] + ' ' + ssrc_fid[2]);
- lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[1] + ' ' + ssrc_fid[1]);
- lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[0] + ' ' + ssrc_fid[0]);
- lines.splice(insertAt, 0, 'a=ssrc-group:SIM ' + ssrc[0] + ' ' + ssrc[1] + ' ' + ssrc[2]);
- sdp = lines.join("\r\n");
- if(!sdp.endsWith("\r\n"))
- sdp += "\r\n";
- return sdp;
- }
- // Helper methods to parse a media object
- function isAudioSendEnabled(media) {
- Janus.debug("isAudioSendEnabled:", media);
- if(!media)
- return true; // Default
- if(media.audio === false)
- return false; // Generic audio has precedence
- if(media.audioSend === undefined || media.audioSend === null)
- return true; // Default
- return (media.audioSend === true);
- }
- function isAudioSendRequired(media) {
- Janus.debug("isAudioSendRequired:", media);
- if(!media)
- return false; // Default
- if(media.audio === false || media.audioSend === false)
- return false; // If we're not asking to capture audio, it's not required
- if(media.failIfNoAudio === undefined || media.failIfNoAudio === null)
- return false; // Default
- return (media.failIfNoAudio === true);
- }
- function isAudioRecvEnabled(media) {
- Janus.debug("isAudioRecvEnabled:", media);
- if(!media)
- return true; // Default
- if(media.audio === false)
- return false; // Generic audio has precedence
- if(media.audioRecv === undefined || media.audioRecv === null)
- return true; // Default
- return (media.audioRecv === true);
- }
- function isVideoSendEnabled(media) {
- Janus.debug("isVideoSendEnabled:", media);
- if(!media)
- return true; // Default
- if(media.video === false)
- return false; // Generic video has precedence
- if(media.videoSend === undefined || media.videoSend === null)
- return true; // Default
- return (media.videoSend === true);
- }
- function isVideoSendRequired(media) {
- Janus.debug("isVideoSendRequired:", media);
- if(!media)
- return false; // Default
- if(media.video === false || media.videoSend === false)
- return false; // If we're not asking to capture video, it's not required
- if(media.failIfNoVideo === undefined || media.failIfNoVideo === null)
- return false; // Default
- return (media.failIfNoVideo === true);
- }
- function isVideoRecvEnabled(media) {
- Janus.debug("isVideoRecvEnabled:", media);
- if(!media)
- return true; // Default
- if(media.video === false)
- return false; // Generic video has precedence
- if(media.videoRecv === undefined || media.videoRecv === null)
- return true; // Default
- return (media.videoRecv === true);
- }
- function isScreenSendEnabled(media) {
- Janus.debug("isScreenSendEnabled:", media);
- if (!media)
- return false;
- if (typeof media.video !== 'object' || typeof media.video.mandatory !== 'object')
- return false;
- var constraints = media.video.mandatory;
- if (constraints.chromeMediaSource)
- return constraints.chromeMediaSource === 'desktop' || constraints.chromeMediaSource === 'screen';
- else if (constraints.mozMediaSource)
- return constraints.mozMediaSource === 'window' || constraints.mozMediaSource === 'screen';
- else if (constraints.mediaSource)
- return constraints.mediaSource === 'window' || constraints.mediaSource === 'screen';
- return false;
- }
- function isDataEnabled(media) {
- Janus.debug("isDataEnabled:", media);
- if(Janus.webRTCAdapter.browserDetails.browser === "edge") {
- Janus.warn("Edge doesn't support data channels yet");
- return false;
- }
- if(media === undefined || media === null)
- return false; // Default
- return (media.data === true);
- }
- function isTrickleEnabled(trickle) {
- Janus.debug("isTrickleEnabled:", trickle);
- return (trickle === false) ? false : true;
- }
- }
|