/blog/
/blog/one/
/blog/resume/
/blog/answer/
/blog/code/
/blog/know/
/blog/posts/1109. 航班预订统计/
/blog/posts/1143. 最长公共子序列/
/blog/posts/1221. 分割平衡字符串/
/blog/posts/1436. 旅行终点站/
/blog/posts/1480. 一维数组的动态和/
/blog/posts/1588. 所有奇数长度子数组的和/
/blog/posts/162. 寻找峰值/
/blog/posts/1646. 获取生成数组中的最大值/
/blog/posts/165. 比较版本号/
/blog/posts/166. 分数到小数/
/blog/posts/187. 重复的DNA序列/
/blog/posts/1894. 找到需要补充粉笔的学生编号/
/blog/posts/208. 实现 Trie (前缀树)/
/blog/posts/211. 添加与搜索单词 - 数据结构设计/
/blog/posts/212. 单词搜索 II/
/blog/posts/223. 矩形面积/
/blog/posts/229. 求众数 II/
/blog/posts/230. 二叉搜索树中第K小的元素/
/blog/posts/240. 搜索二维矩阵 II/
/blog/posts/260. 只出现一次的数字 III/
/blog/posts/273. 整数转换英文表示/
/blog/posts/282. 给表达式添加运算符/
/blog/posts/284. 顶端迭代器/
/blog/posts/292. Nim 游戏/
/blog/posts/299. 猜数字游戏/
/blog/posts/301. 删除无效的括号/
/blog/posts/326. 3的幂/
/blog/posts/335. 路径交叉/
/blog/posts/352. 将数据流变为多个不相交区间/
/blog/posts/36. 有效的数独/
/blog/posts/371. 两整数之和/
/blog/posts/38. 外观数列/
/blog/posts/407. 接雨水 II/
/blog/posts/405. 数字转换为十六进制数/
/blog/posts/430. 扁平化多级双向链表/
/blog/posts/437. 路径总和 III/
/blog/posts/447. 回旋镖的数量/
/blog/posts/453. 最小操作次数使数组元素相等/
/blog/posts/470. 用 Rand7() 实现 Rand10()/
/blog/posts/476. 数字的补数/
/blog/posts/502. IPO/
/blog/posts/517. 超级洗衣机/
/blog/posts/524. 通过删除字母匹配到字典里最长单词/
/blog/posts/528. 按权重随机选择/
/blog/posts/600. 不含连续1的非负整数/
/blog/posts/639. 解码方法 II/
/blog/posts/66. 加一/
/blog/posts/678. 有效的括号字符串/
/blog/posts/68. 文本左右对齐/
/blog/posts/704. 二分查找/
/blog/posts/725. 分隔链表/
/blog/posts/789. 逃脱阻碍者/
/blog/posts/787. K 站中转内最便宜的航班/
/blog/posts/869. 重新排序得到 2 的幂/
/blog/posts/881. 救生艇/
/blog/posts/797. 所有可能的路径/
/blog/posts/
/blog/posts/剑指 Offer 10- I. 斐波那契数列/
/blog/posts/剑指 Offer 22. 链表中倒数第k个节点/
/blog/posts/面试题 17.14. 最小K个数/
/blog/workshop/
/blog/answer/common/
/blog/answer/interview/
/blog/answer/invest/
/blog/answer/webRTC/01-前置知识/
/blog/answer/webRTC/02-RTCPeerConnection/
/blog/answer/webRTC/03-实践/
/blog/answer/webRTC/
/blog/code/lodash/01/
/blog/code/lodash/
/blog/code/nuxt/
/blog/code/npm/01-pify/
/blog/code/npm/02-downlaod/
/blog/code/npm/03-video.js/
/blog/code/npm/04-craco/
/blog/code/npm/05-axios/
/blog/code/npm/
/blog/code/opensource/01-第一期/
/blog/code/opensource/02-第二期/
/blog/code/opensource/04-第四期/
/blog/code/opensource/05-第五期/
/blog/code/opensource/07-第七期/
/blog/code/opensource/08-第八期/
/blog/code/opensource/09-第九期/
/blog/code/opensource/10-第十期/
/blog/code/opensource/11-第十一期 玩具vite/
/blog/code/opensource/12-第十二期 ni/
/blog/code/opensource/13-第十三期 open/
/blog/code/opensource/14-第十四期 promisify/
/blog/code/opensource/15-第十五期 element新增组件功能/
/blog/code/opensource/16-第十六期 一行代码统一规范 包管理器/
/blog/code/opensource/17-第十七期 js-cookie/
/blog/code/opensource/18-第十八期 delay/
/blog/code/opensource/21-第未知期/
/blog/code/opensource/
/blog/code/react/
/blog/code/vitepress/
/blog/know/back/
/blog/know/computer/
/blog/know/front/
/blog/know/javascript/
/blog/know/network/01-网路/
/blog/know/network/
/blog/know/tool/
/blog/know/typescript/
/blog/workshop/cli/01-项目创建/
/blog/workshop/cli/02-npm包发布/
/blog/workshop/cli/
/blog/workshop/windows11/
/blog/answer/common/business/vuepress搭建之旅/
/blog/answer/common/business/编写代码之前的思考/
/blog/answer/common/offer/03. 数组中重复的数字/
/blog/answer/common/interview/
/blog/answer/common/offer/04. 二维数组中的查找/
/blog/answer/common/offer/05. 替换空格/
/blog/answer/common/offer/07. 重建二叉树/
/blog/answer/common/offer/06. 从尾到头打印链表/
/blog/answer/common/offer/09. 用两个栈实现队列/
/blog/answer/common/offer/10- I. 斐波那契数列/
/blog/answer/common/offer/10- II. 青蛙跳台阶问题/
/blog/answer/common/offer/11. 旋转数组的最小数字/
/blog/answer/common/offer/
/blog/answer/common/offer/剑指 Offer 10- I. 斐波那契数列/
/blog/answer/common/offer/剑指 Offer II 069. 山峰数组的顶部/
/blog/answer/common/web/
/blog/answer/common/offer/剑指 Offer 22. 链表中倒数第k个节点/
/blog/answer/common/web/拖拽/
/blog/answer/common/web/类型判断/
/blog/answer/invest/book/
/blog/answer/invest/book/不可不知的经济真相/
/blog/answer/invest/book/投资第一课/
/blog/answer/invest/book/纳瓦尔宝典/
/blog/answer/interview/basic/01-html/
/blog/answer/interview/basic/02-css/
/blog/answer/interview/basic/03-javascript/
/blog/answer/interview/basic/
/blog/answer/interview/basic/vue/
/blog/code/react/react/
/blog/answer/webRTC/janus/01-init/
/blog/answer/webRTC/janus/03-attach/
/blog/answer/webRTC/janus/
/blog/code/react/redux/
/blog/code/react/router/01-环境设置/
/blog/answer/webRTC/janus/02-janus/
/blog/code/react/router/
/blog/know/back/Egg/01-基础/
/blog/know/back/Egg/egg兼容mysql和mogodb/
/blog/know/back/Egg/
/blog/know/back/nodejs/01-install/
/blog/know/back/nodejs/98-process/
/blog/know/back/nodejs/99-file/
/blog/know/back/nodejs/
/blog/know/computer/algorithm/01-多选投票算法/
/blog/know/computer/algorithm/02-二叉树的各种遍历/
/blog/know/computer/algorithm/03-位运算/
/blog/know/computer/algorithm/04-距离相关/
/blog/know/computer/algorithm/06-线性表/
/blog/know/computer/algorithm/05-字符串/
/blog/know/computer/algorithm/07-队列/
/blog/know/computer/algorithm/08-栈/
/blog/know/computer/algorithm/09-哈希表/
/blog/know/computer/algorithm/10-dfs/
/blog/know/computer/algorithm/11-bfs/
/blog/know/computer/algorithm/
/blog/know/computer/data/01-队列/
/blog/know/computer/data/03-链表/
/blog/know/computer/data/04-树/
/blog/know/computer/data/05-栈/
/blog/know/computer/data/06-其他/
/blog/know/computer/data/06-堆/
/blog/know/computer/data/
/blog/know/computer/dayOne/1011. 在 D 天内送达包裹的能力/
/blog/know/computer/dayOne/1310. 子数组异或查询/
/blog/know/computer/dayOne/137. 只出现一次的数字 II/
/blog/know/computer/dayOne/1473. 粉刷房子 III/
/blog/know/computer/dayOne/1482. 制作 m 束花所需的最少天数/
/blog/know/computer/dayOne/1486. 数组异或操作/
/blog/know/computer/dayOne/1720. 解码异或后的数组/
/blog/know/computer/dayOne/1723. 完成所有工作的最短时间/
/blog/know/computer/dayOne/1734. 解码异或后的排列/
/blog/know/computer/dayOne/554. 砖墙/
/blog/know/computer/dayOne/633. 平方数之和/
/blog/know/computer/dayOne/690. 员工的重要性/
/blog/know/computer/dayOne/7. 整数反转/
/blog/know/computer/dayOne/403. 青蛙过河/
/blog/know/computer/dayOne/740. 删除并获得点数/
/blog/know/computer/dayOne/872. 叶子相似的树/
/blog/know/computer/dayOne/938. 二叉搜索树的范围和/
/blog/know/computer/dayOne/
/blog/know/computer/network/01-网路协议/
/blog/know/computer/network/98-关于options请求/
/blog/know/computer/network/
/blog/know/engineering/babel/
/blog/know/engineering/npm/78-npm push/
/blog/know/engineering/npm/
/blog/know/engineering/react/01-简介/
/blog/know/engineering/react/03-react-router/
/blog/know/engineering/react/04-hooks/
/blog/know/engineering/react/
/blog/know/engineering/webpack/01-基础/
/blog/know/engineering/webpack/02-loader/
/blog/know/engineering/webpack/03-plugin/
/blog/know/engineering/webpack/
/blog/know/front/css/01-选择器/
/blog/know/front/css/02-盒模型/
/blog/know/front/css/03-布局/
/blog/know/front/css/04-文本属性/
/blog/know/front/css/
/blog/know/front/network/01-网路/
/blog/know/front/network/
/blog/know/front/html/01-head/
/blog/know/front/html/02-body/
/blog/know/front/html/09-canvas/
/blog/know/front/html/10-svg/
/blog/know/front/html/
/blog/know/front/react/01-xx/
/blog/know/front/react/02-props/
/blog/know/front/react/03-state/
/blog/know/front/react/04-Lifecycle/
/blog/know/front/react/05-hook/
/blog/know/front/react/06-redux/
/blog/know/front/react/
/blog/know/front/webRTC/
/blog/know/front/webpack/01-基础/
/blog/know/front/webpack/02-loader/
/blog/know/front/webpack/03-plugin/
/blog/know/front/webpack/
/blog/know/javascript/BOM和DOM/01-navigator/
/blog/know/javascript/BOM和DOM/
/blog/know/javascript/advance/07-迭代器与生成器/
/blog/know/javascript/advance/11-promise/
/blog/know/javascript/advance/12-正则表达式/
/blog/know/javascript/advance/
/blog/know/javascript/basic/02-类/
/blog/know/javascript/basic/03-数据类型/
/blog/know/javascript/basic/04-函数进阶/
/blog/know/javascript/basic/
/blog/know/javascript/api/99-promise/
/blog/know/javascript/basic/01-语言基础/
/blog/know/javascript/basic/05-原型链/
/blog/know/tool/chromedevtools/
/blog/know/tool/git/01-基础/
/blog/know/tool/git/02-log/
/blog/know/tool/git/03-checkout/
/blog/know/tool/git/09-submodule/
/blog/know/tool/git/04-diff/
/blog/know/tool/git/10-workflow/
/blog/know/tool/git/
/blog/know/tool/vscode/
/blog/know/typescript/basic/01-基础概念/
/blog/know/typescript/basic/02-基础类型/
/blog/know/typescript/basic/03-接口/
/blog/know/typescript/basic/04-类/
/blog/know/typescript/basic/05-函数/
/blog/know/typescript/basic/06-泛型/
/blog/know/typescript/basic/07-枚举/
/blog/know/typescript/basic/08-高级类型/
/blog/know/typescript/basic/09-模块/
/blog/know/typescript/basic/10-模块解析/
/blog/know/typescript/basic/11-命名空间/
/blog/know/typescript/basic/
/blog/know/typescript/declarationfiles/01-示例/
/blog/know/typescript/declarationfiles/02-结构/
/blog/know/typescript/declarationfiles/03-模板/
/blog/know/typescript/declarationfiles/04-最佳实践/
/blog/know/typescript/declarationfiles/05-深入/
/blog/know/typescript/declarationfiles/

示例代码

import * as Janus from './janus.es.js'

Janus.init({
   debug: true,
   dependencies: Janus.useDefaultDependencies(), // or: Janus.useOldDependencies() to get the behaviour of previous Janus versions
   callback: function() {
           // Done!
   });
});

解析

init函数将传入的参数进行初始化, 如果已经初始化就直接调用传入的回调函数

如果没有初始化,就开始初始化:

判断 console 对象, 然后对 根据debug参数进行赋值

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;
        }
    }
}
Janus.log("Initializing library");

然后就是根据传入的 依赖参数 进行相关函数的初始化

var usedDependencies = options.dependencies || Janus.useDefaultDependencies();
Janus.isArray = usedDependencies.isArray;
Janus.webRTCAdapter = usedDependencies.webRTCAdapter;
Janus.httpAPICall = usedDependencies.httpAPICall;
Janus.newWebSocket = usedDependencies.newWebSocket;
Janus.extension = usedDependencies.extension;
Janus.extension.init();

默认依赖中函数提供的底层封装:

  • fetch:用于 httpAPICAll
  • Promise: 异步的一种解决方案
  • Websocket:ws协议的提供
  • extension:如果没有参数传入,会选用默认的扩展

这些所有API并没有所谓的兼容,都是浏览器原生的API,所以在文档中,这里运行是要求了更加新的浏览器,也意味着向下兼容性不高。

默认扩展

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);
			}
		});
	}
};

::: details 关于 useDefaultDependencies

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;
		}
	}
};

:::

源码

::: details 点击查看代码

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;
				}
			}
		}
		Janus.log("Initializing library");

		var usedDependencies = options.dependencies || Janus.useDefaultDependencies();
		Janus.isArray = usedDependencies.isArray;
		Janus.webRTCAdapter = usedDependencies.webRTCAdapter;
		Janus.httpAPICall = usedDependencies.httpAPICall;
		Janus.newWebSocket = usedDependencies.newWebSocket;
		Janus.extension = usedDependencies.extension;
		Janus.extension.init();

		// Helper method to enumerate devices
		Janus.listDevices = function(callback, config) {
			callback = (typeof callback == "function") ? callback : Janus.noop;
			if (config == null) config = { audio: true, video: true };
			if(Janus.isGetUserMediaAvailable()) {
				navigator.mediaDevices.getUserMedia(config)
				.then(function(stream) {
					navigator.mediaDevices.enumerateDevices().then(function(devices) {
						Janus.debug(devices);
						callback(devices);
						// Get rid of the now useless stream
						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();
	}
};

:::