Compare commits

...

378 Commits
0.3.2 ... 4.2.0

Author SHA1 Message Date
cc0b110d61 Utils: style nitpick 2017-09-08 21:54:40 +02:00
82a1e7d253 docs(travis): Update protocol.md - 87b4768 [skip ci] 2017-09-08 19:43:16 +00:00
87b47689eb Docs: more specific version number + tag unreleased methods/events 2017-09-08 21:42:13 +02:00
c3346f9192 Notifications: correct IP address formatting 2017-09-08 00:23:17 +02:00
3b0cf02574 Server: QThread wasn't actually used... 2017-09-08 00:01:26 +02:00
4404889019 Merge pull request #117 from haganbmj/docs-since
docs: Add @since
2017-09-01 14:07:20 +02:00
4e642d97fc docs: Add @since 2017-08-26 16:25:17 -04:00
44bd25646d docs(travis): Update protocol.md - 09b0403 [skip ci] 2017-08-24 18:28:28 +00:00
09b04037a0 Merge pull request #114 from haganbmj/patch-1
Update api doc for SwitchScenes
2017-08-24 20:27:18 +02:00
3c50e1e4d8 Update api doc for SwitchScenes 2017-08-24 10:18:00 -05:00
179ba9a9e6 docs(travis): Update protocol.md - 9ce2b19 [skip ci] 2017-08-19 13:06:57 +00:00
9ce2b1983a Protocol: add Replay Buffer Events and Requests (#104) 2017-08-19 15:05:42 +02:00
37dde278cb CI: updated name hack 2017-08-19 12:29:12 +02:00
1cc57e7d1d CI: hack to show "build names" in Travis 2017-08-19 12:26:23 +02:00
60a5cdb154 docs(travis): Update protocol.md - f290cbc [skip ci] 2017-08-19 10:17:09 +00:00
f290cbc148 Docs: update introduction with other versions 2017-08-19 12:15:57 +02:00
2e2e9b1332 Code: small style fixes 2017-08-19 11:40:42 +02:00
7915e3815b CMake on Windows: copy debug binaries to obs dev folder for testing 2017-08-19 11:40:25 +02:00
beb34deed8 docs(travis): Update protocol.md - a263d8a [skip ci] 2017-08-13 15:42:56 +00:00
a263d8a364 Docs: Initial generation of docs from code comments (#106) 2017-08-13 17:41:42 +02:00
1eccf4c899 Readme: update libraries and contributors 2017-08-12 22:11:06 +02:00
d8d19d839a CI: add branch exclusion for gh-pages 2017-08-12 17:48:23 +02:00
17e573d67f Merge pull request #109 from dragonbane0/request-types-sync-offset
Add request types: SetSyncOffset and GetSyncOffset
2017-08-12 14:51:26 +02:00
59de83b45d Update Protocol.md with the new request types
Added SetSyncOffset and GetSyncOffset to the documentation
2017-08-07 16:02:09 +02:00
712bd6e8f3 Fix if statement
Add missing closing bracket and use !source_name instead of source_name == NULL
2017-08-07 15:34:43 +02:00
75b21d070a Fix spacing 2017-08-07 02:34:54 +02:00
e91c9e7bf4 Fixed spacing 2017-08-07 02:34:20 +02:00
2562272775 Added new request types: SetSyncOffset and GetSyncOffset 2017-08-07 02:19:09 +02:00
819621e307 macOS CI: fix dynlink paths 2017-08-06 22:59:44 +02:00
4b9229311c macOS CI: oops, forgot to update some lines 2017-08-06 22:46:30 +02:00
4c4c1de190 macOS CI: "brew versions" command doesn't work 2017-08-06 22:35:16 +02:00
6b6b7feff2 macOS CI: properly use Qt from Homebrew 2017-08-06 22:28:07 +02:00
9e3ce26ba0 Revert "macOS CI: install qt5 from Homebrew's index"
This reverts commit ee1486274d.
2017-08-06 22:12:36 +02:00
eac19a7c83 Logging: log Qt versions at startup 2017-08-06 21:50:25 +02:00
ee1486274d macOS CI: install qt5 from Homebrew's index 2017-08-06 21:45:24 +02:00
8692e2afda CI: OBS version bump 2017-08-06 07:12:04 +02:00
f995268444 macOS CI: symlink paths were wrong, again 2017-08-06 07:11:03 +02:00
bd2e974718 macOS CI: fix symlink paths 2017-08-06 06:56:00 +02:00
d1c64c7509 macOS CI: more debug statements 2017-08-06 06:30:00 +02:00
30a19cfe8a macOS CI: add debug statements for rpath fix 2017-08-06 06:22:24 +02:00
cc3097b09a GetVersion: add claryfying comment 2017-08-06 05:12:30 +02:00
c00681b52d Protocol: remove useless API version field 2017-08-06 05:10:14 +02:00
61931c179f Protocol: update GetVersion with list of available request types 2017-08-06 00:09:44 +02:00
c7190cb94a Events: add transition info to TransitionBegin 2017-08-05 22:17:13 +02:00
a1bd27dfde Server: fix refactoring mistake 2017-08-05 19:16:30 +02:00
6ac3a3de57 Protocol: add ResetSceneItem for resetting source items (#108)
Use case: Reset media sources for recovery of timed-out input streams.
2017-08-05 19:11:01 +02:00
e198ed7a9c GitHub: update issue template 2017-08-05 18:50:03 +02:00
4b89464349 General: refactor continued again 2017-08-05 18:26:14 +02:00
dba599c127 General: refactor continued 2017-08-05 03:21:28 +02:00
586f9076f0 General: code style refactor 2017-08-05 03:14:07 +02:00
add39cfc5f General: version bump 2017-07-10 14:23:13 +02:00
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
0672b6a157 Fixed typo in PROTOCOL.md 2017-03-02 15:55:46 +01:00
1e71bfa151 Fixed a crash when switching to another scene after switching to another scene collection 2017-03-02 15:36:48 +01:00
d4c2c8197a Merge branch 'master' of github.com:Palakis/obs-websocket 2017-03-02 15:07:11 +01:00
c9baed2df9 Fixes #24 2017-03-02 15:02:26 +01:00
1e2065c84a Merge pull request #50 from G-monitor/chinese
Add chinese translation
2017-03-02 14:24:28 +01:00
537d683dfb Add chinese translation 2017-03-02 17:46:10 +08:00
e6f1b9f8c8 Travis: trying something 2017-03-02 01:08:57 +01:00
5815d2bfce Lower minimum cmake version 2017-03-02 00:49:35 +01:00
7692e93306 Travis: install Qt Websockets from source 2017-03-02 00:44:23 +01:00
8768b83251 Added execute permission to Travis CI scripts 2017-03-02 00:29:36 +01:00
35d18810fc Setting up Travis CI 2017-03-02 00:25:58 +01:00
ccd40a1834 Update README.md 2017-03-01 23:29:13 +01:00
5c1f0c3541 CI: Move cached deps outside of obs-studio source dir 2017-03-01 22:46:17 +01:00
e237e52ae4 CI: Force Qt 5.7.0 like OBS Releases 2017-03-01 22:43:29 +01:00
effec90528 More fixes for appveyor 2017-03-01 22:13:34 +01:00
de4a2247c0 CI: Change MSVC version 2017-03-01 22:06:07 +01:00
b20b7cbc98 Appveyor artifact name change 2017-03-01 22:02:06 +01:00
3c347b9b77 Oops, typo 2017-03-01 21:50:20 +01:00
794c4066d8 Refactored appveyor.yml 2017-03-01 21:49:42 +01:00
8c8e3072a7 Fix appveyor artifact packaging 2017-03-01 21:45:40 +01:00
228708eec4 Added update type SceneItemVisibilityChanged 2017-03-01 21:41:42 +01:00
dcafaedaa8 What the hell happens with LibObs_DIR 2017-03-01 21:22:14 +01:00
a85297f2c8 CI: Build artifact from obs-websocket release folder 2017-03-01 21:12:37 +01:00
91f7450cbd CI Fix 2017-03-01 21:07:50 +01:00
e7b074991d Fix appveyor CI + fix build script 2017-03-01 20:59:35 +01:00
5f83ce2a28 Setting up Appveyor CI 2017-03-01 20:46:21 +01:00
44af896dee Force C++11 on Linux 2017-03-01 17:53:44 +01:00
9eaa9a98ee Use UTF-8 strings for the password 2017-03-01 15:42:38 +01:00
629880cd58 Update README.md 2017-02-28 20:40:51 +01:00
42266ed14f Fixes #17 2017-02-28 16:56:03 +01:00
9dd7a197e4 Update PROTOCOL.md 2017-02-28 16:05:41 +01:00
6f39da20a9 Removed TransitionEnd event + changed TransitionBegin triggering 2017-02-28 16:01:06 +01:00
1ebb6f9257 Transition events + scene change compatibility with OBS 18.0.0 2017-02-28 15:40:40 +01:00
e30e982ef0 Added update type TransitionDurationChanged 2017-02-27 13:24:25 +01:00
42a80c6185 Ability to get and set transition duration 2017-02-27 11:53:44 +01:00
0d495f4d65 Merge pull request #46 from mikhailswift/add_scene_name_to_other_source_commands
Add scene name to SetSceneItemPosition and SetSceneItemTransform
2017-02-27 00:08:06 +01:00
71f5e66bd1 Added back accidental nuked new line 2017-02-26 18:03:20 -05:00
a6f71b68f3 Update protocol.md 2017-02-26 18:02:46 -05:00
a527f343cd Moved some repeated code to utils, added source_name to other commands 2017-02-26 18:01:09 -05:00
f8e1c454d9 Merge pull request #45 from mikhailswift/toggle_source_on_specified_scene
Toggle source on specified scene
2017-02-26 23:24:02 +01:00
da6e55f09f Used same error message elsewhere, fixed null check 2017-02-26 17:13:50 -05:00
d277e2788a Fixed missed changes from currentScene -> scene 2017-02-26 16:40:29 -05:00
53ba747b78 Update protocol readme for added parameter 2017-02-26 16:36:38 -05:00
b9862acd1d Add scene-name optional parmeter to set source render command 2017-02-26 16:35:08 -05:00
d1c19382a1 Fixes #34 2017-02-26 19:46:09 +01:00
4141983ccd Fixed a potential bug + WIP on scene collection/profile change 2017-02-26 17:04:48 +01:00
2b8b5001e0 Merge branch 'master' of github.com:Palakis/obs-websocket 2017-02-25 17:43:52 +01:00
532126561e Rollback changes : my dev environment had the wrong Qt version (5.7.0-1 instead of 5.7.0) 2017-02-25 17:42:58 +01:00
3c026c4eef Update README.md 2017-02-25 17:33:46 +01:00
ae6c15158f Bugfix : copy issue in CMakeLists.txt 2017-02-25 16:39:55 +01:00
6aa57247f9 Better build instructions (including release packager on Windows) 2017-02-25 16:32:20 +01:00
50862ac945 Typo fix 2017-02-25 14:48:09 +01:00
01ce6faa20 Add german translation to CMakeLists.txt 2017-02-24 20:33:45 +01:00
6e571aef95 Fixed issue with exception "Must construct a QApplication before a QWidget" 2017-02-24 20:26:19 +01:00
d6091c83e2 A bit of cleaning 2017-02-24 20:09:42 +01:00
5ca55fc13e sprintf is more cross-platform 2017-02-24 18:30:04 +01:00
2b60da02e8 sprintf_s() is not cross-platform 2017-02-24 17:40:41 +01:00
f02297152c Update Utils.h 2017-02-24 17:37:44 +01:00
af7c0bbc72 Removed a log message with a password in it 2017-02-24 00:50:16 +01:00
40727d5a6c Changed config key names from snake_case to CamelCase 2017-02-23 22:40:11 +01:00
9bdd73dbc6 Merge pull request #41 from Frahmer/german-translation-2
Add german translation
2017-02-23 22:34:22 +01:00
3e1ed09f12 Update README.md 2017-02-23 22:31:56 +01:00
3682c625d7 Add german translation
Add "ServerEnable" and "ServerPort" translation.
2017-02-23 22:30:08 +01:00
70cd52ac95 Update README.md 2017-02-23 22:29:42 +01:00
3264da4b2f Update README.md 2017-02-23 22:29:24 +01:00
06c1648f55 Updated PROTOCOL.md : new table of contents 2017-02-23 22:21:06 +01:00
94c3e5d41d Update PROTOCOL.md : better lisibility 2017-02-23 22:07:14 +01:00
7ae20d8c3b Semver : version number row left shift to 4.0.0 2017-02-23 21:36:00 +01:00
0ff4411abf Bugfix : deadlock when closing the server
Caused by a non-recursive mutex
2017-02-23 21:25:26 +01:00
3d68b7c9e5 Updated locale variables names + WIP Dynamic Server Settings 2017-02-23 21:01:24 +01:00
f8b1cae0c9 Merge pull request #40 from Frahmer/german-translation
Add german translation
2017-02-23 18:28:54 +01:00
5bee0fc453 Add german translation 2017-02-22 19:30:15 +01:00
7162765824 Fix compiler warning 2017-02-20 23:03:00 +01:00
99aa6be887 Updated PROTOCOL.md and fixed some types 2017-02-17 17:01:25 +01:00
afaaff298f Fixes #35 2017-02-17 11:41:41 +01:00
68cf9af6a3 Provide a item's base size in its infos 2017-02-17 11:25:46 +01:00
8d5752d6b5 Version bump to the next release during dev 2017-02-15 20:18:06 +01:00
98dbcc4c69 Copyright update and credit where it's due 2017-02-15 18:33:32 +01:00
687d8fd120 PROTOCOL.md fix 2017-02-15 18:27:37 +01:00
114ace23f7 Added OBS version number in the response fields of GetVersion 2017-02-15 18:25:44 +01:00
3fbc221db0 Style changes 2017-02-15 17:09:34 +01:00
f2e6e137a6 Fixed #21 2017-02-15 11:35:35 +01:00
bb232f1b3e Merge pull request #37 from mikhailswift/add_mute_source_command
Add mute source commands
2017-02-13 21:17:16 +01:00
9dc153bc22 Bugfix : std's map and set can cause crashes 2017-02-13 19:03:52 +01:00
b9bbdf5978 Renamed pClient to pHandler 2017-02-13 18:25:07 +01:00
e0db0e394d Added missing return statements to ToggleMute and SetMute handlers 2017-02-13 12:09:44 -05:00
3e9001721e Fixed incorrect header level for SetMute and ToggleMute 2017-02-13 12:08:36 -05:00
bbf3b0f86f updated mistake in protocol.md 2017-02-12 23:06:00 -05:00
7f3eb9f11b update spaces to tabs in wsrequesthandler for toggle and set mute 2017-02-12 22:54:29 -05:00
c783c51915 update spaces to tabs in wsrequesthandler for set and toggle mute 2017-02-12 22:52:20 -05:00
0816d222c6 Fixed mistake in ToggleMute 2017-02-12 17:55:06 -05:00
af16c70143 Added ToggleMute and SetMute handlers, updated PROTOCOL.md 2017-02-12 18:46:02 +00:00
748b6f6e2e Protect clients object list with a QMutex 2017-02-12 19:20:32 +01:00
3bd600ed52 Preliminary work on event timestamping 2017-02-05 02:00:21 +01:00
78e6ad0f59 Fixes #27 2017-02-05 01:46:38 +01:00
2d71dc68f1 WTH is this 2017-02-05 00:36:55 +01:00
7dc2a00d47 Fixes #10 and #28 2017-02-04 21:38:19 +01:00
7ece78a05b Merge pull request #33 from haganbmj/master
[Protocol] Add OnTransitionChange/OnTransitionListChange (Fixes #19)
2017-02-04 19:06:01 +01:00
ff2bace1bf [Protocol] Add OnTransitionChange/OnTransitionListChange (Fixes #19) 2017-02-03 01:30:54 -05:00
e63ce01bdc Update README.md 2017-01-17 14:07:04 +01:00
4439ce71d0 Really fixed IP address issue 2016-12-13 00:08:32 +01:00
27ec094775 Fixed char pointer issue in log messages 2016-12-12 21:08:59 +01:00
1bed53e07b Changed websocket server name 2016-12-12 18:53:41 +01:00
8ae06c0c4f Update README.md 2016-12-10 21:51:51 +01:00
372db7865b Update README.md 2016-12-10 21:50:19 +01:00
cfeffc551e Bumped installer version 2016-12-10 21:24:22 +01:00
56 changed files with 12199 additions and 1196 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

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

@ -0,0 +1,16 @@
##### Issue type
Bug report? Feature request? Other?
##### Description
*Replace this with a description of the bug encountered or feature requested.*
##### Steps to reproduce and other useful info
*If it's a bug, please describe the steps to reproduce it and PLEASE include an OBS log file. 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.*

BIN
.github/images/mediaunit_logo_black.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

4
.gitignore vendored
View File

@ -1,5 +1,9 @@
*~
.DS_Store
/build/
/build32/
/build64/
/release/
/installer/Output/
.vscode

69
.travis.yml Normal file
View File

@ -0,0 +1,69 @@
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=
cache:
directories:
- node_modules
matrix:
include:
- os: linux
env: _generate_docs
script: "./CI/generate-docs.sh"
- os: linux
env: _linux_build
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
- os: osx
env: _macos_build
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"
- "-d /home/travis/package"
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)

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

@ -0,0 +1,12 @@
#!/bin/sh
set -ex
#export QT_PREFIX="$(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
mkdir build && cd build
cmake .. \
-DQTDIR=/usr/local/opt/qt \
-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

8
CI/build-xenial.sh Executable file
View File

@ -0,0 +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

32
CI/generate-docs.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
echo "-- Generating documentation."
echo "-- Node version: $(node -v)"
echo "-- NPM version: $(npm -v)"
cd docs
npm install
npm run build
echo "-- Documentation successfully generated."
if git diff --quiet; then
echo "-- No documentation changes to commit."
exit 0
fi
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "master" ]; then
echo "-- Skipping documentation deployment because this is either a pull request or a non-master branch."
exit 0
fi
REMOTE_URL="$(git config remote.origin.url)"
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
git config user.name "Travis CI"
git config user.email "$COMMIT_AUTHOR_EMAIL"
git add ./generated
git pull
git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH

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

@ -0,0 +1,28 @@
#!/bin/sh
set -ex
# OBS Studio deps
brew update
brew install ffmpeg
brew install libav
# qtwebsockets deps
brew install qt5
#echo "Qt path: $(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
# Build obs-studio
cd ..
git clone --recursive https://github.com/jp9000/obs-studio
cd obs-studio
git checkout 19.0.3
mkdir build && cd build
cmake .. \
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt/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

@ -0,0 +1,57 @@
#!/bin/sh
set -ex
# OBS Studio deps
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-dev \
libavdevice-dev \
libavfilter-dev \
libavformat-dev \
libavutil-dev \
libcurl4-openssl-dev \
libfontconfig-dev \
libfreetype6-dev \
libgl1-mesa-dev \
libjack-jackd2-dev \
libjansson-dev \
libpulse-dev \
libqt5x11extras5-dev \
libspeexdsp-dev \
libswresample-dev \
libswscale-dev \
libudev-dev \
libv4l-dev \
libvlc-dev \
libx11-dev \
libx264-dev \
libxcb-shm0-dev \
libxcb-xinerama0-dev \
libxcomposite-dev \
libxinerama-dev \
pkg-config \
qtbase5-dev
# obs-websocket deps
apt-get install -y libqt5websockets5-dev
# Build obs-studio
cd /root
git clone https://github.com/jp9000/obs-studio ./obs-studio
cd obs-studio
git checkout 19.0.3
mkdir build && cd build
cmake -DUNIX_STRUCTURE=1 -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4
make install
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>

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

@ -0,0 +1,66 @@
#!/bin/sh
set -e
echo "-- Preparing package build"
export QT_CELLAR_PREFIX="$(find /usr/local/Cellar/qt -d 1 | tail -n 1)"
export WS_LIB="/usr/local/opt/qt/lib/QtWebSockets.framework/QtWebSockets"
export NET_LIB="/usr/local/opt/qt/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"
install_name_tool \
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
-change $QT_CELLAR_PREFIX/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 $QT_CELLAR_PREFIX/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
-change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
./build/QtWebSockets
echo "-- Modifying obs-websocket.so"
install_name_tool \
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
./build/obs-websocket.so
# Check if replacement worked
echo "-- Dependencies for QtNetwork"
otool -L ./build/QtNetwork
echo "-- Dependencies for QtWebSockets"
otool -L ./build/QtWebSockets
echo "-- Dependencies for obs-websocket"
otool -L ./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

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.2)
project(obs-websocket)
set(CMAKE_PREFIX_PATH "${QTDIR}")
@ -7,13 +7,14 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
include(external/FindLibObs.cmake)
find_package(LibObs REQUIRED)
find_package(Qt5Core REQUIRED)
find_package(Qt5WebSockets REQUIRED)
find_package(Qt5Widgets REQUIRED)
add_subdirectory(deps/mbedtls EXCLUDE_FROM_ALL)
set(ENABLED_PROGRAMS false)
set(ENABLE_PROGRAMS false)
set(obs-websocket_SOURCES
obs-websocket.cpp
@ -33,10 +34,13 @@ set(obs-websocket_HEADERS
Utils.h
forms/settings-dialog.h)
# --- Platform-independent build settings ---
add_library(obs-websocket MODULE
${obs-websocket_SOURCES}
${obs-websocket_HEADERS})
add_dependencies(obs-websocket mbedcrypto)
include_directories(
"${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api"
${Qt5Core_INCLUDES}
@ -44,6 +48,7 @@ include_directories(
${Qt5Widgets_INCLUDES}
${mbedcrypto_INCLUDES}
"${CMAKE_SOURCE_DIR}/deps/mbedtls/include")
target_link_libraries(obs-websocket
libobs
Qt5::Core
@ -51,29 +56,107 @@ target_link_libraries(obs-websocket
Qt5::Widgets
mbedcrypto)
# --- End of section ---
# --- Windows-specific build settings and tasks ---
if(WIN32)
if(NOT DEFINED OBS_FRONTEND_LIB)
set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library")
if(OBS_FRONTEND_LIB EQUAL "OBS_FRONTEND_LIB-NOTFOUND")
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(ARCH_NAME "64bit")
set(OBS_BUILDDIR_ARCH "build64")
else()
set(ARCH_NAME "32bit")
set(OBS_BUILDDIR_ARCH "build32")
endif()
include_directories(
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/UI"
)
target_link_libraries(obs-websocket
"${OBS_FRONTEND_LIB}")
add_custom_command(TARGET obs-websocket POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
"${QTDIR}/bin/Qt5WebSockets.dll"
"${QTDIR}/bin/Qt5Network.dll"
"${CMAKE_BINARY_DIR}/$<CONFIG>")
endif()
COMMAND if $<CONFIG:Debug>==1 ("${CMAKE_COMMAND}" -E copy
"${QTDIR}/bin/Qt5WebSocketsd.dll"
"${QTDIR}/bin/Qt5Networkd.dll"
"${CMAKE_BINARY_DIR}/$<CONFIG>")
)
# --- Release package helper ---
# The "release" folder has a structure similar OBS' one on Windows
set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release")
add_custom_command(TARGET obs-websocket POST_BUILD
COMMAND if $<CONFIG:Release>==1 (
"${CMAKE_COMMAND}" -E make_directory
"${RELEASE_DIR}/data/obs-plugins/obs-websocket"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/data"
"${RELEASE_DIR}/data/obs-plugins/obs-websocket")
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:obs-websocket>"
"${QTDIR}/bin/Qt5WebSockets.dll"
"${QTDIR}/bin/Qt5Network.dll"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
# Copy to obs-studio dev environment for immediate testing
COMMAND if $<CONFIG:Debug>==1 (
"${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:obs-websocket>"
"${QTDIR}/bin/Qt5WebSocketsd.dll"
"${QTDIR}/bin/Qt5Networkd.dll"
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
COMMAND if $<CONFIG:Debug>==1 (
"${CMAKE_COMMAND}" -E make_directory
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/data/obs-plugins/obs-websocket")
COMMAND if $<CONFIG:Debug>==1 (
"${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/data"
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/data/obs-plugins/obs-websocket")
)
# --- End of sub-section ---
endif()
# --- End of section ---
# --- Linux-specific build settings and tasks ---
if(UNIX AND NOT APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
target_compile_options(mbedcrypto PRIVATE -fPIC)
set_target_properties(obs-websocket PROPERTIES PREFIX "")
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
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 ---
# -- 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

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,26 +19,51 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <mbedtls/base64.h>
#include <mbedtls/sha256.h>
#include <obs-frontend-api.h>
#include "Config.h"
#include <util/config-file.h>
#include <string>
#define CONFIG_SECTION_NAME "obs-websocket"
#define CONFIG_PARAM_SECRET "auth_hash"
#define CONFIG_PARAM_SALT "auth_salt"
#define CONFIG_PARAM_AUTHREQUIRED "auth_required"
#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"
#include "Config.h"
Config* Config::_instance = new Config();
Config::Config() {
// Default settings
AuthRequired = false;
Secret = "";
Salt = "";
SettingsLoaded = false;
Config::Config() :
ServerEnabled(true),
ServerPort(4444),
DebugEnabled(false),
AuthRequired(false),
Secret(""),
Salt(""),
SettingsLoaded(false) {
// OBS Config defaults
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_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);
}
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&rng);
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
//mbedtls_ctr_drbg_set_prediction_resistance(&rng, MBEDTLS_CTR_DRBG_PR_ON);
SessionChallenge = GenerateSalt();
}
@ -48,44 +73,71 @@ Config::~Config() {
mbedtls_entropy_free(&entropy);
}
void Config::Load() {
config_t* obs_config = obs_frontend_get_global_config();
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);
Salt = config_get_string(obs_config, SECTION_NAME, PARAM_SALT);
}
void Config::Save() {
config_t* obs_config = obs_frontend_get_global_config();
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);
config_set_string(obs_config, SECTION_NAME, PARAM_SALT, Salt);
config_save(obs_config);
}
const char* Config::GenerateSalt() {
// Generate 32 random chars
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);
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) {
@ -97,55 +149,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);
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;
}
void Config::OBSSaveCallback(obs_data_t *save_data, bool saving, void *private_data) {
Config *conf = static_cast<Config *>(private_data);
if (saving) {
obs_data_t *settings = obs_data_create();
obs_data_set_bool(settings, CONFIG_PARAM_AUTHREQUIRED, conf->AuthRequired);
obs_data_set_string(settings, CONFIG_PARAM_SECRET, conf->Secret);
obs_data_set_string(settings, CONFIG_PARAM_SALT, conf->Salt);
obs_data_set_obj(save_data, CONFIG_SECTION_NAME, settings);
}
else {
obs_data_t *settings = obs_data_get_obj(save_data, CONFIG_SECTION_NAME);
if (settings) {
conf->AuthRequired = obs_data_get_bool(settings, CONFIG_PARAM_AUTHREQUIRED);
conf->Secret = obs_data_get_string(settings, CONFIG_PARAM_SECRET);
conf->Salt = obs_data_get_string(settings, CONFIG_PARAM_SALT);
conf->SettingsLoaded = true;
}
}
bfree(hash);
bfree(expected_response);
return authSuccess;
}
Config* Config::Current() {

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,7 +19,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#ifndef CONFIG_H
#define CONFIG_H
#include <obs-module.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
@ -27,11 +26,19 @@ class Config {
public:
Config();
~Config();
void Load();
void Save();
void SetPassword(const char* password);
bool CheckAuth(const char* userChallenge);
const char* GenerateSalt();
static const char* GenerateSecret(const char *password, const char *salt);
static void OBSSaveCallback(obs_data_t *save_data, bool saving, void *);
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,241 +0,0 @@
obs-websocket protocol reference
================================
## 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
- [Events](#events)
- [Requests](#requests)
- [Authentication](#authentication)
## Events
### Description
Events are sent exclusively by the server and broadcast to each connected client.
An event message will contain at least one field :
- **update-type** (string) : the type of event
Additional fields will be present in the event message depending on the event type.
### Event Types
#### "SwitchScenes"
OBS is switching to another scene.
- **scene-name** (string) : The name of the scene being switched to.
#### "ScenesChanged"
The scene list has been modified (Scenes have been added, removed, or renamed).
#### "StreamStarting"
A request to start streaming has been issued.
- **preview-only** (bool) : Always false.
#### "StreamStarted"
Streaming started successfully.
*New in OBS Studio*
#### "StreamStopping"
A request to stop streaming has been issued.
- **preview-only** (bool) : Always false.
#### "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*
#### "StreamStatus"
Sent every 2 seconds with the following information :
- **streaming** (bool) : Current Streaming state.
- **recording** (bool) : Current Recording state.
- **preview-only** (bool) : Always false.
- **bytes-per-sec** (integer) : Amount of data per second (in bytes) transmitted by the stream encoder.
- **kbits-per-sec** (integer) : "bytes-per-sec" converted to kilobits per second
- **strain** (double) : Percentage of dropped frames
- **total-stream-time** (integer) : Total time (in seconds) since the stream started.
- **num-total-frames** (integer) : Total number of frames transmitted since the stream started.
- **num-dropped-frames** (integer) : Number of frames dropped by the encoder since the stream started.
- **fps** (double) : Current framerate.
#### "Exiting"
*New in OBS Studio*
OBS is exiting.
## Requests
### Description
Requests are sent by the client and must have at least the following two fields :
- **"request-type"** (string) : One of the request types listed in the sub-section "[Requests Types](#request-types)".
- **"message-id"** (string) : An identifier defined by the client which will be embedded in the server response.
Depending on the request type additional fields may be required (see the "[Request Types](#request-types)" section below for more information).
Once a request is sent, the server will return a JSON response with the following fields :
- **"message-id"** (string) : The identifier specified in the request.
- **"status"** (string) : Response status, will be one of the following : "ok", "error"
- **"error"** (string) : The error message associated with an "error" status.
Depending on the request type additional fields may be present (see the "[Request Types](#request-types)" section below for more information).
### Request Types
#### "GetVersion"
Returns the latest version of the plugin and the API.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"version"** (double) : OBSRemote API version. Fixed to 1.1 for retrocompatibility.
- **"obs-websocket-version"** (string) : obs-websocket version
#### "GetAuthRequired"
Tells the client if authentication is required. If it is, authentication parameters "challenge" and "salt" are passed in the response fields (see "Authentication").
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"authRequired"** (bool)
- **"challenge"** (string)
- **"salt"** (string)
#### "Authenticate"
Try to authenticate the client on the server.
__Request fields__ :
- **"auth"** (string) : response to the auth challenge (see "Authentication").
__Response__ : OK if auth succeeded, error if invalid credentials. No additional fields.
#### "GetCurrentScene"
Get the current scene's name and items.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"name"** (string) : name of the current scene
- **"sources"** (array of objects) : ordered list of the current scene's items descriptions
Objects in the "sources" array have the following fields :
- **"name"** (string) : name of the source associated with the scene item
- **"type"** (string) : internal source type name
- **"volume"** (double) : audio volume of the source, ranging from 0.0 to 1.0
- **"x"** (double) : X coordinate of the top-left corner of the item in the scene
- **"y"** (double) : Y coordinate of the top-left corner of the item in the scene
- **"cx"** (double) : width of the item (with scale applied)
- **"cy"** (double) : height of the item (with scale applied)
#### "SetCurrentScene"
Switch to the scene specified in "scene-name".
__Request fields__ :
- **"scene-name"** (string) : name of the scene to switch to.
__Response__ : always OK if scene exists, error if it doesn't. No additional fields
#### "GetSceneList"
List OBS' scenes.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"current-scene"** (string) : name of the currently active scene
- **"scenes"** (array of objects) : ordered list of scene descriptions (see `GetCurrentScene` for reference)
#### "SetSourceRender"
Show or hide a specific source in the current scene.
__Request fields__ :
- **"source"** (string) : name of the source in the currently active scene.
- **"render"** (bool) : desired visibility
__Response__ : OK if source exists in the current scene, error otherwise.
#### "StartStopStreaming"
Toggle streaming on or off.
__Request fields__ : none
__Response__ : always OK. No additional fields.
#### "StartStopRecording"
Toggle recording on or off.
__Request fields__ : none
__Response__ : always OK. No additional fields.
*New in OBS Studio*
#### "GetStreamingStatus"
Get current streaming and recording status.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"streaming"** (bool) : streaming status (active or not)
- **"recording"** (bool) : recording status (active or not)
- **"preview-only"** (bool) : always false. Retrocompat with OBSRemote.
#### "GetTransitionList"
List all transitions available in the frontend's dropdown menu.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"current-transition"** (string) : name of the current transition
- **"transitions"** (array of objects) : list of transition descriptions
Objects in the "transitions" array have only one field :
- **"name"** (string) : name of the transition
*New in OBS Studio*
#### "GetCurrentTransition"
Get the name of the currently selected transition in the frontend's dropdown menu.
__Request fields__ : none
__Request__ : always OK, with these additional fields :
- **"name"** (string) : name of the selected transition
*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*
### 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
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`, 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)
```

View File

@ -1,45 +1,70 @@
obs-websocket
==============
Websocket API for OBS Studio.
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.
## Using obs-websocket
The Websocket API server runs on port 4444 and a settings window is available in "Websocket server settings" under OBS' "Tools" menu. The obs-websocket protocol is documented in [PROTOCOL.md](PROTOCOL.md).
A web client and frontend made by [t2t2](https://github.com/t2t2/obs-tablet-remote) (compatible with tablets and other touch interfaces) is available here : http://t2t2.github.io/obs-tablet-remote/
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
There's currently no frontend available for obs-websocket.
It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it.
### Possible use cases
- Remote control OBS from a phone or tablet on the same local network
- Change your stream overlay/graphics based on the current scene (like the AGDQ overlay does)
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
### For developers
The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog).
The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/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 Brendan Hagan
- C#/VB.NET: [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet)
- Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
- Python 3.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik
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 with QtWebSockets, 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 gooder 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
- [yinzara](https://github.com/yinzara) : Code contributions
- [Chris Angelico](https://github.com/Rosuav) : Code contributions
- [Guillaume "Elektordi" Genty](https://github.com/Elektordi) : Code contributions
- [Marwin M](https://github.com/dragonbane0) : Code contributions
### OS X
*To do*
And also: special thanks to supporters of the project!
## 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](.github/images/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](.github/images/mediaunit_logo_black.png)](http://www.mediaunit.no/)

429
Utils.cpp
View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -16,16 +16,51 @@ 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 <QMainWindow>
#include <QDir>
#include <QUrl>
#include <obs-frontend-api.h>
#include <obs.hpp>
#include "obs-websocket.h"
#include "Utils.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();
char* value = "";
for (int i = 0; value != nullptr; i++) {
value = strings[i];
obs_data_t* item = obs_data_create();
obs_data_set_string(item, key, value);
if (value)
obs_data_array_push_back(list, item);
obs_data_release(item);
}
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_scene_enum_items(scene, [](obs_scene_t *scene, obs_sceneitem_t *currentItem, void *param) {
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);
@ -39,24 +74,32 @@ obs_data_array_t* Utils::GetSceneItems(obs_source_t *source) {
}
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
if (!item) {
return NULL;
}
if (!item)
return nullptr;
vec2 pos;
obs_sceneitem_get_pos(item, &pos);
vec2 bounds;
obs_sceneitem_get_bounds(item, &bounds);
vec2 scale;
obs_sceneitem_get_scale(item, &scale);
obs_source_t* item_source = obs_sceneitem_get_source(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_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_double(data, "cx", bounds.x);
obs_data_set_double(data, "cy", bounds.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_bool(data, "render", obs_sceneitem_visible(item));
return data;
@ -70,17 +113,22 @@ 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;
}
if (scene == nullptr)
return nullptr;
obs_scene_enum_items(scene, [](obs_scene_t *scene, obs_sceneitem_t *currentItem, void *param) {
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));
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);
@ -103,7 +151,8 @@ obs_source_t* Utils::GetTransitionFromName(const char *search_name) {
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) {
if (strcmp(transition_name, search_name) == 0)
{
found_transition = transition;
obs_source_addref(found_transition);
break;
@ -115,6 +164,17 @@ 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 = nullptr;
if (!scene_name || !strlen(scene_name))
scene = obs_frontend_get_current_scene();
else
scene = obs_get_source_by_name(scene_name);
return scene;
}
obs_data_array_t* Utils::GetScenes() {
obs_frontend_source_list sceneList = {};
obs_frontend_get_scenes(&sceneList);
@ -144,3 +204,328 @@ obs_data_t* Utils::GetSceneData(obs_source *source) {
obs_data_array_release(scene_items);
return sceneData;
}
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");
bfree(scene_collections);
return list;
}
obs_data_array_t* Utils::GetProfiles() {
char** profiles = obs_frontend_get_profiles();
obs_data_array_t* list = string_list_to_array(profiles, "profile-name");
bfree(profiles);
return list;
}
QSpinBox* Utils::GetTransitionDurationControl() {
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
return window->findChild<QSpinBox*>("transitionDuration");
}
int Utils::GetTransitionDuration() {
QSpinBox* control = GetTransitionDurationControl();
if (control)
return control->value();
else
return -1;
}
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() {
uint32_t version = obs_get_version();
uint8_t major, minor, patch;
major = (version >> 24) & 0xFF;
minor = (version >> 16) & 0xFF;
patch = version & 0xFF;
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) {
QRegExp v4regex("(::ffff:)(((\\d).){3})", Qt::CaseInsensitive);
QString addrString = addr.toString();
if (addrString.contains(v4regex)) {
addrString = QHostAddress(addr.toIPv4Address()).toString();
}
return addrString;
}
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;
}
obs_hotkey_t* Utils::FindHotkeyByName(const char* name) {
struct current_search {
const char* query;
obs_hotkey_t* result;
};
current_search search;
search.query = name;
search.result = nullptr;
obs_enum_hotkeys([](void* data, obs_hotkey_id id, obs_hotkey_t* hotkey) {
current_search* search = static_cast<current_search*>(data);
const char* hk_name = obs_hotkey_get_name(hotkey);
if (strcmp(hk_name, search->query) == 0) {
search->result = hotkey;
blog(LOG_INFO, "Utils::FindHotkeyByName: found %s", hk_name);
return false;
}
return true;
}, &search);
return search.result;
}
bool Utils::ReplayBufferEnabled() {
config_t* profile = obs_frontend_get_profile_config();
const char* outputMode = config_get_string(profile, "Output", "Mode");
if (strcmp(outputMode, "Simple") == 0) {
return config_get_bool(profile, "SimpleOutput", "RecRB");
}
return false;
}
bool Utils::RPHotkeySet() {
obs_output_t* rp_output = obs_frontend_get_replay_buffer_output();
obs_data_t *hotkeys = obs_hotkeys_save_output(rp_output);
obs_data_array_t *bindings = obs_data_get_array(hotkeys,
"ReplayBuffer.Save");
size_t count = obs_data_array_count(bindings);
obs_data_array_release(bindings);
obs_data_release(hotkeys);
obs_output_release(rp_output);
return (count > 0);
}

61
Utils.h
View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,19 +19,68 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#ifndef UTILS_H
#define UTILS_H
#include <obs-module.h>
#include <obs-frontend-api.h>
#include <QSpinBox>
#include <QPushButton>
#include <QLayout>
#include <QListWidget>
#include <QSystemTrayIcon>
#include <QHostAddress>
class Utils
{
#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_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_array_t* GetSceneCollections();
static obs_data_array_t* GetProfiles();
static QSpinBox* GetTransitionDurationControl();
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);
static obs_hotkey_t* FindHotkeyByName(const char* name);
static bool ReplayBufferEnabled();
static bool RPHotkeySet();
};
#endif // UTILS_H

View File

@ -1,41 +1,116 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
/**
* obs-websocket
* Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
* Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 <util/platform.h>
#include <QTimer>
#include <QPushButton>
#include "Config.h"
#include "Utils.h"
#include "WSEvents.h"
WSEvents::WSEvents(WSServer *server) {
_srv = server;
#include "obs-websocket.h"
bool transition_is_cut(obs_source_t* transition) {
if (!transition)
return false;
if (obs_source_get_type(transition) == OBS_SOURCE_TYPE_TRANSITION
&& strcmp(obs_source_get_id(transition), "cut_transition") == 0) {
return true;
}
return false;
}
const char* ns_to_timestamp(uint64_t ns) {
uint64_t ms = ns / (1000 * 1000);
uint64_t secs = ms / 1000;
uint64_t minutes = secs / 60;
uint64_t hours_part = minutes / 60;
uint64_t minutes_part = minutes % 60;
uint64_t secs_part = secs % 60;
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);
return ts;
}
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)));
QTimer* statusTimer = new QTimer();
connect(statusTimer, SIGNAL(timeout()), this, SLOT(StreamStatus()));
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;
QTimer::singleShot(1000, this, SLOT(deferredInitOperations()));
_streaming_active = false;
_recording_active = false;
_stream_starttime = 0;
_rec_starttime = 0;
}
WSEvents::~WSEvents() {
obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this);
}
void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void *private_data)
{
void WSEvents::deferredInitOperations() {
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);
// TODO : implement SourceChanged, SourceOrderChanged and RepopulateSources
if (!owner->_srv)
return;
// TODO : implement SourceOrderChanged and RepopulateSources
if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) {
owner->OnSceneChange();
@ -43,91 +118,312 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void *private
else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) {
owner->OnSceneListChange();
}
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED) {
owner->OnSceneCollectionChange();
}
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED) {
owner->OnSceneCollectionListChange();
}
else if (event == OBS_FRONTEND_EVENT_TRANSITION_CHANGED) {
owner->OnTransitionChange();
}
else if (event == OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED) {
owner->OnTransitionListChange();
}
else if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) {
owner->OnProfileChange();
}
else if (event == OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED) {
owner->OnProfileListChange();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING) {
owner->OnStreamStarting();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) {
owner->_streaming_active = true;
owner->OnStreamStarted();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) {
owner->OnStreamStopping();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) {
owner->_streaming_active = false;
owner->OnStreamStopped();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) {
owner->OnRecordingStarting();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) {
owner->_recording_active = true;
owner->OnRecordingStarted();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) {
owner->OnRecordingStopping();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) {
owner->_recording_active = false;
owner->OnRecordingStopped();
}
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING) {
owner->OnReplayStarting();
}
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED) {
owner->OnReplayStarted();
}
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING) {
owner->OnReplayStopping();
}
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED) {
owner->OnReplayStopped();
}
else if (event == OBS_FRONTEND_EVENT_EXIT) {
obs_frontend_save();
owner->OnExit();
}
}
void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFields = NULL) {
obs_source_t *source = obs_frontend_get_current_scene();
const char *name = obs_source_get_name(source);
void WSEvents::broadcastUpdate(const char* updateType,
obs_data_t* additionalFields = NULL) {
obs_data_t* update = obs_data_create();
obs_data_set_string(update, "update-type", updateType);
if (additionalFields != NULL) {
obs_data_apply(update, additionalFields);
const char* ts = nullptr;
if (_streaming_active) {
ts = ns_to_timestamp(os_gettime_ns() - _stream_starttime);
obs_data_set_string(update, "stream-timecode", ts);
bfree((void*)ts);
}
_srv->broadcast(obs_data_get_json(update));
if (_recording_active) {
ts = ns_to_timestamp(os_gettime_ns() - _rec_starttime);
obs_data_set_string(update, "rec-timecode", ts);
bfree((void*)ts);
}
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);
obs_source_release(source);
}
void WSEvents::connectTransitionSignals(obs_source_t* transition) {
if (transition_handler) {
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);
} else {
transition_handler = nullptr;
}
}
void WSEvents::connectSceneSignals(obs_source_t* scene) {
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);
}
// 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);
}
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());
}
/**
* Indicates a scene change.
*
* @return {String} `scene-name` The new scene.
* @return {Array} `sources` List of sources in the new scene.
*
* @api events
* @name SwitchScenes
* @category scenes
* @since 0.3
*/
void WSEvents::OnSceneChange() {
// Implements an existing update type from bilhamil's OBS Remote
// Default behavior : get the new scene from the running transition
obs_source_t *transition = obs_frontend_get_current_transition();
obs_source_t *new_scene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_B);
if (!new_scene) {
obs_source_release(transition);
return;
}
const char *scene_name = obs_source_get_name(new_scene);
if (!scene_name) {
// Fallback behaviour : get the new scene straight from the API
obs_source_release(new_scene);
new_scene = obs_frontend_get_current_scene();
if (new_scene) {
scene_name = obs_source_get_name(new_scene);
}
}
obs_data_t* data = obs_data_create();
obs_data_set_string(data, "scene-name", scene_name);
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_array_release(scene_items);
obs_source_release(current_scene);
obs_data_release(data);
obs_source_release(new_scene);
obs_source_release(transition);
// 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);
}
}
/**
* The scene list has been modified.
* Scenes have been added, removed, or renamed.
*
* @api events
* @name ScenesChanged
* @category scenes
* @since 0.3
*/
void WSEvents::OnSceneListChange() {
broadcastUpdate("ScenesChanged");
}
/**
* Triggered when switching to another scene collection or when renaming the current scene collection.
*
* @api events
* @name SceneCollectionChanged
* @category scenes
* @since 4.0.0
*/
void WSEvents::OnSceneCollectionChange() {
broadcastUpdate("SceneCollectionChanged");
scene_handler = nullptr;
transition_handler = nullptr;
OnTransitionListChange();
OnTransitionChange();
OnSceneListChange();
OnSceneChange();
}
/**
* Triggered when a scene collection is created, added, renamed, or removed.
*
* @api events
* @name SceneCollectionListChanged
* @category scenes
* @since 4.0.0
*/
void WSEvents::OnSceneCollectionListChange() {
broadcastUpdate("SceneCollectionListChanged");
}
/**
* The active transition has been changed.
*
* @return {String} `transition-name` The name of the new active transition.
*
* @api events
* @name SwitchTransition
* @category transitions
* @since 4.0.0
*/
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));
broadcastUpdate("SwitchTransition", data);
obs_data_release(data);
obs_source_release(current_transition);
}
/**
* The list of available transitions has been modified.
* Transitions have been added, removed, or renamed.
*
* @api events
* @name TransitionListChanged
* @category transitions
* @since 4.0.0
*/
void WSEvents::OnTransitionListChange() {
broadcastUpdate("TransitionListChanged");
}
/**
* Triggered when switching to another profile or when renaming the current profile.
*
* @api events
* @name ProfileChanged
* @category profiles
* @since 4.0.0
*/
void WSEvents::OnProfileChange() {
broadcastUpdate("ProfileChanged");
}
/**
* Triggered when a profile is created, added, renamed, or removed.
*
* @api events
* @name ProfileListChanged
* @category profiles
* @since 4.0.0
*/
void WSEvents::OnProfileListChange() {
broadcastUpdate("ProfileListChanged");
}
/**
* A request to start streaming has been issued.
*
* @return {boolean} `preview-only` Always false (retrocompatibility).
*
* @api events
* @name StreamStarting
* @category streaming
* @since 0.3
*/
void WSEvents::OnStreamStarting() {
// Implements an existing update type from bilhamil's OBS Remote
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "preview-only", false);
@ -136,15 +432,31 @@ void WSEvents::OnStreamStarting() {
obs_data_release(data);
}
/**
* Streaming started successfully.
*
* @api events
* @name StreamStarted
* @category streaming
* @since 0.3
*/
void WSEvents::OnStreamStarted() {
// New update type specific to OBS Studio
_streamStartTime = os_gettime_ns();
_stream_starttime = os_gettime_ns();
_lastBytesSent = 0;
broadcastUpdate("StreamStarted");
}
/**
* A request to stop streaming has been issued.
*
* @return {boolean} `preview-only` Always false (retrocompatibility).
*
* @api events
* @name StreamStopping
* @category streaming
* @since 0.3
*/
void WSEvents::OnStreamStopping() {
// Implements an existing update type from bilhamil's OBS Remote
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "preview-only", false);
@ -153,37 +465,148 @@ void WSEvents::OnStreamStopping() {
obs_data_release(data);
}
/**
* Streaming stopped successfully.
*
* @api events
* @name StreamStopped
* @category streaming
* @since 0.3
*/
void WSEvents::OnStreamStopped() {
// New update type specific to OBS Studio
_streamStartTime = 0;
_stream_starttime = 0;
broadcastUpdate("StreamStopped");
}
/**
* A request to start recording has been issued.
*
* @api events
* @name RecordingStarting
* @category recording
* @since 0.3
*/
void WSEvents::OnRecordingStarting() {
// New update type specific to OBS Studio
broadcastUpdate("RecordingStarting");
}
/**
* Recording started successfully.
*
* @api events
* @name RecordingStarted
* @category recording
* @since 0.3
*/
void WSEvents::OnRecordingStarted() {
// New update type specific to OBS Studio
_rec_starttime = os_gettime_ns();
broadcastUpdate("RecordingStarted");
}
/**
* A request to stop recording has been issued.
*
* @api events
* @name RecordingStopping
* @category recording
* @since 0.3
*/
void WSEvents::OnRecordingStopping() {
// New update type specific to OBS Studio
broadcastUpdate("RecordingStopping");
}
/**
* Recording stopped successfully.
*
* @api events
* @name RecordingStopped
* @category recording
* @since 0.3
*/
void WSEvents::OnRecordingStopped() {
// New update type specific to OBS Studio
_rec_starttime = 0;
broadcastUpdate("RecordingStopped");
}
/**
* A request to start the replay buffer has been issued.
*
* @api events
* @name ReplayStarting
* @category replay buffer
* @since 4.2.0
*/
void WSEvents::OnReplayStarting() {
broadcastUpdate("ReplayStarting");
}
/**
* Replay Buffer started successfully
*
* @api events
* @name ReplayStarted
* @category replay buffer
* @since 4.2.0
*/
void WSEvents::OnReplayStarted() {
broadcastUpdate("ReplayStarted");
}
/**
* A request to start the replay buffer has been issued.
*
* @api events
* @name ReplayStopping
* @category replay buffer
* @since 4.2.0
*/
void WSEvents::OnReplayStopping() {
broadcastUpdate("ReplayStopping");
}
/**
* Replay Buffer stopped successfully
*
* @api events
* @name ReplayStopped
* @category replay buffer
* @since 4.2.0
*/
void WSEvents::OnReplayStopped() {
broadcastUpdate("ReplayStopped");
}
/**
* OBS is exiting.
*
* @api events
* @name Exiting
* @category other
* @since 0.3
*/
void WSEvents::OnExit() {
// New update type specific to OBS Studio
broadcastUpdate("Exiting");
}
/**
* Emit every 2 seconds.
*
* @return {boolean} `streaming` Current streaming state.
* @return {boolean} `recording` Current recording state.
* @return {boolean} `preview-only` Always false (retrocompatibility).
* @return {int} `bytes-per-sec` Amount of data per second (in bytes) transmitted by the stream encoder.
* @return {int} `kbits-per-sec` Amount of data per second (in kilobits) transmitted by the stream encoder.
* @return {double} `strain` Percentage of dropped frames.
* @return {int} `total-stream-time` Total time (in seconds) since the stream started.
* @return {int} `num-total-frames` Total number of frames transmitted since the stream started.
* @return {int} `num-dropped-frames` Number of frames dropped by the encoder since the stream started.
* @return {double} `fps` Current framerate.
*
* @api events
* @name StreamStatus
* @category streaming
* @since 0.3
*/
void WSEvents::StreamStatus() {
bool streaming_active = obs_frontend_streaming_active();
bool recording_active = obs_frontend_recording_active();
@ -200,30 +623,28 @@ void WSEvents::StreamStatus() {
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() - _streamStartTime) / 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 = 0.0;
if (total_frames > 0) {
strain = (dropped_frames / total_frames) * 100.0;
}
float strain = obs_output_get_congestion(stream_output);
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "streaming", streaming_active);
@ -242,3 +663,225 @@ void WSEvents::StreamStatus() {
obs_data_release(data);
obs_output_release(stream_output);
}
/**
* The active transition duration has been changed.
*
* @return {int} `new-duration` New transition duration.
*
* @api events
* @name TransitionDurationChanged
* @category transitions
* @since 4.0.0
*/
void WSEvents::TransitionDurationChanged(int ms) {
obs_data_t* fields = obs_data_create();
obs_data_set_int(fields, "new-duration", ms);
broadcastUpdate("TransitionDurationChanged", fields);
obs_data_release(fields);
}
/**
* A transition (other than "cut") has begun.
*
* @return {String} `name` Transition name.
* @return {int} `duration` Transition duration (in milliseconds).
*
* @api events
* @name TransitionBegin
* @category transitions
* @since 4.0.0
*/
void WSEvents::OnTransitionBegin(void* param, calldata_t* data) {
UNUSED_PARAMETER(data);
WSEvents* instance = static_cast<WSEvents*>(param);
obs_source_t* current_transition = obs_frontend_get_current_transition();
const char* name = obs_source_get_name(current_transition);
int duration = Utils::GetTransitionDuration();
obs_data_t* fields = obs_data_create();
obs_data_set_string(fields, "name", name);
obs_data_set_int(fields, "duration", duration);
instance->broadcastUpdate("TransitionBegin", fields);
obs_data_release(fields);
obs_source_release(current_transition);
}
/**
* Scene items have been reordered.
*
* @return {String} `scene-name` Name of the scene where items have been reordered.
*
* @api events
* @name SourceOrderChanged
* @category sources
* @since 4.0.0
*/
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)));
instance->broadcastUpdate("SourceOrderChanged", fields);
obs_data_release(fields);
}
/**
* An item has been added to the current scene.
*
* @return {String} `scene-name` Name of the scene.
* @return {String} `item-name` Name of the item added to the scene.
*
* @api events
* @name SceneItemAdded
* @category sources
* @since 4.0.0
*/
void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) {
WSEvents* instance = static_cast<WSEvents*>(param);
obs_scene_t* scene = nullptr;
calldata_get_ptr(data, "scene", &scene);
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));
obs_data_t* fields = obs_data_create();
obs_data_set_string(fields, "scene-name", scene_name);
obs_data_set_string(fields, "item-name", sceneitem_name);
instance->broadcastUpdate("SceneItemAdded", fields);
obs_data_release(fields);
}
/**
* An item has been removed from the current scene.
*
* @return {String} `scene-name` Name of the scene.
* @return {String} `item-name` Name of the item removed from the scene.
*
* @api events
* @name SceneItemRemoved
* @category sources
* @since 4.0.0
*/
void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) {
WSEvents* instance = static_cast<WSEvents*>(param);
obs_scene_t* scene = nullptr;
calldata_get_ptr(data, "scene", &scene);
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));
obs_data_t* fields = obs_data_create();
obs_data_set_string(fields, "scene-name", scene_name);
obs_data_set_string(fields, "item-name", sceneitem_name);
instance->broadcastUpdate("SceneItemRemoved", fields);
obs_data_release(fields);
}
/**
* An item's visibility has been toggled.
*
* @return {String} `scene-name` Name of the scene.
* @return {String} `item-name` Name of the item in the scene.
* @return {boolean} `item-visible` New visibility state of the item.
*
* @api events
* @name SceneItemVisibilityChanged
* @category sources
* @since 4.0.0
*/
void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
WSEvents* instance = static_cast<WSEvents*>(param);
obs_scene_t* scene = nullptr;
calldata_get_ptr(data, "scene", &scene);
obs_sceneitem_t* scene_item = nullptr;
calldata_get_ptr(data, "item", &scene_item);
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));
obs_data_t* fields = obs_data_create();
obs_data_set_string(fields, "scene-name", scene_name);
obs_data_set_string(fields, "item-name", sceneitem_name);
obs_data_set_bool(fields, "item-visible", visible);
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
obs_data_release(fields);
}
/**
* The selected preview scene has changed (only available in Studio Mode).
*
* @return {String} `scene-name` Name of the scene being previewed.
* @return {Source|Array} `sources` List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene).
*
* @api events
* @name PreviewSceneChanged
* @category studio mode
* @since 4.1.0
*/
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);
}
}
/**
* Studio Mode has been enabled or disabled.
*
* @return {boolean} `new-state` The new enabled state of Studio Mode.
*
* @api events
* @name StudioModeSwitched
* @category studio mode
* @since 4.1.0
*/
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

@ -1,6 +1,7 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,33 +20,62 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#ifndef WSEVENTS_H
#define WSEVENTS_H
#include <QtWebSockets/QWebSocket>
#include <QTimer>
#include <obs-frontend-api.h>
#include <util/platform.h>
#include <QListWidgetItem>
#include "WSServer.h"
class WSEvents : public QObject
{
class WSEvents : public QObject {
Q_OBJECT
public:
explicit WSEvents(WSServer *server);
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;
private Q_SLOTS:
uint64_t GetStreamingTime();
const char* GetStreamingTimecode();
uint64_t GetRecordingTime();
const char* GetRecordingTimecode();
private slots:
void deferredInitOperations();
void StreamStatus();
void TransitionDurationChanged(int ms);
void SelectedSceneChanged(
QListWidgetItem* current, QListWidgetItem* prev);
void ModeSwitchClicked(bool checked);
private:
WSServer* _srv;
uint64_t _streamStartTime;
signal_handler_t* transition_handler;
signal_handler_t* scene_handler;
bool _streaming_active;
bool _recording_active;
uint64_t _stream_starttime;
uint64_t _rec_starttime;
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();
void OnSceneCollectionListChange();
void OnTransitionChange();
void OnTransitionListChange();
void OnProfileChange();
void OnProfileListChange();
void OnStreamStarting();
void OnStreamStarted();
@ -57,7 +87,19 @@ class WSEvents : public QObject
void OnRecordingStopping();
void OnRecordingStopped();
void OnReplayStarting();
void OnReplayStarted();
void OnReplayStopping();
void OnReplayStopped();
void OnExit();
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);
};
#endif // WSEVENTS_H

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,58 +20,105 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#ifndef WSREQUESTHANDLER_H
#define WSREQUESTHANDLER_H
#include <map>
#include <set>
#include <QtWebSockets/QWebSocket>
#include <QMap>
#include <QWebSocket>
#include <QWebSocketServer>
#include <obs-frontend-api.h>
class WSRequestHandler : public QObject
{
class WSRequestHandler : public QObject {
Q_OBJECT
public:
explicit WSRequestHandler(QWebSocket* client);
~WSRequestHandler();
void sendTextMessage(QString textMessage);
bool isAuthenticated();
private Q_SLOTS:
void processTextMessage(QString textMessage);
void socketDisconnected();
Q_SIGNALS:
void disconnected();
void processIncomingMessage(QString textMessage);
bool hasField(const char* name);
private:
static obs_service_t* _service;
QWebSocket* _client;
bool _authenticated;
const char* _messageId;
const char* _requestType;
obs_data_t *_requestData;
obs_data_t* data;
std::map<std::string, void(*)(WSRequestHandler*)> messageMap;
std::set<std::string> authNotRequired;
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 SendResponse(obs_data_t* response);
static void HandleGetVersion(WSRequestHandler *owner);
static void HandleGetAuthRequired(WSRequestHandler *owner);
static void HandleAuthenticate(WSRequestHandler *owner);
static void HandleGetVersion(WSRequestHandler* req);
static void HandleGetAuthRequired(WSRequestHandler* req);
static void HandleAuthenticate(WSRequestHandler* req);
static void HandleSetCurrentScene(WSRequestHandler *owner);
static void HandleGetCurrentScene(WSRequestHandler *owner);
static void HandleGetSceneList(WSRequestHandler *owner);
static void HandleSetSourceRender(WSRequestHandler *owner);
static void HandleSetCurrentScene(WSRequestHandler* req);
static void HandleGetCurrentScene(WSRequestHandler* req);
static void HandleGetSceneList(WSRequestHandler* req);
static void HandleGetStreamingStatus(WSRequestHandler *owner);
static void HandleStartStopStreaming(WSRequestHandler *owner);
static void HandleStartStopRecording(WSRequestHandler *owner);
static void HandleSetSceneItemRender(WSRequestHandler* req);
static void HandleSetSceneItemPosition(WSRequestHandler* req);
static void HandleSetSceneItemTransform(WSRequestHandler* req);
static void HandleSetSceneItemCrop(WSRequestHandler* req);
static void HandleResetSceneItem(WSRequestHandler* req);
static void HandleGetTransitionList(WSRequestHandler *owner);
static void HandleGetCurrentTransition(WSRequestHandler *owner);
static void HandleSetCurrentTransition(WSRequestHandler *owner);
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 HandleStartStopReplayBuffer(WSRequestHandler* req);
static void HandleStartReplayBuffer(WSRequestHandler* req);
static void HandleStopReplayBuffer(WSRequestHandler* req);
static void HandleSaveReplayBuffer(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 HandleSetSyncOffset(WSRequestHandler* req);
static void HandleGetSyncOffset(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 HandleSetTextGDIPlusProperties(WSRequestHandler* req);
static void HandleGetTextGDIPlusProperties(WSRequestHandler* req);
static void HandleSetBrowserSourceProperties(WSRequestHandler* req);
static void HandleGetBrowserSourceProperties(WSRequestHandler* req);
};
#endif // WSPROTOCOL_H

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -16,73 +16,132 @@ 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 "WSServer.h"
#include "WSRequestHandler.h"
#include "Config.h"
#include <QtWebSockets/QWebSocketServer>
#include <QtWebSockets/QWebSocket>
#include <QtCore/QDebug>
#include <QtCore/QThread>
#include <QtCore/QByteArray>
#include <obs-frontend-api.h>
#include "WSServer.h"
#include "obs-websocket.h"
#include "Config.h"
#include "Utils.h"
QT_USE_NAMESPACE
WSServer::WSServer(quint16 port, QObject *parent) :
QObject(parent),
WSServer* WSServer::Instance = nullptr;
WSServer::WSServer(QObject* parent)
: QObject(parent),
_wsServer(Q_NULLPTR),
_clients()
{
_serverThread = new QThread();
_clients(),
_clMutex(QMutex::Recursive) {
_wsServer = new QWebSocketServer(
QStringLiteral("OBS Websocket API"),
QWebSocketServer::NonSecureMode,
this);
_wsServer->moveToThread(_serverThread);
_serverThread->start();
QStringLiteral("obs-websocket"),
QWebSocketServer::NonSecureMode);
}
WSServer::~WSServer() {
Stop();
}
void WSServer::Start(quint16 port) {
if (port == _wsServer->serverPort())
return;
if(_wsServer->isListening())
Stop();
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
if (serverStarted) {
connect(_wsServer, &QWebSocketServer::newConnection, this, &WSServer::onNewConnection);
connect(_wsServer, SIGNAL(newConnection()),
this, SLOT(onNewConnection()));
}
}
WSServer::~WSServer()
{
void WSServer::Stop() {
_clMutex.lock();
for(QWebSocket* pClient : _clients) {
pClient->close();
}
_clMutex.unlock();
_wsServer->close();
qDeleteAll(_clients.begin(), _clients.end());
}
void WSServer::broadcast(QString message)
{
Q_FOREACH(WSRequestHandler *pClient, _clients) {
if (Config::Current()->AuthRequired == true
&& pClient->isAuthenticated() == false) {
void WSServer::broadcast(QString message) {
_clMutex.lock();
for(QWebSocket* pClient : _clients) {
if (Config::Current()->AuthRequired
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false)) {
// Skip this client if unauthenticated
continue;
}
pClient->sendTextMessage(message);
}
_clMutex.unlock();
}
void WSServer::onNewConnection()
{
void WSServer::onNewConnection() {
QWebSocket* pSocket = _wsServer->nextPendingConnection();
if (pSocket) {
WSRequestHandler *pHandler = new WSRequestHandler(pSocket);
connect(pSocket, SIGNAL(textMessageReceived(const QString&)),
this, SLOT(onTextMessageReceived(QString)));
connect(pSocket, SIGNAL(disconnected()),
this, SLOT(onSocketDisconnected()));
connect(pHandler, &WSRequestHandler::disconnected, this, &WSServer::socketDisconnected);
_clients << pHandler;
pSocket->setProperty(PROP_AUTHENTICATED, false);
_clMutex.lock();
_clients << pSocket;
_clMutex.unlock();
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(" ")
+ Utils::FormatIPAddress(clientAddr);
Utils::SysTrayNotify(msg,
QSystemTrayIcon::Information,
QString(obs_module_text("OBSWebsocket.ConnectNotify.Connected")));
}
}
void WSServer::socketDisconnected()
{
WSRequestHandler *pClient = qobject_cast<WSRequestHandler *>(sender());
void WSServer::onTextMessageReceived(QString message) {
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
if (pSocket) {
WSRequestHandler handler(pSocket);
handler.processIncomingMessage(message);
}
}
if (pClient) {
_clients.removeAll(pClient);
pClient->deleteLater();
void WSServer::onSocketDisconnected() {
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
if (pSocket) {
pSocket->setProperty(PROP_AUTHENTICATED, false);
_clMutex.lock();
_clients.removeAll(pSocket);
_clMutex.unlock();
pSocket->deleteLater();
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(" ")
+ Utils::FormatIPAddress(clientAddr);
Utils::SysTrayNotify(msg,
QSystemTrayIcon::Information,
QString(obs_module_text("OBSWebsocket.ConnectNotify.Disconnected")));
}
}

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,31 +19,34 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#ifndef WSSERVER_H
#define WSSERVER_H
#include <QtCore/QObject>
#include <QtCore/QList>
#include <QtCore/QByteArray>
#include <QObject>
#include <QList>
#include <QMutex>
#include "WSRequestHandler.h"
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
QT_FORWARD_DECLARE_CLASS(QWebSocket)
class WSServer : public QObject
{
class WSServer : public QObject {
Q_OBJECT
public:
explicit WSServer(quint16 port, QObject *parent = Q_NULLPTR);
explicit WSServer(QObject* parent = Q_NULLPTR);
virtual ~WSServer();
void Start(quint16 port);
void Stop();
void broadcast(QString message);
static WSServer* Instance;
private Q_SLOTS:
private slots:
void onNewConnection();
void socketDisconnected();
void onTextMessageReceived(QString message);
void onSocketDisconnected();
private:
QWebSocketServer* _wsServer;
QList<WSRequestHandler *> _clients;
QThread *_serverThread;
QList<QWebSocket*> _clients;
QMutex _clMutex;
};
#endif // WSSERVER_H

50
appveyor.yml Normal file
View File

@ -0,0 +1,50 @@
environment:
CURL_VERSION: 7.39.0
install:
- git submodule update --init --recursive
- cd C:\projects\
- if not exist dependencies2013.zip curl -kLO https://obsproject.com/downloads/dependencies2013.zip -f --retry 5 -C -
- 7z x dependencies2013.zip -odependencies2013
- if not exist qt570.zip curl -kLO https://www.slepin.fr/obs-websocket/ci/qt570.zip -f --retry 5 -C -
- 7z x qt570.zip -o"Qt5.7.0"
- set DepsPath32=%CD%\dependencies2013\win32
- set DepsPath64=%CD%\dependencies2013\win64
- set QTDIR32=%CD%\Qt5.7.0\msvc2013
- set QTDIR64=%CD%\Qt5.7.0\msvc2013_64
- set build_config=Release
- git clone --recursive https://github.com/jp9000/obs-studio
- cd C:\projects\obs-studio\
- git checkout 19.0.3
- mkdir build
- mkdir build32
- mkdir build64
- cd ./build32
- cmake -G "Visual Studio 12 2013" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
- cd ../build64
- cmake -G "Visual Studio 12 2013 Win64" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
- cd C:\projects\obs-websocket\
- mkdir build32
- mkdir build64
- cd ./build32
- cmake -G "Visual Studio 12 2013" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
- cd ../build64
- cmake -G "Visual Studio 12 2013 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
build_script:
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build64\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
before_deploy:
- 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\*
deploy_script:
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1).zip"
test: off
cache:
- C:\projects\dependencies2013.zip
- C:\projects\qt570.zip

6
data/locale/de-DE.ini Normal file
View File

@ -0,0 +1,6 @@
OBSWebsocket.Menu.SettingsItem="Websocket-Server Einstellungen"
OBSWebsocket.Settings.DialogTitle="Websocket-Server Einstellungen"
OBSWebsocket.Settings.ServerEnable="Websocket-Server aktivieren"
OBSWebsocket.Settings.ServerPort="Server Port"
OBSWebsocket.Settings.AuthRequired="Authentifizierung erforderlich"
OBSWebsocket.Settings.Password="Passwort"

View File

@ -1,4 +1,10 @@
Menu.SettingsItem="Websocket server settings"
Settings.DialogTitle="obs-websocket"
Settings.AuthRequired="Enable authentication"
Settings.Password="Password"
OBSWebsocket.Menu.SettingsItem="Websocket server settings"
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.DebugEnable="Enable debug logging"
OBSWebsocket.ConnectNotify.Connected="New WebSocket connection"
OBSWebsocket.ConnectNotify.Disconnected="WebSocket client disconnected"
OBSWebsocket.ConnectNotify.ClientIP="Client Address:"

View File

@ -1,4 +1,9 @@
Menu.SettingsItem="Paramètres du serveur Websocket"
Settings.DialogTitle="obs-websocket"
Settings.AuthRequired="Activer l'authentification"
Settings.Password="Mot de passe"
OBSWebsocket.Menu.SettingsItem="Paramètres du serveur Websocket"
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.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"

6
data/locale/zh-CN.ini Normal file
View File

@ -0,0 +1,6 @@
OBSWebsocket.Menu.SettingsItem="Websocket 服务器设置"
OBSWebsocket.Settings.DialogTitle="obs-websocket 设置"
OBSWebsocket.Settings.ServerEnable="启用 Websocket 服务器"
OBSWebsocket.Settings.ServerPort="服务器端口"
OBSWebsocket.Settings.AuthRequired="启用密码认证"
OBSWebsocket.Settings.Password="密码"

6
data/locale/zh-TW.ini Normal file
View File

@ -0,0 +1,6 @@
OBSWebsocket.Menu.SettingsItem="Websocket 伺服器設定"
OBSWebsocket.Settings.DialogTitle="obs-websocket 設定"
OBSWebsocket.Settings.ServerEnable="啟用 Websocket 伺服器"
OBSWebsocket.Settings.ServerPort="伺服器端口"
OBSWebsocket.Settings.AuthRequired="啟用密碼認證"
OBSWebsocket.Settings.Password="密碼"

11
docs/.editorconfig Normal file
View File

@ -0,0 +1,11 @@
[*]
end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
[*.md, *.mustache]
trim_trailing_whitespace = false
insert_final_newline = false

4
docs/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
logs
*.log
npm-debug.log*

1
docs/.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

21
docs/README.md Normal file
View File

@ -0,0 +1,21 @@
## Installation
Install node and update npm if necessary.
```sh
cd obs-websocket/docs
npm install
```
## Build
```sh
# Just extract the comments.
npm run comments
# Just render the markdown.
npm run docs
# Do both comments and markdown.
npm run build
```

52
docs/comments.js Normal file
View File

@ -0,0 +1,52 @@
const fs = require('fs');
const glob = require('glob');
const parseComments = require('parse-comments');
/**
* Read each file and call `parse-comments` on it.
*
* @param {String|Array} `files` List of file paths to read from.
* @return {Object|Array} Array of `parse-comments` objects.
*/
const parseFiles = files => {
let response = [];
files.forEach(file => {
const f = fs.readFileSync(file, 'utf8').toString();
response = response.concat(parseComments(f));
});
return response;
};
/**
* Filters/sorts the results from `parse-comments`.
* @param {Object|Array} `comments` Array of `parse-comments` objects.
* @return {Object} Filtered comments sorted by `@api` and `@category`.
*/
const processComments = comments => {
let sorted = {};
comments.forEach(comment => {
if (typeof comment.api === 'undefined') return;
// Store the object based on its api (ie. requests, events) and category (ie. general, scenes, etc).
comment.category = comment.category || 'miscellaneous';
// Remove some unnecessary properties to avoid result differences in travis.
comment.comment = undefined;
comment.context = undefined;
// Create an entry in sorted for the api/category if one does not exist.
sorted[comment.api] = sorted[comment.api] || {};
sorted[comment.api][comment.category] = sorted[comment.api][comment.category] || [];
// Store the comment in the appropriate api/category.
sorted[comment.api][comment.category].push(comment);
});
return sorted;
};
const files = glob.sync("./../*.@(cpp|h)");
const comments = processComments(parseFiles(files));
fs.writeFileSync('./generated/comments.json', JSON.stringify(comments, null, 2));

31
docs/docs.js Normal file
View File

@ -0,0 +1,31 @@
const fs = require('fs');
const toc = require('markdown-toc');
const handlebars = require('handlebars');
const helpers = require('handlebars-helpers')({
handlebars: handlebars
});
// Allows pipe characters to be used within markdown tables.
handlebars.registerHelper('depipe', (text) => {
return text.replace('|', `\\|`);
});
const insertHeader = (text) => {
return '<!-- This file was generated based on handlebars templates. Do not edit directly! -->\n\n' + text;
};
/**
* Writes `protocol.md` using `protocol.mustache`.
*
* @param {Object} `data` Data to assign to the mustache template.
*/
const generateProtocol = (templatePath, data) => {
const template = fs.readFileSync(templatePath).toString();
const generated = handlebars.compile(template)(data);
return insertHeader(toc.insert(generated));
};
const comments = fs.readFileSync('./generated/comments.json', 'utf8');
const markdown = generateProtocol('./protocol.hbs', JSON.parse(comments));
fs.writeFileSync('./generated/protocol.md', markdown);

4370
docs/generated/comments.json Normal file

File diff suppressed because it is too large Load Diff

1840
docs/generated/protocol.md Normal file

File diff suppressed because it is too large Load Diff

21
docs/package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "obs-websocket-docs",
"version": "1.0.0",
"description": "",
"main": "docs.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"docs": "node ./docs.js",
"comments": "node ./comments.js",
"build": "npm run comments && npm run docs"
},
"author": "",
"license": "ISC",
"dependencies": {
"glob": "^7.1.2",
"handlebars": "^4.0.10",
"handlebars-helpers": "^0.9.6",
"markdown-toc": "^1.1.0",
"parse-comments": "^0.4.3"
}
}

View File

@ -0,0 +1,11 @@
# Events
Events are broadcast by the server to each connected client when a recognized action occurs within OBS.
An event message will contain at least the following base fields:
- `update-type` _String_: the type of event.
- `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).
Timecodes are sent using the format: `HH:MM:SS.mmm`
Additional fields may be present in the event message depending on the event type.

View File

@ -0,0 +1,39 @@
# obs-websocket 4.2.0 protocol reference
**This is the reference for obs-websocket 4.2.0. See the list below for older versions.**
- [4.1.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.1.0/PROTOCOL.md)
- [4.0.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.0.0/PROTOCOL.md)
# General Introduction
Messages are exchanged between the client and the server as JSON objects.
This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
# Authentication
OBSWebSocket uses SHA256 to transmit credentials.
A request for [`GetAuthRequired`](#getauthrequired) returns 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.
To generate the answer to the auth challenge, follow this procedure:
- Concatenate the user declared password with the `salt` sent by the server (in this order: `password + server salt`).
- Generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64, known as a `base64 secret`.
- Concatenate the base64 secret with the `challenge` sent by the server (in this order: `base64 secret + server challenge`).
- 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`](#authenticate) request.
Pseudo Code Example:
```
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)
```

View File

@ -0,0 +1,11 @@
# Requests
Requests are sent by the client and require at least the following two fields:
- `request-type` _String_: String name of the request type.
- `message-id` _String_: Client defined identifier for the message, will be echoed in the response.
Once a request is sent, the server will return a JSON response with at least the following fields:
- `message-id` _String_: The client defined identifier specified in the request.
- `status` _String_: Response status, will be one of the following: `ok`, `error`
- `error` _String_: An error message accompanying an `error` status.
Additional information may be required/returned depending on the request type. See below for more information.

92
docs/protocol.hbs Normal file
View File

@ -0,0 +1,92 @@
{{#read "partials/introduction.md"}}{{/read}}
# Table of Contents
<!-- toc -->
{{#read "partials/eventsHeader.md"}}{{/read}}
{{#each events}}
## {{capitalizeAll @key}}
{{#each this}}
### {{name}}
{{#eq since "unreleased"}}
- Unreleased
{{else}}
- Added in v{{since}}
{{/eq}}
{{{description}}}
**Response Items:**
{{#if returns.length}}
| Name | Type | Description |
| ---- | :---: | ------------|
{{#each returns}}
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
{{/each}}
{{else}}
_No additional response items._
{{/if}}
---
{{/each}}
{{/each}}
{{#read "partials/requestsHeader.md"}}{{/read}}
{{#each requests}}
## {{capitalizeAll @key}}
{{#each this}}
### {{name}}
{{#eq since "unreleased"}}
- Unreleased
{{else}}
- Added in v{{since}}
{{/eq}}
{{{description}}}
**Request Fields:**
{{#if params.length}}
| Name | Type | Description |
| ---- | :---: | ------------|
{{#each params}}
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
{{/each}}
{{else}}
_No specified parameters._
{{/if}}
**Response Items:**
{{#if returns.length}}
| Name | Type | Description |
| ---- | :---: | ------------|
{{#each returns}}
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
{{/each}}
{{else}}
_No additional response items._
{{/if}}
---
{{/each}}
{{/each}}

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -17,69 +17,94 @@ with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <obs-frontend-api.h>
#include "obs-websocket.h"
#include "Config.h"
#include "WSServer.h"
#include "settings-dialog.h"
#include "ui_settings-dialog.h"
#include "Config.h"
#define CHANGE_ME "changeme"
SettingsDialog::SettingsDialog(QWidget* parent) :
QDialog(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);
connect(ui->authRequired, &QCheckBox::stateChanged,
this, &SettingsDialog::AuthCheckboxChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted,
this, &SettingsDialog::FormAccepted);
AuthCheckboxChanged();
}
void SettingsDialog::showEvent(QShowEvent *event) {
ui->authRequired->setChecked(Config::Current()->AuthRequired);
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);
}
void SettingsDialog::ToggleShowHide() {
if (!isVisible()) {
void SettingsDialog::ToggleShowHide()
{
if (!isVisible())
setVisible(true);
}
else {
else
setVisible(false);
}
}
void SettingsDialog::AuthCheckboxChanged() {
if (ui->authRequired->isChecked()) {
void SettingsDialog::AuthCheckboxChanged()
{
if (ui->authRequired->isChecked())
ui->password->setEnabled(true);
}
else {
else
ui->password->setEnabled(false);
}
}
void SettingsDialog::FormAccepted() {
if (ui->authRequired->isChecked()) {
if (ui->password->text() != CHANGE_ME) {
QByteArray pwd = ui->password->text().toLocal8Bit();
void SettingsDialog::FormAccepted()
{
Config* conf = Config::Current();
conf->ServerEnabled = ui->serverEnabled->isChecked();
conf->ServerPort = ui->serverPort->value();
conf->DebugEnabled = ui->debugEnabled->isChecked();
if (ui->authRequired->isChecked())
{
if (ui->password->text() != CHANGE_ME)
{
QByteArray pwd = ui->password->text().toUtf8();
const char *new_password = pwd;
blog(LOG_INFO, "new password : %s", new_password);
Config::Current()->SetPassword(new_password);
conf->SetPassword(new_password);
}
if (strcmp(Config::Current()->Secret, "") != 0) {
Config::Current()->AuthRequired = true;
if (strcmp(Config::Current()->Secret, "") != 0)
conf->AuthRequired = true;
else
conf->AuthRequired = false;
}
else {
Config::Current()->AuthRequired = false;
}
}
else {
Config::Current()->AuthRequired = false;
else
{
conf->AuthRequired = false;
}
obs_frontend_save();
conf->Save();
if (conf->ServerEnabled)
WSServer::Instance->Start(conf->ServerPort);
else
WSServer::Instance->Stop();
}
SettingsDialog::~SettingsDialog()

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>354</width>
<height>110</height>
<width>407</width>
<height>175</height>
</rect>
</property>
<property name="sizePolicy">
@ -17,7 +17,7 @@
</sizepolicy>
</property>
<property name="windowTitle">
<string>Settings.DialogTitle</string>
<string>OBSWebsocket.Settings.DialogTitle</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
@ -28,24 +28,64 @@
</property>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="3" column="0">
<widget class="QLabel" name="lbl_password">
<item row="3" column="1">
<widget class="QCheckBox" name="authRequired">
<property name="text">
<string>Settings.Password</string>
<string>OBSWebsocket.Settings.AuthRequired</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="0">
<widget class="QLabel" name="lbl_password">
<property name="text">
<string>OBSWebsocket.Settings.Password</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="authRequired">
<item row="1" column="1">
<widget class="QCheckBox" name="serverEnabled">
<property name="text">
<string>Settings.AuthRequired</string>
<string>OBSWebsocket.Settings.ServerEnable</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lbl_serverPort">
<property name="text">
<string>OBSWebsocket.Settings.ServerPort</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="serverPort">
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>4444</number>
</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>
@ -73,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>
@ -89,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

@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "obs-websocket"
#define MyAppVersion "0.3.1"
#define MyAppVersion "4.2.0"
#define MyAppPublisher "St<53>phane Lepin"
#define MyAppURL "http://github.com/Palakis/obs-websocket"

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,33 +19,42 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs-module.h>
#include <obs-frontend-api.h>
#include <QAction>
#include <QMainWindow>
#include <QTimer>
#include "obs-websocket.h"
#include "WSEvents.h"
#include "WSServer.h"
#include "WSEvents.h"
#include "Config.h"
#include "forms/settings-dialog.h"
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
WSEvents *eventHandler;
WSServer *server;
SettingsDialog* settings_dialog;
bool obs_module_load(void)
{
blog(LOG_INFO, "[obs-websockets] you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
bool obs_module_load(void) {
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
blog(LOG_INFO, "qt version (compile-time): %s ; qt version (run-time): %s",
QT_VERSION_STR, qVersion());
server = new WSServer(4444);
eventHandler = new WSEvents(server);
// Core setup
Config* config = Config::Current();
config->Load();
obs_frontend_add_save_callback(Config::OBSSaveCallback, Config::Current());
WSServer::Instance = new WSServer();
WSEvents::Instance = new WSEvents(WSServer::Instance);
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(obs_module_text("Menu.SettingsItem"));
if (config->ServerEnabled)
WSServer::Instance->Start(config->ServerPort);
// UI setup
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);
settings_dialog = new SettingsDialog();
QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window();
settings_dialog = new SettingsDialog(main_window);
obs_frontend_pop_ui_translation();
auto menu_cb = [] {
@ -53,11 +62,13 @@ bool obs_module_load(void)
};
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
// Loading finished
blog(LOG_INFO, "module loaded!");
return true;
}
void obs_module_unload()
{
blog(LOG_INFO, "[obs-websockets] goodbye !");
void obs_module_unload() {
blog(LOG_INFO, "goodbye!");
}

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,6 +19,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#ifndef OBSWEBSOCKET_H
#define OBSWEBSOCKET_H
#define OBS_WEBSOCKET_VERSION "0.3.2"
#define PROP_AUTHENTICATED "wsclient_authenticated"
#define OBS_WEBSOCKET_VERSION "4.2.0"
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
#endif // OBSWEBSOCKET_H