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