├── .gitignore ├── image.png ├── image-1.png ├── .vscode └── launch.json ├── operating-systems.js ├── package.json ├── README.md ├── index.js ├── index.html ├── renderer.js ├── LICENSE ├── obsRecorder.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | osn-data/ 3 | videos/ 4 | -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CNunicorn6/obs-studio-node-demo/HEAD/image.png -------------------------------------------------------------------------------- /image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CNunicorn6/obs-studio-node-demo/HEAD/image-1.png -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceFolder}", 9 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" 12 | }, 13 | "args" : ["."], 14 | "outputCapture": "std" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /operating-systems.js: -------------------------------------------------------------------------------- 1 | // Modified from https://github.com/stream-labs/streamlabs-obs/blob/staging/app/util/operating-systems.ts 2 | 3 | const OS = { 4 | Windows: 'win32', 5 | Mac: 'darwin', 6 | } 7 | 8 | function byOS(handlers) { 9 | const handler = handlers[process.platform]; 10 | 11 | if (typeof handler === 'function') return handler(); 12 | 13 | return handler; 14 | } 15 | 16 | function getOS() { 17 | return process.platform 18 | } 19 | 20 | module.exports.OS = OS 21 | module.exports.byOS = byOS 22 | module.exports.getOS = getOS -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obs-studio-node-demo", 3 | "version": "1.0.0", 4 | "description": "Electron 和 OBS-Studio-node的demo示例", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "electron .", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "streaming" 12 | ], 13 | "author": "", 14 | "license": "GPL-2.0", 15 | "devDependencies": { 16 | "rxjs": "^6.6.3", 17 | "uuid": "^8.3.0" 18 | }, 19 | "dependencies": { 20 | "electron": "^10.2.0", 21 | "node-window-rendering": "https://slobs-node-window-rendering.s3-us-west-2.amazonaws.com/node-window-rendering-0.0.0.tar.gz", 22 | "obs-studio-node": "https://s3-us-west-2.amazonaws.com/obsstudionodes3.streamlabs.com/osn-0.15.6-release-win64.tar.gz" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Very simple example of [obs-studio-node] usage 3 | ``` 4 | 首先请允许我吐槽一下obs-studio-node 5 | 阿巴阿巴阿巴 6 | ``` 7 | 8 | ## 环境与版本 9 | ``` 10 | node: v12.16.3 11 | electron: v10.2.0 12 | yarn: v1.22.21 13 | obs-studio-node: https://s3-us-west-2.amazonaws.com/obsstudionodes3.streamlabs.com/osn-0.15.6-release-win64.tar.gz 14 | ``` 15 | 16 | ## 参照说明 17 | ``` 18 | 本着开源学习的态度,对github中的一些项目进行了参考借鉴。以下是参考的项目 19 | 20 | [streamlabs-obs](https://github.com/stream-labs/streamlabs-obs) : github上开源的obs软件,目前已经上线,自行百度下载,主要参考此软件进行调用 21 | [OBS-Studio](https://obsproject.com/zh-cn) : 同样也是github上开源的obs软件,以上两款软件对obs-studio-node都进行了一定程度的封装,不过仔细耐心的开下去还是可以讲究明白的 22 | [obs-studio-node-example](https://github.com/Envek/obs-studio-node-example) : 本实例主要参考了此demo示例,感谢作者的开源奉献(此demo做了mac系统兼容,可用作参考) 23 | [obs-example](https://github.com/qlteacher/obs-example) : 相同的obs-studio-node的demo示例 24 | ``` 25 | 26 | ## 兼容问题 27 | ``` 28 | 由于本人使用的windows系统的电脑,所以demo可能在mac系统兼容上做的不是很好, 29 | 如果你使用了mac系统请引入node-window-rendering 此依赖,从而兼容mac系统(最好使用yarn引入) 30 | ``` 31 | 32 | **Windows端注意,如果你的电脑是双显卡,请阅读下面的文字** 33 | 34 | 对于 Windows 10 1909 或更高版本: 35 | 打开window设置搜索"图形设置"进入后点击浏览按钮 选择/项目地址/node_modules/obs-studio-node/obs64.exe选择添加后,点击下方多出来的obs64.exe设置为省电模式即可解决黑屏问题 36 | ![alt text](image.png) 37 | ![alt text](image-1.png) 38 | 39 | 如果你是Windows 10 1909之前的版本请自行参照[Laptop](https://obsproject.com/forum/threads/laptop-black-screen-when-capturing-read-here-first.5965/)帖子中的情况去设置。 40 | 41 | ## 文件说明 42 | ``` 43 | index.html: view页面 44 | renderer.js: 页面逻辑处理 45 | index.js: Electron 主进程入口文件 46 | obsRecorder.js: obs-studio-node 简单的封装 47 | 48 | osn-data文件夹: 存放obs的日志文件和配置文件 (编译后才会有) 49 | ``` 50 | 51 | ## Setup 52 | 53 | ``` 54 | yarn install 55 | ``` 56 | 57 | ## Run 58 | 59 | ``` 60 | yarn start 61 | ``` 62 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, ipcMain } = require("electron"); 2 | const path = require("path"); 3 | 4 | const obsRecorder = require("./obsRecorder"); 5 | 6 | ipcMain.handle("recording-start", (event) => { 7 | obsRecorder.start(); 8 | return { recording: true }; 9 | }); 10 | 11 | ipcMain.handle("recording-stop", (event) => { 12 | obsRecorder.stop(); 13 | return { recording: false }; 14 | }); 15 | 16 | ipcMain.handle("isVirtualCamPluginInstalled", (event) => { 17 | return obsRecorder.isVirtualCamPluginInstalled(); 18 | }); 19 | ipcMain.handle("startVirtualCam", (event) => { 20 | return obsRecorder.startVirtualCam(); 21 | }); 22 | ipcMain.handle("stopVirtualCam", (event) => { 23 | return obsRecorder.stopVirtualCam(); 24 | }); 25 | ipcMain.handle("installVirtualCamPlugin", (event) => { 26 | return obsRecorder.installVirtualCamPlugin(); 27 | }); 28 | ipcMain.handle("uninstallVirtualCamPlugin", (event) => { 29 | return obsRecorder.uninstallVirtualCamPlugin(); 30 | }); 31 | 32 | app.on("will-quit", obsRecorder.shutdown); 33 | 34 | function createWindow() { 35 | // Create the browser window. 36 | const win = new BrowserWindow({ 37 | width: 800, 38 | height: 600, 39 | autoHideMenuBar: true, 40 | webPreferences: { 41 | nodeIntegration: true, 42 | enableRemoteModule: true, 43 | }, 44 | }); 45 | 46 | ipcMain.handle("recording-init", (event) => { 47 | obsRecorder.initialize(win); 48 | return true; 49 | }); 50 | 51 | ipcMain.handle("preview-init", (event, bounds) => { 52 | return obsRecorder.setupPreview(win, bounds); 53 | }); 54 | 55 | ipcMain.handle("preview-bounds", (event, bounds) => { 56 | return obsRecorder.resizePreview(win, bounds); 57 | }); 58 | 59 | ipcMain.handle('update-rtmp', (event, bounds) => { 60 | return obsRecorder.udpateRtmp(win, bounds); 61 | }); 62 | 63 | 64 | // 开始直播/结束直播 65 | ipcMain.on('toggleStreaming', (event, bounds) => { 66 | event.returnValue = obsRecorder.toggleStreaming(bounds); 67 | }); 68 | 69 | // 源设置 70 | ipcMain.on('showSourceInfo', (event, bounds) => { 71 | event.returnValue = obsRecorder.showSourceInfo(bounds); 72 | }); 73 | 74 | // 显示器设置 75 | ipcMain.on('selectDisPlay', (event, bounds) => { 76 | event.returnValue = obsRecorder.selectDisPlay(bounds); 77 | }); 78 | 79 | ipcMain.on('getAllScene', (event) => { 80 | event.returnValue = obsRecorder.getAllScene(); 81 | }); 82 | 83 | ipcMain.on('getALlCameras', (event) => { 84 | event.returnValue = obsRecorder.getALlCameras(); 85 | }); 86 | 87 | ipcMain.on('getSetting', (event, bounds) => { 88 | event.returnValue = obsRecorder.getSetting(bounds); 89 | }); 90 | 91 | 92 | // and load the index.html of the app. 93 | win.loadFile("index.html"); 94 | 95 | // Open the DevTools. 96 | win.webContents.openDevTools(); 97 | } 98 | 99 | // This method will be called when Electron has finished 100 | // initialization and is ready to create browser windows. 101 | // Some APIs can only be used after this event occurs. 102 | app.whenReady().then(createWindow); 103 | 104 | // Quit when all windows are closed. 105 | app.on("window-all-closed", () => { 106 | // On macOS it is common for applications and their menu bar 107 | // to stay active until the user quits explicitly with Cmd + Q 108 | if (process.platform !== "darwin") { 109 | app.quit(); 110 | } 111 | }); 112 | 113 | app.on("activate", () => { 114 | // On macOS it's common to re-create a window in the app when the 115 | // dock icon is clicked and there are no other windows open. 116 | if (BrowserWindow.getAllWindows().length === 0) { 117 | createWindow(); 118 | } 119 | }); 120 | 121 | // In this file you can include the rest of your app's specific main process 122 | // code. You can also put them in separate files and require them here. 123 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello World! 6 | 10 | 35 | 36 | 37 |

Hello from OBS Studio Node!

38 | 39 |
40 | Recording:
41 | 44 | 45 | 0:00:00.0 46 | 47 | 48 |
49 |
50 | Virtual Camera:
51 | 57 | 63 | 66 | 69 | ... 70 |
71 |
72 | 设置:
73 | 选择显示器: 74 | 75 |
76 | 选择摄像头: 77 | 78 |
79 |
80 | RTMP参数设置:
81 | rtmp_server: 82 | 83 | rtmp_key: 84 | 85 | 86 | 87 |
88 |
89 | 90 | 用于查看obs中的设置功能,可以根据obsRecorder.js中的setSetting方法修改对应的值(注意有些值需要重启软件) 91 |
92 | setting分类: 93 | 102 | 103 | 104 |
105 |
106 | 110 | 111 | 120 |
121 |

122 | 
123 |     
Initializing...
124 | 125 | 126 | 127 | 128 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
CPU 129 | 138 | Loading... 139 |
Dropped framesLoading...
Dropped framesLoading...
BandwidthLoading...
FramerateLoading...
158 | 159 |
160 |     We are using node ,
161 |     Chrome ,
162 |     and Electron .
163 |     
164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /renderer.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer, shell, remote } = require("electron"); 2 | const path = require("path"); 3 | 4 | async function initOBS() { 5 | const result = await ipcRenderer.invoke("recording-init"); 6 | console.debug("initOBS result:", result); 7 | if (result) { 8 | ipcRenderer.on("performanceStatistics", (_event, data) => 9 | onPerformanceStatistics(data) 10 | ); 11 | } 12 | } 13 | 14 | async function startRecording() { 15 | const result = await ipcRenderer.invoke("recording-start"); 16 | console.debug("startRecording result:", result); 17 | return result; 18 | } 19 | 20 | async function stopRecording() { 21 | const result = await ipcRenderer.invoke("recording-stop"); 22 | console.debug("stopRecording result:", result); 23 | return result; 24 | } 25 | 26 | let recording = false; 27 | let virtualCamRunning = false; 28 | let recordingStartedAt = null; 29 | let timer = null; 30 | 31 | async function switchRecording() { 32 | if (recording) { 33 | recording = (await stopRecording()).recording; 34 | } else { 35 | recording = (await startRecording()).recording; 36 | } 37 | updateRecordingUI(); 38 | } 39 | 40 | function updateRecordingUI() { 41 | const button = document.getElementById("rec-button"); 42 | button.disabled = false; 43 | if (recording) { 44 | button.innerText = "⏹️ Stop recording"; 45 | startTimer(); 46 | } else { 47 | button.innerText = "⏺️ Start recording"; 48 | stopTimer(); 49 | } 50 | 51 | getStream(); 52 | 53 | const displaySelect = document.getElementById("displaySelect"); 54 | remote.screen.getAllDisplays().forEach(function (dispaly, index) { 55 | console.log("dispaly", dispaly); 56 | displaySelect.options.add( 57 | new Option(Math.floor(dispaly.size.height * dispaly.scaleFactor) + "*" + Math.floor(dispaly.size.width * dispaly.scaleFactor), index) 58 | ); 59 | }); 60 | 61 | //设置主显示器 62 | displaySelect.selectedIndex = 0; 63 | displaySelectChange(); 64 | 65 | const cameras = ipcRenderer.sendSync("getALlCameras"); 66 | const cameraSelect = document.getElementById("cameraSelect"); 67 | cameras.forEach(function (camera) { 68 | cameraSelect.options.add(new Option(camera.name, camera.value)); 69 | }); 70 | 71 | const allScene = ipcRenderer.sendSync("getAllScene"); 72 | console.log(allScene); 73 | 74 | const sceneSelect = document.getElementById("sceneSelect"); 75 | allScene.forEach(function (scene) { 76 | sceneSelect.options.add(new Option(scene.name, scene.name)); 77 | }); 78 | sceneSelect.selectedIndex = 0; 79 | 80 | const sourceSelect = document.getElementById("sourceSelect"); 81 | if (allScene.length == 1) { 82 | allScene[0].items.forEach(function (item) { 83 | sourceSelect.options.add(new Option(item, item)); 84 | }); 85 | } 86 | } 87 | 88 | async function updateVirtualCamUI() { 89 | if (await ipcRenderer.invoke("isVirtualCamPluginInstalled")) { 90 | document.querySelector("#install-virtual-cam-plugin-button").style.display = 91 | "none"; 92 | if (virtualCamRunning) { 93 | document.querySelector("#virtual-cam-plugin-status").innerText = 94 | "Running"; 95 | document.querySelector("#stop-virtual-cam-button").style.display = ""; 96 | document.querySelector("#start-virtual-cam-button").style.display = 97 | "none"; 98 | document.querySelector( 99 | "#uninstall-virtual-cam-plugin-button" 100 | ).style.display = "none"; 101 | } else { 102 | document.querySelector("#virtual-cam-plugin-status").innerText = 103 | "Plugin installed"; 104 | document.querySelector("#stop-virtual-cam-button").style.display = "none"; 105 | document.querySelector("#start-virtual-cam-button").style.display = ""; 106 | document.querySelector( 107 | "#uninstall-virtual-cam-plugin-button" 108 | ).style.display = ""; 109 | } 110 | } else { 111 | document.querySelector("#virtual-cam-plugin-status").innerText = 112 | "Plugin not installed"; 113 | document.querySelector("#install-virtual-cam-plugin-button").style.display = 114 | ""; 115 | document.querySelector( 116 | "#uninstall-virtual-cam-plugin-button" 117 | ).style.display = "none"; 118 | document.querySelector("#start-virtual-cam-button").style.display = "none"; 119 | document.querySelector("#stop-virtual-cam-button").style.display = "none"; 120 | } 121 | } 122 | 123 | async function uninstallVirtualCamPlugin() { 124 | await ipcRenderer.invoke("uninstallVirtualCamPlugin"); 125 | updateVirtualCamUI(); 126 | } 127 | 128 | async function installVirtualCamPlugin() { 129 | await ipcRenderer.invoke("installVirtualCamPlugin"); 130 | updateVirtualCamUI(); 131 | } 132 | 133 | async function startVirtualCam() { 134 | await ipcRenderer.invoke("startVirtualCam"); 135 | virtualCamRunning = true; 136 | updateVirtualCamUI(); 137 | } 138 | 139 | async function stopVirtualCam() { 140 | await ipcRenderer.invoke("stopVirtualCam"); 141 | virtualCamRunning = false; 142 | updateVirtualCamUI(); 143 | } 144 | 145 | function startTimer() { 146 | recordingStartedAt = Date.now(); 147 | timer = setInterval(updateTimer, 100); 148 | } 149 | 150 | function stopTimer() { 151 | clearInterval(timer); 152 | } 153 | 154 | function updateTimer() { 155 | const diff = Date.now() - recordingStartedAt; 156 | const timerElem = document.getElementById("rec-timer"); 157 | const decimals = `${Math.floor((diff % 1000) / 100)}`; 158 | const seconds = `${Math.floor((diff % 60000) / 1000)}`.padStart(2, "0"); 159 | const minutes = `${Math.floor((diff % 3600000) / 60000)}`.padStart(2, "0"); 160 | const hours = `${Math.floor(diff / 3600000)}`.padStart(2, "0"); 161 | timerElem.innerText = `${hours}:${minutes}:${seconds}.${decimals}`; 162 | } 163 | 164 | function openFolder() { 165 | shell.openPath(remote.app.getPath("videos")); 166 | } 167 | 168 | function onPerformanceStatistics(data) { 169 | document.querySelector( 170 | ".performanceStatistics #cpu" 171 | ).innerText = `${data.CPU} %`; 172 | document.querySelector(".performanceStatistics #cpuMeter").value = data.CPU; 173 | document.querySelector( 174 | ".performanceStatistics #numberDroppedFrames" 175 | ).innerText = data.numberDroppedFrames; 176 | document.querySelector( 177 | ".performanceStatistics #percentageDroppedFrames" 178 | ).innerText = `${data.percentageDroppedFrames} %`; 179 | document.querySelector(".performanceStatistics #bandwidth").innerText = 180 | data.bandwidth; 181 | document.querySelector( 182 | ".performanceStatistics #frameRate" 183 | ).innerText = `${Math.round(data.frameRate)} fps`; 184 | } 185 | 186 | const previewContainer = document.getElementById("preview"); 187 | 188 | async function setupPreview() { 189 | const { width, height, x, y } = previewContainer.getBoundingClientRect(); 190 | const result = await ipcRenderer.invoke("preview-init", { 191 | width, 192 | height, 193 | x, 194 | y, 195 | }); 196 | previewContainer.style = `height: ${result.height}px`; 197 | } 198 | 199 | async function resizePreview() { 200 | const { width, height, x, y } = previewContainer.getBoundingClientRect(); 201 | const result = await ipcRenderer.invoke("preview-bounds", { 202 | width, 203 | height, 204 | x, 205 | y, 206 | }); 207 | previewContainer.style = `height: ${result.height}px`; 208 | } 209 | 210 | // 保存设置 211 | async function saveSetting() { 212 | const server = document.getElementById("rtmp_server").value; 213 | const key = document.getElementById("rtmp_key").value; 214 | console.log(server, key); 215 | await ipcRenderer.invoke("update-rtmp", { server, key }); 216 | getStream(); 217 | } 218 | 219 | // 获取setting 220 | function getSetting() { 221 | const cate = document.getElementById("OBSSettingsCategories"); 222 | const result = ipcRenderer.sendSync("getSetting", { 223 | name: cate.options[cate.selectedIndex].text, 224 | }); 225 | console.log(result); 226 | } 227 | 228 | // 选择摄像头 229 | function cameraSelectChange() { 230 | const select = document.getElementById("cameraSelect"); 231 | const result = ipcRenderer.sendSync("cameraSelect", { 232 | id: select.options[select.selectedIndex].value, 233 | }); 234 | console.log(result); 235 | } 236 | 237 | // 选择显示器 238 | function displaySelectChange() { 239 | const select = document.getElementById("displaySelect"); 240 | const result = ipcRenderer.sendSync("selectDisPlay", { 241 | id: select.options[select.selectedIndex].value, 242 | }); 243 | console.log(result); 244 | } 245 | 246 | // 设置并展示源数据 247 | function showSourceInfo() { 248 | const sourceSelect = document.getElementById("sourceSelect"); 249 | const result = ipcRenderer.sendSync("showSourceInfo", { 250 | id: sourceSelect.options[sourceSelect.selectedIndex].value, 251 | }); 252 | console.log(result); 253 | document.getElementById("response").innerHTML = JSON.stringify(result); 254 | } 255 | 256 | // 是否直播 257 | function streaming() { 258 | const streamingButton = document.getElementById("streaming"); 259 | let status = Boolean( 260 | JSON.parse(localStorage.getItem("streaming_status")).status 261 | ); 262 | if (!status) { 263 | console.log("开始直播"); 264 | streamingButton.innerText = "结束直播"; 265 | } else { 266 | console.log("结束直播"); 267 | streamingButton.innerText = "开始直播"; 268 | } 269 | ipcRenderer.sendSync("toggleStreaming", status); 270 | localStorage.setItem("streaming_status", JSON.stringify({ status: !status })); 271 | console.log("修改后", localStorage.getItem("streaming_status")); 272 | } 273 | 274 | // 获取流数据 275 | function getStream() { 276 | const rtmp_server = document.getElementById("rtmp_server"); 277 | const rtmp_key = document.getElementById("rtmp_key"); 278 | 279 | const streamSettings = ipcRenderer.sendSync("getSetting", { name: "Stream" }); 280 | 281 | streamSettings.forEach((subCate) => { 282 | subCate.parameters.forEach((parameter) => { 283 | switch (parameter.name) { 284 | case "service": { 285 | break; 286 | } 287 | case "server": { 288 | rtmp_server.value = parameter.currentValue; 289 | break; 290 | } 291 | case "key": { 292 | rtmp_key.value = parameter.currentValue; 293 | break; 294 | } 295 | } 296 | }); 297 | }); 298 | } 299 | 300 | const currentWindow = remote.getCurrentWindow(); 301 | currentWindow.on("resize", resizePreview); 302 | document.addEventListener("scroll", resizePreview); 303 | var ro = new ResizeObserver(resizePreview); 304 | ro.observe(document.querySelector("#preview")); 305 | 306 | try { 307 | initOBS(); 308 | setupPreview(); 309 | updateRecordingUI(); 310 | updateVirtualCamUI(); 311 | } catch (err) { 312 | console.log(err); 313 | } 314 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /obsRecorder.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const { Subject } = require("rxjs"); 3 | const { first } = require("rxjs/operators"); 4 | const { byOS, OS, getOS } = require("./operating-systems"); 5 | 6 | const osn = require("obs-studio-node"); 7 | const { v4: uuid } = require("uuid"); 8 | const videoPath = require("electron").app.getPath("videos"); 9 | let nwr; 10 | 11 | // NWR is used to handle display rendering via IOSurface on mac 12 | if (getOS() === OS.Mac) { 13 | nwr = require("node-window-rendering"); 14 | } 15 | 16 | let obsInitialized = false; 17 | let scene = null; 18 | 19 | // When packaged, we need to fix some paths 20 | function fixPathWhenPackaged(p) { 21 | return p.replace("app.asar", "app.asar.unpacked"); 22 | } 23 | 24 | // Init the library, launch OBS Studio instance, configure it, set up sources and scene 25 | function initialize(win) { 26 | if (obsInitialized) { 27 | console.warn("OBS is already initialized, skipping initialization."); 28 | return; 29 | } 30 | 31 | initOBS(); 32 | configureOBS(); 33 | scene = setupScene(); 34 | setupSources(scene); 35 | obsInitialized = true; 36 | 37 | const perfStatTimer = setInterval(() => { 38 | win.webContents.send( 39 | "performanceStatistics", 40 | osn.NodeObs.OBS_API_getPerformanceStatistics() 41 | ); 42 | }, 1000); 43 | 44 | win.on("close", () => clearInterval(perfStatTimer)); 45 | } 46 | 47 | function initOBS() { 48 | console.debug("Initializing OBS..."); 49 | osn.NodeObs.IPC.host(`obs-studio-node-example-${uuid()}`); 50 | osn.NodeObs.SetWorkingDirectory( 51 | fixPathWhenPackaged(path.join(__dirname, "node_modules", "obs-studio-node")) 52 | ); 53 | 54 | const obsDataPath = fixPathWhenPackaged(path.join(__dirname, "osn-data")); // OBS Studio configs and logs 55 | // Arguments: locale, path to directory where configuration and logs will be stored, your application version 56 | const initResult = osn.NodeObs.OBS_API_initAPI("en-US", obsDataPath, "1.0.0"); 57 | 58 | if (initResult !== 0) { 59 | const errorReasons = { 60 | "-2": "DirectX could not be found on your system. Please install the latest version of DirectX for your machine here and try again.", 61 | "-5": "Failed to initialize OBS. Your video drivers may be out of date, or Streamlabs OBS may not be supported on your system.", 62 | }; 63 | 64 | const errorMessage = 65 | errorReasons[initResult.toString()] || 66 | `An unknown error #${initResult} was encountered while initializing OBS.`; 67 | 68 | console.error("OBS init failure", errorMessage); 69 | 70 | shutdown(); 71 | 72 | throw Error(errorMessage); 73 | } 74 | 75 | osn.NodeObs.OBS_service_connectOutputSignals((signalInfo) => { 76 | signals.next(signalInfo); 77 | }); 78 | 79 | console.debug("OBS initialized"); 80 | } 81 | 82 | function configureOBS() { 83 | console.debug("Configuring OBS"); 84 | setSetting("Output", "Mode", "Advanced"); 85 | const availableEncoders = getAvailableValues( 86 | "Output", 87 | "Recording", 88 | "RecEncoder" 89 | ); 90 | setSetting("Output", "RecEncoder", availableEncoders.slice(-1)[0] || "x264"); 91 | setSetting("Output", "RecFilePath", videoPath); 92 | setSetting("Output", "RecFormat", "mkv"); 93 | setSetting("Output", "VBitrate", 10000); // 10 Mbps 94 | setSetting("Video", "FPSCommon", 60); 95 | 96 | console.debug("OBS Configured"); 97 | } 98 | 99 | function isVirtualCamPluginInstalled() { 100 | return osn.NodeObs.OBS_service_isVirtualCamPluginInstalled(); 101 | } 102 | 103 | function installVirtualCamPlugin() { 104 | osn.NodeObs.OBS_service_installVirtualCamPlugin(); 105 | return osn.NodeObs.OBS_service_isVirtualCamPluginInstalled(); 106 | } 107 | 108 | function uninstallVirtualCamPlugin() { 109 | osn.NodeObs.OBS_service_uninstallVirtualCamPlugin(); 110 | return !osn.NodeObs.OBS_service_isVirtualCamPluginInstalled(); 111 | } 112 | 113 | function startVirtualCam() { 114 | osn.NodeObs.OBS_service_createVirtualWebcam("obs-studio-node-example-cam"); 115 | osn.NodeObs.OBS_service_startVirtualWebcam(); 116 | } 117 | 118 | function stopVirtualCam() { 119 | osn.NodeObs.OBS_service_stopVirtualWebcam(); 120 | osn.NodeObs.OBS_service_removeVirtualWebcam(); 121 | } 122 | 123 | // Get information about prinary display 124 | function displayInfo() { 125 | const { screen } = require("electron"); 126 | const primaryDisplay = screen.getPrimaryDisplay(); 127 | const { width, height } = primaryDisplay.size; 128 | const { scaleFactor } = primaryDisplay; 129 | return { 130 | id: primaryDisplay.id, 131 | width, 132 | height, 133 | scaleFactor: scaleFactor, 134 | aspectRatio: width / height, 135 | physicalWidth: width * scaleFactor, 136 | physicalHeight: height * scaleFactor, 137 | }; 138 | } 139 | 140 | function getCameraSource() { 141 | console.debug("Trying to set up web camera..."); 142 | 143 | // Setup input without initializing any device just to get list of available ones 144 | const dummyInput = byOS({ 145 | [OS.Windows]: () => 146 | osn.InputFactory.create("dshow_input", "video", { 147 | audio_device_id: "does_not_exist", 148 | video_device_id: "does_not_exist", 149 | }), 150 | [OS.Mac]: () => 151 | osn.InputFactory.create("av_capture_input", "video", { 152 | device: "does_not_exist", 153 | }), 154 | }); 155 | 156 | const cameraItems = dummyInput.properties.get( 157 | byOS({ [OS.Windows]: "video_device_id", [OS.Mac]: "device" }) 158 | ).details.items; 159 | 160 | dummyInput.release(); 161 | 162 | if (cameraItems.length === 0) { 163 | console.debug("No camera found!!"); 164 | return null; 165 | } 166 | 167 | const deviceId = cameraItems[0].value; 168 | cameraItems[0].selected = true; 169 | console.debug("cameraItemsName: " + cameraItems[0].name); 170 | 171 | const obsCameraInput = byOS({ 172 | [OS.Windows]: () => 173 | osn.InputFactory.create("dshow_input", "video", { 174 | video_device_id: deviceId, 175 | }), 176 | [OS.Mac]: () => 177 | osn.InputFactory.create("av_capture_input", "video", { 178 | device: deviceId, 179 | }), 180 | }); 181 | 182 | // Set res_type to 1 183 | let settings = obsCameraInput.settings; 184 | settings["res_type"] = 1; 185 | obsCameraInput.update(settings); // res_type = 0 : Device Default // res_type = 1 : Custom 186 | return obsCameraInput; 187 | } 188 | 189 | function setupScene() { 190 | const videoSource = osn.InputFactory.create( 191 | byOS({ [OS.Windows]: "monitor_capture", [OS.Mac]: "display_capture" }), 192 | "desktop-video" 193 | ); 194 | 195 | const { physicalWidth, physicalHeight, aspectRatio } = displayInfo(); 196 | 197 | // Update source settings: 198 | let settings = videoSource.settings; 199 | settings["width"] = physicalWidth; 200 | settings["height"] = physicalHeight; 201 | videoSource.update(settings); 202 | videoSource.save(); 203 | 204 | // Set output video size to 1920x1080 205 | const outputWidth = 1920; 206 | const outputHeight = Math.round(outputWidth / aspectRatio); 207 | setSetting("Video", "Base", `${outputWidth}x${outputHeight}`); 208 | setSetting("Video", "Output", `${outputWidth}x${outputHeight}`); 209 | const videoScaleFactor = physicalWidth / outputWidth; 210 | 211 | // A scene is necessary here to properly scale captured screen size to output video size 212 | const scene = osn.SceneFactory.create("test-scene"); 213 | const sceneItem = scene.add(videoSource); 214 | sceneItem.scale = { x: 1.0 / videoScaleFactor, y: 1.0 / videoScaleFactor }; 215 | 216 | // If camera is available, make it 1/3 width of video and place it to right down corner of display 217 | const cameraSource = getCameraSource(); 218 | if (cameraSource) { 219 | // resolutionStr should be "[width]x[height]". For example: "1280x720" 获取摄像头分辨率 220 | let resolutionStr = cameraSource.properties.get("resolution").value; 221 | let resolution = resolutionStr.split("x"); 222 | let cameraWidth = Number(resolution[0]); 223 | let cameraHeight = Number(resolution[1]); 224 | 225 | const cameraItem = scene.add(cameraSource); 226 | const cameraScaleFactor = 1.0 / ((3.0 * cameraWidth) / outputWidth); 227 | cameraItem.scale = { x: cameraScaleFactor, y: cameraScaleFactor }; 228 | cameraItem.position = { 229 | x: outputWidth - cameraWidth * cameraScaleFactor - outputWidth / 10, 230 | y: outputHeight - cameraHeight * cameraScaleFactor - outputHeight / 10, 231 | }; 232 | } 233 | 234 | return scene; 235 | } 236 | 237 | function getAudioDevices(type, subtype) { 238 | const dummyDevice = osn.InputFactory.create(type, subtype, { 239 | device_id: "does_not_exist", 240 | }); 241 | const devices = dummyDevice.properties 242 | .get("device_id") 243 | .details.items.map(({ name, value }) => { 244 | return { device_id: value, name }; 245 | }); 246 | dummyDevice.release(); 247 | return devices; 248 | } 249 | 250 | function setupSources() { 251 | osn.Global.setOutputSource(0, scene); 252 | 253 | setSetting("Output", "Track1Name", "Mixed: all sources"); 254 | let currentTrack = 2; 255 | 256 | getAudioDevices( 257 | byOS({ 258 | [OS.Windows]: "wasapi_output_capture", 259 | [OS.Mac]: "coreaudio_output_capture", 260 | }), 261 | "desktop-audio" 262 | ).forEach((metadata) => { 263 | if (metadata.device_id === "default") return; 264 | const source = osn.InputFactory.create( 265 | byOS({ 266 | [OS.Windows]: "wasapi_output_capture", 267 | [OS.Mac]: "coreaudio_output_capture", 268 | }), 269 | "desktop-audio", 270 | { device_id: metadata.device_id } 271 | ); 272 | setSetting("Output", `Track${currentTrack}Name`, metadata.name); 273 | source.audioMixers = 1 | (1 << (currentTrack - 1)); // Bit mask to output to only tracks 1 and current track 274 | osn.Global.setOutputSource(currentTrack, source); 275 | currentTrack++; 276 | }); 277 | 278 | getAudioDevices( 279 | byOS({ 280 | [OS.Windows]: "wasapi_input_capture", 281 | [OS.Mac]: "coreaudio_input_capture", 282 | }), 283 | "mic-audio" 284 | ).forEach((metadata) => { 285 | if (metadata.device_id === "default") return; 286 | const source = osn.InputFactory.create( 287 | byOS({ 288 | [OS.Windows]: "wasapi_input_capture", 289 | [OS.Mac]: "coreaudio_input_capture", 290 | }), 291 | "mic-audio", 292 | { device_id: metadata.device_id } 293 | ); 294 | setSetting("Output", `Track${currentTrack}Name`, metadata.name); 295 | source.audioMixers = 1 | (1 << (currentTrack - 1)); // Bit mask to output to only tracks 1 and current track 296 | osn.Global.setOutputSource(currentTrack, source); 297 | currentTrack++; 298 | }); 299 | 300 | setSetting("Output", "RecTracks", parseInt("1".repeat(currentTrack - 1), 2)); // Bit mask of used tracks: 1111 to use first four (from available six) 301 | } 302 | 303 | const displayId = "display1"; 304 | 305 | function setupPreview(window, bounds) { 306 | osn.NodeObs.OBS_content_createSourcePreviewDisplay( 307 | window.getNativeWindowHandle(), 308 | scene.name, // or use camera source Id here 309 | displayId 310 | ); 311 | osn.NodeObs.OBS_content_setShouldDrawUI(displayId, false); 312 | osn.NodeObs.OBS_content_setPaddingSize(displayId, 0); 313 | // Match padding color with main window background color 314 | osn.NodeObs.OBS_content_setPaddingColor(displayId, 255, 255, 255); 315 | 316 | return resizePreview(window, bounds); 317 | } 318 | let existingWindow = false; 319 | let initY = 0; 320 | function resizePreview(window, bounds) { 321 | let { aspectRatio, scaleFactor } = displayInfo(); 322 | if (getOS() === OS.Mac) { 323 | scaleFactor = 1; 324 | } 325 | const displayWidth = Math.floor(bounds.width); 326 | const displayHeight = Math.round(displayWidth / aspectRatio); 327 | const displayX = Math.floor(bounds.x); 328 | const displayY = Math.floor(bounds.y); 329 | if (initY === 0) { 330 | initY = displayY; 331 | } 332 | osn.NodeObs.OBS_content_resizeDisplay( 333 | displayId, 334 | displayWidth * scaleFactor, 335 | displayHeight * scaleFactor 336 | ); 337 | 338 | if (getOS() === OS.Mac) { 339 | if (existingWindow) { 340 | nwr.destroyWindow(displayId); 341 | nwr.destroyIOSurface(displayId); 342 | } 343 | const surface = osn.NodeObs.OBS_content_createIOSurface(displayId); 344 | nwr.createWindow(displayId, window.getNativeWindowHandle()); 345 | nwr.connectIOSurface(displayId, surface); 346 | nwr.moveWindow( 347 | displayId, 348 | displayX * scaleFactor, 349 | (initY - displayY + initY) * scaleFactor 350 | ); 351 | existingWindow = true; 352 | } else { 353 | osn.NodeObs.OBS_content_moveDisplay( 354 | displayId, 355 | displayX * scaleFactor, 356 | displayY * scaleFactor 357 | ); 358 | } 359 | 360 | return { height: displayHeight }; 361 | } 362 | 363 | async function start() { 364 | if (!obsInitialized) initialize(); 365 | 366 | let signalInfo; 367 | 368 | console.debug("Starting recording..."); 369 | osn.NodeObs.OBS_service_startRecording(); 370 | 371 | console.debug("Started?"); 372 | signalInfo = await getNextSignalInfo(); 373 | 374 | if (signalInfo.signal === "Stop") { 375 | throw Error(signalInfo.error); 376 | } 377 | 378 | console.debug( 379 | "Started signalInfo.type:", 380 | signalInfo.type, 381 | '(expected: "recording")' 382 | ); 383 | console.debug( 384 | "Started signalInfo.signal:", 385 | signalInfo.signal, 386 | '(expected: "start")' 387 | ); 388 | console.debug("Started!"); 389 | } 390 | 391 | async function stop() { 392 | let signalInfo; 393 | 394 | console.debug("Stopping recording..."); 395 | osn.NodeObs.OBS_service_stopRecording(); 396 | console.debug("Stopped?"); 397 | 398 | signalInfo = await getNextSignalInfo(); 399 | 400 | console.debug( 401 | "On stop signalInfo.type:", 402 | signalInfo.type, 403 | '(expected: "recording")' 404 | ); 405 | console.debug( 406 | "On stop signalInfo.signal:", 407 | signalInfo.signal, 408 | '(expected: "stopping")' 409 | ); 410 | 411 | signalInfo = await getNextSignalInfo(); 412 | 413 | console.debug( 414 | "After stop signalInfo.type:", 415 | signalInfo.type, 416 | '(expected: "recording")' 417 | ); 418 | console.debug( 419 | "After stop signalInfo.signal:", 420 | signalInfo.signal, 421 | '(expected: "stop")' 422 | ); 423 | 424 | console.debug("Stopped!"); 425 | } 426 | 427 | function shutdown() { 428 | if (!obsInitialized) { 429 | console.debug("OBS is already shut down!"); 430 | return false; 431 | } 432 | 433 | console.debug("Shutting down OBS..."); 434 | 435 | try { 436 | osn.NodeObs.OBS_service_removeCallback(); 437 | osn.NodeObs.IPC.disconnect(); 438 | obsInitialized = false; 439 | } catch (e) { 440 | throw Error("Exception when shutting down OBS process" + e); 441 | } 442 | 443 | console.debug("OBS shutdown successfully"); 444 | 445 | return true; 446 | } 447 | 448 | function setSetting(category, parameter, value) { 449 | let oldValue; 450 | 451 | // Getting settings container 452 | const settings = osn.NodeObs.OBS_settings_getSettings(category).data; 453 | 454 | settings.forEach((subCategory) => { 455 | subCategory.parameters.forEach((param) => { 456 | if (param.name === parameter) { 457 | oldValue = param.currentValue; 458 | param.currentValue = value; 459 | } 460 | }); 461 | }); 462 | 463 | // Saving updated settings container 464 | if (value != oldValue) { 465 | osn.NodeObs.OBS_settings_saveSettings(category, settings); 466 | } 467 | } 468 | 469 | function getAvailableValues(category, subcategory, parameter) { 470 | const categorySettings = osn.NodeObs.OBS_settings_getSettings(category).data; 471 | if (!categorySettings) { 472 | console.warn(`There is no category ${category} in OBS settings`); 473 | return []; 474 | } 475 | 476 | const subcategorySettings = categorySettings.find( 477 | (sub) => sub.nameSubCategory === subcategory 478 | ); 479 | if (!subcategorySettings) { 480 | console.warn( 481 | `There is no subcategory ${subcategory} for OBS settings category ${category}` 482 | ); 483 | return []; 484 | } 485 | 486 | const parameterSettings = subcategorySettings.parameters.find( 487 | (param) => param.name === parameter 488 | ); 489 | if (!parameterSettings) { 490 | console.warn( 491 | `There is no parameter ${parameter} for OBS settings category ${category}.${subcategory}` 492 | ); 493 | return []; 494 | } 495 | 496 | return parameterSettings.values.map((value) => Object.values(value)[0]); 497 | } 498 | 499 | const signals = new Subject(); 500 | 501 | function getNextSignalInfo() { 502 | return new Promise((resolve, reject) => { 503 | signals.pipe(first()).subscribe((signalInfo) => resolve(signalInfo)); 504 | setTimeout(() => reject("Output signal timeout"), 30000); 505 | }); 506 | } 507 | 508 | function busySleep(sleepDuration) { 509 | var now = new Date().getTime(); 510 | while (new Date().getTime() < now + sleepDuration) { 511 | /* do nothing */ 512 | } 513 | } 514 | 515 | // 设置显示器 516 | function selectDisPlay(index) { 517 | const scene = osn.SceneFactory.fromName('test-scene'); 518 | //console.log(scene.getItems().length) 519 | 520 | scene.getItems().map(item => { 521 | //console.log(item.source.name) 522 | // 删除 523 | if ('desktop-video' === item.source.name) { 524 | osn.InputFactory.fromName(item.source.name).release() 525 | item.remove(); 526 | } 527 | }) 528 | 529 | const videoSource = osn.InputFactory.create('monitor_capture', 'desktop-video'); 530 | const { physicalWidth, physicalHeight, aspectRatio } = displayInfo(index.id); 531 | // Update source settings: 532 | let settings = videoSource.settings; 533 | 534 | // 这个参数修改使用哪个显示器,从0开始 535 | settings['monitor'] = parseInt(index.id) 536 | settings['width'] = physicalWidth; 537 | settings['height'] = physicalHeight; 538 | videoSource.update(settings); 539 | videoSource.save(); 540 | 541 | const newitem = scene.add(videoSource); 542 | const outputWidth = 1920; 543 | const videoScaleFactor = physicalWidth / outputWidth; 544 | const outputHeight = Math.round(outputWidth / aspectRatio); 545 | setSetting('Video', 'Base', `${outputWidth}x${outputHeight}`); 546 | setSetting('Video', 'Output', `${outputWidth}x${outputHeight}`); 547 | newitem.scale = { x: 1.0 / videoScaleFactor, y: 1.0 / videoScaleFactor }; 548 | newitem.moveBottom() 549 | 550 | scene.save() 551 | return scene; 552 | } 553 | 554 | // 获取设置信息 555 | function getSetting(cate) { 556 | // console.log(electron.screen.getAllDisplays()) 557 | return osn.NodeObs.OBS_settings_getSettings(cate.name).data; 558 | } 559 | 560 | // 获取摄像头数据 561 | function getALlCameras() { 562 | const dummyInput = osn.InputFactory.create('dshow_input', 'video', { 563 | audio_device_id: 'does_not_exist', 564 | video_device_id: 'does_not_exist', 565 | }); 566 | 567 | const cameraItems = dummyInput.properties.get('video_device_id').details.items; 568 | 569 | dummyInput.release(); 570 | 571 | return cameraItems; 572 | } 573 | 574 | // 获取显示器 575 | function getAllScene() { 576 | // 遍历 osn.Global.getOutputSource() 根据type判断 ESourceType === 3 577 | //console.log(scene) 578 | //console.log(scene.name) 579 | return new Array({ 580 | name: scene.name, items: scene.getItems().map(function (item) { 581 | return item.source.name; 582 | }) 583 | }); 584 | // return new Array(scene); 585 | } 586 | 587 | // 设置并展示源数据 588 | function showSourceInfo(name) { 589 | return scene.getItems().filter(item => { 590 | return name.id == item.source.name 591 | }).map(item => { 592 | let r = { name: item.source.name, width: item.source.width, height: item.source.height, x: item.position.x, y: item.position.y, visible: item.visible }; 593 | console.log(r) 594 | return r; 595 | }) 596 | } 597 | 598 | // 修改流地址 599 | function udpateRtmp(window, settings) { 600 | 601 | // 设置流地址和key 602 | setSetting('Stream', 'server', settings.server) 603 | setSetting('Stream', 'key', settings.key) 604 | return true; 605 | 606 | } 607 | 608 | // 开始直播和结束直播 609 | function toggleStreaming(state) { 610 | console.debug('streamingState:',state) 611 | if (!state) { 612 | osn.NodeObs.OBS_service_startStreaming(); 613 | } else { 614 | osn.NodeObs.OBS_service_stopStreaming(true); 615 | } 616 | } 617 | 618 | module.exports.initialize = initialize; 619 | module.exports.start = start; 620 | module.exports.isVirtualCamPluginInstalled = isVirtualCamPluginInstalled; 621 | module.exports.installVirtualCamPlugin = installVirtualCamPlugin; 622 | module.exports.uninstallVirtualCamPlugin = uninstallVirtualCamPlugin; 623 | module.exports.startVirtualCam = startVirtualCam; 624 | module.exports.stopVirtualCam = stopVirtualCam; 625 | module.exports.stop = stop; 626 | module.exports.shutdown = shutdown; 627 | module.exports.setupPreview = setupPreview; 628 | module.exports.resizePreview = resizePreview; 629 | module.exports.selectDisPlay = selectDisPlay; 630 | module.exports.getSetting = getSetting; 631 | module.exports.getALlCameras = getALlCameras; 632 | module.exports.getAllScene = getAllScene; 633 | module.exports.showSourceInfo = showSourceInfo; 634 | module.exports.udpateRtmp = udpateRtmp; 635 | module.exports.toggleStreaming = toggleStreaming; -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@electron/get@^1.0.1": 6 | version "1.12.3" 7 | resolved "https://registry.npm.taobao.org/@electron/get/download/@electron/get-1.12.3.tgz#fa2723385c4b565a34c4c82f46087aa2a5fbf6d0" 8 | integrity sha1-+icjOFxLVlo0xMgvRgh6oqX79tA= 9 | dependencies: 10 | debug "^4.1.1" 11 | env-paths "^2.2.0" 12 | filenamify "^4.1.0" 13 | fs-extra "^8.1.0" 14 | got "^9.6.0" 15 | progress "^2.0.3" 16 | semver "^6.2.0" 17 | sumchecker "^3.0.1" 18 | optionalDependencies: 19 | global-agent "^2.0.2" 20 | global-tunnel-ng "^2.7.1" 21 | 22 | "@sindresorhus/is@^0.14.0": 23 | version "0.14.0" 24 | resolved "https://registry.npm.taobao.org/@sindresorhus/is/download/@sindresorhus/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" 25 | integrity sha1-n7OjzzEyMoFR81PeRjLgHlIQK+o= 26 | 27 | "@szmarczak/http-timer@^1.1.2": 28 | version "1.1.2" 29 | resolved "https://registry.npm.taobao.org/@szmarczak/http-timer/download/@szmarczak/http-timer-1.1.2.tgz?cache=0&sync_timestamp=1580758852337&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40szmarczak%2Fhttp-timer%2Fdownload%2F%40szmarczak%2Fhttp-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" 30 | integrity sha1-sWZeLEYaLNkvTBu/UNVFTeDUtCE= 31 | dependencies: 32 | defer-to-connect "^1.0.1" 33 | 34 | "@types/node@^12.0.12": 35 | version "12.19.15" 36 | resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-12.19.15.tgz?cache=0&sync_timestamp=1611168821881&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-12.19.15.tgz#0de7e978fb43db62da369db18ea088a63673c182" 37 | integrity sha1-DefpePtD22LaNp2xjqCIpjZzwYI= 38 | 39 | boolean@^3.0.1: 40 | version "3.0.2" 41 | resolved "https://registry.npm.taobao.org/boolean/download/boolean-3.0.2.tgz#df1baa18b6a2b0e70840475e1d93ec8fe75b2570" 42 | integrity sha1-3xuqGLaisOcIQEdeHZPsj+dbJXA= 43 | 44 | buffer-crc32@~0.2.3: 45 | version "0.2.13" 46 | resolved "https://registry.npm.taobao.org/buffer-crc32/download/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" 47 | integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= 48 | 49 | buffer-from@^1.0.0: 50 | version "1.1.1" 51 | resolved "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 52 | integrity sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8= 53 | 54 | cacheable-request@^6.0.0: 55 | version "6.1.0" 56 | resolved "https://registry.npm.taobao.org/cacheable-request/download/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" 57 | integrity sha1-IP+4vRYrpL4R6VZ9gj22UQUsqRI= 58 | dependencies: 59 | clone-response "^1.0.2" 60 | get-stream "^5.1.0" 61 | http-cache-semantics "^4.0.0" 62 | keyv "^3.0.0" 63 | lowercase-keys "^2.0.0" 64 | normalize-url "^4.1.0" 65 | responselike "^1.0.2" 66 | 67 | clone-response@^1.0.2: 68 | version "1.0.2" 69 | resolved "https://registry.npm.taobao.org/clone-response/download/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" 70 | integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= 71 | dependencies: 72 | mimic-response "^1.0.0" 73 | 74 | concat-stream@^1.6.2: 75 | version "1.6.2" 76 | resolved "https://registry.npm.taobao.org/concat-stream/download/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 77 | integrity sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ= 78 | dependencies: 79 | buffer-from "^1.0.0" 80 | inherits "^2.0.3" 81 | readable-stream "^2.2.2" 82 | typedarray "^0.0.6" 83 | 84 | config-chain@^1.1.11: 85 | version "1.1.12" 86 | resolved "https://registry.npm.taobao.org/config-chain/download/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" 87 | integrity sha1-D96NCRIA616AjK8l/mGMAvSOTvo= 88 | dependencies: 89 | ini "^1.3.4" 90 | proto-list "~1.2.1" 91 | 92 | core-js@^3.6.5: 93 | version "3.8.3" 94 | resolved "https://registry.npm.taobao.org/core-js/download/core-js-3.8.3.tgz?cache=0&sync_timestamp=1611040756909&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-3.8.3.tgz#c21906e1f14f3689f93abcc6e26883550dd92dd0" 95 | integrity sha1-whkG4fFPNon5OrzG4miDVQ3ZLdA= 96 | 97 | core-util-is@~1.0.0: 98 | version "1.0.2" 99 | resolved "https://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 100 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 101 | 102 | debug@^2.6.9: 103 | version "2.6.9" 104 | resolved "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz?cache=0&sync_timestamp=1607566517339&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdebug%2Fdownload%2Fdebug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 105 | integrity sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8= 106 | dependencies: 107 | ms "2.0.0" 108 | 109 | debug@^4.1.0, debug@^4.1.1: 110 | version "4.3.1" 111 | resolved "https://registry.npm.taobao.org/debug/download/debug-4.3.1.tgz?cache=0&sync_timestamp=1607566517339&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdebug%2Fdownload%2Fdebug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" 112 | integrity sha1-8NIpxQXgxtjEmsVT0bE9wYP2su4= 113 | dependencies: 114 | ms "2.1.2" 115 | 116 | decompress-response@^3.3.0: 117 | version "3.3.0" 118 | resolved "https://registry.npm.taobao.org/decompress-response/download/decompress-response-3.3.0.tgz?cache=0&sync_timestamp=1589512178920&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdecompress-response%2Fdownload%2Fdecompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" 119 | integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= 120 | dependencies: 121 | mimic-response "^1.0.0" 122 | 123 | defer-to-connect@^1.0.1: 124 | version "1.1.3" 125 | resolved "https://registry.npm.taobao.org/defer-to-connect/download/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" 126 | integrity sha1-MxrgUMCNz3ifjIOnuB8O2U9KxZE= 127 | 128 | define-properties@^1.1.3: 129 | version "1.1.3" 130 | resolved "https://registry.npm.taobao.org/define-properties/download/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" 131 | integrity sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE= 132 | dependencies: 133 | object-keys "^1.0.12" 134 | 135 | detect-node@^2.0.4: 136 | version "2.0.4" 137 | resolved "https://registry.npm.taobao.org/detect-node/download/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" 138 | integrity sha1-AU7o+PZpxcWAI9pkuBecCDooxGw= 139 | 140 | duplexer3@^0.1.4: 141 | version "0.1.4" 142 | resolved "https://registry.npm.taobao.org/duplexer3/download/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" 143 | integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= 144 | 145 | electron@^10.2.0: 146 | version "10.4.7" 147 | resolved "https://nexus.chard.org.cn/repository/hjblm-npm-group/electron/-/electron-10.4.7.tgz#3ec7d46d02c0a6b4955f5fbf19a84d0e0c711184" 148 | integrity sha512-je+AokZfKldI5GItXOx5pwBEAnbEqTrEPhaRUm2RN0OFBPXO+7wjJ3X+HvvlOHvKtfZrlU+57Dmkg1DseSFOPA== 149 | dependencies: 150 | "@electron/get" "^1.0.1" 151 | "@types/node" "^12.0.12" 152 | extract-zip "^1.0.3" 153 | 154 | encodeurl@^1.0.2: 155 | version "1.0.2" 156 | resolved "https://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 157 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 158 | 159 | end-of-stream@^1.1.0: 160 | version "1.4.4" 161 | resolved "https://registry.npm.taobao.org/end-of-stream/download/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 162 | integrity sha1-WuZKX0UFe682JuwU2gyl5LJDHrA= 163 | dependencies: 164 | once "^1.4.0" 165 | 166 | env-paths@^2.2.0: 167 | version "2.2.0" 168 | resolved "https://registry.npm.taobao.org/env-paths/download/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" 169 | integrity sha1-zcpVfcAJFSkX1hZuL+vh8DloXkM= 170 | 171 | es6-error@^4.1.1: 172 | version "4.1.1" 173 | resolved "https://registry.npm.taobao.org/es6-error/download/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" 174 | integrity sha1-njr0B0Wd7tR+mpH5uIWoTrBcVh0= 175 | 176 | escape-string-regexp@^1.0.2: 177 | version "1.0.5" 178 | resolved "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 179 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 180 | 181 | escape-string-regexp@^4.0.0: 182 | version "4.0.0" 183 | resolved "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 184 | integrity sha1-FLqDpdNz49MR5a/KKc9b+tllvzQ= 185 | 186 | extract-zip@^1.0.3: 187 | version "1.7.0" 188 | resolved "https://registry.npm.taobao.org/extract-zip/download/extract-zip-1.7.0.tgz?cache=0&sync_timestamp=1591773022084&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fextract-zip%2Fdownload%2Fextract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" 189 | integrity sha1-VWzDrp339FLEk6DPtRzDAneUCSc= 190 | dependencies: 191 | concat-stream "^1.6.2" 192 | debug "^2.6.9" 193 | mkdirp "^0.5.4" 194 | yauzl "^2.10.0" 195 | 196 | fd-slicer@~1.1.0: 197 | version "1.1.0" 198 | resolved "https://registry.npm.taobao.org/fd-slicer/download/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" 199 | integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= 200 | dependencies: 201 | pend "~1.2.0" 202 | 203 | filename-reserved-regex@^2.0.0: 204 | version "2.0.0" 205 | resolved "https://registry.npm.taobao.org/filename-reserved-regex/download/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" 206 | integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik= 207 | 208 | filenamify@^4.1.0: 209 | version "4.2.0" 210 | resolved "https://registry.npm.taobao.org/filenamify/download/filenamify-4.2.0.tgz?cache=0&sync_timestamp=1600940576832&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffilenamify%2Fdownload%2Ffilenamify-4.2.0.tgz#c99716d676869585b3b5d328b3f06590d032e89f" 211 | integrity sha1-yZcW1naGlYWztdMos/BlkNAy6J8= 212 | dependencies: 213 | filename-reserved-regex "^2.0.0" 214 | strip-outer "^1.0.1" 215 | trim-repeated "^1.0.0" 216 | 217 | fs-extra@^8.1.0: 218 | version "8.1.0" 219 | resolved "https://registry.npm.taobao.org/fs-extra/download/fs-extra-8.1.0.tgz?cache=0&sync_timestamp=1611075555649&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffs-extra%2Fdownload%2Ffs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" 220 | integrity sha1-SdQ8RaiM2Wd2aMt74bRu/bjS4cA= 221 | dependencies: 222 | graceful-fs "^4.2.0" 223 | jsonfile "^4.0.0" 224 | universalify "^0.1.0" 225 | 226 | get-stream@^4.1.0: 227 | version "4.1.0" 228 | resolved "https://registry.npm.taobao.org/get-stream/download/get-stream-4.1.0.tgz?cache=0&sync_timestamp=1597056535605&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fget-stream%2Fdownload%2Fget-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" 229 | integrity sha1-wbJVV189wh1Zv8ec09K0axw6VLU= 230 | dependencies: 231 | pump "^3.0.0" 232 | 233 | get-stream@^5.1.0: 234 | version "5.2.0" 235 | resolved "https://registry.npm.taobao.org/get-stream/download/get-stream-5.2.0.tgz?cache=0&sync_timestamp=1597056535605&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fget-stream%2Fdownload%2Fget-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" 236 | integrity sha1-SWaheV7lrOZecGxLe+txJX1uItM= 237 | dependencies: 238 | pump "^3.0.0" 239 | 240 | global-agent@^2.0.2: 241 | version "2.1.12" 242 | resolved "https://registry.npm.taobao.org/global-agent/download/global-agent-2.1.12.tgz#e4ae3812b731a9e81cbf825f9377ef450a8e4195" 243 | integrity sha1-5K44Ercxqegcv4Jfk3fvRQqOQZU= 244 | dependencies: 245 | boolean "^3.0.1" 246 | core-js "^3.6.5" 247 | es6-error "^4.1.1" 248 | matcher "^3.0.0" 249 | roarr "^2.15.3" 250 | semver "^7.3.2" 251 | serialize-error "^7.0.1" 252 | 253 | global-tunnel-ng@^2.7.1: 254 | version "2.7.1" 255 | resolved "https://registry.npm.taobao.org/global-tunnel-ng/download/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" 256 | integrity sha1-0DtRAt/eOmmRT17n2GdhyjXVfY8= 257 | dependencies: 258 | encodeurl "^1.0.2" 259 | lodash "^4.17.10" 260 | npm-conf "^1.1.3" 261 | tunnel "^0.0.6" 262 | 263 | globalthis@^1.0.1: 264 | version "1.0.1" 265 | resolved "https://registry.npm.taobao.org/globalthis/download/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9" 266 | integrity sha1-QBFvXZwHH56PsAN2VN8as6g7fvk= 267 | dependencies: 268 | define-properties "^1.1.3" 269 | 270 | got@^9.6.0: 271 | version "9.6.0" 272 | resolved "https://registry.npm.taobao.org/got/download/got-9.6.0.tgz?cache=0&sync_timestamp=1607658223946&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgot%2Fdownload%2Fgot-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" 273 | integrity sha1-7fRefWf5lUVwXeH3u+7rEhdl7YU= 274 | dependencies: 275 | "@sindresorhus/is" "^0.14.0" 276 | "@szmarczak/http-timer" "^1.1.2" 277 | cacheable-request "^6.0.0" 278 | decompress-response "^3.3.0" 279 | duplexer3 "^0.1.4" 280 | get-stream "^4.1.0" 281 | lowercase-keys "^1.0.1" 282 | mimic-response "^1.0.1" 283 | p-cancelable "^1.0.0" 284 | to-readable-stream "^1.0.0" 285 | url-parse-lax "^3.0.0" 286 | 287 | graceful-fs@^4.1.6, graceful-fs@^4.2.0: 288 | version "4.2.4" 289 | resolved "https://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.2.4.tgz?cache=0&sync_timestamp=1588086924019&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgraceful-fs%2Fdownload%2Fgraceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" 290 | integrity sha1-Ila94U02MpWMRl68ltxGfKB6Kfs= 291 | 292 | http-cache-semantics@^4.0.0: 293 | version "4.1.0" 294 | resolved "https://registry.npm.taobao.org/http-cache-semantics/download/http-cache-semantics-4.1.0.tgz?cache=0&sync_timestamp=1583107845365&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-cache-semantics%2Fdownload%2Fhttp-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" 295 | integrity sha1-SekcXL82yblLz81xwj1SSex045A= 296 | 297 | inherits@^2.0.3, inherits@~2.0.3: 298 | version "2.0.4" 299 | resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 300 | integrity sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w= 301 | 302 | ini@^1.3.4: 303 | version "1.3.8" 304 | resolved "https://registry.npm.taobao.org/ini/download/ini-1.3.8.tgz?cache=0&sync_timestamp=1607907802342&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fini%2Fdownload%2Fini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" 305 | integrity sha1-op2kJbSIBvNHZ6Tvzjlyaa8oQyw= 306 | 307 | isarray@~1.0.0: 308 | version "1.0.0" 309 | resolved "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 310 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 311 | 312 | json-buffer@3.0.0: 313 | version "3.0.0" 314 | resolved "https://registry.npm.taobao.org/json-buffer/download/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" 315 | integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= 316 | 317 | json-stringify-safe@^5.0.1: 318 | version "5.0.1" 319 | resolved "https://registry.npm.taobao.org/json-stringify-safe/download/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 320 | integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= 321 | 322 | jsonfile@^4.0.0: 323 | version "4.0.0" 324 | resolved "https://registry.npm.taobao.org/jsonfile/download/jsonfile-4.0.0.tgz?cache=0&sync_timestamp=1604164898625&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjsonfile%2Fdownload%2Fjsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 325 | integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= 326 | optionalDependencies: 327 | graceful-fs "^4.1.6" 328 | 329 | keyv@^3.0.0: 330 | version "3.1.0" 331 | resolved "https://registry.npm.taobao.org/keyv/download/keyv-3.1.0.tgz?cache=0&sync_timestamp=1600337477435&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkeyv%2Fdownload%2Fkeyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" 332 | integrity sha1-7MIoSG9pmR5J6UdkhaW+Ho/FxNk= 333 | dependencies: 334 | json-buffer "3.0.0" 335 | 336 | lodash@^4.17.10: 337 | version "4.17.20" 338 | resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.20.tgz?cache=0&sync_timestamp=1597336147792&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" 339 | integrity sha1-tEqbYpe8tpjxxRo1RaKzs2jVnFI= 340 | 341 | lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: 342 | version "1.0.1" 343 | resolved "https://registry.npm.taobao.org/lowercase-keys/download/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" 344 | integrity sha1-b54wtHCE2XGnyCD/FabFFnt0wm8= 345 | 346 | lowercase-keys@^2.0.0: 347 | version "2.0.0" 348 | resolved "https://registry.npm.taobao.org/lowercase-keys/download/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" 349 | integrity sha1-JgPni3tLAAbLyi+8yKMgJVislHk= 350 | 351 | lru-cache@^6.0.0: 352 | version "6.0.0" 353 | resolved "https://registry.npm.taobao.org/lru-cache/download/lru-cache-6.0.0.tgz?cache=0&sync_timestamp=1594427569171&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flru-cache%2Fdownload%2Flru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 354 | integrity sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ= 355 | dependencies: 356 | yallist "^4.0.0" 357 | 358 | matcher@^3.0.0: 359 | version "3.0.0" 360 | resolved "https://registry.npm.taobao.org/matcher/download/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" 361 | integrity sha1-vZBg9MW3CqgEHMxvgDaHYJlPMMo= 362 | dependencies: 363 | escape-string-regexp "^4.0.0" 364 | 365 | mimic-response@^1.0.0, mimic-response@^1.0.1: 366 | version "1.0.1" 367 | resolved "https://registry.npm.taobao.org/mimic-response/download/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" 368 | integrity sha1-SSNTiHju9CBjy4o+OweYeBSHqxs= 369 | 370 | minimist@^1.2.5: 371 | version "1.2.5" 372 | resolved "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 373 | integrity sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI= 374 | 375 | mkdirp@^0.5.4: 376 | version "0.5.5" 377 | resolved "https://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 378 | integrity sha1-2Rzv1i0UNsoPQWIOJRKI1CAJne8= 379 | dependencies: 380 | minimist "^1.2.5" 381 | 382 | ms@2.0.0: 383 | version "2.0.0" 384 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz?cache=0&sync_timestamp=1607433988749&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 385 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 386 | 387 | ms@2.1.2: 388 | version "2.1.2" 389 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&sync_timestamp=1607433988749&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 390 | integrity sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk= 391 | 392 | nan@^2.14.0: 393 | version "2.18.0" 394 | resolved "https://nexus.chard.org.cn/repository/hjblm-npm-group/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" 395 | integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== 396 | 397 | "node-window-rendering@https://slobs-node-window-rendering.s3-us-west-2.amazonaws.com/node-window-rendering-0.0.0.tar.gz": 398 | version "1.0.0" 399 | resolved "https://slobs-node-window-rendering.s3-us-west-2.amazonaws.com/node-window-rendering-0.0.0.tar.gz#114a25488e6133e712b7e6143a22a70f1b8bab57" 400 | dependencies: 401 | nan "^2.14.0" 402 | 403 | normalize-url@^4.1.0: 404 | version "4.5.0" 405 | resolved "https://registry.npm.taobao.org/normalize-url/download/normalize-url-4.5.0.tgz?cache=0&sync_timestamp=1602432879767&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnormalize-url%2Fdownload%2Fnormalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" 406 | integrity sha1-RTNUCH5sqWlXvY9br3U/WYIUISk= 407 | 408 | npm-conf@^1.1.3: 409 | version "1.1.3" 410 | resolved "https://registry.npm.taobao.org/npm-conf/download/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" 411 | integrity sha1-JWzEe9DiGMJZxOlVC/QTvCGSr/k= 412 | dependencies: 413 | config-chain "^1.1.11" 414 | pify "^3.0.0" 415 | 416 | object-keys@^1.0.12: 417 | version "1.1.1" 418 | resolved "https://registry.npm.taobao.org/object-keys/download/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" 419 | integrity sha1-HEfyct8nfzsdrwYWd9nILiMixg4= 420 | 421 | "obs-studio-node@https://s3-us-west-2.amazonaws.com/obsstudionodes3.streamlabs.com/osn-0.15.6-release-win64.tar.gz": 422 | version "0.15.6" 423 | resolved "https://s3-us-west-2.amazonaws.com/obsstudionodes3.streamlabs.com/osn-0.15.6-release-win64.tar.gz#dc8643c6ff0539d0df39a8293c959d0dcd038979" 424 | 425 | once@^1.3.1, once@^1.4.0: 426 | version "1.4.0" 427 | resolved "https://registry.npm.taobao.org/once/download/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 428 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 429 | dependencies: 430 | wrappy "1" 431 | 432 | p-cancelable@^1.0.0: 433 | version "1.1.0" 434 | resolved "https://registry.npm.taobao.org/p-cancelable/download/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" 435 | integrity sha1-0HjRWjr0CSIMiG8dmgyi5EGrJsw= 436 | 437 | pend@~1.2.0: 438 | version "1.2.0" 439 | resolved "https://registry.npm.taobao.org/pend/download/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" 440 | integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= 441 | 442 | pify@^3.0.0: 443 | version "3.0.0" 444 | resolved "https://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" 445 | integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= 446 | 447 | prepend-http@^2.0.0: 448 | version "2.0.0" 449 | resolved "https://registry.npm.taobao.org/prepend-http/download/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" 450 | integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= 451 | 452 | process-nextick-args@~2.0.0: 453 | version "2.0.1" 454 | resolved "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 455 | integrity sha1-eCDZsWEgzFXKmud5JoCufbptf+I= 456 | 457 | progress@^2.0.3: 458 | version "2.0.3" 459 | resolved "https://registry.npm.taobao.org/progress/download/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" 460 | integrity sha1-foz42PW48jnBvGi+tOt4Vn1XLvg= 461 | 462 | proto-list@~1.2.1: 463 | version "1.2.4" 464 | resolved "https://registry.npm.taobao.org/proto-list/download/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" 465 | integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= 466 | 467 | pump@^3.0.0: 468 | version "3.0.0" 469 | resolved "https://registry.npm.taobao.org/pump/download/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 470 | integrity sha1-tKIRaBW94vTh6mAjVOjHVWUQemQ= 471 | dependencies: 472 | end-of-stream "^1.1.0" 473 | once "^1.3.1" 474 | 475 | readable-stream@^2.2.2: 476 | version "2.3.7" 477 | resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 478 | integrity sha1-Hsoc9xGu+BTAT2IlKjamL2yyO1c= 479 | dependencies: 480 | core-util-is "~1.0.0" 481 | inherits "~2.0.3" 482 | isarray "~1.0.0" 483 | process-nextick-args "~2.0.0" 484 | safe-buffer "~5.1.1" 485 | string_decoder "~1.1.1" 486 | util-deprecate "~1.0.1" 487 | 488 | responselike@^1.0.2: 489 | version "1.0.2" 490 | resolved "https://registry.npm.taobao.org/responselike/download/responselike-1.0.2.tgz?cache=0&sync_timestamp=1570573217730&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresponselike%2Fdownload%2Fresponselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" 491 | integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= 492 | dependencies: 493 | lowercase-keys "^1.0.0" 494 | 495 | roarr@^2.15.3: 496 | version "2.15.4" 497 | resolved "https://registry.npm.taobao.org/roarr/download/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" 498 | integrity sha1-9f55W3uDjM/jXcYI4Cgrnrouev0= 499 | dependencies: 500 | boolean "^3.0.1" 501 | detect-node "^2.0.4" 502 | globalthis "^1.0.1" 503 | json-stringify-safe "^5.0.1" 504 | semver-compare "^1.0.0" 505 | sprintf-js "^1.1.2" 506 | 507 | rxjs@^6.6.3: 508 | version "6.6.3" 509 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" 510 | integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== 511 | dependencies: 512 | tslib "^1.9.0" 513 | 514 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 515 | version "5.1.2" 516 | resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz?cache=0&sync_timestamp=1589129010497&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-buffer%2Fdownload%2Fsafe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 517 | integrity sha1-mR7GnSluAxN0fVm9/St0XDX4go0= 518 | 519 | semver-compare@^1.0.0: 520 | version "1.0.0" 521 | resolved "https://registry.npm.taobao.org/semver-compare/download/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" 522 | integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= 523 | 524 | semver@^6.2.0: 525 | version "6.3.0" 526 | resolved "https://registry.npm.taobao.org/semver/download/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 527 | integrity sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0= 528 | 529 | semver@^7.3.2: 530 | version "7.3.4" 531 | resolved "https://registry.npm.taobao.org/semver/download/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" 532 | integrity sha1-J6qn0uTKdkUvmNOt0JOnLJQ+3Jc= 533 | dependencies: 534 | lru-cache "^6.0.0" 535 | 536 | serialize-error@^7.0.1: 537 | version "7.0.1" 538 | resolved "https://registry.npm.taobao.org/serialize-error/download/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" 539 | integrity sha1-8TYLBEf2H/tIPsQVfHN/q313jhg= 540 | dependencies: 541 | type-fest "^0.13.1" 542 | 543 | sprintf-js@^1.1.2: 544 | version "1.1.2" 545 | resolved "https://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" 546 | integrity sha1-2hdlJiv4wPVxdJ8q1sJjACB65nM= 547 | 548 | string_decoder@~1.1.1: 549 | version "1.1.1" 550 | resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz?cache=0&sync_timestamp=1565170823020&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstring_decoder%2Fdownload%2Fstring_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 551 | integrity sha1-nPFhG6YmhdcDCunkujQUnDrwP8g= 552 | dependencies: 553 | safe-buffer "~5.1.0" 554 | 555 | strip-outer@^1.0.1: 556 | version "1.0.1" 557 | resolved "https://registry.npm.taobao.org/strip-outer/download/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" 558 | integrity sha1-sv0qv2YEudHmATBXGV34Nrip1jE= 559 | dependencies: 560 | escape-string-regexp "^1.0.2" 561 | 562 | sumchecker@^3.0.1: 563 | version "3.0.1" 564 | resolved "https://registry.npm.taobao.org/sumchecker/download/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" 565 | integrity sha1-Y3fplnlauwttNI6bPh37JDRajkI= 566 | dependencies: 567 | debug "^4.1.0" 568 | 569 | to-readable-stream@^1.0.0: 570 | version "1.0.0" 571 | resolved "https://registry.npm.taobao.org/to-readable-stream/download/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" 572 | integrity sha1-zgqgwvPfat+FLvtASng+d8BHV3E= 573 | 574 | trim-repeated@^1.0.0: 575 | version "1.0.0" 576 | resolved "https://registry.npm.taobao.org/trim-repeated/download/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" 577 | integrity sha1-42RqLqTokTEr9+rObPsFOAvAHCE= 578 | dependencies: 579 | escape-string-regexp "^1.0.2" 580 | 581 | tslib@^1.9.0: 582 | version "1.11.1" 583 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" 584 | integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== 585 | 586 | tunnel@^0.0.6: 587 | version "0.0.6" 588 | resolved "https://registry.npm.taobao.org/tunnel/download/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" 589 | integrity sha1-cvExSzSlsZLbASMk3yzFh8pH+Sw= 590 | 591 | type-fest@^0.13.1: 592 | version "0.13.1" 593 | resolved "https://registry.npm.taobao.org/type-fest/download/type-fest-0.13.1.tgz?cache=0&sync_timestamp=1606468897926&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftype-fest%2Fdownload%2Ftype-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" 594 | integrity sha1-AXLLW86AsL1ULqNI21DH4hg02TQ= 595 | 596 | typedarray@^0.0.6: 597 | version "0.0.6" 598 | resolved "https://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 599 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 600 | 601 | universalify@^0.1.0: 602 | version "0.1.2" 603 | resolved "https://registry.npm.taobao.org/universalify/download/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 604 | integrity sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY= 605 | 606 | url-parse-lax@^3.0.0: 607 | version "3.0.0" 608 | resolved "https://registry.npm.taobao.org/url-parse-lax/download/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" 609 | integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= 610 | dependencies: 611 | prepend-http "^2.0.0" 612 | 613 | util-deprecate@~1.0.1: 614 | version "1.0.2" 615 | resolved "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 616 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 617 | 618 | uuid@^8.3.0: 619 | version "8.3.0" 620 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" 621 | integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== 622 | 623 | wrappy@1: 624 | version "1.0.2" 625 | resolved "https://registry.npm.taobao.org/wrappy/download/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 626 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 627 | 628 | yallist@^4.0.0: 629 | version "4.0.0" 630 | resolved "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 631 | integrity sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI= 632 | 633 | yauzl@^2.10.0: 634 | version "2.10.0" 635 | resolved "https://registry.npm.taobao.org/yauzl/download/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" 636 | integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= 637 | dependencies: 638 | buffer-crc32 "~0.2.3" 639 | fd-slicer "~1.1.0" 640 | --------------------------------------------------------------------------------