| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505 |
- /*
- 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 !== undefined) {
- 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 !== undefined) {
- 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;
- // Initialization
- Janus.init = function(options) {
- options = options || {};
- options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop;
- if(Janus.initDone === true) {
- // 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 i in options.debug) {
- var d = options.debug[i];
- 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;
- }
- }
- }
- 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
- try {
- var tracks = stream.getTracks();
- for(var i in tracks) {
- var mst = tracks[i];
- if(mst !== null && mst !== undefined)
- mst.stop();
- }
- } catch(e) {}
- });
- })
- .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) {
- if(Janus.webRTCAdapter.browserDetails.browser === 'chrome') {
- var chromever = Janus.webRTCAdapter.browserDetails.version;
- if(chromever >= 52) {
- element.srcObject = stream;
- } else if(typeof element.src !== 'undefined') {
- element.src = URL.createObjectURL(stream);
- } else {
- Janus.error("Error attaching stream to element");
- }
- } else {
- element.srcObject = stream;
- }
- };
- Janus.reattachMediaStream = function(to, from) {
- if(Janus.webRTCAdapter.browserDetails.browser === 'chrome') {
- var chromever = Janus.webRTCAdapter.browserDetails.version;
- if(chromever >= 52) {
- to.srcObject = from.srcObject;
- } else if(typeof to.src !== 'undefined') {
- to.src = from.src;
- } else {
- Janus.error("Error reattaching stream to element");
- }
- } else {
- to.srcObject = from.srcObject;
- }
- };
- // 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] !== null && Janus.sessions[s] !== undefined &&
- Janus.sessions[s].destroyOnUnload) {
- Janus.log("Destroying session " + s);
- Janus.sessions[s].destroy({asyncRequest: false, 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 i in RTCRtpSender.getCapabilities("video").codecs) {
- var codec = RTCRtpSender.getCapabilities("video").codecs[i];
- 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(!('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
- const 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 !== undefined && window.RTCPeerConnection !== null;
- };
- // 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 !== undefined && navigator.mediaDevices !== null &&
- navigator.mediaDevices.getUserMedia !== undefined && navigator.mediaDevices.getUserMedia !== null;
- };
- // 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) {
- if(Janus.initDone === undefined) {
- gatewayCallbacks.error("Library not initialized");
- return {};
- }
- if(!Janus.isWebrtcSupported()) {
- gatewayCallbacks.error("WebRTC not supported by this browser");
- return {};
- }
- Janus.log("Library initialized: " + Janus.initDone);
- 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(gatewayCallbacks.server === null || gatewayCallbacks.server === undefined) {
- gatewayCallbacks.error("Invalid server url");
- return {};
- }
- var websockets = false;
- var ws = null;
- var wsHandlers = {};
- var wsKeepaliveTimeoutId = null;
- var servers = null, 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;
- if(iceServers === undefined || iceServers === null)
- 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;
- if(ipv6Support === undefined || ipv6Support === null)
- ipv6Support = false;
- // 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.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 !== undefined && maxev !== null)
- longpoll = longpoll + "&maxev=" + maxev;
- if(token !== null && token !== undefined)
- longpoll = longpoll + "&token=" + encodeURIComponent(token);
- if(apisecret !== null && apisecret !== undefined)
- 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["rtcgw"] === "keepalive") {
- // Nothing happened
- Janus.vdebug("Got a keepalive on session " + sessionId);
- return;
- } else if(json["rtcgw"] === "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 !== null && transaction !== undefined) {
- var reportSuccess = transactions[transaction];
- if(reportSuccess !== null && reportSuccess !== undefined) {
- reportSuccess(json);
- }
- delete transactions[transaction];
- }
- return;
- } else if(json["rtcgw"] === "success") {
- // Success!
- Janus.debug("Got a success on session " + sessionId);
- Janus.debug(json);
- var transaction = json["transaction"];
- if(transaction !== null && transaction !== undefined) {
- var reportSuccess = transactions[transaction];
- if(reportSuccess !== null && reportSuccess !== undefined) {
- reportSuccess(json);
- }
- delete transactions[transaction];
- }
- return;
- } else if(json["rtcgw"] === "trickle") {
- // We got a trickle candidate from Janus
- var sender = json["sender"];
- if(sender === undefined || sender === null) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(pluginHandle === undefined || pluginHandle === null) {
- 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["rtcgw"] === "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 === undefined || sender === null) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(pluginHandle === undefined || pluginHandle === null) {
- Janus.debug("This handle is not attached to this session");
- return;
- }
- pluginHandle.webrtcState(true);
- return;
- } else if(json["rtcgw"] === "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 === undefined || sender === null) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(pluginHandle === undefined || pluginHandle === null) {
- Janus.debug("This handle is not attached to this session");
- return;
- }
- pluginHandle.webrtcState(false, json["reason"]);
- pluginHandle.hangup();
- } else if(json["rtcgw"] === "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 === undefined || sender === null) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(pluginHandle === undefined || pluginHandle === null) {
- // Don't warn here because destroyHandle causes this situation.
- return;
- }
- pluginHandle.detached = true;
- pluginHandle.ondetached();
- pluginHandle.detach();
- } else if(json["rtcgw"] === "media") {
- // Media started/stopped flowing
- Janus.debug("Got a media event on session " + sessionId);
- Janus.debug(json);
- var sender = json["sender"];
- if(sender === undefined || sender === null) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(pluginHandle === undefined || pluginHandle === null) {
- Janus.debug("This handle is not attached to this session");
- return;
- }
- pluginHandle.mediaState(json["type"], json["receiving"]);
- } else if(json["rtcgw"] === "slowlink") {
- Janus.debug("Got a slowlink event on session " + sessionId);
- Janus.debug(json);
- // Trouble uplink or downlink
- var sender = json["sender"];
- if(sender === undefined || sender === null) {
- Janus.warn("Missing sender...");
- return;
- }
- var pluginHandle = pluginHandles[sender];
- if(pluginHandle === undefined || pluginHandle === null) {
- Janus.debug("This handle is not attached to this session");
- return;
- }
- pluginHandle.slowLink(json["uplink"], json["lost"]);
- } else if(json["rtcgw"] === "error") {
- // Oops, something wrong happened
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- Janus.debug(json);
- var transaction = json["transaction"];
- if(transaction !== null && transaction !== undefined) {
- var reportSuccess = transactions[transaction];
- if(reportSuccess !== null && reportSuccess !== undefined) {
- reportSuccess(json);
- }
- delete transactions[transaction];
- }
- return;
- } else if(json["rtcgw"] === "event") {
- Janus.debug("Got a plugin event on session " + sessionId);
- Janus.debug(json);
- var sender = json["sender"];
- if(sender === undefined || sender === null) {
- Janus.warn("Missing sender...");
- return;
- }
- var plugindata = json["plugindata"];
- if(plugindata === undefined || plugindata === null) {
- 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 === undefined || pluginHandle === null) {
- Janus.warn("This handle is not attached to this session");
- return;
- }
- var jsep = json["jsep"];
- if(jsep !== undefined && jsep !== null) {
- Janus.debug("Handling SDP as well...");
- Janus.debug(jsep);
- }
- var callback = pluginHandle.onmessage;
- if(callback !== null && callback !== undefined) {
- 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["rtcgw"] === "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["rtcgw"] + "' on session " + sessionId);
- Janus.debug(json);
- }
- }
- // Private helper to send keep-alive messages on WebSockets
- function keepAlive() {
- if(server === null || !websockets || !connected)
- return;
- wsKeepaliveTimeoutId = setTimeout(keepAlive, keepAlivePeriod);
- var request = { "rtcgw": "keepalive", "session_id": sessionId, "transaction": Janus.randomString(12) };
- if(token !== null && token !== undefined)
- request["token"] = token;
- if(apisecret !== null && apisecret !== undefined)
- request["apisecret"] = apisecret;
- ws.send(JSON.stringify(request));
- }
- // Private method to create a session
- function createSession(callbacks) {
- var transaction = Janus.randomString(12);
- // console.log("jannus create_token",stream);
- var request = {
- "rtcgw": "create",
- "transaction": transaction,
- "token":window.EZUIKit.opt.stream,
- "device": window.EZUIKit.opt.deviceSerial,
- "channel": window.EZUIKit.opt.channelNo,
- };
- if(callbacks["reconnect"]) {
- // We're reconnecting, claim the session
- connected = false;
- request["rtcgw"] = "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 !== null && token !== undefined)
- request["token"] = token;
- if(apisecret !== null && apisecret !== undefined)
- request["apisecret"] = apisecret;
- if(server === null && 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, 'rtcgw-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["rtcgw"] !== "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 === null || !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["rtcgw"] !== "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 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;
- var asyncRequest = true;
- if(callbacks.asyncRequest !== undefined && callbacks.asyncRequest !== null)
- asyncRequest = (callbacks.asyncRequest === true);
- var notifyDestroyed = true;
- if(callbacks.notifyDestroyed !== undefined && callbacks.notifyDestroyed !== null)
- notifyDestroyed = (callbacks.notifyDestroyed === true);
- var cleanupHandles = false;
- if(callbacks.cleanupHandles !== undefined && callbacks.cleanupHandles !== null)
- cleanupHandles = (callbacks.cleanupHandles === true);
- Janus.log("Destroying session " + sessionId + " (async=" + asyncRequest + ")");
- if(!connected) {
- Janus.warn("Is the server down? (connected=false)");
- callbacks.success();
- return;
- }
- if(sessionId === undefined || sessionId === null) {
- Janus.warn("No session to destroy");
- callbacks.success();
- if(notifyDestroyed)
- gatewayCallbacks.destroyed();
- return;
- }
- if(cleanupHandles) {
- for(var handleId in pluginHandles)
- destroyHandle(handleId, { noRequest: true });
- }
- // No need to destroy all handles first, Janus will do that itself
- var request = { "rtcgw": "destroy", "transaction": Janus.randomString(12) };
- if(token !== null && token !== undefined)
- request["token"] = token;
- if(apisecret !== null && apisecret !== undefined)
- request["apisecret"] = apisecret;
- 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);
- ws.send(JSON.stringify(request));
- return;
- }
- Janus.httpAPICall(server + "/" + sessionId, {
- verb: 'POST',
- async: asyncRequest, // Sometimes we need false here, or destroying in onbeforeunload won't work
- withCredentials: withCredentials,
- body: request,
- success: function(json) {
- Janus.log("Destroyed session:");
- Janus.debug(json);
- sessionId = null;
- connected = false;
- if(json["rtcgw"] !== "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 === undefined || plugin === null) {
- 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 = { "rtcgw": "attach", "plugin": plugin, "opaque_id": opaqueId, "transaction": transaction };
- if(handleToken !== null && handleToken !== undefined)
- request["token"] = handleToken;
- if(apisecret !== null && apisecret !== undefined)
- request["apisecret"] = apisecret;
- if(websockets) {
- transactions[transaction] = function(json) {
- Janus.debug(json);
- if(json["rtcgw"] !== "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["rtcgw"] !== "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
- }
- });
- }
- // 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var message = callbacks.message;
- var jsep = callbacks.jsep;
- var transaction = Janus.randomString(12);
- var request = { "rtcgw": "message", "body": message, "transaction": transaction };
- if(pluginHandle.token !== null && pluginHandle.token !== undefined)
- request["token"] = pluginHandle.token;
- if(apisecret !== null && apisecret !== undefined)
- request["apisecret"] = apisecret;
- if(jsep !== null && jsep !== undefined)
- request.jsep = jsep;
- 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["rtcgw"] === "success") {
- // We got a success, must have been a synchronous transaction
- var plugindata = json["plugindata"];
- if(plugindata === undefined || plugindata === null) {
- 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["rtcgw"] !== "ack") {
- // Not a success and not an ack, must be an error
- if(json["error"] !== undefined && json["error"] !== null) {
- 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["rtcgw"] === "success") {
- // We got a success, must have been a synchronous transaction
- var plugindata = json["plugindata"];
- if(plugindata === undefined || plugindata === null) {
- 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["rtcgw"] !== "ack") {
- // Not a success and not an ack, must be an error
- if(json["error"] !== undefined && json["error"] !== null) {
- 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- return;
- }
- var request = { "rtcgw": "trickle", "candidate": candidate, "transaction": Janus.randomString(12) };
- if(pluginHandle.token !== null && pluginHandle.token !== undefined)
- request["token"] = pluginHandle.token;
- if(apisecret !== null && apisecret !== undefined)
- 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["rtcgw"] !== "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, incoming, pendingText) {
- var pluginHandle = pluginHandles[handleId];
- if(pluginHandle === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- 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 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 i in config.dataChannel[label].pending) {
- var text = config.dataChannel[label].pending[i];
- Janus.log("Sending string on data channel <" + label + ">: " + text);
- config.dataChannel[label].send(text);
- }
- config.dataChannel[label].pending = [];
- }
- // Notify the open data channel
- pluginHandle.ondataopen(label);
- }
- }
- var onDataChannelError = function(error) {
- Janus.error('Got error on data channel:', error);
- // TODO
- }
- if(!incoming) {
- // FIXME Add options (ordered, maxRetransmits, etc.)
- config.dataChannel[dclabel] = config.pc.createDataChannel(dclabel, {ordered:false});
- } 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(pendingText)
- config.dataChannel[dclabel].pending.push(pendingText);
- }
- // 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- var text = callbacks.text;
- if(text === null || text === undefined) {
- Janus.warn("Invalid text");
- callbacks.error("Invalid text");
- 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, false, text);
- callbacks.success();
- return;
- }
- if(config.dataChannel[label].readyState !== "open") {
- config.dataChannel[label].pending.push(text);
- callbacks.success();
- return;
- }
- Janus.log("Sending string on data channel <" + label + ">: " + text);
- config.dataChannel[label].send(text);
- 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- if(config.dtmfSender === null || config.dtmfSender === undefined) {
- // Create the DTMF sender the proper way, if possible
- if(config.pc !== undefined && config.pc !== null) {
- 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 === null || config.dtmfSender === undefined) {
- Janus.warn("Invalid DTMF configuration");
- callbacks.error("Invalid DTMF configuration");
- return;
- }
- }
- var dtmf = callbacks.dtmf;
- if(dtmf === null || dtmf === undefined) {
- Janus.warn("Invalid DTMF parameters");
- callbacks.error("Invalid DTMF parameters");
- return;
- }
- var tones = dtmf.tones;
- if(tones === null || tones === undefined) {
- Janus.warn("Invalid DTMF string");
- callbacks.error("Invalid DTMF string");
- return;
- }
- var duration = dtmf.duration;
- if(duration === null || duration === undefined)
- duration = 500; // We choose 500ms as the default duration for a tone
- var gap = dtmf.gap;
- if(gap === null || gap === undefined)
- 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 asyncRequest = true;
- if(callbacks.asyncRequest !== undefined && callbacks.asyncRequest !== null)
- asyncRequest = (callbacks.asyncRequest === true);
- var noRequest = true;
- if(callbacks.noRequest !== undefined && callbacks.noRequest !== null)
- noRequest = (callbacks.noRequest === true);
- Janus.log("Destroying handle " + handleId + " (async=" + asyncRequest + ")");
- cleanupWebrtc(handleId);
- var pluginHandle = pluginHandles[handleId];
- if(pluginHandle === null || pluginHandle === undefined || 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 = { "rtcgw": "detach", "transaction": Janus.randomString(12) };
- if(pluginHandle.token !== null && pluginHandle.token !== undefined)
- request["token"] = pluginHandle.token;
- if(apisecret !== null && apisecret !== undefined)
- 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',
- async: asyncRequest, // Sometimes we need false here, or destroying in onbeforeunload won't work
- withCredentials: withCredentials,
- body: request,
- success: function(json) {
- Janus.log("Destroyed handle:");
- Janus.debug(json);
- if(json["rtcgw"] !== "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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- 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 i in transceivers) {
- var t = transceivers[i];
- 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 i in transceivers) {
- var t = transceivers[i];
- 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 === true) {
- 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";
- }
- 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 == null ||
- (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;
- Janus.log("Adding onended callback to track:", event.track);
- event.track.onended = function(ev) {
- Janus.log("Remote track muted/removed:", ev);
- if(config.remoteStream) {
- config.remoteStream.removeTrack(ev.target);
- pluginHandle.onremotestream(config.remoteStream);
- }
- };
- event.track.onmute = event.track.onended;
- event.track.onunmute = function(ev) {
- Janus.log("Remote track flowing again:", ev);
- try {
- config.remoteStream.addTrack(ev.target);
- pluginHandle.onremotestream(config.remoteStream);
- } catch(e) {
- Janus.error(e);
- };
- };
- };
- }
- if(addTracks && stream !== null && stream !== undefined) {
- Janus.log('Adding local stream');
- var simulcast2 = callbacks.simulcast2 === true ? true : false;
- stream.getTracks().forEach(function(track) {
- Janus.log('Adding local track:', track);
- if(!simulcast2) {
- config.pc.addTrack(track, stream);
- } else {
- if(track.kind === "audio") {
- config.pc.addTrack(track, stream);
- } else {
- Janus.log('Enabling rid-based simulcasting:', track);
- const 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 data channel");
- createDataChannel(handleId, Janus.dataChanDefaultLabel, false);
- config.pc.ondatachannel = function(event) {
- Janus.log("Data channel created by Janus:", event);
- createDataChannel(handleId, event.channel.label, 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 === null || jsep === undefined) {
- 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;
- }
- callbacks.media = callbacks.media || { audio: true, video: true };
- var media = callbacks.media;
- var pluginHandle = pluginHandles[handleId];
- if(pluginHandle === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- 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 === undefined || config.pc === null) {
- // Nope, new PeerConnection
- media.update = false;
- media.keepAudio = false;
- media.keepVideo = false;
- } else if(config.pc !== undefined && config.pc !== null) {
- 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 !== null && callbacks.stream !== undefined) {
- // 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 === null || config.myStream === undefined) {
- // 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() === null
- || config.myStream.getAudioTracks() === undefined
- || 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.keepVideo = 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 === null || config.myStream === undefined) {
- // 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() === null
- || config.myStream.getVideoTracks() === undefined
- || 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 s = config.myStream.getAudioTracks()[0];
- Janus.log("Removing audio track:", s);
- config.myStream.removeTrack(s);
- try {
- s.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 index in config.pc.getSenders()) {
- var s = config.pc.getSenders()[index];
- if(s && s.track && s.track.kind === "audio") {
- Janus.log("Removing audio sender:", s);
- config.pc.removeTrack(s);
- }
- }
- }
- }
- }
- if(media.removeVideo || media.replaceVideo) {
- if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) {
- var s = config.myStream.getVideoTracks()[0];
- Janus.log("Removing video track:", s);
- config.myStream.removeTrack(s);
- try {
- s.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 index in config.pc.getSenders()) {
- var s = config.pc.getSenders()[index];
- if(s && s.track && s.track.kind === "video") {
- Janus.log("Removing video sender:", s);
- config.pc.removeTrack(s);
- }
- }
- }
- }
- }
- }
- // Was a MediaStream object passed, or do we need to take care of that?
- if(callbacks.stream !== null && callbacks.stream !== undefined) {
- 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
- try {
- // Try a MediaStreamTrack.stop() for each track
- var tracks = config.myStream.getTracks();
- for(var i in tracks) {
- var mst = tracks[i];
- Janus.log(mst);
- if(mst !== null && mst !== undefined)
- mst.stop();
- }
- } catch(e) {
- // Do nothing if this fails
- }
- 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 === true && media != undefined && media != null) {
- if(typeof media.audio === 'object') {
- audioSupport = media.audio;
- }
- }
- var videoSupport = isVideoSendEnabled(media);
- if(videoSupport === true && media != undefined && media != null) {
- var simulcast = callbacks.simulcast === true ? true : false;
- var simulcast2 = callbacks.simulcast2 === true ? true : false;
- if((simulcast || simulcast2) && !jsep && (media.video === undefined || media.video === false))
- 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(!media.screenshareFrameRate) {
- media.screenshareFrameRate = 3;
- }
- 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/
- navigator.mediaDevices.getDisplayMedia({ video: true })
- .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 === null || media === undefined || 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('enumerateDevices 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- if(jsep !== undefined && jsep !== null) {
- if(config.pc === null) {
- 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- var simulcast = callbacks.simulcast === true ? true : false;
- 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 i in transceivers) {
- var t = transceivers[i];
- 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 ? true : false;
- 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 = {};
- const 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;
- }
- Janus.log("Offer ready");
- Janus.debug(callbacks);
- 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- var simulcast = callbacks.simulcast === true ? true : false;
- 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 i in transceivers) {
- var t = transceivers[i];
- 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);
- const 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;
- }
- 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle, not sending anything");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- Janus.log("Sending offer/answer SDP...");
- if(config.mySdp === null || config.mySdp === undefined) {
- 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- 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 getStats is supported
- if(config.pc.getStats && Janus.webRTCAdapter.browserDetails.browser === "chrome") {
- if(remote && (config.remoteStream === null || config.remoteStream === undefined)) {
- Janus.warn("Remote stream unavailable");
- return 0;
- } else if(!remote && (config.myStream === null || config.myStream === undefined)) {
- Janus.warn("Local stream unavailable");
- return 0;
- }
- if(config.volume[stream].timer === null || config.volume[stream].timer === undefined) {
- Janus.log("Starting " + stream + " volume monitor");
- config.volume[stream].timer = setInterval(function() {
- config.pc.getStats(function(stats) {
- var results = stats.result();
- for(var i=0; i<results.length; i++) {
- var res = results[i];
- if(res.type == 'ssrc') {
- if(remote && res.stat('audioOutputLevel'))
- config.volume[stream].value = parseInt(res.stat('audioOutputLevel'));
- else if(!remote && res.stat('audioInputLevel'))
- config.volume[stream].value = parseInt(res.stat('audioInputLevel'));
- }
- }
- });
- }, 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- return true;
- }
- var config = pluginHandle.webrtcStuff;
- if(config.pc === null || config.pc === undefined) {
- Janus.warn("Invalid PeerConnection");
- return true;
- }
- if(config.myStream === undefined || config.myStream === null) {
- Janus.warn("Invalid local MediaStream");
- return true;
- }
- if(video) {
- // Check video track
- if(config.myStream.getVideoTracks() === null
- || config.myStream.getVideoTracks() === undefined
- || 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() === null
- || config.myStream.getAudioTracks() === undefined
- || 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 === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- return false;
- }
- var config = pluginHandle.webrtcStuff;
- if(config.pc === null || config.pc === undefined) {
- Janus.warn("Invalid PeerConnection");
- return false;
- }
- if(config.myStream === undefined || config.myStream === null) {
- Janus.warn("Invalid local MediaStream");
- return false;
- }
- if(video) {
- // Mute/unmute video track
- if(config.myStream.getVideoTracks() === null
- || config.myStream.getVideoTracks() === undefined
- || config.myStream.getVideoTracks().length === 0) {
- Janus.warn("No video track");
- return false;
- }
- config.myStream.getVideoTracks()[0].enabled = mute ? false : true;
- return true;
- } else {
- // Mute/unmute audio track
- if(config.myStream.getAudioTracks() === null
- || config.myStream.getAudioTracks() === undefined
- || config.myStream.getAudioTracks().length === 0) {
- Janus.warn("No audio track");
- return false;
- }
- config.myStream.getAudioTracks()[0].enabled = mute ? false : true;
- return true;
- }
- }
- function getBitrate(handleId) {
- var pluginHandle = pluginHandles[handleId];
- if(pluginHandle === null || pluginHandle === undefined ||
- pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
- Janus.warn("Invalid handle");
- return "Invalid handle";
- }
- var config = pluginHandle.webrtcStuff;
- if(config.pc === null || config.pc === undefined)
- return "Invalid PeerConnection";
- // Start getting the bitrate, if getStats is supported
- if(config.pc.getStats) {
- if(config.bitrate.timer === null || config.bitrate.timer === undefined) {
- 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 === null || pluginHandle === undefined) {
- // Nothing to clean
- return;
- }
- var config = pluginHandle.webrtcStuff;
- if(config !== null && config !== undefined) {
- if(hangupRequest === true) {
- // Send a hangup request (we don't really care about the response)
- var request = { "rtcgw": "hangup", "transaction": Janus.randomString(12) };
- if(pluginHandle.token !== null && pluginHandle.token !== undefined)
- request["token"] = pluginHandle.token;
- if(apisecret !== null && apisecret !== undefined)
- 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;
- try {
- // Try a MediaStreamTrack.stop() for each track
- if(!config.streamExternal && config.myStream !== null && config.myStream !== undefined) {
- Janus.log("Stopping local stream tracks");
- var tracks = config.myStream.getTracks();
- for(var i in tracks) {
- var mst = tracks[i];
- Janus.log(mst);
- if(mst !== null && mst !== undefined)
- mst.stop();
- }
- }
- } catch(e) {
- // Do nothing if this fails
- }
- 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;
- }
- 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 === undefined || media === null)
- 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 === undefined || media === null)
- 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 === undefined || media === null)
- 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 === undefined || media === null)
- 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 === undefined || media === null)
- 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 === undefined || media === null)
- 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 === undefined || media === null)
- 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);
- if(trickle === undefined || trickle === null)
- return true; // Default is true
- return (trickle === true);
- }
- };
|