Compare commits

..

221 Commits
4.0.0 ... 4.1.0

Author SHA1 Message Date
e3ad148c15 Request types: get/set/save streaming settings (PR #100)
* Adding support for changing streaming server settings

* Updates after initial code review for customized rtmp settings

* Updating PROTOCOL.MD documentment for streaming service settings

* Changes based on code review
2017-07-06 13:07:06 +02:00
acffacd67d Request/Response: refactor logging code 2017-07-04 21:20:34 +02:00
54a16f4d2f Events: refactor logging code 2017-07-04 21:18:42 +02:00
35cb506d6e Merge pull request #98 from haganbmj/request-logging
General: Toggle to log incoming requests
2017-07-04 20:20:33 +02:00
7675a1ee58 General: Toggle to log outgoing response and events 2017-07-04 14:17:51 -04:00
863f5e28b3 Merge pull request #99 from Rosuav/patch-1
Fix formatting (trivial)
2017-07-03 15:54:19 +02:00
1a6f6096e4 Fix formatting (trivial) 2017-07-03 23:39:42 +10:00
b57982b6cb General: Toggle to log incoming requests 2017-06-29 22:11:51 -05:00
4132356141 Protocol: Add Get and Set Browser Source Properties (#95)
* Protocol: Add Get and Set Browser Source Properties

* address white space - I don't see the indentation issue at my end for protocol.md but I'll try
2017-06-22 18:51:19 +02:00
e560e95310 Update README.md 2017-06-22 17:11:57 +02:00
65f4ff6a30 Protocol: add request types to get/set Text GDI Plus sources (#94)
* Protocol: add request types to get/set Text GDI Plus sources

* address comments
2017-06-20 16:07:47 +02:00
ab38f33530 docs: Update README to add a Python library 2017-06-20 10:54:30 +02:00
1f04ee8252 CI: Fix wrong OS X deps paths 2017-06-20 09:41:07 +02:00
739bd6f696 CI: Fix wrong Qt paths 2017-06-08 12:25:44 +02:00
066145ab31 Server: replace Q_FOREACH with ranged for-loops 2017-06-08 11:18:50 +02:00
54d0f764d4 CI: Fix OS X non-working @rpath replace 2017-06-06 11:57:04 +02:00
1398689ebf CI: Fix OS X Qt 5.8 fetch 2017-06-06 11:09:53 +02:00
647625628d CI: Fix dependency versions 2017-06-06 10:35:36 +02:00
e17df69b80 Merge remote-tracking branch 'origin/master' 2017-06-06 09:50:12 +02:00
f001d18eea Protocol: add request types to get/set current recording folder 2017-06-06 09:50:02 +02:00
f49980350a General: code style refresh 2017-06-06 09:30:07 +02:00
d0a90ecea4 Update PROTOCOL.md 2017-06-06 00:54:32 +02:00
4506b46ba0 Docs: update Contributing Guidelines 2017-05-31 15:29:23 +02:00
a8c36d7366 Docs: add issue template 2017-05-31 15:24:58 +02:00
781eaec683 Docs: add Contributing Guidelines 2017-05-31 15:08:30 +02:00
bdee8f318a Update package-osx.sh 2017-05-30 22:29:06 +02:00
82dac4d208 CI: Create "latest" copy of OS X artifact 2017-05-30 22:06:23 +02:00
386e1f3b46 General: 80 columns refactor in C/C++ headers 2017-04-26 10:07:42 +02:00
a3cbbf3ea9 General: refactor to limit linesize to 80 columns 2017-04-26 10:02:02 +02:00
e647debcfb Handler: fix possible duplicate data release 2017-04-25 08:53:24 +02:00
fba9bd2b76 Auth: refactor CheckAuth and GenerateSecret to avoid hazardous operations 2017-04-24 22:45:08 +02:00
56217e0176 Revert "CMake: work towards more auto-detect on Linux"
This reverts commit 77c5801c4c.
2017-04-24 13:33:50 +02:00
77c5801c4c CMake: work towards more auto-detect on Linux 2017-04-24 13:14:51 +02:00
7db879cca9 Protocol spec: Specify plugin version number 2017-04-24 12:38:53 +02:00
8d0cb2e875 Auth: free string after use 2017-04-24 11:01:11 +02:00
dc7b386295 Create README.md 2017-04-24 10:20:46 +02:00
03c8c6385c Bugfix: wrong behaviour in TransitionToProgram with a specif. transition 2017-04-23 23:28:06 +02:00
841de2f752 Systray: notify disconnects 2017-04-23 19:12:53 +02:00
9de6e229d9 Preliminary work on Recording folder get/set 2017-04-23 18:54:24 +02:00
8dfe471ef2 Server: small refactor 2017-04-23 15:41:39 +02:00
da05f315be Systray: Localizable notification 2017-04-23 15:30:09 +02:00
d014a7ab25 Requests: Start/Stop Streaming/Recording request now have errors 2017-04-23 15:22:10 +02:00
d0118c63c0 Bugfix: PreviewSceneChanged not emitted when swapping scenes 2017-04-23 12:28:51 +02:00
9ddfad99ea Bugfix: calling SetPreviewScene with an unknown source doesn't errors 2017-04-23 12:09:58 +02:00
82d74fcb2f CI: Build plugin with latest known stable release of OBS 2017-04-21 21:20:17 +02:00
0f4f029a76 Tray icon: remove debug message 2017-04-21 17:34:33 +02:00
2c581e9998 Merge remote-tracking branch 'origin/master' 2017-04-21 16:19:59 +02:00
f2028c506a UI: System tray notification on new connections 2017-04-21 16:16:11 +02:00
200db77140 UI: System tray notification on new connections 2017-04-21 15:59:32 +02:00
007604cc21 Readme: changed G-monitor's username 2017-04-21 14:21:07 +02:00
89486e9172 Readme: fix layout again 2017-04-21 14:17:55 +02:00
a60ca96fd1 Readme: update layout 2017-04-21 14:06:14 +02:00
0ade2c869d Readme: fix layout 2017-04-21 14:03:16 +02:00
9986382850 Supporters: add MediaUnit 2017-04-21 14:02:22 +02:00
89b9165c25 Protocol spec: update ToC 2017-04-21 13:42:50 +02:00
5d290165a2 Add request type GetSpecialSources 2017-04-21 13:39:28 +02:00
d267171cc7 Merge remote-tracking branch 'origin/master' 2017-04-21 11:14:56 +02:00
cebe325e81 New request type GetMute + request handler refactor 2017-04-21 11:14:47 +02:00
4e6178881b Update README.md 2017-04-21 08:10:43 +02:00
6bca8194cb Update README.md 2017-04-21 00:28:51 +02:00
a4885f332d README: Add sponsor mention for Support Class 2017-04-21 00:28:20 +02:00
cf97fb2051 Misc: add Support Class logo for sponsor mention 2017-04-20 23:38:43 +02:00
20a8853854 Protocol spec: better readability 2017-04-20 23:26:41 +02:00
dd487a5055 Version bump 2017-04-20 22:58:52 +02:00
72ca07f571 Merge branch 'preview-api' 2017-04-20 22:27:46 +02:00
c7305889c3 Events: add scene description to SwitchScenes and PreviewSceneChanged 2017-04-20 19:41:58 +02:00
bc24497760 Merge branch 'master' of github.com:Palakis/obs-websocket 2017-04-20 17:22:19 +02:00
200e65c730 OS X: Fix locales packaging 2017-04-20 17:22:01 +02:00
b32d8ef1e7 Merge remote-tracking branch 'origin/master' 2017-04-20 17:09:22 +02:00
8cfc613a3d Protocol spec: add missing scene visibility field in spec 2017-04-20 17:09:13 +02:00
5fbc2cbac7 OS X: fix linked libraries paths 2017-04-20 16:44:00 +02:00
9d7a32aa1f OS X: Fixed server threading issue 2017-04-20 16:24:41 +02:00
bcc9ef82d1 Oops, forgot to uncomment something 2017-04-20 16:16:36 +02:00
d6b28191e8 Fix another linker issue in OS X 2017-04-20 15:58:21 +02:00
acd8a9ea69 Travis: don't include unwanted artifacts 2017-04-20 14:35:48 +02:00
d4b8a8ff9e OS X: Fix linker issue 2017-04-20 14:25:56 +02:00
a698f7bdf5 Merge branch 'master' of github.com:Palakis/obs-websocket 2017-04-20 14:00:35 +02:00
16f07ff0c3 Travis OS X Packaging: include QtWebSockets in pkg 2017-04-20 13:58:31 +02:00
ed4526751e Better param checks for Studio Mode request types 2017-04-20 10:06:43 +02:00
e241518f8d Add StudioModeSwitched event 2017-04-20 09:53:31 +02:00
a6677edbf5 Add ability to specify a transition when calling TransitionToProgram 2017-04-19 18:53:09 +02:00
5748e163f8 Added GetPreviewScene and modified GetStudioModeStatus 2017-04-19 15:21:21 +02:00
85fa6b60e2 Update PROTOCOL.md 2017-04-19 15:00:47 +02:00
f0bb941c47 New request type : TransitionToProgram 2017-04-19 14:36:53 +02:00
b7df1e8596 Add SetPreviewScene request type and PreviewSceneChanged event 2017-04-19 13:43:58 +02:00
ff8eda3682 Fixed detection and control behaviour for some functions used by Preview API 2017-04-19 11:50:49 +02:00
8c4bd91c78 New request types to get and set the status of Studio Mode 2017-04-19 11:34:37 +02:00
07c868edcd Preliminary work on the Preview request types 2017-04-19 11:21:10 +02:00
69061869a7 Update README.md 2017-04-17 02:17:45 +02:00
98b2ac9bdc Update BUILDING.md 2017-04-17 02:14:55 +02:00
ef2bbff4e5 Travis: add support for tag package naming 2017-04-17 01:32:22 +02:00
7bb8e56072 Travis: fix OS X Packages project path 2017-04-17 00:56:29 +02:00
df0926f6fd Travis: OS X deploy 2017-04-17 00:52:07 +02:00
75572279a9 Travis: OS X packaging 2017-04-17 00:40:50 +02:00
328c6a0f7c Travis: woops, wrong section 2017-04-16 05:12:00 +02:00
ecd5062975 Travis: allow all branches for Linux deploy 2017-04-16 05:03:44 +02:00
c01ae5610d Travis: pass tag env var to docker container 2017-04-16 04:55:03 +02:00
564e7f31c3 Travis: better idea 2017-04-16 04:42:25 +02:00
efa61952b3 Travis: better file glob 2017-04-16 04:35:08 +02:00
ae27a26ebe Travis: fix checkinstall issue with version numbers 2017-04-16 04:19:22 +02:00
d81076f720 Fix Travis deploy behaviour 2017-04-16 03:54:19 +02:00
528f16c5e1 Travis: different approach for Linux builds upload 2017-04-16 03:43:25 +02:00
13ac8bfa90 Travis: testing the contents of the folder but outside of the container 2017-04-16 03:11:53 +02:00
935c58b17b Travis: trying packaging in another dir for upload 2017-04-16 01:32:32 +02:00
31e133bf06 Travis: gosh 2017-04-16 01:22:26 +02:00
eb7fb6694c Travis: okay, trying another strategy to get rid of build failures 2017-04-16 00:58:46 +02:00
a298577da1 Travis: wrong path for artifacts 2017-04-16 00:43:20 +02:00
8cd6d43ec4 Travis: wth happens with checkinstall builds 2017-04-16 00:27:02 +02:00
b8fd143cc1 Travis Linux: fix possible permissions issue with the built package 2017-04-15 23:15:04 +02:00
045ae058a3 Travis: Linux upload still not working 2017-04-15 22:56:38 +02:00
52c9816db2 Travis: trying again 2017-04-15 22:42:58 +02:00
32440580f6 Travis: dynamically find artifacts to upload 2017-04-15 22:30:41 +02:00
906d986f4b Travis: trying to make artifact upload working 2017-04-15 22:20:03 +02:00
0198651ca0 Travis: trying to resolve the issue with failing Linux packaging 2017-04-15 22:09:24 +02:00
d911c40897 Travis: fix file permissions (again) 2017-04-15 21:58:58 +02:00
ca8a117335 Travis: renamed some scripts 2017-04-15 21:49:41 +02:00
f75e2f0ada Travis: push build artifacts to S3 2017-04-15 20:11:45 +02:00
ea12b62235 Fix typo in Protocol spec 2017-04-15 19:27:37 +02:00
0af4af7e50 Merge remote-tracking branch 'origin/master' 2017-04-15 17:15:28 +02:00
f017491f44 Travis: fix Debian package version 2017-04-15 17:08:34 +02:00
ed433d3312 Travis: fix script permission 2017-04-15 16:45:21 +02:00
9ba2e83857 Fixed typos in CMakeLists.txt 2017-04-15 15:52:15 +02:00
af0ec74704 Travis: Linux packaging (again) 2017-04-15 15:40:34 +02:00
251402e844 Merge remote-tracking branch 'origin/master' 2017-04-15 15:24:56 +02:00
1a5318ebe0 Travis: Linux packaging 2017-04-15 15:24:46 +02:00
15aac3f436 Update README.md 2017-04-15 13:59:41 +02:00
5f373632e6 Merge pull request #67 from inpothet/master
Dutch Locale
2017-04-15 13:58:19 +02:00
0d518bf8df Dutch Locale 2017-04-15 13:45:44 +02:00
16b735c3a4 Travis: Some preliminary work on OS X packaging 2017-04-15 13:28:18 +02:00
1239f094fd Trying something 2017-04-15 13:00:16 +02:00
6986fa51eb Travis: keep only the Docker method for Linux builds
This avoids hacks and allows us to use a recent Ubuntu release even of Travis' Ubuntu infrastructure
2017-04-15 01:26:27 +02:00
f4bc88bf73 Travis: remove possibly useless line 2017-04-15 01:24:31 +02:00
ad915536a3 Travis: Removed debug ls command 2017-04-15 01:21:23 +02:00
f48b31664e Trying with absolute paths 2017-04-15 01:08:48 +02:00
d6c0eb1998 Trying something else 2017-04-15 00:50:31 +02:00
d3dcfba463 Trying something 2017-04-15 00:42:47 +02:00
3619dcd777 Merge remote-tracking branch 'origin/master' 2017-04-14 23:41:59 +02:00
86c002e318 Okay, rolling back some changes 2017-04-14 23:41:25 +02:00
42a807ce9c Okay, rolling back some changes 2017-04-14 23:40:16 +02:00
9bfff9539e Meh, not working as expected 2017-04-14 23:27:39 +02:00
edf4d919fe Encore a fix for Travis OS X 2017-04-14 23:12:36 +02:00
b0fb3d8c61 Fix for Travis OS X 2017-04-14 22:47:37 +02:00
5143090142 Work on OS X Travis builds 2017-04-14 22:38:57 +02:00
bc1555ca5b Update before-script-osx.sh 2017-04-14 22:19:48 +02:00
146a392f1a Merge remote-tracking branch 'asquelt/master' 2017-04-14 22:11:24 +02:00
e2fc205a35 Update README.md 2017-04-14 21:25:41 +02:00
d4bdb216d2 verifying libobs location 2017-04-14 15:59:47 +02:00
bffc1d6a1f verifying libobs location 2017-04-14 15:51:11 +02:00
df0d890c11 Fixed typo 2017-04-14 15:46:10 +02:00
dceee4dede verifying libobs location 2017-04-14 15:39:14 +02:00
dedbbf12bb Implement #63 2017-04-14 15:36:05 +02:00
bdb53d07f5 upstream proposed fix for #59 2017-04-14 15:21:33 +02:00
547991df72 Try to do something for #59 2017-04-14 15:11:53 +02:00
fd17557a96 try to build osx version 2017-04-12 15:20:22 +02:00
a9e928a799 try to build osx version 2017-04-12 15:08:49 +02:00
84b3e086e6 try to build osx version 2017-04-12 14:38:12 +02:00
5e3c794aec try to build osx version 2017-04-11 14:16:10 +02:00
98acbb2b2b try to build osx version 2017-04-11 13:45:07 +02:00
318f0753b0 try to build osx version 2017-04-11 13:25:08 +02:00
8908adbdee try to build osx version 2017-04-11 13:07:44 +02:00
fad7a26550 try to build osx version 2017-04-11 12:49:48 +02:00
38005fc0ef Merge pull request #60 from asquelt/locale
Polish locale
2017-04-10 12:59:43 +02:00
e3d3181ad6 try to build osx version 2017-04-06 17:43:55 +02:00
83d069e4d9 try to build osx version 2017-04-06 17:42:51 +02:00
f2db5a8229 try to build osx version 2017-04-06 17:07:14 +02:00
6ebb777256 Polish locale 2017-04-06 16:27:57 +02:00
db50c531d9 try docker 2017-04-06 13:26:40 +02:00
218f7af2e3 try docker 2017-04-06 13:19:33 +02:00
a19d2956c1 try docker 2017-04-06 13:07:42 +02:00
7a6b5b965d try docker 2017-04-06 13:01:09 +02:00
8a871aaddd try docker 2017-04-06 12:56:59 +02:00
bec0a0df10 try docker 2017-04-06 12:52:57 +02:00
1068a2dfbe try docker 2017-04-06 12:47:57 +02:00
b62be5d584 try docker 2017-04-06 12:44:57 +02:00
9686019693 try docker 2017-04-06 12:38:59 +02:00
32a066b4d4 try docker 2017-04-06 12:31:49 +02:00
d5a415e01e try to build osx version 2017-04-06 11:46:39 +02:00
33f83b8486 try to build osx version 2017-04-06 11:34:10 +02:00
adedf8d34f try to build osx version 2017-04-06 11:23:30 +02:00
9243071f03 try to build osx version 2017-04-06 11:12:05 +02:00
36050850d4 try to build osx version 2017-04-06 11:01:03 +02:00
c6d5cc555a try to build osx version 2017-04-05 13:33:36 +02:00
b271aa2aaa try to build osx version 2017-04-05 13:19:57 +02:00
d24961b3b9 try to build osx version 2017-04-05 12:59:07 +02:00
e52543efe7 try to build osx version 2017-04-05 09:34:15 +02:00
0b0e27ad3b try to build osx version 2017-04-05 00:41:35 +02:00
9f460f6c99 try to build osx version 2017-04-05 00:36:07 +02:00
c4529bb9a3 try to build osx version 2017-04-05 00:01:53 +02:00
5a069d2ffc try to build osx version 2017-04-04 23:24:07 +02:00
4fc78e455a Merge branch 'master' of github.com:asquelt/obs-websocket 2017-04-04 22:57:22 +02:00
75e2198315 try to build osx version 2017-04-04 22:57:16 +02:00
0d4bb4ed2d try to build osx version 2017-04-04 22:53:38 +02:00
577738ad0a try to build osx version 2017-04-04 21:43:19 +02:00
a8dfdb03fb try to build osx version 2017-04-04 21:08:31 +02:00
aebd470d49 try to build osx version 2017-04-04 20:28:39 +02:00
ce3dfd9678 try to build osx version 2017-04-04 20:20:31 +02:00
b82801b145 try to build osx version 2017-04-04 19:32:07 +02:00
a6ab35f1fb Revert "try to build osx version and xenial docker version"
This reverts commit a265cea4fb.
2017-04-04 19:03:52 +02:00
a265cea4fb try to build osx version and xenial docker version 2017-04-04 18:56:05 +02:00
166760651e try to build osx version and xenial docker version 2017-04-04 18:53:02 +02:00
41a9191223 try to build osx version and xenial docker version 2017-04-04 18:47:21 +02:00
1465e7760e try to build osx version 2017-04-04 18:20:35 +02:00
1a043f1dc0 try travis build with libqt5websockets5-dev binary 2017-04-04 17:34:54 +02:00
70c5b00c90 try travis build with libqt5websockets5-dev binary 2017-04-04 17:22:32 +02:00
bb5177dc79 debug 2017-04-04 17:06:40 +02:00
f60a9a632b try travis build with xenial (16.04) 2017-04-04 16:29:53 +02:00
024c47132b try travis build with qt532-trusty installed 2017-04-04 16:21:24 +02:00
73ce9cb4cc try travis build with qtbase5-private-dev installed 2017-04-04 15:51:44 +02:00
f45d439094 try travis build with qtcreator installed 2017-04-04 14:10:32 +02:00
2d5749a78c New request type : GetTransitionDuration 2017-03-19 23:57:01 +01:00
f078a10028 New request types for streaming and recording control
`StartStreaming`, `StopStreaming`, `StartRecording` and `StopRecording`
2017-03-19 23:50:23 +01:00
701f88e532 Added "SetSceneItemCrop" request type 2017-03-19 23:38:26 +01:00
1524a1997d Update README.md 2017-03-15 20:35:57 +01:00
b16f812f91 Update README.md
Mentions to contributors and added a link to my C# library
2017-03-15 19:55:38 +01:00
cde307d644 Update README.md 2017-03-15 16:29:17 +01:00
c47f6d093c Create BUILDING.md 2017-03-15 16:28:09 +01:00
f5336938c9 Update README.md 2017-03-12 11:57:41 +01:00
4985548edf Update pt-BR.ini 2017-03-10 08:33:39 +01:00
8bc4841e84 Merge pull request #53 from laris151/laris151-portuguese
Add Portuguese Translate
2017-03-10 08:32:46 +01:00
634f9833a1 Add Portuguese Translate 2017-03-09 22:28:04 -03:00
80052e62ee Update README.md 2017-03-05 17:10:18 +01:00
38 changed files with 3786 additions and 709 deletions

22
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,22 @@
## Contributing to obs-websocket
### Translating obs-websocket to your language
Localization happens on Crowdin: https://crowdin.com/project/obs-websocket
### Writing code for obs-websocket
#### Coding Guidelines
- Function and variable names: snake_case for C names, CamelCase for C++ names
- Tabs are 8 columns wide
- 80 columns max.
#### Commit Guidelines
- Commits follow the 50/72 standard:
- 50 characters max for the title
- One empty line after the title
- Description wrapped to 72 columns max per line.
- Commit titles:
- Use present tense
- Prefix the title with a "scope" name
- e.g: "CI: fix wrong behaviour when packaging for OS X"
- Typical scopes: CI, General, Request, Event, Server
- Look at existing commits for more examples

18
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,18 @@
#### Issue type
- [ ] Bug
- [ ] Feature request
- [ ] Other
#### Description
*Replace this with a description of the bug encountered or feature requested.*
#### Steps to reproduce
*If it's a bug, please describe the steps to reproduce it. Otherwise, remove this section.*
#### Technical information
- **Operating System** :
- **OBS Studio version** :
#### Development Environment
*If you're trying to compile obs-websocket, please describe your compiler type and version (e.g: GCC 4.7, VC2013, ...), and the CMake settings used.
Remove this section if it doesn't apply to your case.*

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
*~
.DS_Store
/build/
/build32/
/build64/

View File

@ -1,11 +1,55 @@
language: cpp
env:
global:
# AWS key ID
- secure: pAiNUGVbjP12BfnWPk0FFTkbnk4Tocvv88XiT3rzRqkQaD7/iyEogLBfHM4nOEgFiIMHbC41aE83w5JgRNPwn6mTgoQBOglzqq1tGuXfqPyV2VStk8beji1evubGoVjjPaoPTFyIdQc5GGxdHyogI/ed9Hb3ccyykYvjyolj9XoCiW42QHx60AHGwl+So+dEa8xydj9SLRPlZ/AitmI/cPVN3YotA7s37BLFiab54enxk7T4rwpR1nU0HVfoCpn5F4wZYxRq+LlSVFzC8vVE9cpDSLS5kjrZIZaT18tYG1/untCj+wqMIZbghaJXLtPSRW2YPHcJTz8q1YSXnJ19+0uiAIMAqaVv0kD5BAM97byYDBW+b9H6SYFkb/Pw/qcK9amMzMBjDPFpYFkl9Q2kzhsNs3HsZf/flSZjtrkQJiP3SOi/KvKzVK9X4Wym6hYZWHgmMTTYFrvr6BYnf2GkpfKNjm1d2kc0NNrq4d5H4NOEQB8MP+QH+o+BPeM6d9dthrUc1Pw+BXzOAr85CN4qtpPGoAl/Dbfgd6eu/88E2LpUufW2VFAOPWjykSOqzSN3orh7AaWuE34VFEnQ+2y3uIE8AKoyXzJv6zYkyNnNewKZeGe2kKYNwLn5UxQA9JEj7a+tvVevk4xBSkkjFAvjSG2z8/F1FXNbEfoLX1Hz/bU=
# AWS key secret
- secure: bGwljoP3E1OVBXLXox0O6p8kwQXLcNQ8YDKVa4H8u9Y+Ic7uqE4iV3rYS3ynNWSBMVRWY3ZbyClnhrCNwRhBAlcd8qWSJdpjVzs6HdQyzhuKa1P3V4FJPb7upGP/5R/DECGwex8Mun9dmXpYDak75LxfKIJUidPis5VDCYqul7k/xVVCou6Ctjpj7vQhWXDj2G/py+mdB8DERhymnQCtyK1Ziu8c4QlFKByZmnD72GFm/h3JPI1Pq1V2mz3x6x6GaYjb9Rdbd0UNwqjGQX4q2M/c3GEJa6B2JBCoTncawNZBNnPUF9qtv+zh0TNaNHMRWX13AJ/qYB+nVDub0C9b/6Mc48mt0Tv4ze15MproVrylZdV6qHYEG8yGPBqpTVbRP6gv6Y2TXIHWoTzqA+F/Gv2IDChyHXsld/MQQS2MSo5iaYktIrZKtX8Z0qAmTzPwIVBromaSI3vrE7UH0fRSQ6fAM8+Tn+MRthOBdqu23kS1dnG+X2CPbUhBfsJp0OSwVQD5jQtA51/sREVeGFiJvzQIkvwQDjb5MYilsRnwmoBXemkLmqaviXVY4rz1o5AIvz2pgZS2YggK1xHZCuI5tSjcNEkb77VwZTfsqrdDo9EJh6VgfdnGlHQhR2/A5hUJ4ANpJ/LgZlgfVp71Xg2GWQW6M4Znc5uj6A6xLBkO6FA=
matrix:
include:
- os: linux
dist: trusty
sudo: required
before_install: "./CI/install-dependencies-linux.sh"
before_script: "./CI/before-script-linux.sh"
- os: linux
dist: trusty
sudo: required
services:
- docker
before_install:
- docker run -d --name xenial -v $(dirname $(pwd)):/root -v /home/travis/package:/package
-e TRAVIS_BRANCH="$TRAVIS_BRANCH" -e TRAVIS_TAG="$TRAVIS_TAG" -w /root nimmis/ubuntu:16.04
- docker exec -it xenial /root/obs-websocket/CI/install-dependencies-xenial.sh
script:
- docker exec -it xenial /root/obs-websocket/CI/build-xenial.sh
after_success:
- docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh
script: cd ./build && make -j4 && cd -
- os: osx
osx_image: xcode8.3
before_install: "./CI/install-dependencies-osx.sh"
script: "./CI/build-osx.sh"
after_success: "./CI/package-osx.sh"
deploy:
- provider: s3
region: eu-central-1
bucket: obs-websocket-linux-builds
access_key_id: "$AWS_ID"
secret_access_key: "$AWS_SECRET"
local_dir: /home/travis/package
skip_cleanup: true
acl: public_read
on:
repo: Palakis/obs-websocket
condition: "$TRAVIS_OS_NAME = linux"
all_branches: true
- provider: s3
region: eu-central-1
bucket: obs-websocket-osx-builds
access_key_id: "$AWS_ID"
secret_access_key: "$AWS_SECRET"
local_dir: release
skip_cleanup: true
acl: public_read
on:
repo: Palakis/obs-websocket
condition: "$TRAVIS_OS_NAME = osx"
all_branches: true

29
BUILDING.md Normal file
View File

@ -0,0 +1,29 @@
# Compiling obs-websocket
## Prerequisites
You'll need [QT 5.7.0](https://download.qt.io/official_releases/qt/5.7/5.7.0/), CMake, and a working development environment for OBS Studio installed on your computer.
## Windows
In cmake-gui, you'll have to set the following variables :
- **QTDIR** (path) : location of the Qt environment suited for your compiler and architecture
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source code of OBS Studio
- **LIBOBS_LIB** (filepath) : location of the obs.lib file
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
## Linux
On Debian/Ubuntu :
```
sudo apt-get install libqt5websockets5-dev
git clone --recursive https://github.com/Palakis/obs-websocket.git
cd obs-websocket
mkdir build && cd build
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4
sudo make install
```
## OS X
*To do*
## Automated Builds
- Windows : [![Automated Build status for Windows](https://ci.appveyor.com/api/projects/status/github/Palakis/obs-websocket)](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
- Linux & OS X : [![Automated Build status for Linux & OS X](https://travis-ci.org/Palakis/obs-websocket.svg?branch=master)](https://travis-ci.org/Palakis/obs-websocket)

11
CI/build-osx.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
set -ex
mkdir build && cd build
cmake .. \
-DQTDIR=/usr/local/opt/qt5 \
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
-DCMAKE_INSTALL_PREFIX=/usr \
&& make -j4

View File

@ -1,6 +1,8 @@
#!/bin/sh
set -ex
cd /root/obs-websocket
mkdir build && cd build
cmake -DLIBOBS_INCLUDE_DIR="../../obs-studio/libobs" -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4
make -j4

27
CI/install-dependencies-osx.sh Executable file
View File

@ -0,0 +1,27 @@
#!/bin/sh
set -ex
# OBS Studio deps
brew update
brew install ffmpeg
brew install libav
# qtwebsockets deps
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/fdb7c6e960e830b3bf630850c0002c5df9f68ed8/Formula/qt5.rb
# Build obs-studio
cd ..
git clone --recursive https://github.com/jp9000/obs-studio
cd obs-studio
git checkout 19.0.2
mkdir build && cd build
cmake .. \
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake \
&& make -j4
sudo make install
# Packages app
cd ..
curl -L -O https://www.slepin.fr/obs-websocket/ci/Packages.pkg -f --retry 5 -C -
sudo installer -pkg ./Packages.pkg -target /

View File

@ -2,18 +2,22 @@
set -ex
# OBS Studio deps
sudo add-apt-repository ppa:kirillshkrogalev/ffmpeg-next -y
sudo apt-get -qq update
sudo apt-get install -y \
apt-get -qq update
apt-get install -y \
libc-dev-bin libc6-dev \
git \
build-essential
apt-get install -y \
build-essential \
checkinstall \
cmake \
libasound2-dev \
libavcodec-ffmpeg-dev \
libavdevice-ffmpeg-dev \
libavfilter-ffmpeg-dev \
libavformat-ffmpeg-dev \
libavutil-ffmpeg-dev \
libavcodec-dev \
libavdevice-dev \
libavfilter-dev \
libavformat-dev \
libavutil-dev \
libcurl4-openssl-dev \
libfontconfig-dev \
libfreetype6-dev \
@ -23,8 +27,8 @@ sudo apt-get install -y \
libpulse-dev \
libqt5x11extras5-dev \
libspeexdsp-dev \
libswresample-ffmpeg-dev \
libswscale-ffmpeg-dev \
libswresample-dev \
libswscale-dev \
libudev-dev \
libv4l-dev \
libvlc-dev \
@ -37,25 +41,17 @@ sudo apt-get install -y \
pkg-config \
qtbase5-dev
# qtwebsockets deps
sudo apt-get install -y qt5-qmake
# obs-websocket deps
cd ..
git clone https://github.com/qt/qtwebsockets/ ./qtwebsockets
cd qtwebsockets
git checkout v5.3.0
qmake
make -j4
sudo make install
apt-get install -y libqt5websockets5-dev
# Build obs-studio
cd ..
cd /root
git clone https://github.com/jp9000/obs-studio ./obs-studio
cd obs-studio
git checkout 19.0.2
mkdir build && cd build
cmake -DUNIX_STRUCTURE=1 -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4
sudo make install
make install
sudo ldconfig
ldconfig

View File

@ -0,0 +1,843 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PROJECT</key>
<dict>
<key>PACKAGE_FILES</key>
<dict>
<key>DEFAULT_INSTALL_LOCATION</key>
<string>/</string>
<key>HIERARCHY</key>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../build/QtNetwork</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>292</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../build/QtWebSockets</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>292</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>bin</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Resources</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Contents</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>OBS.app</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Applications</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../build/obs-websocket.so</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>bin</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../data</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>obs-websocket</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>plugins</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>obs-studio</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Application Support</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Automator</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Documentation</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Extensions</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Filesystems</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Frameworks</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Input Methods</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Internet Plug-Ins</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>LaunchAgents</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>LaunchDaemons</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>PreferencePanes</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Preferences</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Printers</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>PrivilegedHelperTools</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>QuickLook</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>QuickTime</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Screen Savers</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Scripts</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Services</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Widgets</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Library</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Shared</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>1023</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Users</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>/</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<key>PAYLOAD_TYPE</key>
<integer>0</integer>
<key>VERSION</key>
<integer>4</integer>
</dict>
<key>PACKAGE_SCRIPTS</key>
<dict>
<key>RESOURCES</key>
<array/>
</dict>
<key>PACKAGE_SETTINGS</key>
<dict>
<key>AUTHENTICATION</key>
<integer>1</integer>
<key>CONCLUSION_ACTION</key>
<integer>0</integer>
<key>IDENTIFIER</key>
<string>fr.palakis.obswebsocket</string>
<key>OVERWRITE_PERMISSIONS</key>
<false/>
<key>VERSION</key>
<string>4.0.0</string>
</dict>
<key>PROJECT_COMMENTS</key>
<dict>
<key>NOTES</key>
<data>
PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M
IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv
c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l
cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7
IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250
ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp
dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u
dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD
b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuMTMiPgo8c3R5bGUg
dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5
Pgo8L2JvZHk+CjwvaHRtbD4K
</data>
</dict>
<key>PROJECT_SETTINGS</key>
<dict>
<key>BUILD_PATH</key>
<dict>
<key>PATH</key>
<string>../../release</string>
<key>PATH_TYPE</key>
<integer>1</integer>
</dict>
<key>EXCLUDED_FILES</key>
<array>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.DS_Store</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove .DS_Store files</string>
<key>PROXY_TOOLTIP</key>
<string>Remove ".DS_Store" files created by the Finder.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.pbdevelopment</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove .pbdevelopment files</string>
<key>PROXY_TOOLTIP</key>
<string>Remove ".pbdevelopment" files created by ProjectBuilder or Xcode.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>CVS</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.cvsignore</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.cvspass</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.svn</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.git</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.gitignore</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove SCM metadata</string>
<key>PROXY_TOOLTIP</key>
<string>Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>classes.nib</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>designable.db</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>info.nib</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Optimize nib files</string>
<key>PROXY_TOOLTIP</key>
<string>Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>Resources Disabled</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove Resources Disabled folders</string>
<key>PROXY_TOOLTIP</key>
<string>Remove "Resources Disabled" folders.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>SEPARATOR</key>
<true/>
</dict>
</array>
<key>NAME</key>
<string>obs-websocket</string>
</dict>
</dict>
<key>TYPE</key>
<integer>1</integer>
<key>VERSION</key>
<integer>2</integer>
</dict>
</plist>

59
CI/package-osx.sh Executable file
View File

@ -0,0 +1,59 @@
#!/bin/sh
set -e
echo "-- Preparing package build"
export QT_PREFIX="/usr/local/opt/qt5"
export WS_LIB="$QT_PREFIX/lib/QtWebSockets.framework/QtWebSockets"
export NET_LIB="$QT_PREFIX/lib/QtNetwork.framework/QtNetwork"
export GIT_HASH=$(git rev-parse --short HEAD)
export VERSION="$GIT_HASH-$TRAVIS_BRANCH"
export LATEST_VERSION="$TRAVIS_BRANCH"
if [ -n "${TRAVIS_TAG}" ]; then
export VERSION="$TRAVIS_TAG"
export LATEST_VERSION="$TRAVIS_TAG"
fi
export FILENAME="obs-websocket-$VERSION.pkg"
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
echo "-- Copying Qt dependencies"
cp $WS_LIB ./build
cp $NET_LIB ./build
chmod +rw ./build/QtWebSockets ./build/QtNetwork
echo "-- Modifying QtNetwork"
# TODO : put a loop in there
install_name_tool \
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
./build/QtNetwork
echo "-- Modifying QtWebSockets"
install_name_tool \
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
./build/QtWebSockets
echo "-- Modifying obs-websocket.so"
install_name_tool \
-change "$QT_PREFIX/lib/QtWebSockets.framework/Versions/5/QtWebSockets" @rpath/QtWebSockets \
-change "$QT_PREFIX/lib/QtWidgets.framework/Versions/5/QtWidgets" @rpath/QtWidgets \
-change "$QT_PREFIX/lib/QtNetwork.framework/Versions/5/QtNetwork" @rpath/QtNetwork \
-change "$QT_PREFIX/lib/QtGui.framework/Versions/5/QtGui" @rpath/QtGui \
-change "$QT_PREFIX/lib/QtCore.framework/Versions/5/QtCore" @rpath/QtCore \
./build/obs-websocket.so
chmod -w ./build/QtWebSockets ./build/QtNetwork
echo "-- Actual package build"
packagesbuild ./CI/osx/obs-websocket.pkgproj
echo "-- Renaming obs-websocket.pkg to $FILENAME"
mv ./release/obs-websocket.pkg ./release/$FILENAME
cp ./release/$FILENAME ./release/$LATEST_FILENAME

24
CI/package-xenial.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
set -e
cd /root/obs-websocket
export GIT_HASH=$(git rev-parse --short HEAD)
export PKG_VERSION="1-$GIT_HASH-$TRAVIS_BRANCH-git"
if [ -n "${TRAVIS_TAG}" ]; then
export PKG_VERSION="$TRAVIS_TAG"
fi
cd /root/obs-websocket/build
PAGER=cat checkinstall -y --type=debian --fstrans=no --nodoc \
--backup=no --deldoc=yes --install=no \
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
--pkglicense="GPLv2.0" --maintainer="contact@slepin.fr" \
--requires="libqt5websockets5" --pkggroup="video" \
--pkgsource="https://github.com/Palakis/obs-websocket" \
--pakdir="/package"
chmod ao+r /package/*

View File

@ -7,6 +7,7 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
include(external/FindLibObs.cmake)
find_package(LibObs REQUIRED)
find_package(Qt5Core REQUIRED)
find_package(Qt5WebSockets REQUIRED)
@ -54,6 +55,7 @@ target_link_libraries(obs-websocket
Qt5::WebSockets
Qt5::Widgets
mbedcrypto)
# --- End of section ---
# --- Windows-specific build settings and tasks ---
@ -62,7 +64,7 @@ if(WIN32)
set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library")
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
endif()
target_link_libraries(obs-websocket
"${OBS_FRONTEND_LIB}")
@ -118,11 +120,20 @@ if(UNIX AND NOT APPLE)
target_link_libraries(obs-websocket
obs-frontend-api)
file(GLOB locale_files data/locale/*.ini)
install(TARGETS obs-websocket
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/obs-plugins)
install(FILES data/locale/en-US.ini data/locale/fr-FR.ini data/locale/de-DE.ini
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins")
install(FILES ${locale_files}
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
endif()
# --- End of section ---
# TODO : OS X build settings and tasks
# -- OS X specific build settings and tasks --
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default")
set_target_properties(obs-websocket PROPERTIES PREFIX "")
target_link_libraries(obs-websocket "${OBS_FRONTEND_LIB}")
endif()
# -- End of section --

View File

@ -20,12 +20,14 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <mbedtls/sha256.h>
#include <obs-frontend-api.h>
#include <util/config-file.h>
#include <string>
#include "Config.h"
#define SECTION_NAME "WebsocketAPI"
#define PARAM_ENABLE "ServerEnabled"
#define PARAM_PORT "ServerPort"
#define PARAM_DEBUG "DebugEnabled"
#define PARAM_AUTHREQUIRED "AuthRequired"
#define PARAM_SECRET "AuthSecret"
#define PARAM_SALT "AuthSalt"
@ -37,6 +39,8 @@ Config::Config()
// Default settings
ServerEnabled = true;
ServerPort = 4444;
DebugEnabled = false;
AuthRequired = false;
Secret = "";
@ -47,12 +51,20 @@ Config::Config()
config_t* obs_config = obs_frontend_get_global_config();
if (obs_config)
{
config_set_default_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
config_set_default_uint(obs_config, SECTION_NAME, PARAM_PORT, ServerPort);
config_set_default_bool(obs_config,
SECTION_NAME, PARAM_ENABLE, ServerEnabled);
config_set_default_uint(obs_config,
SECTION_NAME, PARAM_PORT, ServerPort);
config_set_default_bool(obs_config,
SECTION_NAME, PARAM_DEBUG, DebugEnabled);
config_set_default_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_default_string(obs_config, SECTION_NAME, PARAM_SECRET, Secret);
config_set_default_string(obs_config, SECTION_NAME, PARAM_SALT, Salt);
config_set_default_bool(obs_config,
SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_default_string(obs_config,
SECTION_NAME, PARAM_SECRET, Secret);
config_set_default_string(obs_config,
SECTION_NAME, PARAM_SALT, Salt);
}
mbedtls_entropy_init(&entropy);
@ -75,6 +87,8 @@ void Config::Load()
ServerEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_ENABLE);
ServerPort = config_get_uint(obs_config, SECTION_NAME, PARAM_PORT);
DebugEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_DEBUG);
AuthRequired = config_get_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED);
Secret = config_get_string(obs_config, SECTION_NAME, PARAM_SECRET);
@ -87,6 +101,8 @@ void Config::Save()
config_set_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
config_set_uint(obs_config, SECTION_NAME, PARAM_PORT, ServerPort);
config_set_bool(obs_config, SECTION_NAME, PARAM_DEBUG, DebugEnabled);
config_set_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_string(obs_config, SECTION_NAME, PARAM_SECRET, Secret);
@ -98,43 +114,42 @@ void Config::Save()
const char* Config::GenerateSalt()
{
// Generate 32 random chars
unsigned char *random_chars = (unsigned char *)bzalloc(32);
unsigned char* random_chars = (unsigned char*)bzalloc(32);
mbedtls_ctr_drbg_random(&rng, random_chars, 32);
// Convert the 32 random chars to a base64 string
unsigned char *salt = (unsigned char*)bzalloc(64);
char* salt = (char*)bzalloc(64);
size_t salt_bytes;
mbedtls_base64_encode(salt, 64, &salt_bytes, random_chars, 32);
salt[salt_bytes] = 0; // Null-terminate the string
mbedtls_base64_encode(
(unsigned char*)salt, 64, &salt_bytes,
random_chars, 32);
bfree(random_chars);
return (char *)salt;
return salt;
}
const char* Config::GenerateSecret(const char *password, const char *salt)
{
size_t passwordLength = strlen(password);
size_t saltLength = strlen(salt);
// Concatenate the password and the salt
unsigned char *passAndSalt = (unsigned char*)bzalloc(passwordLength + saltLength);
memcpy(passAndSalt, password, passwordLength);
memcpy(passAndSalt + passwordLength, salt, saltLength);
passAndSalt[passwordLength + saltLength] = 0; // Null-terminate the string
std::string passAndSalt = "";
passAndSalt += password;
passAndSalt += salt;
// Generate a SHA256 hash of the password
unsigned char *challengeHash = (unsigned char *)bzalloc(32);
mbedtls_sha256(passAndSalt, passwordLength + saltLength, challengeHash, 0);
unsigned char* challengeHash = (unsigned char*)bzalloc(32);
mbedtls_sha256(
(unsigned char*)passAndSalt.c_str(), passAndSalt.length(),
challengeHash, 0);
// Encode SHA256 hash to Base64
unsigned char *challenge = (unsigned char*)bzalloc(64);
char* challenge = (char*)bzalloc(64);
size_t challenge_bytes = 0;
mbedtls_base64_encode(challenge, 64, &challenge_bytes, challengeHash, 32);
challenge[64] = 0; // Null-terminate the string
mbedtls_base64_encode(
(unsigned char*)challenge, 64, &challenge_bytes,
challengeHash, 32);
bfree(passAndSalt);
bfree(challengeHash);
return (char*)challenge;
return challenge;
}
void Config::SetPassword(const char *password)
@ -148,32 +163,34 @@ void Config::SetPassword(const char *password)
bool Config::CheckAuth(const char *response)
{
size_t secretLength = strlen(this->Secret);
size_t sessChallengeLength = strlen(this->SessionChallenge);
// Concatenate auth secret with the challenge sent to the user
char *challengeAndResponse = (char*)bzalloc(secretLength + sessChallengeLength);
memcpy(challengeAndResponse, this->Secret, secretLength);
memcpy(challengeAndResponse + secretLength, this->SessionChallenge, sessChallengeLength);
challengeAndResponse[secretLength + sessChallengeLength] = 0; // Null-terminate the string
std::string challengeAndResponse = "";
challengeAndResponse += this->Secret;
challengeAndResponse += this->SessionChallenge;
// Generate a SHA256 hash of challengeAndResponse
unsigned char *hash = (unsigned char*)bzalloc(32);
mbedtls_sha256((unsigned char*)challengeAndResponse, secretLength + sessChallengeLength, hash, 0);
unsigned char* hash = (unsigned char*)bzalloc(32);
mbedtls_sha256(
(unsigned char*)challengeAndResponse.c_str(),
challengeAndResponse.length(),
hash, 0);
// Encode the SHA256 hash to Base64
unsigned char *expected_response = (unsigned char*)bzalloc(64);
char* expected_response = (char*)bzalloc(64);
size_t base64_size = 0;
mbedtls_base64_encode(expected_response, 64, &base64_size, hash, 32);
expected_response[64] = 0; // Null-terminate the string
mbedtls_base64_encode(
(unsigned char*)expected_response, 64, &base64_size,
hash, 32);
if (strcmp((char*)expected_response, response) == 0) {
bool authSuccess = false;
if (strcmp(expected_response, response) == 0) {
SessionChallenge = GenerateSalt();
return true;
}
else {
return false;
authSuccess = true;
}
bfree(hash);
bfree(expected_response);
return authSuccess;
}
Config* Config::Current()

View File

@ -33,10 +33,13 @@ class Config
void SetPassword(const char *password);
bool CheckAuth(const char *userChallenge);
const char* GenerateSalt();
static const char* GenerateSecret(const char *password, const char *salt);
static const char* GenerateSecret(
const char *password, const char *salt);
bool ServerEnabled;
uint64_t ServerPort;
bool DebugEnabled;
bool AuthRequired;
const char *Secret;

View File

@ -1,68 +1,148 @@
obs-websocket protocol reference
obs-websocket 4.1 protocol reference
================================
**This is the reference for the latest 4.1 developement build. [See here for obs-websocket 4.0.0!](https://github.com/Palakis/obs-websocket/blob/4.0.0/PROTOCOL.md)**
## General Introduction
Messages exchanged between the client and the server are JSON objects.
The protocol in general is based on the OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
### Table of contents
* [Authentication](#authentication)
* [Events](#events)
- [Description](#description)
- [Event Types](#event-types)
- ["SwitchScenes"](#switchscenes)
- ["ScenesChanged"](#sceneschanged)
- ["SourceOrderChanged"](#sourceorderchanged)
- ["SceneItemAdded"](#sceneitemadded)
- ["SceneItemRemoved"](#sceneitemremoved)
- ["SceneItemVisibilityChanged"](#sceneitemvisibilitychanged)
- ["SceneCollectionChanged"](#scenecollectionchanged)
- ["SceneCollectionListChanged"](#scenecollectionlistchanged)
- ["SwitchTransition"](#switchtransition)
- ["TransitionDurationChanged"](#transitiondurationchanged)
- ["TransitionListChanged"](#transitionlistchanged)
- ["TransitionBegin"](#transitionbegin)
- ["ProfileChanged"](#profilechanged)
- ["ProfileListChanged"](#profilelistchanged)
- ["StreamStarting"](#streamstarting)
- ["StreamStarted"](#streamstarted)
- ["StreamStopping"](#streamstopping)
- ["StreamStopped"](#streamstopped)
- ["RecordingStarting"](#recordingstarting)
- ["RecordingStarted"](#recordingstarted)
- ["RecordingStopping"](#recordingstopping)
- ["RecordingStopped"](#recordingstopped)
- ["StreamStatus"](#streamstatus)
- ["Exiting"](#exiting)
- **Scenes**
- ["SwitchScenes"](#switchscenes)
- ["ScenesChanged"](#sceneschanged)
- **Scene Items**
- ["SourceOrderChanged"](#sourceorderchanged)
- ["SceneItemAdded"](#sceneitemadded)
- ["SceneItemRemoved"](#sceneitemremoved)
- ["SceneItemVisibilityChanged"](#sceneitemvisibilitychanged)
- **Scene Collections**
- ["SceneCollectionChanged"](#scenecollectionchanged)
- ["SceneCollectionListChanged"](#scenecollectionlistchanged)
- **Transitions**
- ["SwitchTransition"](#switchtransition)
- ["TransitionDurationChanged"](#transitiondurationchanged)
- ["TransitionListChanged"](#transitionlistchanged)
- ["TransitionBegin"](#transitionbegin)
- **Studio Mode**
- ["PreviewSceneChanged"](#previewscenechanged)
- ["StudioModeSwitched"](#studiomodeswitched)
- **Profiles**
- ["ProfileChanged"](#profilechanged)
- ["ProfileListChanged"](#profilelistchanged)
- **Streaming**
- ["StreamStarting"](#streamstarting)
- ["StreamStarted"](#streamstarted)
- ["StreamStopping"](#streamstopping)
- ["StreamStopped"](#streamstopped)
- ["StreamStatus"](#streamstatus)
- **Recording**
- ["RecordingStarting"](#recordingstarting)
- ["RecordingStarted"](#recordingstarted)
- ["RecordingStopping"](#recordingstopping)
- ["RecordingStopped"](#recordingstopped)
- **Other**
- ["Exiting"](#exiting)
* [Requests](#requests)
- [Description](#description-1)
- [Request Types](#request-types)
- ["GetVersion"](#getversion)
- ["GetAuthRequired"](#getauthrequired)
- ["Authenticate"](#authenticate)
- ["GetCurrentScene"](#getcurrentscene)
- ["SetCurrentScene"](#setcurrentscene)
- ["GetSceneList"](#getscenelist)
- ["SetSourceRender"](#setsourcerender)
- ["StartStopStreaming"](#startstopstreaming)
- ["StartStopRecording"](#startstoprecording)
- ["GetStreamingStatus"](#getstreamingstatus)
- ["GetTransitionList"](#gettransitionlist)
- ["GetCurrentTransition"](#getcurrenttransition)
- ["SetCurrentTransition"](#setcurrenttransition)
- ["SetTransitionDuration"](#settransitionduration)
- ["SetVolume"](#setvolume)
- ["GetVolume"](#getvolume)
- ["SetMute"](#setmute)
- ["ToggleMute"](#togglemute)
- ["SetSceneItemPosition"](#setsceneitemposition)
- ["SetSceneItemTransform"](#setsceneitemtransform)
- ["SetCurrentSceneCollection"](#setcurrentscenecollection)
- ["GetCurrentSceneCollection"](#getcurrentscenecollection)
- ["ListSceneCollections"](#listscenecollections)
- ["SetCurrentProfile"](#setcurrentprofile)
- ["GetCurrentProfile"](#getcurrentprofile)
- ["ListProfiles"](#listprofiles)
* [Authentication](#authentication)
- **General**
- ["GetVersion"](#getversion)
- ["GetAuthRequired"](#getauthrequired)
- ["Authenticate"](#authenticate)
- **Scenes**
- ["GetCurrentScene"](#getcurrentscene)
- ["SetCurrentScene"](#setcurrentscene)
- ["GetSceneList"](#getscenelist)
- **Studio Mode**
- ["GetStudioModeStatus"](#getstudiomodestatus)
- ["SetPreviewScene"](#setpreviewscene)
- ["TransitionToProgram"](#transitiontoprogram)
- ["EnableStudioMode"](#enablestudiomode)
- ["DisableStudioMode"](#disablestudiomode)
- ["ToggleStudioMode"](#togglestudiomode)
- **Streaming**
- ["StartStopStreaming"](#startstopstreaming)
- ["StartStreaming"](#startstreaming)
- ["StopStreaming"](#stopstreaming)
- ["GetStreamingStatus"](#getstreamingstatus)
- **Recording**
- ["StartStopRecording"](#startstoprecording)
- ["StartRecording"](#startrecording)
- ["StopRecording"](#stoprecording)
- ["GetStreamingStatus"](#getstreamingstatus)
- ["SetRecordingFolder"](#setrecordingfolder)
- ["GetRecordingFolder"](#getrecordingfolder)
- **Transitions**
- ["GetTransitionList"](#gettransitionlist)
- ["GetCurrentTransition"](#getcurrenttransition)
- ["SetCurrentTransition"](#setcurrenttransition)
- ["GetTransitionDuration"](#gettransitionduration)
- ["SetTransitionDuration"](#settransitionduration)
- **Sources**
- ["GetCurrentScene"](#getcurrentscene)
- ["GetSceneList"](#getscenelist)
- ["GetSpecialSources"](#getspecialsources)
- ["GetTextGDIPlusProperties"](#gettextgdiplusproperties)
- ["SetTextGDIPlusProperties"](#settextgdiplusproperties)
- ["GetBrowserSourceProperties"](#getbrowsersourceproperties)
- ["SetBrowserSourceProperties"](#setbrowsersourceproperties)
- ["SetVolume"](#setvolume)
- ["GetVolume"](#getvolume)
- ["SetMute"](#setmute)
- ["GetMute"](#getmute)
- ["ToggleMute"](#togglemute)
- **Scene Items**
- ["SetSceneItemRender"](#setsourcerender) (a.k.a `SetSourceRender`)
- ["SetSceneItemPosition"](#setsceneitemposition)
- ["SetSceneItemTransform"](#setsceneitemtransform)
- ["SetSceneItemCrop"](#setsceneitemcrop)
- **Scene Collections**
- ["ListSceneCollections"](#listscenecollections)
- ["SetCurrentSceneCollection"](#setcurrentscenecollection)
- ["GetCurrentSceneCollection"](#getcurrentscenecollection)
- **Streaming Server Settings**
- ["GetStreamSettings"](#getstreamsettings)
- ["SetStreamSettings"](#setstreamsettings)
- ["SaveStreamSettings"](#savestreamsettings)
- **Profiles**
- ["ListProfiles"](#listprofiles)
- ["SetCurrentProfile"](#setcurrentprofile)
- ["GetCurrentProfile"](#getcurrentprofile)
## Authentication
A call to [`GetAuthRequired`](#getauthrequired) gives the client two elements :
- A challenge : a random string that will be used to generate the auth response
- A salt : applied to the password when generating the auth response
The client knows a password and must it to authenticate itself to the server.
However, it must keep this password secret, and it is the purpose of the authentication mecanism used by obs-websocket.
After a call to [`GetAuthRequired`](#getauthrequired), the client knows a password (kept secret), a challenge and a salt (sent by the server).
To generate the answer to the auth challenge, follow this procedure :
- Concatenate the password with the salt sent by the server (in this order : password + server salt), then generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64.
- Concatenate the base64 secret with the challenge sent by the server (in this order : base64 secret + server challenge), then generate a binary SHA256 hash of the result and encode it to base64.
- Voilà, this last base64 string is the auth response. You may now use it to authenticate to the server with the `Authenticate` request.
Here's how it looks in pseudocode :
```
password = "supersecretpassword"
challenge = "ztTBnnuqrqaKDzRM3xcVdbYm"
salt = "PZVbYpvAnZut2SS6JNJytDm9"
secret_string = password + salt
secret_hash = binary_sha256(secret_string)
secret = base64_encode(secret_hash)
auth_response_string = secret + challenge
auth_response_hash = binary_sha256(auth_response_string)
auth_response = base64_encode(auth_response_hash)
```
A client can then authenticate to the server by calling [`Authenticate`](#authenticate) with the computed challenge response.
## Events
### Description
@ -80,6 +160,7 @@ Additional fields will be present in the event message depending on the event ty
#### "SwitchScenes"
OBS is switching to another scene (called at the end of the transition).
- **scene-name** (string) : The name of the scene being switched to.
- **sources** (array of objects) : List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene).
---
@ -148,6 +229,19 @@ A transition other than "Cut" has begun.
---
#### "PreviewSceneChanged"
The selected Preview scene changed (only in Studio Mode).
- **scene-name** (string) : Name of the scene being previewed.
- **sources** (array of objects) : List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene).
---
#### "StudioModeSwitched"
Studio Mode has been switched on or off.
- **"new-state"** (bool) : new state of Studio Mode: true if enabled, false if disabled.
---
#### "ProfileChanged"
Triggered when switching to another profile or when renaming the current profile.
@ -166,7 +260,6 @@ A request to start streaming has been issued.
#### "StreamStarted"
Streaming started successfully.
*New in OBS Studio*
---
@ -178,31 +271,26 @@ A request to stop streaming has been issued.
#### "StreamStopped"
Streaming stopped successfully.
*New in OBS Studio*
---
#### "RecordingStarting"
A request to start recording has been issued.
*New in OBS Studio*
---
#### "RecordingStarted"
Recording started successfully.
*New in OBS Studio*
---
#### "RecordingStopping"
A request to stop streaming has been issued.
*New in OBS Studio*
---
#### "RecordingStopped"
Recording stopped successfully.
*New in OBS Studio*
---
@ -222,8 +310,7 @@ Sent every 2 seconds with the following information :
---
#### "Exiting"
OBS is exiting.
*New in OBS Studio*
OBS is exiting.
---
@ -294,6 +381,7 @@ Objects in the "sources" array have the following fields :
- **"source_cy"** (integer) : height of the item (without scale applied)
- **"cx"** (double) : width of the item (with scale applied)
- **"cy"** (double) : height of the item (with scale applied)
- **"render"** (bool) : visibility of the source in the scene
---
@ -329,8 +417,73 @@ __Response__ : OK if source exists in the current scene, error otherwise.
---
#### "GetStudioModeStatus"
Tells if Studio Mode is currently enabled or disabled.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"studio-mode"** (bool) : true if OBS is in Studio Mode, false otherwise.
---
#### "GetPreviewScene"
Studio Mode only. Gets the name of the currently Previewed scene, along with a list of its sources.
__Request fields__ : none
__Response__ : OK if Studio Mode is enabled, with the same fields as [`GetCurrentScene`](#getcurrentscene), error otherwise.
---
#### "SetPreviewScene"
Studio Mode only. Sets the specified scene as the Previewed scene in Studio Mode.
__Request fields__ :
- **"scene-name"** (string) : name of the scene to selected as the preview of Studio Mode
__Response__ : OK if Studio Mode is enabled and specified scene exists, error otherwise.
---
#### "TransitionToProgram"
Studio Mode only. Transitions the currently previewed scene to Program (main output).
__Request fields__ :
- **"with-transition"** (object, optional) : if specified, use this transition when switching from preview to program. This will change the current transition in the frontend to this one.
__Response__ : OK if studio mode is enabled and optional transition exists, error otherwise.
An object passed as `"with-transition"` in a request must have the following fields :
- **"name"** (string, optional) : transition name
- **"duration"** (integer, optional) : transition duration in milliseconds
---
#### "EnableStudioMode"
Enables Studio Mode.
__Request fields__ : none
__Response__ : always OK. No additional fields.
---
#### "DisableStudioMode"
Disables Studio Mode.
__Request fields__ : none
__Response__ : always OK. No additional fields.
---
#### "ToggleStudioMode"
Toggles Studio Mode on or off.
__Request fields__ : none
__Response__ : always OK. No additional fields.
---
#### "StartStopStreaming"
Toggle streaming on or off.
Toggles streaming on or off.
__Request fields__ : none
__Response__ : always OK. No additional fields.
@ -338,11 +491,80 @@ __Response__ : always OK. No additional fields.
---
#### "StartStopRecording"
Toggle recording on or off.
Toggles recording on or off.
__Request fields__ :
- **"stream"** (object; optional) : See 'stream' parameter in 'StartStreaming'. Ignored if stream is already started.
__Response__ : always OK. No additional fields.
---
#### "StartStreaming"
Start streaming.
__Request fields__ :
- **"stream"** (object; optional) : If specified allows for special configuration of the stream
The 'stream' object has the following fields:
- **"settings"** (object; optional) : The settings for the stream
- **"type"** (string; optional) : If specified ensures the type of the stream matches the given type (usually 'rtmp\_custom' or 'rtmp\_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the 'settings' object or an error will occur starting the stream.
- **"metadata"** (object; optional) : Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the stream.
The 'settings' object has the following fields:
- **"server"** (string; optional) : The publish URL
- **"key"** (string; optional) : The publish key of the stream
- **"use-auth"** (bool; optional) : should authentication be used when connecting to the streaming server
- **"username"** (string; optional) : if authentication is enabled, the username for access to the streaming server. Ignored if 'use-auth' is not specified as 'true'.
- **"password"** (string; optional) : if authentication is enabled, the password for access to the streaming server. Ignored if 'use-auth' is not specified as 'true'.
The 'metadata' object supports passing any string, numeric or boolean field.
__Response__ : Error if streaming is already active, OK otherwise. No additional fields.
---
#### "StopStreaming"
Stop streaming.
__Request fields__ : none
__Response__ : always OK. No additional fields.
*New in OBS Studio*
__Response__ : Error if streaming is already inactive, OK otherwise. No additional fields.
---
#### "StartRecording"
Start recording.
__Request fields__ : none
__Response__ : Error if recording is already active, OK otherwise. No additional fields.
---
#### "StopRecording"
Stop recording.
__Request fields__ : none
__Response__ : Error if recording is already inactive, OK otherwise. No additional fields.
---
#### "SetRecordingFolder"
Change the current recording folder.
__Request fields__ :
- **"rec-folder"** (string) : path of the desired recording folder
__Response__ : OK if path is valid, error otherwise.
---
#### "GetRecordingFolder"
Get the path of the current recording folder.
__Request fields__ : none
__Response__ : OK with these additional fields :
- **"rec-folder"** (string) : path of the current recording folder
---
@ -353,6 +575,8 @@ __Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"streaming"** (bool) : streaming status (active or not)
- **"recording"** (bool) : recording status (active or not)
- **stream-timecode** (string, optional) : time elapsed between now and stream start (only present if OBS Studio is streaming)
- **rec-timecode** (string, optional) : time elapsed between now and recording start (only present if OBS Studio is recording)
- **"preview-only"** (bool) : always false. Retrocompat with OBSRemote.
---
@ -368,8 +592,6 @@ __Response__ : always OK, with these additional fields :
Objects in the "transitions" array have only one field :
- **"name"** (string) : name of the transition
*New in OBS Studio*
---
#### "GetCurrentTransition"
@ -380,17 +602,13 @@ __Response__ : always OK, with these additional fields :
- **"name"** (string) : name of the selected transition
- **"duration"** (integer, only if transition supports this) : transition duration
*New in OBS Studio*
---
#### "SetCurrentTransition"
__Request fields__ :
- **"transition-name"** (string) : The name of the transition.
__Response__ : OK if specified transition exists, error otherwise.
*New in OBS Studio*
__Response__ : OK if specified transition exists, error otherwise.
---
@ -402,7 +620,14 @@ __Request fields__ :
__Response__ : always OK.
*New in OBS Studio*
---
#### "GetTransitionDuration"
Set the duration of the currently selected transition.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"transition-duration"** (integer) : current transition duration, in milliseconds
---
@ -415,8 +640,6 @@ __Request fields__ :
__Response__ : OK if specified source exists, error otherwise.
*Updated for OBS Studio*
---
#### "GetVolume"
@ -426,11 +649,9 @@ __Request fields__ :
- **"source"** (string) : name of the source
__Response__ : OK if source exists, with these additional fields :
- **"name"** (string) : name of the requested source
- **"volume"** (double) : volume of the requested source, on a linear scale (0.0 to 1.0)
- **"muted"** (bool) : mute status of the requested source
*Updated for OBS Studio*
- **"name"** (string) : source name
- **"volume"** (double) : source volume, on a linear scale (0.0 to 1.0)
- **"muted"** (bool) : source mute status
---
@ -443,7 +664,17 @@ __Request fields__ :
__Response__ : OK if specified source exists, error otherwise.
*Updated for OBS Studio*
---
#### "GetMute"
Get mute status of a specific source.
__Request fields__ :
- **"source"** (string) : the name of the source
__Response__ : OK if source exists, with these additional fields :
- **"name"** (string) : source name
- **"muted"** (bool) : source mute status
---
@ -455,7 +686,19 @@ __Request fields__ :
__Response__ : OK if specified source exists, error otherwise.
*Updated for OBS Studio*
---
#### "GetSpecialSources"
Get configured special sources like Desktop Audio and Mic/Aux sources.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"desktop-1"** (string, optional) : Name of the first Desktop Audio capture source
- **"desktop-1"** (string, optional) : Name of the second Desktop Audio capture source
- **"mic-1"** (string, optional) : Name of the first Mic/Aux input source
- **"mic-2"** (string, optional) : Name of the second Mic/Aux input source
- **"mic-3"** (string, optional) : Name of the third Mic/Aux input source
---
@ -468,8 +711,6 @@ __Request fields__ :
__Response__ : OK if specified item exists, error otherwise.
*New in OBS Studio*
---
#### "SetSceneItemTransform"
@ -482,7 +723,18 @@ __Request fields__ :
__Response__ : OK if specified item exists, error otherwise.
*New in OBS Studio*
---
#### "SetSceneItemCrop"
__Request fields__ :
- **"item"** (string) : Name of the scene item
- **"scene-name"** (string, optional) : Scene the item belongs to. Default : current scene.
- **"top"** (integer)
- **"bottom"** (integer)
- **"left"** (integer)
- **"right"** (integer)
__Response__ : OK if specified item exists, error otherwise.
---
@ -516,6 +768,53 @@ __Response__ : OK with these additional fields :
---
#### "GetStreamSettings"
Gets the current streaming server settings
__Request fields__ : none
__Response__ : OK with these additional fields :
- **"type"** (string) : The type of streaming service configuration usually 'rtmp\_custom' or 'rtmp\_common'
- **"settings"** (object) : The actual settings of the stream (i.e. server, key, use-auth, username, password)
The 'settings' object has the following fields however they may vary by 'type':
- **"server"** (string) : The publish URL
- **"key"** (string) : The publish key of the stream
- **"use-auth"** (bool) : should authentication be used when connecting to the streaming server
- **"username"** (string) : if authentication is enabled, the username for access to the streaming server
- **"password"** (string) : if authentication is enabled, the password for access to the streaming server
--
#### "SetStreamSettings"
Sets one or more attributes of the current streaming server settings. Any options not passed will remain unchanged. Returns the updated settings in response.
If 'type' is different than the current streaming service type, all settings are required.
Returns the full settings of the stream (i.e. the same as GetStreamSettings)
__Request fields__ :
- **"type"** (string) : The type of streaming service configuration usually 'rtmp\_custom' or 'rtmp\_common'
- **"settings"** (object) : The actual settings of the stream (i.e. server, key, use-auth, username, password)
- **"save"** (bool) : If specified as true, saves the settings to disk
The 'settings' object has the following fields however they may vary by 'type':
- **"server"** (string; optional) : The publish URL
- **"key"** (string; optional) : The publish key of the stream
- **"use-auth"** (bool; optional) : should authentication be used when connecting to the streaming server
- **"username"** (string; optional) : if authentication is enabled, the username for access to the streaming server
- **"password"** (string; optional) : if authentication is enabled, the password for access to the streaming server
__Response__ : OK with the same fields as the request (except 'save')
---
#### "SaveStreamSettings"
Saves the current streaming server settings to disk
__Request fields__ : none
__Response__ : OK
#### "SetCurrentProfile"
Change the current profile.
@ -546,31 +845,123 @@ __Response__ : OK with the additional fields :
---
### Authentication
A call to `GetAuthRequired` gives the client two elements :
- A challenge : a random string that will be used to generate the auth response
- A salt : applied to the password when generating the auth response
#### "GetTextGDIPlusProperties"
Gets current properties for Text GDI Plus source.
The client knows a password and must it to authenticate itself to the server.
However, it must keep this password secret, and it is the purpose of the authentication mecanism used by obs-websocket.
__Request fields__ :
- **"source"** (string) : name of the source in the currently active scene.
- **"scene-name"** (string; optional) : name of the scene the source belongs to. defaults to current scene.
After a call to `GetAuthRequired`, the client knows a password (kept secret), a challenge and a salt (sent by the server).
To generate the answer to the auth challenge, follow this procedure :
- Concatenate the password with the salt sent by the server (in this order : password + server salt), then generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64.
- Concatenate the base64 secret with the challenge sent by the server (in this order : base64 secret + server challenge), then generate a binary SHA256 hash of the result and encode it to base64.
- Voilà, this last base64 string is the auth response. You may now use it to authenticate to the server with the `Authenticate` request.
__Response__ : OK if source exists in the current scene with these additional fields when fields are set, error otherwise.
- **"align"** (string) : "left","center","right" : text alignment
- **"bk_color"** (integer) : background color
- **"bk_opacity"** (integer) : background opacity range 0 to 100
- **"chatlog"** (bool) : chat log
- **"chatlog_lines"** (integer) : chat log lines
- **"color"** (integer) : text color
- **"extents"** (bool) : extents
- **"extents_wrap"** (bool) : extents wrap
- **"extents_cx"** (integer) : extents cx
- **"extents_cy"** (integer) : extents cy
- **"file"** (string) : file path name
- **"read_from_file"** (bool) : read text from file specified
- **"font"** (object) : holds font data for face, flags, size and style
-- Example: "font": {"face": "Arial","flags": 0,"size": 150,"style": ""}
- **"face"** (string) : font face i.e. Arial
- **"flags"** (integer) : font text style flag i.e. Bold 1, Italic 2, Bold Italic 3, Underline 5, Strikeout 8
- **"size"** (integer) : font text size
- **"style"** (string) : font style (unknown function)
- **"gradient"** (bool) : gradient
- **"gradient_color"** (integer) : gradient color
- **"gradient_dir"** (float) : gradient direction
- **"gradient_opacity"** (integer) : gradient opacity range 0 to 100
- **"outline"** (bool) : outline
- **"outline_color"** (integer) : outline color
- **"outline_size"** (integer) : outline size
- **"outline_opacity"** (integer) : outline opacity range 0 to 100
- **"text"** (string) : text to be displayed
- **"valign"** (string) : "top","center","bottom" : text vertical alignment
- **"vertical"** (bool) : vertical text
- **"render"** (bool) : visibility of the scene item
Here's how it looks in pseudocode :
```
password = "supersecretpassword"
challenge = "ztTBnnuqrqaKDzRM3xcVdbYm"
salt = "PZVbYpvAnZut2SS6JNJytDm9"
---
secret_string = password + salt
secret_hash = binary_sha256(secret_string)
secret = base64_encode(secret_hash)
#### "SetTextGDIPlusProperties"
Sets current properties for Text GDI Plus source.
auth_response_string = secret + challenge
auth_response_hash = binary_sha256(auth_response_string)
auth_response = base64_encode(auth_response_hash)
```
__Request fields__ :
- **"source"** (string) : name of the source in the currently active scene.
- **"scene-name"** (string; optional) : name of the scene the source belongs to. defaults to current scene.
- **"align"** (string; optional) : "left","center","right" : text alignment
- **"bk_color"** (integer; optional) : background color
- **"bk_opacity"** (integer; optional) : background opacity range 0 to 100
- **"chatlog"** (bool; optional) : chat log
- **"chatlog_lines"** (integer; optional) : chat log lines
- **"color"** (integer; optional) : text color
- **"extents"** (bool; optional) : extents
- **"extents_wrap"** (bool; optional) : extents wrap
- **"extents_cx"** (integer; optional) : extents cx
- **"extents_cy"** (integer; optional) : extents cy
- **"file"** (string; optional) : file path name
- **"read_from_file"** (bool; optional) : read text from file specified
- **"font"** (object; optional) : holds font data for face, flags, size and style
-- Example: "font":{"face": "Arial","flags": 0,"size": 150,"style": ""}
- **"face"** (string; optional) : font face i.e. Arial
-- Example: "font":{"face": "Arial"}
- **"flags"** (integer; optional) : font text style flag i.e. Bold 1, Italic 2, Bold Italic 3, Underline 5, Strikeout 8
- **"size"** (integer; optional) : font text size
-- Example: "font": {"size":125}
- **"style"** (string; optional) : font style (unknown function)
- **"gradient"** (bool; optional) : gradient
- **"gradient_color"** (integer; optional) : gradient color
- **"gradient_dir"** (float; optional) : gradient direction
- **"gradient_opacity"** (integer; optional) : gradient opacity range 0 to 100
- **"outline"** (bool; optional) : outline
- **"outline_color"** (integer; optional) : outline color
- **"outline_size"** (integer; optional) : outline size
- **"outline_opacity"** (integer; optional) : outline opacity range 0 to 100
- **"text"** (string; optional) : text to be displayed
- **"valign"** (string; optional) : "top","center","bottom" : text vertical alignment
- **"vertical"** (bool; optional) : vertical text
- **"render"** (bool; optional) : visibility of the scene item
__Response__ : OK if source exists in the current scene, error otherwise.
---
#### "GetBrowserSourceProperties"
Gets current properties for Browser Source.
__Request fields__ :
- **"source"** (string) : name of the source in the currently active scene.
- **"scene-name"** (string; optional) : name of the scene the source belongs to. defaults to current scene.
__Response__ : OK if source exists in the current scene with these additional fields when fields are set, error otherwise.
- **"is_local_file"** (bool) : use local file
- **"url"** (string) : url or file path
- **"css"** (string) : cascading style sheet code
- **"width"** (integer) : width
- **"height"** (integer) : height
- **"fps"** (integer) : frames per second
- **"shutdown"** (bool) : shutdown when sorce is not visible
- **"render"** (bool; optional) : visibility of the scene item
---
#### "SetBrowserSourceProperties"
Sets current properties for Browser Source.
__Request fields__ :
- **"source"** (string) : name of the source in the currently active scene.
- **"scene-name"** (string; optional) : name of the scene the source belongs to. defaults to current scene.
- **"is_local_file"** (bool; optional) : use local file
- **"url"** (string; optional) : url or file path
- **"css"** (string; optional) : cascading style sheet code
- **"width"** (integer; optional) : width
- **"height"** (integer; optional) : height
- **"fps"** (integer; optional) : frames per second
- **"shutdown"** (bool; optional) : shutdown when sorce is not visible
- **"render"** (bool; optional) : visibility of the scene item
---

View File

@ -2,6 +2,10 @@ obs-websocket
==============
Remote control of OBS Studio made easy.
Follow the project on Twitter for news & updates : [@obswebsocket](https://twitter.com/obswebsocket)
[![Gitter chat](https://badges.gitter.im/obs-websocket/obs-websocket.png)](https://gitter.im/obs-websocket/obs-websocket) [![Build Status - Windows](https://ci.appveyor.com/api/projects/status/github/Palakis/obs-websocket)](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [![Build Status - Linux & OS X](https://travis-ci.org/Palakis/obs-websocket.svg?branch=master)](https://travis-ci.org/Palakis/obs-websocket)
## Downloads
Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
@ -20,37 +24,42 @@ The server is a typical Websockets server running by default on port 4444 (the p
The protocol understood by the server is documented in [PROTOCOL.md](PROTOCOL.md).
Here's a list of available language APIs for obs-websocket :
- Javascript (browser & nodejs) : [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by haganbmj
- Javascript (browser & nodejs) : [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by Brendan Hagan
- C#/VB.NET : [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet)
- Python : [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
I'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop me an email at `contact at slepin dot fr` !
## Compiling obs-websocket
### Prerequisites
You'll need [QT 5.7.0](https://download.qt.io/official_releases/qt/5.7/5.7.0/), CMake, and a working development environment for OBS Studio installed on your computer.
See the [build instructions](BUILDING.md).
### Windows
In cmake-gui, you'll have to set the following variables :
- **QTDIR** (path) : location of the Qt environment suited for your compiler and architecture
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source code of OBS Studio
- **LIBOBS_LIB** (filepath) : location of the obs.lib file
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
## Translations
**We need your help on translations**. Please join the localization project on Crowdin: https://crowdin.com/project/obs-websocket
### Linux
On Debian/Ubuntu :
```
sudo apt-get install libqt5websockets5-dev
git clone --recursive https://github.com/Palakis/obs-websocket.git
cd obs-websocket
mkdir build && cd build
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4
sudo make install
```
## Special thanks
In order of appearance:
- [Brendan H.](https://github.com/haganbmj) : Code contributions and better English in the Protocol specification
- [Mikhail Swift](https://github.com/mikhailswift) : Code contributions
- [Tobias Frahmer](https://github.com/Frahmer) : German translation
- [Genture](https://github.com/Genteure) : Simplified Chinese and Traditional Chinese translations
- [Larissa Gabilan](https://github.com/laris151) : Portuguese translation
- [Andy Asquelt](https://github.com/asquelt) : Polish translation
- [Marcel Haazen](https://github.com/inpothet) : Dutch translation
- [Peter Antonvich](https://github.com/pantonvich) : Code contributions
### OS X
*To do*
And also: special thanks to supporters of the project!
### Automated Builds
- Windows : [![Automated Build status for Windows](https://ci.appveyor.com/api/projects/status/github/Palakis/obs-websocket)](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
- Linux & OS X : coming soon
## Supporters
They have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them!
---
[Support Class](http://supportclass.net) designs and develops professional livestreams, with services ranging from broadcast graphics design and integration to event organization, along many other skills.
[![Support Class](doc/supportclass_logo_blacktext.png)](http://supportclass.net)
---
[MediaUnit](http://www.mediaunit.no) is a Norwegian media company developing products and services for the media industry, primarly focused on web and events.
[![MediaUnit](doc/mediaunit_logo_black.png)](http://www.mediaunit.no/)

399
Utils.cpp
View File

@ -16,25 +16,29 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include "Utils.h"
#include <obs-frontend-api.h>
#include <obs.hpp>
#include <QMainWindow>
#include <QSpinBox>
#include <QDir>
#include <QUrl>
#include "Utils.h"
#include "obs-websocket.h"
Q_DECLARE_METATYPE(OBSScene);
obs_data_array_t* string_list_to_array(char** strings, char* key)
{
if (!strings)
return obs_data_array_create();
obs_data_array_t *list = obs_data_array_create();
obs_data_array_t* list = obs_data_array_create();
char* value = "";
for (int i = 0; value != nullptr; i++)
{
value = strings[i];
obs_data_t *item = obs_data_create();
obs_data_t* item = obs_data_create();
obs_data_set_string(item, key, value);
if (value)
@ -46,17 +50,19 @@ obs_data_array_t* string_list_to_array(char** strings, char* key)
return list;
}
obs_data_array_t* Utils::GetSceneItems(obs_source_t *source) {
obs_data_array_t *items = obs_data_array_create();
obs_scene_t *scene = obs_scene_from_source(source);
if (scene == NULL) {
return NULL;
}
obs_data_array_t* Utils::GetSceneItems(obs_source_t* source)
{
obs_data_array_t* items = obs_data_array_create();
obs_scene_t* scene = obs_scene_from_source(source);
obs_scene_enum_items(scene, [](obs_scene_t *scene, obs_sceneitem_t *currentItem, void *param) {
obs_data_array_t *data = static_cast<obs_data_array_t *>(param);
obs_data_t *item_data = GetSceneItemData(currentItem);
if (!scene)
return nullptr;
obs_scene_enum_items(scene, [](obs_scene_t* scene, obs_sceneitem_t* currentItem, void* param)
{
obs_data_array_t* data = static_cast<obs_data_array_t* >(param);
obs_data_t* item_data = GetSceneItemData(currentItem);
obs_data_array_insert(data, 0, item_data);
obs_data_release(item_data);
@ -66,10 +72,10 @@ obs_data_array_t* Utils::GetSceneItems(obs_source_t *source) {
return items;
}
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t *item) {
if (!item) {
return NULL;
}
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item)
{
if (!item)
return nullptr;
vec2 pos;
obs_sceneitem_get_pos(item, &pos);
@ -81,22 +87,26 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t *item) {
float item_width = float(obs_source_get_width(item_source));
float item_height = float(obs_source_get_height(item_source));
obs_data_t *data = obs_data_create();
obs_data_set_string(data, "name", obs_source_get_name(obs_sceneitem_get_source(item)));
obs_data_set_string(data, "type", obs_source_get_id(obs_sceneitem_get_source(item)));
obs_data_set_double(data, "volume", obs_source_get_volume(obs_sceneitem_get_source(item)));
obs_data_t* data = obs_data_create();
obs_data_set_string(data, "name",
obs_source_get_name(obs_sceneitem_get_source(item)));
obs_data_set_string(data, "type",
obs_source_get_id(obs_sceneitem_get_source(item)));
obs_data_set_double(data, "volume",
obs_source_get_volume(obs_sceneitem_get_source(item)));
obs_data_set_double(data, "x", pos.x);
obs_data_set_double(data, "y", pos.y);
obs_data_set_int(data, "source_cx", (int)item_width);
obs_data_set_int(data, "source_cy", (int)item_height);
obs_data_set_double(data, "cx", item_width * scale.x);
obs_data_set_double(data, "cy", item_height * scale.y);
obs_data_set_double(data, "cx", item_width* scale.x);
obs_data_set_double(data, "cy", item_height* scale.y);
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
return data;
}
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* name) {
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* name)
{
struct current_search {
const char* query;
obs_sceneitem_t* result;
@ -104,18 +114,21 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* n
current_search search;
search.query = name;
search.result = NULL;
search.result = nullptr;
obs_scene_t *scene = obs_scene_from_source(source);
if (scene == NULL) {
return NULL;
}
obs_scene_t* scene = obs_scene_from_source(source);
if (scene == nullptr)
return nullptr;
obs_scene_enum_items(scene, [](obs_scene_t *scene, obs_sceneitem_t *currentItem, void *param) {
current_search *search = static_cast<current_search *>(param);
const char* currentItemName = obs_source_get_name(obs_sceneitem_get_source(currentItem));
if (strcmp(currentItemName, search->query) == 0) {
obs_scene_enum_items(scene, [](obs_scene_t* scene, obs_sceneitem_t* currentItem, void* param)
{
current_search* search = static_cast<current_search* >(param);
const char* currentItemName =
obs_source_get_name(obs_sceneitem_get_source(currentItem));
if (strcmp(currentItemName, search->query) == 0)
{
search->result = currentItem;
obs_sceneitem_addref(search->result);
return false;
@ -127,17 +140,20 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* n
return search.result;
}
obs_source_t* Utils::GetTransitionFromName(const char *search_name) {
obs_source_t *found_transition = NULL;
obs_source_t* Utils::GetTransitionFromName(const char* search_name)
{
obs_source_t* found_transition = NULL;
obs_frontend_source_list transition_list = {};
obs_frontend_get_transitions(&transition_list);
for (size_t i = 0; i < transition_list.sources.num; i++) {
obs_source_t *transition = transition_list.sources.array[i];
for (size_t i = 0; i < transition_list.sources.num; i++)
{
obs_source_t* transition = transition_list.sources.array[i];
const char *transition_name = obs_source_get_name(transition);
if (strcmp(transition_name, search_name) == 0) {
const char* transition_name = obs_source_get_name(transition);
if (strcmp(transition_name, search_name) == 0)
{
found_transition = transition;
obs_source_addref(found_transition);
break;
@ -149,44 +165,47 @@ obs_source_t* Utils::GetTransitionFromName(const char *search_name) {
return found_transition;
}
obs_source_t* Utils::GetSceneFromNameOrCurrent(const char *scene_name) {
obs_source_t* scene;
if (!scene_name || !strlen(scene_name)) {
obs_source_t* Utils::GetSceneFromNameOrCurrent(const char* scene_name)
{
obs_source_t* scene = nullptr;
if (!scene_name || !strlen(scene_name))
scene = obs_frontend_get_current_scene();
}
else {
else
scene = obs_get_source_by_name(scene_name);
}
return scene;
}
obs_data_array_t* Utils::GetScenes() {
obs_data_array_t* Utils::GetScenes()
{
obs_frontend_source_list sceneList = {};
obs_frontend_get_scenes(&sceneList);
obs_data_array_t* scenes = obs_data_array_create();
for (size_t i = 0; i < sceneList.sources.num; i++) {
obs_source_t *scene = sceneList.sources.array[i];
obs_data_t *scene_data = GetSceneData(scene);
for (size_t i = 0; i < sceneList.sources.num; i++)
{
obs_source_t* scene = sceneList.sources.array[i];
obs_data_t* scene_data = GetSceneData(scene);
obs_data_array_push_back(scenes, scene_data);
obs_data_release(scene_data);
}
obs_frontend_source_list_free(&sceneList);
obs_frontend_source_list_free(&sceneList);
return scenes;
}
obs_data_t* Utils::GetSceneData(obs_source *source) {
obs_data_array_t *scene_items = GetSceneItems(source);
obs_data_t* Utils::GetSceneData(obs_source* source)
{
obs_data_array_t* scene_items = GetSceneItems(source);
obs_data_t* sceneData = obs_data_create();
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
obs_data_set_array(sceneData, "sources", scene_items);
obs_data_array_release(scene_items);
return sceneData;
}
@ -194,7 +213,7 @@ obs_data_t* Utils::GetSceneData(obs_source *source) {
obs_data_array_t* Utils::GetSceneCollections()
{
char** scene_collections = obs_frontend_get_scene_collections();
obs_data_array_t *list = string_list_to_array(scene_collections, "sc-name");
obs_data_array_t* list = string_list_to_array(scene_collections, "sc-name");
bfree(scene_collections);
return list;
@ -203,7 +222,7 @@ obs_data_array_t* Utils::GetSceneCollections()
obs_data_array_t* Utils::GetProfiles()
{
char** profiles = obs_frontend_get_profiles();
obs_data_array_t *list = string_list_to_array(profiles, "profile-name");
obs_data_array_t* list = string_list_to_array(profiles, "profile-name");
bfree(profiles);
return list;
@ -211,7 +230,7 @@ obs_data_array_t* Utils::GetProfiles()
QSpinBox* Utils::GetTransitionDurationControl()
{
QMainWindow *window = (QMainWindow*)obs_frontend_get_main_window();
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
return window->findChild<QSpinBox*>("transitionDuration");
}
@ -219,13 +238,9 @@ int Utils::GetTransitionDuration()
{
QSpinBox* control = GetTransitionDurationControl();
if (control)
{
return control->value();
}
else
{
return -1;
}
}
void Utils::SetTransitionDuration(int ms)
@ -233,9 +248,147 @@ void Utils::SetTransitionDuration(int ms)
QSpinBox* control = GetTransitionDurationControl();
if (control && ms >= 0)
{
control->setValue(ms);
}
bool Utils::SetTransitionByName(const char* transition_name)
{
obs_source_t* transition = GetTransitionFromName(transition_name);
if (transition)
{
obs_frontend_set_current_transition(transition);
obs_source_release(transition);
return true;
}
else
{
return false;
}
}
QPushButton* Utils::GetPreviewModeButtonControl()
{
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
return main->findChild<QPushButton*>("modeSwitch");
}
QListWidget* Utils::GetSceneListControl()
{
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
return main->findChild<QListWidget*>("scenes");
}
obs_scene_t* Utils::SceneListItemToScene(QListWidgetItem* item)
{
if (!item)
return nullptr;
QVariant item_data = item->data(static_cast<int>(Qt::UserRole));
return item_data.value<OBSScene>();
}
QLayout* Utils::GetPreviewLayout()
{
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
return main->findChild<QLayout*>("previewLayout");
}
bool Utils::IsPreviewModeActive()
{
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
// Clue 1 : "Studio Mode" button is toggled on
bool buttonToggledOn = GetPreviewModeButtonControl()->isChecked();
// Clue 2 : Preview layout has more than one item
int previewChildCount = GetPreviewLayout()->count();
blog(LOG_INFO, "preview layout children count : %d", previewChildCount);
return buttonToggledOn || (previewChildCount >= 2);
}
void Utils::EnablePreviewMode()
{
if (!IsPreviewModeActive())
GetPreviewModeButtonControl()->click();
}
void Utils::DisablePreviewMode()
{
if (IsPreviewModeActive())
GetPreviewModeButtonControl()->click();
}
void Utils::TogglePreviewMode()
{
GetPreviewModeButtonControl()->click();
}
obs_scene_t* Utils::GetPreviewScene()
{
if (IsPreviewModeActive())
{
QListWidget* sceneList = GetSceneListControl();
QList<QListWidgetItem*> selected = sceneList->selectedItems();
// Qt::UserRole == QtUserRole::OBSRef
obs_scene_t* scene = Utils::SceneListItemToScene(selected.first());
obs_scene_addref(scene);
return scene;
}
return nullptr;
}
bool Utils::SetPreviewScene(const char* name)
{
if (IsPreviewModeActive())
{
QListWidget* sceneList = GetSceneListControl();
QList<QListWidgetItem*> matchingItems =
sceneList->findItems(name, Qt::MatchExactly);
if (matchingItems.count() > 0)
{
sceneList->setCurrentItem(matchingItems.first());
return true;
}
else
{
return false;
}
}
return false;
}
void Utils::TransitionToProgram()
{
if (!IsPreviewModeActive())
return;
// WARNING : if the layout created in OBS' CreateProgramOptions() changes
// then this won't work as expected
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
// The program options widget is the second item in the left-to-right layout
QWidget* programOptions = GetPreviewLayout()->itemAt(1)->widget();
// The "Transition" button lies in the mainButtonLayout
// which is the first itemin the program options' layout
QLayout* mainButtonLayout = programOptions->layout()->itemAt(1)->layout();
QWidget* transitionBtnWidget = mainButtonLayout->itemAt(0)->widget();
// Try to cast that widget into a button
QPushButton* transitionBtn = qobject_cast<QPushButton*>(transitionBtnWidget);
// Perform a click on that button
transitionBtn->click();
}
const char* Utils::OBSVersionString() {
@ -246,8 +399,124 @@ const char* Utils::OBSVersionString() {
minor = (version >> 16) & 0xFF;
patch = version & 0xFF;
char *result = (char*)bmalloc(sizeof(char) * 12);
char* result = (char*)bmalloc(sizeof(char) * 12);
sprintf(result, "%d.%d.%d", major, minor, patch);
return result;
}
QSystemTrayIcon* Utils::GetTrayIcon()
{
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
return main->findChildren<QSystemTrayIcon*>().first();
}
void Utils::SysTrayNotify(QString &text, QSystemTrayIcon::MessageIcon icon, QString title)
{
if (!QSystemTrayIcon::supportsMessages())
return;
QSystemTrayIcon* trayIcon = GetTrayIcon();
if (trayIcon)
trayIcon->showMessage(title, text, icon);
}
QString Utils::FormatIPAddress(QHostAddress &addr)
{
if (addr.protocol() == QAbstractSocket::IPv4Protocol)
QString v4addr = addr.toString().replace("::fff:", "");
return addr.toString();
}
const char* Utils::GetRecordingFolder()
{
config_t* profile = obs_frontend_get_profile_config();
const char* outputMode = config_get_string(profile, "Output", "Mode");
if (strcmp(outputMode, "Advanced") == 0)
{
// Advanced mode
return config_get_string(profile, "AdvOut", "RecFilePath");
}
else
{
// Simple mode
return config_get_string(profile, "SimpleOutput", "FilePath");
}
}
bool Utils::SetRecordingFolder(const char* path)
{
if (!QDir(path).exists())
return false;
config_t* profile = obs_frontend_get_profile_config();
const char* outputMode = config_get_string(profile, "Output", "Mode");
if (strcmp(outputMode, "Advanced") == 0)
{
config_set_string(profile, "AdvOut", "RecFilePath", path);
}
else
{
config_set_string(profile, "SimpleOutput", "FilePath", path);
}
config_save(profile);
return true;
}
QString* Utils::ParseDataToQueryString(obs_data_t * data)
{
QString* query = nullptr;
if (data)
{
obs_data_item_t* item = obs_data_first(data);
if (item)
{
query = new QString();
bool isFirst = true;
do
{
if (!obs_data_item_has_user_value(item))
continue;
if (!isFirst)
query->append('&');
else
isFirst = false;
const char* attrName = obs_data_item_get_name(item);
query->append(attrName).append("=");
switch (obs_data_item_gettype(item))
{
case OBS_DATA_BOOLEAN:
query->append(obs_data_item_get_bool(item)?"true":"false");
break;
case OBS_DATA_NUMBER:
switch (obs_data_item_numtype(item))
{
case OBS_DATA_NUM_DOUBLE:
query->append(QString::number(obs_data_item_get_double(item)));
break;
case OBS_DATA_NUM_INT:
query->append(QString::number(obs_data_item_get_int(item)));
break;
case OBS_DATA_NUM_INVALID:
break;
}
break;
case OBS_DATA_STRING:
query->append(QUrl::toPercentEncoding(QString(obs_data_item_get_string(item))));
break;
default:
//other types are not supported
break;
}
} while ( obs_data_item_next( &item ) );
}
}
return query;
}

47
Utils.h
View File

@ -20,20 +20,27 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define UTILS_H
#include <QSpinBox>
#include <QPushButton>
#include <QLayout>
#include <QListWidget>
#include <QSystemTrayIcon>
#include <QHostAddress>
#include <stdio.h>
#include <obs-module.h>
#include <util/config-file.h>
class Utils
{
public:
static obs_data_array_t* GetSceneItems(obs_source_t *source);
static obs_data_t* GetSceneItemData(obs_scene_item *item);
static obs_sceneitem_t* GetSceneItemFromName(obs_source_t *source, const char *name);
static obs_source_t* GetTransitionFromName(const char *search_name);
static obs_source_t* GetSceneFromNameOrCurrent(const char *scene_name);
static obs_data_array_t* GetSceneItems(obs_source_t* source);
static obs_data_t* GetSceneItemData(obs_scene_item* item);
static obs_sceneitem_t* GetSceneItemFromName(
obs_source_t* source, const char* name);
static obs_source_t* GetTransitionFromName(const char* search_name);
static obs_source_t* GetSceneFromNameOrCurrent(const char* scene_name);
static obs_data_array_t* GetScenes();
static obs_data_t* GetSceneData(obs_source *source);
static obs_data_t* GetSceneData(obs_source* source);
static obs_data_array_t* GetSceneCollections();
static obs_data_array_t* GetProfiles();
@ -42,7 +49,35 @@ class Utils
static int GetTransitionDuration();
static void SetTransitionDuration(int ms);
static bool SetTransitionByName(const char* transition_name);
static QPushButton* GetPreviewModeButtonControl();
static QLayout* GetPreviewLayout();
static QListWidget* GetSceneListControl();
static obs_scene_t* SceneListItemToScene(QListWidgetItem* item);
static bool IsPreviewModeActive();
static void EnablePreviewMode();
static void DisablePreviewMode();
static void TogglePreviewMode();
static obs_scene_t* GetPreviewScene();
static bool SetPreviewScene(const char* name);
static void TransitionToProgram();
static const char* OBSVersionString();
static QSystemTrayIcon* GetTrayIcon();
static void SysTrayNotify(
QString &text,
QSystemTrayIcon::MessageIcon n,
QString title = QString("obs-websocket"));
static QString FormatIPAddress(QHostAddress &addr);
static const char* GetRecordingFolder();
static bool SetRecordingFolder(const char* path);
static QString* ParseDataToQueryString(obs_data_t * data);
};
#endif // UTILS_H

View File

@ -19,16 +19,18 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <util/platform.h>
#include <QTimer>
#include <QPushButton>
#include "Config.h"
#include "Utils.h"
#include "WSEvents.h"
#include "obs-websocket.h"
bool transition_is_cut(obs_source_t *transition)
bool transition_is_cut(obs_source_t* transition)
{
if (!transition)
return false;
if (obs_source_get_type(transition) == OBS_SOURCE_TYPE_TRANSITION
if (obs_source_get_type(transition) == OBS_SOURCE_TYPE_TRANSITION
&& strcmp(obs_source_get_id(transition), "cut_transition") == 0)
{
return true;
@ -49,36 +51,39 @@ const char* ns_to_timestamp(uint64_t ns)
uint64_t ms_part = ms % 1000;
char* ts = (char*)bmalloc(64);
sprintf(ts, "%02d:%02d:%02d.%03d", hours_part, minutes_part, secs_part, ms_part);
sprintf(ts, "%02d:%02d:%02d.%03d",
hours_part, minutes_part, secs_part, ms_part);
return ts;
}
WSEvents::WSEvents(WSServer *srv)
WSEvents* WSEvents::Instance = nullptr;
WSEvents::WSEvents(WSServer* srv)
{
_srv = srv;
obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this);
QSpinBox* duration_control = Utils::GetTransitionDurationControl();
connect(duration_control, SIGNAL(valueChanged(int)), this, SLOT(TransitionDurationChanged(int)));
connect(duration_control, SIGNAL(valueChanged(int)),
this, SLOT(TransitionDurationChanged(int)));
QTimer *statusTimer = new QTimer();
connect(statusTimer, SIGNAL(timeout()), this, SLOT(StreamStatus()));
QTimer* statusTimer = new QTimer();
connect(statusTimer, SIGNAL(timeout()),
this, SLOT(StreamStatus()));
statusTimer->start(2000); // equal to frontend's constant BITRATE_UPDATE_SECONDS
QListWidget* sceneList = Utils::GetSceneListControl();
connect(sceneList, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
this, SLOT(SelectedSceneChanged(QListWidgetItem*, QListWidgetItem*)));
QPushButton* modeSwitch = Utils::GetPreviewModeButtonControl();
connect(modeSwitch, SIGNAL(clicked(bool)), this, SLOT(ModeSwitchClicked(bool)));
transition_handler = nullptr;
scene_handler = nullptr;
WSEvents* instance = this;
QTimer::singleShot(1000, [instance]() {
obs_source_t* transition = obs_frontend_get_current_transition();
instance->connectTransitionSignals(transition);
obs_source_release(transition);
obs_source_t* scene = obs_frontend_get_current_scene();
instance->connectSceneSignals(scene);
obs_source_release(scene);
});
QTimer::singleShot(1000, this, SLOT(deferredInitOperations()));
_streaming_active = false;
_recording_active = false;
@ -92,9 +97,20 @@ WSEvents::~WSEvents()
obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this);
}
void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void *private_data)
void WSEvents::deferredInitOperations()
{
WSEvents *owner = static_cast<WSEvents *>(private_data);
obs_source_t* transition = obs_frontend_get_current_transition();
connectTransitionSignals(transition);
obs_source_release(transition);
obs_source_t* scene = obs_frontend_get_current_scene();
connectSceneSignals(scene);
obs_source_release(scene);
}
void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private_data)
{
WSEvents* owner = static_cast<WSEvents*>(private_data);
if (!owner->_srv)
return;
@ -175,11 +191,11 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void *private
}
}
void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFields = NULL)
void WSEvents::broadcastUpdate(const char* updateType, obs_data_t* additionalFields = NULL)
{
obs_data_t *update = obs_data_create();
obs_data_t* update = obs_data_create();
obs_data_set_string(update, "update-type", updateType);
const char* ts = nullptr;
if (_streaming_active)
{
@ -194,12 +210,14 @@ void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFie
obs_data_set_string(update, "rec-timecode", ts);
bfree((void*)ts);
}
if (additionalFields != NULL) {
obs_data_apply(update, additionalFields);
}
_srv->broadcast(obs_data_get_json(update));
if (additionalFields != NULL)
obs_data_apply(update, additionalFields);
const char *json = obs_data_get_json(update);
_srv->broadcast(json);
if (Config::Current()->DebugEnabled)
blog(LOG_DEBUG, "Update << '%s'", json);
obs_data_release(update);
}
@ -208,13 +226,16 @@ void WSEvents::connectTransitionSignals(obs_source_t* transition)
{
if (transition_handler)
{
signal_handler_disconnect(transition_handler, "transition_start", OnTransitionBegin, this);
signal_handler_disconnect(transition_handler,
"transition_start", OnTransitionBegin, this);
}
if (!transition_is_cut(transition))
{
transition_handler = obs_source_get_signal_handler(transition);
signal_handler_connect(transition_handler, "transition_start", OnTransitionBegin, this); }
signal_handler_connect(transition_handler,
"transition_start", OnTransitionBegin, this);
}
else
{
transition_handler = nullptr;
@ -223,37 +244,81 @@ void WSEvents::connectTransitionSignals(obs_source_t* transition)
void WSEvents::connectSceneSignals(obs_source_t* scene)
{
// TODO : connect to all scenes, not just the current one.
if (scene_handler)
{
signal_handler_disconnect(scene_handler, "reorder", OnSceneReordered, this);
signal_handler_disconnect(scene_handler, "item_add", OnSceneItemAdd, this);
signal_handler_disconnect(scene_handler, "item_remove", OnSceneItemDelete, this);
signal_handler_disconnect(scene_handler, "item_visible", OnSceneItemVisibilityChanged, this);
signal_handler_disconnect(scene_handler,
"reorder", OnSceneReordered, this);
signal_handler_disconnect(scene_handler,
"item_add", OnSceneItemAdd, this);
signal_handler_disconnect(scene_handler,
"item_remove", OnSceneItemDelete, this);
signal_handler_disconnect(scene_handler,
"item_visible", OnSceneItemVisibilityChanged, this);
}
// TODO : connect to all scenes, not just the current one.
scene_handler = obs_source_get_signal_handler(scene);
signal_handler_connect(scene_handler, "reorder", OnSceneReordered, this);
signal_handler_connect(scene_handler, "item_add", OnSceneItemAdd, this);
signal_handler_connect(scene_handler, "item_remove", OnSceneItemDelete, this);
signal_handler_connect(scene_handler, "item_visible", OnSceneItemVisibilityChanged, this);
signal_handler_connect(scene_handler,
"reorder", OnSceneReordered, this);
signal_handler_connect(scene_handler,
"item_add", OnSceneItemAdd, this);
signal_handler_connect(scene_handler,
"item_remove", OnSceneItemDelete, this);
signal_handler_connect(scene_handler,
"item_visible", OnSceneItemVisibilityChanged, this);
}
uint64_t WSEvents::GetStreamingTime()
{
if (_streaming_active)
return (os_gettime_ns() - _stream_starttime);
else
return 0;
}
const char* WSEvents::GetStreamingTimecode()
{
return ns_to_timestamp(GetStreamingTime());
}
uint64_t WSEvents::GetRecordingTime()
{
if (_recording_active)
return (os_gettime_ns() - _rec_starttime);
else
return 0;
}
const char* WSEvents::GetRecordingTimecode()
{
return ns_to_timestamp(GetRecordingTime());
}
void WSEvents::OnSceneChange()
{
// Implements an existing update type from bilhamil's OBS Remote
obs_data_t *data = obs_data_create();
obs_data_t* data = obs_data_create();
obs_source_t* current_scene = obs_frontend_get_current_scene();
obs_data_array_t* scene_items = Utils::GetSceneItems(current_scene);
connectSceneSignals(current_scene);
obs_data_set_string(data, "scene-name", obs_source_get_name(current_scene));
obs_data_set_array(data, "sources", scene_items);
broadcastUpdate("SwitchScenes", data);
obs_data_release(data);
obs_data_array_release(scene_items);
obs_source_release(current_scene);
obs_data_release(data);
// Dirty fix : OBS blocks signals when swapping scenes in Studio Mode
// after transition end, so SelectedSceneChanged is never called...
if (Utils::IsPreviewModeActive())
{
QListWidget* list = Utils::GetSceneListControl();
SelectedSceneChanged(list->currentItem(), nullptr);
}
}
void WSEvents::OnSceneListChange()
@ -285,11 +350,12 @@ void WSEvents::OnTransitionChange()
obs_source_t* current_transition = obs_frontend_get_current_transition();
connectTransitionSignals(current_transition);
obs_data_t *data = obs_data_create();
obs_data_set_string(data, "transition-name", obs_source_get_name(current_transition));
obs_data_t* data = obs_data_create();
obs_data_set_string(data, "transition-name",
obs_source_get_name(current_transition));
broadcastUpdate("SwitchTransition", data);
obs_data_release(data);
obs_source_release(current_transition);
}
@ -312,7 +378,7 @@ void WSEvents::OnProfileListChange()
void WSEvents::OnStreamStarting()
{
// Implements an existing update type from bilhamil's OBS Remote
obs_data_t *data = obs_data_create();
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "preview-only", false);
broadcastUpdate("StreamStarting", data);
@ -331,7 +397,7 @@ void WSEvents::OnStreamStarted()
void WSEvents::OnStreamStopping()
{
// Implements an existing update type from bilhamil's OBS Remote
obs_data_t *data = obs_data_create();
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "preview-only", false);
broadcastUpdate("StreamStopping", data);
@ -383,41 +449,43 @@ void WSEvents::StreamStatus()
bool streaming_active = obs_frontend_streaming_active();
bool recording_active = obs_frontend_recording_active();
obs_output_t *stream_output = obs_frontend_get_streaming_output();
obs_output_t* stream_output = obs_frontend_get_streaming_output();
if (!stream_output || !streaming_active) {
if (stream_output) {
if (!stream_output || !streaming_active)
{
if (stream_output)
obs_output_release(stream_output);
}
return;
}
uint64_t bytes_sent = obs_output_get_total_bytes(stream_output);
uint64_t bytes_sent_time = os_gettime_ns();
if (bytes_sent < _lastBytesSent) {
if (bytes_sent < _lastBytesSent)
bytes_sent = 0;
}
if (bytes_sent == 0) {
if (bytes_sent == 0)
_lastBytesSent = 0;
}
uint64_t bytes_between = bytes_sent - _lastBytesSent;
double time_passed = double(bytes_sent_time - _lastBytesSentTime) / 1000000000.0;
double time_passed =
double(bytes_sent_time - _lastBytesSentTime) / 1000000000.0;
uint64_t bytes_per_sec = bytes_between / time_passed;
_lastBytesSent = bytes_sent;
_lastBytesSentTime = bytes_sent_time;
uint64_t totalStreamTime = (os_gettime_ns() - _stream_starttime) / 1000000000;
uint64_t totalStreamTime =
(os_gettime_ns() - _stream_starttime) / 1000000000;
int total_frames = obs_output_get_total_frames(stream_output);
int dropped_frames = obs_output_get_frames_dropped(stream_output);
float strain = obs_output_get_congestion(stream_output);
obs_data_t *data = obs_data_create();
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "streaming", streaming_active);
obs_data_set_bool(data, "recording", recording_active);
obs_data_set_int(data, "bytes-per-sec", bytes_per_sec);
@ -455,22 +523,23 @@ void WSEvents::OnTransitionBegin(void* param, calldata_t* data)
blog(LOG_INFO, "transition begin");
}
void WSEvents::OnSceneReordered(void *param, calldata_t *data)
void WSEvents::OnSceneReordered(void* param, calldata_t* data)
{
WSEvents* instance = static_cast<WSEvents*>(param);
obs_scene_t* scene = nullptr;
calldata_get_ptr(data, "scene", &scene);
obs_data_t *fields = obs_data_create();
obs_data_set_string(fields, "scene-name", obs_source_get_name(obs_scene_get_source(scene)));
obs_data_t* fields = obs_data_create();
obs_data_set_string(fields, "scene-name",
obs_source_get_name(obs_scene_get_source(scene)));
instance->broadcastUpdate("SourceOrderChanged", fields);
obs_data_release(fields);
}
void WSEvents::OnSceneItemAdd(void *param, calldata_t *data)
void WSEvents::OnSceneItemAdd(void* param, calldata_t* data)
{
WSEvents* instance = static_cast<WSEvents*>(param);
@ -480,8 +549,10 @@ void WSEvents::OnSceneItemAdd(void *param, calldata_t *data)
obs_sceneitem_t* scene_item = nullptr;
calldata_get_ptr(data, "item", &scene_item);
const char* scene_name = obs_source_get_name(obs_scene_get_source(scene));
const char* sceneitem_name = obs_source_get_name(obs_sceneitem_get_source(scene_item));
const char* scene_name =
obs_source_get_name(obs_scene_get_source(scene));
const char* sceneitem_name =
obs_source_get_name(obs_sceneitem_get_source(scene_item));
obs_data_t* fields = obs_data_create();
obs_data_set_string(fields, "scene-name", scene_name);
@ -492,7 +563,7 @@ void WSEvents::OnSceneItemAdd(void *param, calldata_t *data)
obs_data_release(fields);
}
void WSEvents::OnSceneItemDelete(void *param, calldata_t *data)
void WSEvents::OnSceneItemDelete(void* param, calldata_t* data)
{
WSEvents* instance = static_cast<WSEvents*>(param);
@ -502,8 +573,10 @@ void WSEvents::OnSceneItemDelete(void *param, calldata_t *data)
obs_sceneitem_t* scene_item = nullptr;
calldata_get_ptr(data, "item", &scene_item);
const char* scene_name = obs_source_get_name(obs_scene_get_source(scene));
const char* sceneitem_name = obs_source_get_name(obs_sceneitem_get_source(scene_item));
const char* scene_name =
obs_source_get_name(obs_scene_get_source(scene));
const char* sceneitem_name =
obs_source_get_name(obs_sceneitem_get_source(scene_item));
obs_data_t* fields = obs_data_create();
obs_data_set_string(fields, "scene-name", scene_name);
@ -514,7 +587,7 @@ void WSEvents::OnSceneItemDelete(void *param, calldata_t *data)
obs_data_release(fields);
}
void WSEvents::OnSceneItemVisibilityChanged(void *param, calldata_t *data)
void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data)
{
WSEvents* instance = static_cast<WSEvents*>(param);
@ -527,8 +600,10 @@ void WSEvents::OnSceneItemVisibilityChanged(void *param, calldata_t *data)
bool visible = false;
calldata_get_bool(data, "visible", &visible);
const char* scene_name = obs_source_get_name(obs_scene_get_source(scene));
const char* sceneitem_name = obs_source_get_name(obs_sceneitem_get_source(scene_item));
const char* scene_name =
obs_source_get_name(obs_scene_get_source(scene));
const char* sceneitem_name =
obs_source_get_name(obs_sceneitem_get_source(scene_item));
obs_data_t* fields = obs_data_create();
obs_data_set_string(fields, "scene-name", scene_name);
@ -538,4 +613,35 @@ void WSEvents::OnSceneItemVisibilityChanged(void *param, calldata_t *data)
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
obs_data_release(fields);
}
}
void WSEvents::SelectedSceneChanged(QListWidgetItem* current, QListWidgetItem* prev)
{
if (Utils::IsPreviewModeActive())
{
obs_scene_t* scene = Utils::SceneListItemToScene(current);
if (!scene) return;
obs_source_t* scene_source = obs_scene_get_source(scene);
obs_data_array_t* scene_items = Utils::GetSceneItems(scene_source);
obs_data_t* data = obs_data_create();
obs_data_set_string(data, "scene-name", obs_source_get_name(scene_source));
obs_data_set_array(data, "sources", scene_items);
broadcastUpdate("PreviewSceneChanged", data);
obs_data_array_release(scene_items);
obs_data_release(data);
}
}
void WSEvents::ModeSwitchClicked(bool checked)
{
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "new-state", checked);
broadcastUpdate("StudioModeSwitched", data);
obs_data_release(data);
}

View File

@ -21,27 +21,39 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define WSEVENTS_H
#include <obs-frontend-api.h>
#include <QListWidgetItem>
#include "WSServer.h"
class WSEvents : public QObject
class WSEvents : public QObject
{
Q_OBJECT
public:
explicit WSEvents(WSServer *srv);
explicit WSEvents(WSServer* srv);
~WSEvents();
static void FrontendEventHandler(enum obs_frontend_event event, void *private_data);
static void FrontendEventHandler(
enum obs_frontend_event event, void* private_data);
void connectTransitionSignals(obs_source_t* transition);
void connectSceneSignals(obs_source_t* scene);
static WSEvents* Instance;
uint64_t GetStreamingTime();
const char* GetStreamingTimecode();
uint64_t GetRecordingTime();
const char* GetRecordingTimecode();
private Q_SLOTS:
void deferredInitOperations();
void StreamStatus();
void TransitionDurationChanged(int ms);
void SelectedSceneChanged(
QListWidgetItem* current, QListWidgetItem* prev);
void ModeSwitchClicked(bool checked);
private:
WSServer *_srv;
signal_handler_t *transition_handler;
signal_handler_t *scene_handler;
WSServer* _srv;
signal_handler_t* transition_handler;
signal_handler_t* scene_handler;
bool _streaming_active;
bool _recording_active;
@ -52,8 +64,9 @@ class WSEvents : public QObject
uint64_t _lastBytesSent;
uint64_t _lastBytesSentTime;
void broadcastUpdate(const char *updateType, obs_data_t *additionalFields);
void broadcastUpdate(const char* updateType,
obs_data_t* additionalFields);
void OnSceneChange();
void OnSceneListChange();
void OnSceneCollectionChange();
@ -77,12 +90,12 @@ class WSEvents : public QObject
void OnExit();
static void OnTransitionBegin(void *param, calldata_t *data);
static void OnTransitionBegin(void* param, calldata_t* data);
static void OnSceneReordered(void *param, calldata_t *data);
static void OnSceneItemAdd(void *param, calldata_t *data);
static void OnSceneItemDelete(void *param, calldata_t *data);
static void OnSceneItemVisibilityChanged(void *param, calldata_t *data);
static void OnSceneReordered(void* param, calldata_t* data);
static void OnSceneItemAdd(void* param, calldata_t* data);
static void OnSceneItemDelete(void* param, calldata_t* data);
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
};
#endif // WSEVENTS_H

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define WSREQUESTHANDLER_H
#include <obs-frontend-api.h>
#include <QtWebSockets/QWebSocket>
#include <QtWebSockets/QWebSocketServer>
@ -29,57 +30,87 @@ class WSRequestHandler : public QObject
Q_OBJECT
public:
explicit WSRequestHandler(QWebSocket *client);
explicit WSRequestHandler(QWebSocket* client);
~WSRequestHandler();
void processIncomingMessage(QString textMessage);
bool hasField(const char* name);
private:
QWebSocket *_client;
const char *_messageId;
const char *_requestType;
obs_data_t *_requestData;
static obs_service_t* _service;
QWebSocket* _client;
const char* _messageId;
const char* _requestType;
obs_data_t* data;
QMap<QString, void(*)(WSRequestHandler*)> messageMap;
QSet<QString> authNotRequired;
void SendOKResponse(obs_data_t *additionalFields = NULL);
void SendErrorResponse(const char *errorMessage);
static void ErrNotImplemented(WSRequestHandler *owner);
void SendOKResponse(obs_data_t* additionalFields = NULL);
void SendErrorResponse(const char* errorMessage);
void SendResponse(obs_data_t* response);
static void HandleGetVersion(WSRequestHandler* req);
static void HandleGetAuthRequired(WSRequestHandler* req);
static void HandleAuthenticate(WSRequestHandler* req);
static void HandleSetCurrentScene(WSRequestHandler* req);
static void HandleGetCurrentScene(WSRequestHandler* req);
static void HandleGetSceneList(WSRequestHandler* req);
static void HandleSetSceneItemRender(WSRequestHandler* req);
static void HandleSetSceneItemPosition(WSRequestHandler* req);
static void HandleSetSceneItemTransform(WSRequestHandler* req);
static void HandleSetSceneItemCrop(WSRequestHandler* req);
static void HandleGetStreamingStatus(WSRequestHandler* req);
static void HandleStartStopStreaming(WSRequestHandler* req);
static void HandleStartStopRecording(WSRequestHandler* req);
static void HandleStartStreaming(WSRequestHandler* req);
static void HandleStopStreaming(WSRequestHandler* req);
static void HandleStartRecording(WSRequestHandler* req);
static void HandleStopRecording(WSRequestHandler* req);
static void HandleSetRecordingFolder(WSRequestHandler* req);
static void HandleGetRecordingFolder(WSRequestHandler* req);
static void HandleGetTransitionList(WSRequestHandler* req);
static void HandleGetCurrentTransition(WSRequestHandler* req);
static void HandleSetCurrentTransition(WSRequestHandler* req);
static void HandleSetVolume(WSRequestHandler* req);
static void HandleGetVolume(WSRequestHandler* req);
static void HandleToggleMute(WSRequestHandler* req);
static void HandleSetMute(WSRequestHandler* req);
static void HandleGetMute(WSRequestHandler* req);
static void HandleGetSpecialSources(WSRequestHandler* req);
static void HandleSetCurrentSceneCollection(WSRequestHandler* req);
static void HandleGetCurrentSceneCollection(WSRequestHandler* req);
static void HandleListSceneCollections(WSRequestHandler* req);
static void HandleSetCurrentProfile(WSRequestHandler* req);
static void HandleGetCurrentProfile(WSRequestHandler* req);
static void HandleListProfiles(WSRequestHandler* req);
static void HandleSetStreamSettings(WSRequestHandler* req);
static void HandleGetStreamSettings(WSRequestHandler* req);
static void HandleSaveStreamSettings(WSRequestHandler* req);
static void HandleSetTransitionDuration(WSRequestHandler* req);
static void HandleGetTransitionDuration(WSRequestHandler* req);
static void HandleGetStudioModeStatus(WSRequestHandler* req);
static void HandleGetPreviewScene(WSRequestHandler* req);
static void HandleSetPreviewScene(WSRequestHandler* req);
static void HandleTransitionToProgram(WSRequestHandler* req);
static void HandleEnableStudioMode(WSRequestHandler* req);
static void HandleDisableStudioMode(WSRequestHandler* req);
static void HandleToggleStudioMode(WSRequestHandler* req);
static void HandleGetVersion(WSRequestHandler *owner);
static void HandleGetAuthRequired(WSRequestHandler *owner);
static void HandleAuthenticate(WSRequestHandler *owner);
static void HandleSetCurrentScene(WSRequestHandler *owner);
static void HandleGetCurrentScene(WSRequestHandler *owner);
static void HandleGetSceneList(WSRequestHandler *owner);
static void HandleSetSourceRender(WSRequestHandler *owner);
static void HandleSetSceneItemPosition(WSRequestHandler *owner);
static void HandleSetSceneItemTransform(WSRequestHandler *owner);
static void HandleGetStreamingStatus(WSRequestHandler *owner);
static void HandleStartStopStreaming(WSRequestHandler *owner);
static void HandleStartStopRecording(WSRequestHandler *owner);
static void HandleGetTransitionList(WSRequestHandler *owner);
static void HandleGetCurrentTransition(WSRequestHandler *owner);
static void HandleSetCurrentTransition(WSRequestHandler *owner);
static void HandleSetVolume(WSRequestHandler *owner);
static void HandleGetVolume(WSRequestHandler *owner);
static void HandleToggleMute(WSRequestHandler *owner);
static void HandleSetMute(WSRequestHandler *owner);
static void HandleSetCurrentSceneCollection(WSRequestHandler *owner);
static void HandleGetCurrentSceneCollection(WSRequestHandler *owner);
static void HandleListSceneCollections(WSRequestHandler *owner);
static void HandleSetCurrentProfile(WSRequestHandler *owner);
static void HandleGetCurrentProfile(WSRequestHandler *owner);
static void HandleListProfiles(WSRequestHandler *owner);
static void HandleSetTransitionDuration(WSRequestHandler *owner);
static void HandleSetTextGDIPlusProperties(WSRequestHandler* req);
static void HandleGetTextGDIPlusProperties(WSRequestHandler* req);
static void HandleSetBrowserSourceProperties(WSRequestHandler* req);
static void HandleGetBrowserSourceProperties(WSRequestHandler* req);
};
#endif // WSPROTOCOL_H

View File

@ -24,12 +24,13 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "WSServer.h"
#include "obs-websocket.h"
#include "Config.h"
#include "Utils.h"
QT_USE_NAMESPACE
WSServer* WSServer::Instance = new WSServer();
WSServer* WSServer::Instance = nullptr;
WSServer::WSServer(QObject *parent) :
WSServer::WSServer(QObject* parent) :
QObject(parent),
_wsServer(Q_NULLPTR),
_clients(),
@ -40,16 +41,14 @@ WSServer::WSServer(QObject *parent) :
_wsServer = new QWebSocketServer(
QStringLiteral("obs-websocket"),
QWebSocketServer::NonSecureMode,
this);
_serverThread);
_wsServer->moveToThread(_serverThread);
_serverThread->start();
}
WSServer::~WSServer()
{
Stop();
delete _serverThread;
}
@ -64,15 +63,15 @@ void WSServer::Start(quint16 port)
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
if (serverStarted)
{
connect(_wsServer, &QWebSocketServer::newConnection, this, &WSServer::onNewConnection);
connect(_wsServer, &QWebSocketServer::newConnection,
this, &WSServer::onNewConnection);
}
}
void WSServer::Stop()
{
_clMutex.lock();
Q_FOREACH(QWebSocket *pClient, _clients)
{
for(QWebSocket* pClient : _clients) {
pClient->close();
}
_clMutex.unlock();
@ -84,8 +83,7 @@ void WSServer::broadcast(QString message)
{
_clMutex.lock();
Q_FOREACH(QWebSocket *pClient, _clients)
{
for(QWebSocket* pClient : _clients) {
if (Config::Current()->AuthRequired
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false))
{
@ -101,26 +99,39 @@ void WSServer::broadcast(QString message)
void WSServer::onNewConnection()
{
QWebSocket *pSocket = _wsServer->nextPendingConnection();
QWebSocket* pSocket = _wsServer->nextPendingConnection();
if (pSocket)
{
connect(pSocket, &QWebSocket::textMessageReceived, this, &WSServer::textMessageReceived);
connect(pSocket, &QWebSocket::disconnected, this, &WSServer::socketDisconnected);
connect(pSocket, &QWebSocket::textMessageReceived,
this, &WSServer::textMessageReceived);
connect(pSocket, &QWebSocket::disconnected,
this, &WSServer::socketDisconnected);
pSocket->setProperty(PROP_AUTHENTICATED, false);
_clMutex.lock();
_clients << pSocket;
_clMutex.unlock();
QByteArray client_ip = pSocket->peerAddress().toString().toUtf8();
blog(LOG_INFO, "new client connection from %s:%d", client_ip.constData(), pSocket->peerPort());
QHostAddress clientAddr = pSocket->peerAddress();
QString clientIp = Utils::FormatIPAddress(clientAddr);
blog(LOG_INFO, "new client connection from %s:%d",
clientIp.toUtf8().constData(), pSocket->peerPort());
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
+ QString(" ")
+ clientAddr.toString();
Utils::SysTrayNotify(msg,
QSystemTrayIcon::Information,
QString(obs_module_text("OBSWebsocket.ConnectNotify.Connected")));
}
}
void WSServer::textMessageReceived(QString message)
void WSServer::textMessageReceived(QString message)
{
QWebSocket *pSocket = qobject_cast<QWebSocket *>(sender());
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
if (pSocket)
{
@ -131,7 +142,7 @@ void WSServer::textMessageReceived(QString message)
void WSServer::socketDisconnected()
{
QWebSocket *pSocket = qobject_cast<QWebSocket *>(sender());
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
if (pSocket)
{
@ -140,10 +151,21 @@ void WSServer::socketDisconnected()
_clMutex.lock();
_clients.removeAll(pSocket);
_clMutex.unlock();
pSocket->deleteLater();
QByteArray client_ip = pSocket->peerAddress().toString().toUtf8();
blog(LOG_INFO, "client %s:%d disconnected", client_ip.constData(), pSocket->peerPort());
QHostAddress clientAddr = pSocket->peerAddress();
QString clientIp = Utils::FormatIPAddress(clientAddr);
blog(LOG_INFO, "client %s:%d disconnected",
clientIp.toUtf8().constData(), pSocket->peerPort());
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
+ QString(" ")
+ clientAddr.toString();
Utils::SysTrayNotify(msg,
QSystemTrayIcon::Information,
QString(obs_module_text("OBSWebsocket.ConnectNotify.Disconnected")));
}
}
}

View File

@ -33,7 +33,7 @@ class WSServer : public QObject
Q_OBJECT
public:
explicit WSServer(QObject *parent = Q_NULLPTR);
explicit WSServer(QObject* parent = Q_NULLPTR);
virtual ~WSServer();
void Start(quint16 port);
void Stop();
@ -46,10 +46,10 @@ class WSServer : public QObject
void socketDisconnected();
private:
QWebSocketServer *_wsServer;
QList<QWebSocket *> _clients;
QWebSocketServer* _wsServer;
QList<QWebSocket*> _clients;
QMutex _clMutex;
QThread *_serverThread;
QThread* _serverThread;
};
#endif // WSSERVER_H

View File

@ -15,7 +15,7 @@ install:
- set build_config=Release
- git clone --recursive https://github.com/jp9000/obs-studio
- cd C:\projects\obs-studio\
- git checkout 18.0.0
- git checkout 19.0.2
- mkdir build
- mkdir build32
- mkdir build64

View File

@ -3,4 +3,8 @@ OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Enable Websocket server"
OBSWebsocket.Settings.ServerPort="Server Port"
OBSWebsocket.Settings.AuthRequired="Enable authentication"
OBSWebsocket.Settings.Password="Password"
OBSWebsocket.Settings.Password="Password"
OBSWebsocket.Settings.DebugEnable="Enable debug logging"
OBSWebsocket.ConnectNotify.Connected="New WebSocket connection"
OBSWebsocket.ConnectNotify.Disconnected="WebSocket client disconnected"
OBSWebsocket.ConnectNotify.ClientIP="Client Address:"

View File

@ -3,4 +3,7 @@ OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Activer le serveur Websockets"
OBSWebsocket.Settings.ServerPort="Port du serveur"
OBSWebsocket.Settings.AuthRequired="Activer l'authentification"
OBSWebsocket.Settings.Password="Mot de passe"
OBSWebsocket.Settings.Password="Mot de passe"
OBSWebsocket.ConnectNotify.Connected="Nouvelle connexion WebSocket"
OBSWebsocket.ConnectNotify.Disconnected="Déconnexion WebSocket"
OBSWebsocket.ConnectNotify.ClientIP="Adresse du client :"

6
data/locale/nl-NL.ini Normal file
View File

@ -0,0 +1,6 @@
OBSWebsocket.Menu.SettingsItem="Websocket server instellingen"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Activeer Websocket server"
OBSWebsocket.Settings.ServerPort="Server Poort"
OBSWebsocket.Settings.AuthRequired="Activeer authenticatie"
OBSWebsocket.Settings.Password="Wachtwoord"

6
data/locale/pl-PL.ini Normal file
View File

@ -0,0 +1,6 @@
OBSWebsocket.Menu.SettingsItem="Ustawienia serwera zdalnego sterowania"
OBSWebsocket.Settings.DialogTitle="Serwer zdalnego sterowania"
OBSWebsocket.Settings.ServerEnable="Włącz serwer zdalnego sterowania (Websocket)"
OBSWebsocket.Settings.ServerPort="Port serwera"
OBSWebsocket.Settings.AuthRequired="Wymagaj hasła"
OBSWebsocket.Settings.Password="Hasło"

6
data/locale/pt-BR.ini Normal file
View File

@ -0,0 +1,6 @@
OBSWebsocket.Menu.SettingsItem="Configuraçes do Servidor Websocket"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Habilitar o Servidor Websocket"
OBSWebsocket.Settings.ServerPort="Porta do Servidor"
OBSWebsocket.Settings.AuthRequired="Autenticação Requerida"
OBSWebsocket.Settings.Password="Senha"

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -26,24 +26,29 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define CHANGE_ME "changeme"
SettingsDialog::SettingsDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::SettingsDialog)
SettingsDialog::SettingsDialog(QWidget* parent) :
QDialog(parent, Qt::Dialog),
ui(new Ui::SettingsDialog)
{
ui->setupUi(this);
connect(ui->authRequired, &QCheckBox::stateChanged, this, &SettingsDialog::AuthCheckboxChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::FormAccepted);
ui->setupUi(this);
connect(ui->authRequired, &QCheckBox::stateChanged,
this, &SettingsDialog::AuthCheckboxChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted,
this, &SettingsDialog::FormAccepted);
AuthCheckboxChanged();
}
void SettingsDialog::showEvent(QShowEvent *event)
void SettingsDialog::showEvent(QShowEvent* event)
{
Config* conf = Config::Current();
ui->serverEnabled->setChecked(conf->ServerEnabled);
ui->serverPort->setValue(conf->ServerPort);
ui->debugEnabled->setChecked(conf->DebugEnabled);
ui->authRequired->setChecked(conf->AuthRequired);
ui->password->setText(CHANGE_ME);
@ -51,22 +56,18 @@ void SettingsDialog::showEvent(QShowEvent *event)
void SettingsDialog::ToggleShowHide()
{
if (!isVisible()) {
if (!isVisible())
setVisible(true);
}
else {
else
setVisible(false);
}
}
void SettingsDialog::AuthCheckboxChanged()
{
if (ui->authRequired->isChecked()) {
if (ui->authRequired->isChecked())
ui->password->setEnabled(true);
}
else {
else
ui->password->setEnabled(false);
}
}
void SettingsDialog::FormAccepted()
@ -75,6 +76,8 @@ void SettingsDialog::FormAccepted()
conf->ServerEnabled = ui->serverEnabled->isChecked();
conf->ServerPort = ui->serverPort->value();
conf->DebugEnabled = ui->debugEnabled->isChecked();
if (ui->authRequired->isChecked())
{
@ -85,15 +88,11 @@ void SettingsDialog::FormAccepted()
conf->SetPassword(new_password);
}
if (strcmp(Config::Current()->Secret, "") != 0)
{
conf->AuthRequired = true;
}
else
{
conf->AuthRequired = false;
}
}
else
{
@ -103,16 +102,12 @@ void SettingsDialog::FormAccepted()
conf->Save();
if (conf->ServerEnabled)
{
WSServer::Instance->Start(conf->ServerPort);
}
else
{
WSServer::Instance->Stop();
}
}
SettingsDialog::~SettingsDialog()
{
delete ui;
delete ui;
}

View File

@ -27,12 +27,12 @@ class SettingsDialog;
class SettingsDialog : public QDialog
{
Q_OBJECT
Q_OBJECT
public:
explicit SettingsDialog(QWidget *parent = 0);
~SettingsDialog();
void showEvent(QShowEvent *event);
explicit SettingsDialog(QWidget* parent = 0);
~SettingsDialog();
void showEvent(QShowEvent* event);
void ToggleShowHide();
private Q_SLOTS:
@ -40,7 +40,7 @@ private Q_SLOTS:
void FormAccepted();
private:
Ui::SettingsDialog *ui;
Ui::SettingsDialog* ui;
};
#endif // SETTINGSDIALOG_H

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>407</width>
<height>155</height>
<height>175</height>
</rect>
</property>
<property name="sizePolicy">
@ -79,6 +79,16 @@
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="debugEnabled">
<property name="text">
<string>OBSWebsocket.Settings.DebugEnable</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -103,11 +113,11 @@
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
<y>274</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
<y>294</y>
</hint>
</hints>
</connection>
@ -119,11 +129,11 @@
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
<y>280</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
<y>294</y>
</hint>
</hints>
</connection>

View File

@ -31,7 +31,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
WSEvents *eventHandler;
SettingsDialog *settings_dialog;
bool obs_module_load(void)
@ -42,13 +41,15 @@ bool obs_module_load(void)
Config* config = Config::Current();
config->Load();
WSServer::Instance = new WSServer();
WSEvents::Instance = new WSEvents(WSServer::Instance);
if (config->ServerEnabled)
WSServer::Instance->Start(config->ServerPort);
eventHandler = new WSEvents(WSServer::Instance);
// UI setup
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(obs_module_text("OBSWebsocket.Menu.SettingsItem"));
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(
obs_module_text("OBSWebsocket.Menu.SettingsItem"));
obs_frontend_push_ui_translation(obs_module_get_string);
QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window();

View File

@ -20,7 +20,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define OBSWEBSOCKET_H
#define PROP_AUTHENTICATED "wsclient_authenticated"
#define OBS_WEBSOCKET_VERSION "4.0.0"
#define OBS_WEBSOCKET_VERSION "4.1.0"
#define API_VERSION 1.3
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)