├── README.md └── docs ├── configuration.md ├── displays.md ├── index.md ├── recording.md ├── scenes-and-sources.md └── transitions.md /README.md: -------------------------------------------------------------------------------- 1 | # `obs-studio-node` Docs 2 | This is some documentation created by reverse engineering [Streamlabs OBS](github.com/stream-labs/streamlabs-obs/), heavily based on the tests done by @Envek, @EmptyCrown and others in [obs-studio-node-example](https://github.com/Envek/obs-studio-node-example). 3 | 4 | If you can improve those docs, feel free to create a PR! 5 | 6 | ## What is `obs-studio-node`? 7 | See [here](https://github.com/stream-labs/obs-studio-node#libobs-via-node-bindings). 8 | 9 | ## Building 10 | See [here](https://github.com/stream-labs/obs-studio-node#building) and [here](https://github.com/Envek/obs-studio-node-example#use-with-your-own-build-of-obs-studio-node). 11 | 12 | ## Docs 13 | Click [here](./docs/index.md). 14 | 15 | ## Why? 16 | I created a PR to the official `obs-studio-node` Repository in May 2020 but they keep ignoring it. That's why I moved the docs here. -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration for recording 2 | ```JavaScript 3 | function getAvailableValues(category, subcategory, parameter) { 4 | const categorySettings = osn.NodeObs.OBS_settings_getSettings(category).data; 5 | if (!categorySettings) { 6 | console.warn(`There is no category ${category} in OBS settings`); 7 | return []; 8 | } 9 | 10 | const subcategorySettings = categorySettings.find( 11 | (sub) => sub.nameSubCategory === subcategory, 12 | ); 13 | if (!subcategorySettings) { 14 | console.warn(`There is no subcategory ${subcategory} for OBS settings category ${category}`); 15 | return []; 16 | } 17 | 18 | const parameterSettings = subcategorySettings.parameters.find( 19 | (param) => param.name === parameter, 20 | ); 21 | if (!parameterSettings) { 22 | console.warn(`There is no parameter ${parameter} for OBS settings category ${category}.${subcategory}`); 23 | return []; 24 | } 25 | 26 | return parameterSettings.values.map((value) => Object.values(value)[0]); 27 | } 28 | 29 | function setSetting(category, parameter, value) { 30 | let oldValue; 31 | // Getting settings container 32 | const settings = osn.NodeObs.OBS_settings_getSettings(category).data; 33 | 34 | settings.forEach((subCategory) => { 35 | subCategory.parameters.forEach((param) => { 36 | if (param.name === parameter) { 37 | oldValue = param.currentValue; 38 | param.currentValue = value; 39 | } 40 | }); 41 | }); 42 | // Saving updated settings container 43 | if (value != oldValue) { 44 | osn.NodeObs.OBS_settings_saveSettings(category, settings); 45 | } 46 | } 47 | 48 | setSetting("Output", "Mode", "Simple"); 49 | const availableEncoders = getAvailableValues("Output", "Recording", "RecEncoder"); 50 | setSetting("Output", "RecEncoder", availableEncoders.slice(-1)[0] || "x264"); 51 | setSetting("Output", "FilePath", "/videos"); 52 | setSetting("Output", "RecFormat", "mkv"); 53 | setSetting("Output", "VBitrate", 10000); // 10 Mbps 54 | setSetting("Video", "FPSCommon", 60); 55 | ``` -------------------------------------------------------------------------------- /docs/displays.md: -------------------------------------------------------------------------------- 1 | # Displays 2 | You can create a display like so: 3 | ```JavaScript 4 | const displayId = "myDisplay"; 5 | const displayWidth = 960; 6 | const displayHeight = 540; 7 | const resized = () => { 8 | const { width, height } = previewWindow.getContentBounds(); 9 | osn.NodeObs.OBS_content_resizeDisplay(displayId, width, height + 20); 10 | osn.NodeObs.OBS_content_setPaddingSize(displayId, 5); 11 | }; 12 | previewWindow = new BrowserWindow({ 13 | width: displayWidth, 14 | height: displayHeight, 15 | // if you use this, the window will automatically close 16 | // when the parent window is closed 17 | parent: parentWindow, 18 | useContentSize: true, 19 | }); 20 | previewWindow.on("close", () => { 21 | osn.NodeObs.OBS_content_destroyDisplay(displayId); 22 | previewWindow = undefined; 23 | }); 24 | previewWindow.on("resize", resized); 25 | 26 | osn.NodeObs.OBS_content_createSourcePreviewDisplay( 27 | previewWindow.getNativeWindowHandle(), 28 | "", // or use camera source Id here 29 | displayId, 30 | ); 31 | osn.NodeObs.OBS_content_setShouldDrawUI(displayId, false); 32 | osn.NodeObs.OBS_content_setPaddingColor(displayId, 255, 255, 255); 33 | resized(); 34 | ``` 35 | 36 | You need a `BrowserWindow` and OBS will draw the display on top of it. Be sure to listen to all events and resize or move the distplay accordingly, as it will otherwise be on top of your other HTML elements which can be in the same `BrowserWindow`. 37 | 38 | You can set a padding with 39 | ```JavaScript 40 | osn.NodeObs.OBS_content_setPaddingSize(displayId, 5); 41 | osn.NodeObs.OBS_content_setPaddingColor(displayId, 255, 255, 255); 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | ## Example Projects 3 | - [obs-studio-node-example](https://github.com/Envek/obs-studio-node-example/), a minimalistic screen and webcam recording example. This documentation is heavily based on their work. 4 | - [AGView](https://github.com/hrueger/AGView), a slide-based presenting software. It's a little more complex than [obs-studio-node-example](https://github.com/Envek/obs-studio-node-example/) becuase it uses TypeScript and Angular, but all the `obs-studio-node` code lives in [this file](https://github.com/hrueger/AGView/blob/master/src/worker/obs.ts). 5 | 6 | ## Table of Contents 7 | 1. Initialization and Shutdown (you are here, see below) 8 | 2. [Configuration](./configuration.md) 9 | 3. [Scenes and Sources](./scenes-and-sources.md) 10 | 4. [Transitions](./transitions.md) 11 | 5. [Recording](./recording.md) 12 | 6. [Displays](./displays.md) 13 | 14 | ## Usage 15 | > **Important:** If used with Electron, everything shown here has to be done in the **main** process, not in the renderer process. If you get `Uncaught Error: Failed to host and connect`, this could be the problem. 16 | 17 | ### Import `obs-studio-node` 18 | TypeScript: 19 | ```TypeScript or ES2015 20 | import * as osn from "obs-studio-node"; 21 | ``` 22 | JavaScript: 23 | ```JavaScript 24 | const osn = require("obs-studio-node"); 25 | ``` 26 | 27 | ### Initialization 28 | ```JavaScript 29 | const { Subject } = require("rxjs"); 30 | // Usually some UUIDs go there 31 | osn.NodeObs.IPC.host("obs-studio-node-example"); 32 | // set the working dir 33 | // when packaged, the osn folder is unpacked from electron's asar file 34 | // so we have to fix the path by replacing `app.asar` with `app.asar.unpacked` 35 | osn.NodeObs.SetWorkingDirectory(path.join(__dirname, "../../node_modules/obs-studio-node").replace("app.asar", "app.asar.unpacked")); 36 | // OBS Studio configs and logs 37 | const obsDataPath = path.join(__dirname, "../../osn-data").replace("app.asar", "app.asar.unpacked"); 38 | // init api with locale, data path and version 39 | const initResult = osn.NodeObs.OBS_API_initAPI("en-US", obsDataPath, "1.0.0"); 40 | 41 | if (initResult !== 0) { 42 | const errorReasons = { 43 | "-2": "DirectX could not be found on your system. Please install the latest version of DirectX for your machine here and try again.", 44 | "-5": "Failed to initialize OBS. Your video drivers may be out of date, or Streamlabs OBS may not be supported on your system.", 45 | }; 46 | 47 | const errorMessage = errorReasons[initResult.toString()] || `An unknown error #${initResult} was encountered while initializing OBS.`; 48 | 49 | console.error("OBS init failure", errorMessage); 50 | 51 | // see below for this function 52 | shutdown(); 53 | 54 | throw Error(errorMessage); 55 | } 56 | 57 | // obs-studio-node sends output signals 58 | // Using them, we can determine, if a function was 59 | // executed correctly. For example, when we tell 60 | // osn to stop recording, we don't know if that was 61 | // successfull until we receive a recording stopped 62 | // singal. The function below makes those signals 63 | // easier to use. 64 | const signals = new Subject() 65 | osn.NodeObs.OBS_service_connectOutputSignals((signalInfo) => { 66 | signals.next(signalInfo); 67 | }); 68 | function getNextSignalInfo() { 69 | return new Promise((resolve, reject) => { 70 | signals.pipe(first()).subscribe(signalInfo => resolve(signalInfo)); 71 | setTimeout(() => reject('Output signal timeout'), 30000); 72 | }); 73 | } 74 | ``` 75 | 76 | ### Shutting down 77 | ```JavaScript 78 | function shutdown() { 79 | try { 80 | osn.NodeObs.OBS_service_removeCallback(); 81 | osn.NodeObs.IPC.disconnect(); 82 | } catch (e) { 83 | throw Error(`Exception when shutting down OBS process${e}`); 84 | } 85 | } 86 | ``` 87 | -------------------------------------------------------------------------------- /docs/recording.md: -------------------------------------------------------------------------------- 1 | # Recording 2 | ## Start 3 | ```JavaScript 4 | 5 | function getNextSignalInfo() { 6 | return new Promise((resolve, reject) => { 7 | signals.pipe(first()).subscribe(signalInfo => resolve(signalInfo)); 8 | setTimeout(() => reject('Output signal timeout'), 30000); 9 | }); 10 | } 11 | let signalInfo; 12 | console.debug('Starting recording...'); 13 | osn.NodeObs.OBS_service_startRecording(); 14 | console.debug('Started?'); 15 | signalInfo = await getNextSignalInfo(); 16 | if (signalInfo.signal === 'Stop') { 17 | throw Error(signalInfo.error); 18 | } 19 | console.debug('Started signalInfo.type:', signalInfo.type, '(expected: "recording")'); 20 | console.debug('Started signalInfo.signal:', signalInfo.signal, '(expected: "start")'); 21 | console.debug('Started!'); 22 | ``` 23 | 24 | ## Stop 25 | ```JavaScript 26 | let signalInfo; 27 | console.debug('Stopping recording...'); 28 | osn.NodeObs.OBS_service_stopRecording(); 29 | console.debug('Stopped?'); 30 | signalInfo = await getNextSignalInfo(); 31 | console.debug('On stop signalInfo.type:', signalInfo.type, '(expected: "recording")'); 32 | console.debug('On stop signalInfo.signal:', signalInfo.signal, '(expected: "stopping")'); 33 | signalInfo = await getNextSignalInfo(); 34 | console.debug('After stop signalInfo.type:', signalInfo.type, '(expected: "recording")'); 35 | console.debug('After stop signalInfo.signal:', signalInfo.signal, '(expected: "stop")'); 36 | console.debug('Stopped!'); 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/scenes-and-sources.md: -------------------------------------------------------------------------------- 1 | # Scenes and Sources 2 | ## Creating 3 | You can create a scene like so: 4 | ```JavaScript 5 | const scene = osn.SceneFactory.create("myScene"); 6 | ``` 7 | Then, you can create a source and add it: 8 | ```JavaScript 9 | const source = osn.InputFactory.create("image_source", "logo", { file: path.join(__dirname, "../assets/icons/favicon.png") }); 10 | const sceneItem = scene.add(source); 11 | ``` 12 | 13 | The `osn.InputFactory.create()` method needs the parameters `id`, `name` and optionally `settings`. The `id` could for example be `image_source`, `browser_source`, `ffmpeg_source` or `text_gdiplus`. In the settings object you can specify `is_local_file: true` and `local_file: "myPathToTheFile"`. For videos, you can say `looping: true`. The settings for `text_gdiplus` look different: `read_from_file: true, file: "myPathToTheFile"`. 14 | 15 | ## Audio for `ffmpeg_sources` 16 | If you have a `ffmpeg_source`, you can set the `monitoringType` to one of the following values: 17 | ```TypeScript 18 | // to not output any sound 19 | s.monitoringType = osn.EMonitoringType.None; // is the same as s.monitoringType = 0; 20 | // to hear it but not have it on the recording or stream 21 | s.monitoringType = osn.EMonitoringType.MonitoringOnly; // is the same as s.monitoringType = 1; 22 | // to hear it and record or stream it 23 | s.monitoringType = osn.EMonitoringType.MonitoringAndOutput; // is the same as s.monitoringType = 2; 24 | ``` 25 | 26 | ## Finding 27 | If you know the name of any object (for example a scene or a source), you can get it with the `[factory].fromName(name)`. For example: 28 | ```JavaScript 29 | const myScene = osn.SceneFactory.fromName("myScene"); 30 | ``` 31 | 32 | ## Working with scene items 33 | You can modify the properties of the scene item to for example move or scale the source: 34 | ```JavaScript 35 | sceneItem.position = { x: 50, y: 50 }; 36 | sceneItem.scale = { x: 0.5, y: 0.7 }; 37 | ``` 38 | 39 | ## Setting the scene as an output 40 | ```JavaScript 41 | osn.Global.setOutputSource(0, scene); 42 | ``` -------------------------------------------------------------------------------- /docs/transitions.md: -------------------------------------------------------------------------------- 1 | # Transitions 2 | To transition between scenes, you first need to create a transition and set it as output. 3 | ```JavaScript 4 | const transition = osn.TransitionFactory.create(transitionType, "myTransition", {}); 5 | transition.set(scene); 6 | osn.Global.setOutputSource(0, transition); 7 | ``` 8 | You can set the scene using the `transition.set(scene)` method and transition to another scene like this: 9 | ```JavaScript 10 | transition.start(300, scene); 11 | ``` 12 | The `start` method needs the duration of the transition and the scene object to transition to. 13 | --------------------------------------------------------------------------------